diff --git a/api/api_full.go b/api/api_full.go index c63d52df9..47e2f71d7 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -52,7 +52,7 @@ type FullNode interface { MinerRegister(context.Context, address.Address) error MinerUnregister(context.Context, address.Address) error MinerAddresses(context.Context) ([]address.Address, error) - MinerCreateBlock(context.Context, address.Address, *types.TipSet, *types.Ticket, types.ElectionProof, []*types.SignedMessage, uint64, uint64) (*types.BlockMsg, error) + MinerCreateBlock(context.Context, address.Address, *types.TipSet, *types.Ticket, *types.EPostProof, []*types.SignedMessage, uint64, uint64) (*types.BlockMsg, error) // // UX ? diff --git a/api/struct.go b/api/struct.go index f6bc160cb..bbd200b7d 100644 --- a/api/struct.go +++ b/api/struct.go @@ -58,10 +58,10 @@ type FullNodeStruct struct { MpoolPush func(context.Context, *types.SignedMessage) error `perm:"write"` MpoolPushMessage func(context.Context, *types.Message) (*types.SignedMessage, error) `perm:"sign"` - MinerRegister func(context.Context, address.Address) error `perm:"admin"` - MinerUnregister func(context.Context, address.Address) error `perm:"admin"` - MinerAddresses func(context.Context) ([]address.Address, error) `perm:"write"` - MinerCreateBlock func(context.Context, address.Address, *types.TipSet, *types.Ticket, types.ElectionProof, []*types.SignedMessage, uint64, uint64) (*types.BlockMsg, error) `perm:"write"` + MinerRegister func(context.Context, address.Address) error `perm:"admin"` + MinerUnregister func(context.Context, address.Address) error `perm:"admin"` + MinerAddresses func(context.Context) ([]address.Address, error) `perm:"write"` + MinerCreateBlock func(context.Context, address.Address, *types.TipSet, *types.Ticket, *types.EPostProof, []*types.SignedMessage, uint64, uint64) (*types.BlockMsg, error) `perm:"write"` WalletNew func(context.Context, string) (address.Address, error) `perm:"write"` WalletHas func(context.Context, address.Address) (bool, error) `perm:"write"` @@ -237,7 +237,7 @@ func (c *FullNodeStruct) MinerAddresses(ctx context.Context) ([]address.Address, return c.Internal.MinerAddresses(ctx) } -func (c *FullNodeStruct) MinerCreateBlock(ctx context.Context, addr address.Address, base *types.TipSet, ticket *types.Ticket, eproof types.ElectionProof, msgs []*types.SignedMessage, height, ts uint64) (*types.BlockMsg, error) { +func (c *FullNodeStruct) MinerCreateBlock(ctx context.Context, addr address.Address, base *types.TipSet, ticket *types.Ticket, eproof *types.EPostProof, msgs []*types.SignedMessage, height, ts uint64) (*types.BlockMsg, error) { return c.Internal.MinerCreateBlock(ctx, addr, base, ticket, eproof, msgs, height, ts) } diff --git a/chain/actors/actor_miner.go b/chain/actors/actor_miner.go index 2f2a1df33..b5442b1fa 100644 --- a/chain/actors/actor_miner.go +++ b/chain/actors/actor_miner.go @@ -389,9 +389,8 @@ func (sma StorageMinerActor) ProveCommitSector(act *types.Actor, vmctx types.VMC } type SubmitPoStParams struct { - Proof []byte + Proof types.EPostProof DoneSet types.BitField - // TODO: once the spec changes finish, we have more work to do here... } func ProvingPeriodEnd(setPeriodEnd, height uint64) (uint64, uint64) { @@ -402,8 +401,6 @@ func ProvingPeriodEnd(setPeriodEnd, height uint64) (uint64, uint64) { return end, period } -// TODO: this is a dummy method that allows us to plumb in other parts of the -// system for now. func (sma StorageMinerActor) SubmitPoSt(act *types.Actor, vmctx types.VMContext, params *SubmitPoStParams) ([]byte, ActorError) { oldstate, self, err := loadState(vmctx) if err != nil { @@ -491,10 +488,20 @@ func (sma StorageMinerActor) SubmitPoSt(act *types.Actor, vmctx types.VMContext, } faults := self.CurrentFaultSet.All() + _ = faults + + _ = seed + //VerifyPoStRandomness() + + convertToCandidates := func(wins []types.EPostTicket) []sectorbuilder.EPostCandidate { + panic("NYI") + } + winners := convertToCandidates(params.Proof.Winners) + + proverID := vmctx.Message().To // TODO: normalize to ID address if ok, lerr := sectorbuilder.VerifyPost(vmctx.Context(), mi.SectorSize, - sectorbuilder.NewSortedSectorInfo(sectorInfos), seed, params.Proof, - faults); !ok || lerr != nil { + sectorbuilder.NewSortedSectorInfo(sectorInfos), params.Proof.PostRand, params.Proof.Proof, winners, proverID); !ok || lerr != nil { if lerr != nil { // TODO: study PoST errors return nil, aerrors.Absorb(lerr, 4, "PoST error") diff --git a/chain/actors/cbor_gen.go b/chain/actors/cbor_gen.go index 53de185e1..522896008 100644 --- a/chain/actors/cbor_gen.go +++ b/chain/actors/cbor_gen.go @@ -836,11 +836,8 @@ func (t *SubmitPoStParams) MarshalCBOR(w io.Writer) error { return err } - // t.t.Proof ([]uint8) (slice) - if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajByteString, uint64(len(t.Proof)))); err != nil { - return err - } - if _, err := w.Write(t.Proof); err != nil { + // t.t.Proof (types.EPostProof) (struct) + if err := t.Proof.MarshalCBOR(w); err != nil { return err } @@ -866,22 +863,14 @@ func (t *SubmitPoStParams) UnmarshalCBOR(r io.Reader) error { return fmt.Errorf("cbor input had wrong number of fields") } - // t.t.Proof ([]uint8) (slice) + // t.t.Proof (types.EPostProof) (struct) - maj, extra, err = cbg.CborReadHeader(br) - if err != nil { - return err - } - if extra > 8192 { - return fmt.Errorf("t.Proof: array too large (%d)", extra) - } + { + + if err := t.Proof.UnmarshalCBOR(br); err != nil { + return err + } - if maj != cbg.MajByteString { - return fmt.Errorf("expected byte array") - } - t.Proof = make([]byte, extra) - if _, err := io.ReadFull(br, t.Proof); err != nil { - return err } // t.t.DoneSet (types.BitField) (struct) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 763a7179d..e0db5e1ec 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -13,6 +13,7 @@ import ( offline "github.com/ipfs/go-ipfs-exchange-offline" "github.com/ipfs/go-merkledag" peer "github.com/libp2p/go-libp2p-peer" + "go.opencensus.io/trace" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/api" @@ -22,6 +23,7 @@ import ( "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/lib/sectorbuilder" "github.com/filecoin-project/lotus/node/repo" block "github.com/ipfs/go-block-format" @@ -52,6 +54,7 @@ type ChainGen struct { w *wallet.Wallet + eppProvs map[address.Address]ElectionPoStProver Miners []address.Address mworkers []address.Address receivers []address.Address @@ -155,6 +158,11 @@ func NewGenerator() (*ChainGen, error) { return nil, xerrors.Errorf("MakeGenesisBlock failed to set miner address") } + mgen := make(map[address.Address]ElectionPoStProver) + for _, m := range minercfg.MinerAddrs { + mgen[m] = &eppProvider{} + } + sm := stmgr.NewStateManager(cs) gen := &ChainGen{ @@ -166,6 +174,7 @@ func NewGenerator() (*ChainGen, error) { w: w, Miners: minercfg.MinerAddrs, + eppProvs: mgen, mworkers: minercfg.Workers, banker: banker, receivers: receievers, @@ -191,13 +200,13 @@ func (cg *ChainGen) GenesisCar() ([]byte, error) { out := new(bytes.Buffer) if err := car.WriteCar(context.TODO(), dserv, []cid.Cid{cg.Genesis().Cid()}, out); err != nil { - return nil, err + return nil, xerrors.Errorf("genesis car write car failed: %w", err) } return out.Bytes(), nil } -func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m address.Address, round int64) (types.ElectionProof, *types.Ticket, error) { +func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m address.Address, round int64) (*types.EPostProof, *types.Ticket, error) { lastTicket := pts.MinTicket() @@ -208,8 +217,7 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add return nil, nil, xerrors.Errorf("get miner worker: %w", err) } - vrfBase := TicketHash(lastTicket, uint64(round)) - vrfout, err := ComputeVRF(ctx, cg.w.Sign, worker, vrfBase) + vrfout, err := ComputeVRF(ctx, cg.w.Sign, worker, m, DSepTicket, lastTicket.VRFProof) if err != nil { return nil, nil, xerrors.Errorf("compute VRF: %w", err) } @@ -218,7 +226,7 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add VRFProof: vrfout, } - win, eproof, err := IsRoundWinner(ctx, pts, round, m, &mca{w: cg.w, sm: cg.sm}) + win, eproof, err := IsRoundWinner(ctx, pts, round, m, cg.eppProvs[m], &mca{w: cg.w, sm: cg.sm}) if err != nil { return nil, nil, xerrors.Errorf("checking round winner failed: %w", err) } @@ -282,7 +290,7 @@ func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Ad }, nil } -func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, eproof types.ElectionProof, ticket *types.Ticket, height uint64, msgs []*types.SignedMessage) (*types.FullBlock, error) { +func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, eproof *types.EPostProof, ticket *types.Ticket, height uint64, msgs []*types.SignedMessage) (*types.FullBlock, error) { var ts uint64 if cg.Timestamper != nil { @@ -356,6 +364,8 @@ type MiningCheckAPI interface { StateMinerWorker(context.Context, address.Address, *types.TipSet) (address.Address, error) + StateMinerSectorSize(context.Context, address.Address, *types.TipSet) (uint64, error) + WalletSign(context.Context, address.Address, []byte) (*types.Signature, error) } @@ -384,11 +394,38 @@ func (mca mca) StateMinerWorker(ctx context.Context, maddr address.Address, ts * return stmgr.GetMinerWorkerRaw(ctx, mca.sm, ts.ParentState(), maddr) } +func (mca mca) StateMinerSectorSize(ctx context.Context, maddr address.Address, ts *types.TipSet) (uint64, error) { + return stmgr.GetMinerSectorSize(ctx, mca.sm, ts, maddr) +} + func (mca mca) WalletSign(ctx context.Context, a address.Address, v []byte) (*types.Signature, error) { return mca.w.Sign(ctx, a, v) } -func IsRoundWinner(ctx context.Context, ts *types.TipSet, round int64, miner address.Address, a MiningCheckAPI) (bool, types.ElectionProof, error) { +type ElectionPoStProver interface { + GenerateCandidates(context.Context, []byte) ([]sectorbuilder.EPostCandidate, error) + ComputeProof(context.Context, []byte, []sectorbuilder.EPostCandidate) ([]byte, error) +} + +type eppProvider struct { +} + +func (epp *eppProvider) GenerateCandidates(ctx context.Context, eprand []byte) ([]sectorbuilder.EPostCandidate, error) { + return []sectorbuilder.EPostCandidate{ + sectorbuilder.EPostCandidate{ + SectorID: 1, + PartialTicket: [32]byte{}, + Ticket: [32]byte{}, + SectorChallengeIndex: 1, + }, + }, nil +} + +func (epp *eppProvider) ComputeProof(ctx context.Context, eprand []byte, winners []sectorbuilder.EPostCandidate) ([]byte, error) { + return []byte("this is an election post proof"), nil +} + +func IsRoundWinner(ctx context.Context, ts *types.TipSet, round int64, miner address.Address, epp ElectionPoStProver, a MiningCheckAPI) (bool, *types.EPostProof, error) { r, err := a.ChainGetRandomness(ctx, ts.Key(), round-build.EcRandomnessLookback) if err != nil { return false, nil, xerrors.Errorf("chain get randomness: %w", err) @@ -399,23 +436,111 @@ func IsRoundWinner(ctx context.Context, ts *types.TipSet, round int64, miner add return false, nil, xerrors.Errorf("failed to get miner worker: %w", err) } - vrfout, err := ComputeVRF(ctx, a.WalletSign, mworker, r) + vrfout, err := ComputeVRF(ctx, a.WalletSign, mworker, miner, DSepElectionPost, r) if err != nil { return false, nil, xerrors.Errorf("failed to compute VRF: %w", err) } + candidates, err := epp.GenerateCandidates(ctx, vrfout) + if err != nil { + return false, nil, xerrors.Errorf("failed to generate electionPoSt candidates") + } + pow, err := a.StateMinerPower(ctx, miner, ts) if err != nil { return false, nil, xerrors.Errorf("failed to check power: %w", err) } - return types.PowerCmp(vrfout, pow.MinerPower, pow.TotalPower), vrfout, nil + ssize, err := a.StateMinerSectorSize(ctx, miner, ts) + if err != nil { + return false, nil, xerrors.Errorf("failed to look up miners sector size: %w", err) + } + + var winners []sectorbuilder.EPostCandidate + for _, c := range candidates { + if types.IsTicketWinner(c.PartialTicket[:], ssize, pow.TotalPower, 1) { + winners = append(winners, c) + } + } + + // no winners, sad + if len(winners) == 0 { + return false, nil, nil + } + + proof, err := epp.ComputeProof(ctx, vrfout, winners) + if err != nil { + return false, nil, xerrors.Errorf("failed to compute snark for election proof: %w", err) + } + + ept := types.EPostProof{ + Proof: proof, + PostRand: vrfout, + } + for _, win := range winners { + ept.Winners = append(ept.Winners, types.EPostTicket{ + Partial: win.PartialTicket[:], + SectorID: win.SectorID, + ChallengeIndex: win.SectorChallengeIndex, + }) + } + + return true, &ept, nil } type SignFunc func(context.Context, address.Address, []byte) (*types.Signature, error) -func ComputeVRF(ctx context.Context, sign SignFunc, w address.Address, input []byte) ([]byte, error) { - sig, err := sign(ctx, w, input) +const ( + DSepTicket = 1 + DSepElectionPost = 2 +) + +func hashVRFBase(personalization uint64, miner address.Address, input []byte) ([]byte, error) { + if miner.Protocol() != address.ID { + return nil, xerrors.Errorf("miner address for compute VRF must be an ID address") + } + + var persbuf [8]byte + binary.LittleEndian.PutUint64(persbuf[:], personalization) + + h := sha256.New() + h.Write(persbuf[:]) + h.Write([]byte{0}) + h.Write(input) + h.Write([]byte{0}) + h.Write(miner.Bytes()) + + return h.Sum(nil), nil +} + +func VerifyVRF(ctx context.Context, worker, miner address.Address, p uint64, input, vrfproof []byte) error { + ctx, span := trace.StartSpan(ctx, "VerifyVRF") + defer span.End() + + vrfBase, err := hashVRFBase(p, miner, input) + if err != nil { + return xerrors.Errorf("computing vrf base failed: %w", err) + } + + sig := &types.Signature{ + Type: types.KTBLS, + Data: vrfproof, + } + + if err := sig.Verify(worker, vrfBase); err != nil { + return xerrors.Errorf("vrf was invalid: %w", err) + } + + return nil +} + +func ComputeVRF(ctx context.Context, sign SignFunc, worker, miner address.Address, p uint64, input []byte) ([]byte, error) { + sigInput, err := hashVRFBase(p, miner, input) + if err != nil { + return nil, err + } + + sig, err := sign(ctx, worker, sigInput) if err != nil { return nil, err } @@ -427,11 +552,15 @@ func ComputeVRF(ctx context.Context, sign SignFunc, w address.Address, input []b return sig.Data, nil } -func TicketHash(t *types.Ticket, round uint64) []byte { +func TicketHash(t *types.Ticket, addr address.Address) []byte { h := sha256.New() + h.Write(t.VRFProof) - var roundbuf [8]byte - binary.LittleEndian.PutUint64(roundbuf[:], round) - h.Write(roundbuf[:]) + + // Field Delimeter + h.Write([]byte{0}) + + h.Write(addr.Bytes()) + return h.Sum(nil) } diff --git a/chain/gen/mining.go b/chain/gen/mining.go index 51036ad5b..5789de2a2 100644 --- a/chain/gen/mining.go +++ b/chain/gen/mining.go @@ -18,7 +18,7 @@ import ( "github.com/filecoin-project/lotus/chain/wallet" ) -func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wallet, miner address.Address, parents *types.TipSet, ticket *types.Ticket, proof types.ElectionProof, msgs []*types.SignedMessage, height, timestamp uint64) (*types.FullBlock, error) { +func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wallet, miner address.Address, parents *types.TipSet, ticket *types.Ticket, proof *types.EPostProof, msgs []*types.SignedMessage, height, timestamp uint64) (*types.FullBlock, error) { st, recpts, err := sm.TipSetState(ctx, parents) if err != nil { return nil, xerrors.Errorf("failed to load tipset state: %w", err) @@ -35,7 +35,7 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wal Ticket: ticket, Height: height, Timestamp: timestamp, - ElectionProof: proof, + EPostProof: *proof, ParentStateRoot: st, ParentMessageReceipts: recpts, } diff --git a/chain/gen/utils.go b/chain/gen/utils.go index 1a86e0d1f..38591870d 100644 --- a/chain/gen/utils.go +++ b/chain/gen/utils.go @@ -383,9 +383,12 @@ func MakeGenesisBlock(bs bstore.Blockstore, balances map[address.Address]types.B } b := &types.BlockHeader{ - Miner: actors.InitAddress, - Ticket: genesisticket, - ElectionProof: []byte("the Genesis block"), + Miner: actors.InitAddress, + Ticket: genesisticket, + EPostProof: types.EPostProof{ + Proof: []byte("not a real proof"), + PostRand: []byte("i guess this is kinda random"), + }, Parents: []cid.Cid{}, Height: 0, ParentWeight: types.NewInt(0), diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index b507fd387..6540e86ed 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -7,6 +7,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/address" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/sectorbuilder" amt "github.com/filecoin-project/go-amt-ipld" cid "github.com/ipfs/go-cid" @@ -177,6 +178,26 @@ func GetMinerSectorSet(ctx context.Context, sm *StateManager, ts *types.TipSet, return LoadSectorsFromSet(ctx, sm.ChainStore().Blockstore(), mas.Sectors) } +func GetSectorsForElectionPost(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (*sectorbuilder.SortedSectorInfo, error) { + sectors, err := GetMinerSectorSet(ctx, sm, ts, maddr) + if err != nil { + return nil, xerrors.Errorf("failed to get sector set for miner: %w", err) + } + + var uselessOtherArray []sectorbuilder.SectorInfo + for _, s := range sectors { + var uselessBuffer [32]byte + copy(uselessBuffer[:], s.CommR) + uselessOtherArray = append(uselessOtherArray, sectorbuilder.SectorInfo{ + SectorID: s.SectorID, + CommR: uselessBuffer, + }) + } + + ssi := sectorbuilder.NewSortedSectorInfo(uselessOtherArray) + return &ssi, nil +} + func GetMinerSectorSize(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (uint64, error) { var mas actors.StorageMinerActorState _, err := sm.LoadActorState(ctx, maddr, &mas, ts) diff --git a/chain/store/store.go b/chain/store/store.go index 8f42390d5..5b047a38f 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -770,7 +770,7 @@ func (cs *ChainStore) TryFillTipSet(ts *types.TipSet) (*FullTipSet, error) { return NewFullTipSet(out), nil } -func ticketHash(t *types.Ticket, round int64) []byte { +func drawRandomness(t *types.Ticket, round int64) []byte { h := sha256.New() var buf [8]byte binary.LittleEndian.PutUint64(buf[:], uint64(round)) @@ -795,7 +795,7 @@ func (cs *ChainStore) GetRandomness(ctx context.Context, blks []cid.Cid, round i mtb := nts.MinTicketBlock() if int64(nts.Height()) <= round { - return ticketHash(nts.MinTicketBlock().Ticket, round), nil + return drawRandomness(nts.MinTicketBlock().Ticket, round), nil } // special case for lookback behind genesis block @@ -803,7 +803,7 @@ func (cs *ChainStore) GetRandomness(ctx context.Context, blks []cid.Cid, round i if mtb.Height == 0 { // round is negative - thash := ticketHash(mtb.Ticket, round*-1) + thash := drawRandomness(mtb.Ticket, round*-1) // for negative lookbacks, just use the hash of the positive tickethash value h := sha256.Sum256(thash) diff --git a/chain/sync.go b/chain/sync.go index c804afcd3..af915cfba 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -32,6 +32,7 @@ import ( "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/sectorbuilder" ) var log = logging.Logger("chain") @@ -467,23 +468,8 @@ func (syncer *Syncer) minerIsValid(ctx context.Context, maddr address.Address, b return nil } -func (syncer *Syncer) validateTicket(ctx context.Context, mworker address.Address, ticket *types.Ticket, base *types.TipSet, round uint64) error { - ctx, span := trace.StartSpan(ctx, "validateTickets") - defer span.End() - - sig := &types.Signature{ - Type: types.KTBLS, - Data: ticket.VRFProof, - } - - vrfBase := gen.TicketHash(base.MinTicket(), round) - - // TODO: ticket signatures should also include miner address - if err := sig.Verify(mworker, vrfBase); err != nil { - return xerrors.Errorf("invalid ticket, VRFProof invalid: %w", err) - } - - return nil +func (syncer *Syncer) validateTicket(ctx context.Context, maddr, mworker address.Address, ticket *types.Ticket, base *types.TipSet) error { + return gen.VerifyVRF(ctx, mworker, maddr, gen.DSepTicket, base.MinTicket().VRFProof, ticket.VRFProof) } var ErrTemporal = errors.New("temporal error") @@ -515,13 +501,20 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err } winnerCheck := async.Err(func() error { - mpow, tpow, err := stmgr.GetPower(ctx, syncer.sm, baseTs, h.Miner) + _, tpow, err := stmgr.GetPower(ctx, syncer.sm, baseTs, h.Miner) if err != nil { return xerrors.Errorf("failed getting power: %w", err) } - if !types.PowerCmp(h.ElectionProof, mpow, tpow) { - return xerrors.Errorf("miner created a block but was not a winner") + ssize, err := stmgr.GetMinerSectorSize(ctx, syncer.sm, baseTs, h.Miner) + if err != nil { + return xerrors.Errorf("failed to get sector size for block miner: %w", err) + } + + for _, t := range h.EPostProof.Winners { + if !types.IsTicketWinner(t.Partial, ssize, tpow, 1) { + return xerrors.Errorf("miner created a block but was not a winner") + } } return nil }) @@ -578,20 +571,15 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err }) tktsCheck := async.Err(func() error { - if err := syncer.validateTicket(ctx, waddr, h.Ticket, baseTs, h.Height); err != nil { + if err := syncer.validateTicket(ctx, h.Miner, waddr, h.Ticket, baseTs); err != nil { return xerrors.Errorf("validating block tickets failed: %w", err) } return nil }) eproofCheck := async.Err(func() error { - rand, err := syncer.sm.ChainStore().GetRandomness(ctx, baseTs.Cids(), int64(h.Height-build.EcRandomnessLookback)) - if err != nil { - return xerrors.Errorf("failed to get randomness for verifying election proof: %w", err) - } - - if err := VerifyElectionProof(ctx, h.ElectionProof, rand, waddr); err != nil { - return xerrors.Errorf("checking eproof failed: %w", err) + if err := syncer.VerifyElectionPoStProof(ctx, h, baseTs, waddr); err != nil { + return xerrors.Errorf("invalid election post: %w", err) } return nil }) @@ -615,6 +603,49 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err return merr } +func (syncer *Syncer) VerifyElectionPoStProof(ctx context.Context, h *types.BlockHeader, baseTs *types.TipSet, waddr address.Address) error { + rand, err := syncer.sm.ChainStore().GetRandomness(ctx, baseTs.Cids(), int64(h.Height-build.EcRandomnessLookback)) + if err != nil { + return xerrors.Errorf("failed to get randomness for verifying election proof: %w", err) + } + + if err := VerifyElectionPoStVRF(ctx, h.EPostProof.PostRand, rand, waddr, h.Miner); err != nil { + return xerrors.Errorf("checking eproof failed: %w", err) + } + + ssize, err := stmgr.GetMinerSectorSize(ctx, syncer.sm, baseTs, h.Miner) + if err != nil { + return xerrors.Errorf("failed to get sector size for miner: %w", err) + } + + var winners []sectorbuilder.EPostCandidate + for _, t := range h.EPostProof.Winners { + var partial [32]byte + copy(partial[:], t.Partial) + winners = append(winners, sectorbuilder.EPostCandidate{ + PartialTicket: partial, + SectorID: t.SectorID, + SectorChallengeIndex: t.ChallengeIndex, + }) + } + + sectorInfo, err := stmgr.GetSectorsForElectionPost(ctx, syncer.sm, baseTs, h.Miner) + if err != nil { + return xerrors.Errorf("getting election post sector set: %w", err) + } + + ok, err := sectorbuilder.VerifyPost(ctx, ssize, *sectorInfo, h.EPostProof.PostRand, h.EPostProof.Proof, winners, waddr) + if err != nil { + return xerrors.Errorf("failed to verify election post: %w", err) + } + + if !ok { + return xerrors.Errorf("election post was invalid") + } + + return nil +} + func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock, baseTs *types.TipSet) error { nonces := make(map[address.Address]uint64) balances := make(map[address.Address]types.BigInt) @@ -1058,14 +1089,9 @@ func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error return nil } -func VerifyElectionProof(ctx context.Context, eproof []byte, rand []byte, worker address.Address) error { - sig := types.Signature{ - Data: eproof, - Type: types.KTBLS, - } - - if err := sig.Verify(worker, rand); err != nil { - return xerrors.Errorf("failed to verify election proof signature: %w", err) +func VerifyElectionPoStVRF(ctx context.Context, evrf []byte, rand []byte, worker, miner address.Address) error { + if err := gen.VerifyVRF(ctx, worker, miner, gen.DSepElectionPost, rand, evrf); err != nil { + return xerrors.Errorf("failed to verify post_randomness vrf: %w", err) } return nil diff --git a/chain/types/blockheader.go b/chain/types/blockheader.go index c0fc59214..9cac4fd42 100644 --- a/chain/types/blockheader.go +++ b/chain/types/blockheader.go @@ -20,14 +20,24 @@ type Ticket struct { VRFProof []byte } -type ElectionProof []byte +type EPostTicket struct { + Partial []byte + SectorID uint64 + ChallengeIndex uint64 +} + +type EPostProof struct { + Proof []byte + PostRand []byte + Winners []EPostTicket +} type BlockHeader struct { Miner address.Address Ticket *Ticket - ElectionProof []byte + EPostProof EPostProof Parents []cid.Cid @@ -162,29 +172,31 @@ func CidArrsEqual(a, b []cid.Cid) bool { var blocksPerEpoch = NewInt(build.BlocksPerEpoch) -func PowerCmp(eproof ElectionProof, mpow, totpow BigInt) bool { +func IsTicketWinner(partialTicket []byte, ssizeI uint64, totpow BigInt, sampleRate int64) bool { + ssize := NewInt(ssizeI) /* Need to check that - (h(vrfout) + 1) / (max(h) + 1) <= e * minerPower / totalPower + (h(vrfout) + 1) / (max(h) + 1) <= e * sectorSize / totalPower max(h) == 2^256-1 which in terms of integer math means: - (h(vrfout) + 1) * totalPower <= e * minerPower * 2^256 + (h(vrfout) + 1) * totalPower <= e * sectorSize * 2^256 in 2^256 space, it is equivalent to: - h(vrfout) * totalPower < e * minerPower * 2^256 + h(vrfout) * totalPower < e * sectorSize * 2^256 */ - h := sha256.Sum256(eproof) + h := sha256.Sum256(partialTicket) lhs := BigFromBytes(h[:]).Int lhs = lhs.Mul(lhs, totpow.Int) + lhs = lhs.Mul(lhs, big.NewInt(sampleRate)) - // rhs = minerPower * 2^256 - // rhs = minerPower << 256 - rhs := new(big.Int).Lsh(mpow.Int, 256) + // rhs = sectorSize * 2^256 + // rhs = sectorSize << 256 + rhs := new(big.Int).Lsh(ssize.Int, 256) rhs = rhs.Mul(rhs, blocksPerEpoch.Int) - // h(vrfout) * totalPower < e * minerPower * 2^256? + // h(vrfout) * totalPower < e * sectorSize * 2^256? return lhs.Cmp(rhs) == -1 } diff --git a/chain/types/blockheader_test.go b/chain/types/blockheader_test.go index 6c41f3b4b..60f8b7770 100644 --- a/chain/types/blockheader_test.go +++ b/chain/types/blockheader_test.go @@ -23,8 +23,10 @@ func testBlockHeader(t testing.TB) *BlockHeader { } return &BlockHeader{ - Miner: addr, - ElectionProof: []byte("cats won the election"), + Miner: addr, + EPostProof: EPostProof{ + Proof: []byte("pruuf"), + }, Ticket: &Ticket{ VRFProof: []byte("vrf proof0000000vrf proof0000000"), }, diff --git a/chain/types/cbor_gen.go b/chain/types/cbor_gen.go index a0af53450..aa821245c 100644 --- a/chain/types/cbor_gen.go +++ b/chain/types/cbor_gen.go @@ -33,11 +33,8 @@ func (t *BlockHeader) MarshalCBOR(w io.Writer) error { return err } - // t.t.ElectionProof ([]uint8) (slice) - if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajByteString, uint64(len(t.ElectionProof)))); err != nil { - return err - } - if _, err := w.Write(t.ElectionProof); err != nil { + // t.t.EPostProof (types.EPostProof) (struct) + if err := t.EPostProof.MarshalCBOR(w); err != nil { return err } @@ -141,22 +138,14 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { } } - // t.t.ElectionProof ([]uint8) (slice) + // t.t.EPostProof (types.EPostProof) (struct) - maj, extra, err = cbg.CborReadHeader(br) - if err != nil { - return err - } - if extra > 8192 { - return fmt.Errorf("t.ElectionProof: array too large (%d)", extra) - } + { + + if err := t.EPostProof.UnmarshalCBOR(br); err != nil { + return err + } - if maj != cbg.MajByteString { - return fmt.Errorf("expected byte array") - } - t.ElectionProof = make([]byte, extra) - if _, err := io.ReadFull(br, t.ElectionProof); err != nil { - return err } // t.t.Parents ([]cid.Cid) (slice) @@ -335,6 +324,205 @@ func (t *Ticket) UnmarshalCBOR(r io.Reader) error { return nil } +func (t *EPostProof) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{131}); err != nil { + return err + } + + // t.t.Proof ([]uint8) (slice) + if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajByteString, uint64(len(t.Proof)))); err != nil { + return err + } + if _, err := w.Write(t.Proof); err != nil { + return err + } + + // t.t.PostRand ([]uint8) (slice) + if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajByteString, uint64(len(t.PostRand)))); err != nil { + return err + } + if _, err := w.Write(t.PostRand); err != nil { + return err + } + + // t.t.Winners ([]types.EPostTicket) (slice) + if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.Winners)))); err != nil { + return err + } + for _, v := range t.Winners { + if err := v.MarshalCBOR(w); err != nil { + return err + } + } + return nil +} + +func (t *EPostProof) UnmarshalCBOR(r io.Reader) error { + br := cbg.GetPeeker(r) + + maj, extra, err := cbg.CborReadHeader(br) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.t.Proof ([]uint8) (slice) + + maj, extra, err = cbg.CborReadHeader(br) + if err != nil { + return err + } + if extra > 8192 { + return fmt.Errorf("t.Proof: array too large (%d)", extra) + } + + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + t.Proof = make([]byte, extra) + if _, err := io.ReadFull(br, t.Proof); err != nil { + return err + } + // t.t.PostRand ([]uint8) (slice) + + maj, extra, err = cbg.CborReadHeader(br) + if err != nil { + return err + } + if extra > 8192 { + return fmt.Errorf("t.PostRand: array too large (%d)", extra) + } + + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + t.PostRand = make([]byte, extra) + if _, err := io.ReadFull(br, t.PostRand); err != nil { + return err + } + // t.t.Winners ([]types.EPostTicket) (slice) + + maj, extra, err = cbg.CborReadHeader(br) + if err != nil { + return err + } + if extra > 8192 { + return fmt.Errorf("t.Winners: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + if extra > 0 { + t.Winners = make([]EPostTicket, extra) + } + for i := 0; i < int(extra); i++ { + + var v EPostTicket + if err := v.UnmarshalCBOR(br); err != nil { + return err + } + + t.Winners[i] = v + } + + return nil +} + +func (t *EPostTicket) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write([]byte{131}); err != nil { + return err + } + + // t.t.Partial ([]uint8) (slice) + if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajByteString, uint64(len(t.Partial)))); err != nil { + return err + } + if _, err := w.Write(t.Partial); err != nil { + return err + } + + // t.t.SectorID (uint64) (uint64) + if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.SectorID))); err != nil { + return err + } + + // t.t.ChallengeIndex (uint64) (uint64) + if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.ChallengeIndex))); err != nil { + return err + } + return nil +} + +func (t *EPostTicket) UnmarshalCBOR(r io.Reader) error { + br := cbg.GetPeeker(r) + + maj, extra, err := cbg.CborReadHeader(br) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.t.Partial ([]uint8) (slice) + + maj, extra, err = cbg.CborReadHeader(br) + if err != nil { + return err + } + if extra > 8192 { + return fmt.Errorf("t.Partial: array too large (%d)", extra) + } + + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + t.Partial = make([]byte, extra) + if _, err := io.ReadFull(br, t.Partial); err != nil { + return err + } + // t.t.SectorID (uint64) (uint64) + + maj, extra, err = cbg.CborReadHeader(br) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.SectorID = uint64(extra) + // t.t.ChallengeIndex (uint64) (uint64) + + maj, extra, err = cbg.CborReadHeader(br) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.ChallengeIndex = uint64(extra) + return nil +} + func (t *Message) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) diff --git a/chain/types/mock/chain.go b/chain/types/mock/chain.go index 474d8a92b..37d2e8a53 100644 --- a/chain/types/mock/chain.go +++ b/chain/types/mock/chain.go @@ -34,8 +34,10 @@ func MkBlock(parents *types.TipSet, weightInc uint64, ticketNonce uint64) *types } return &types.BlockHeader{ - Miner: addr, - ElectionProof: []byte("cats won the election"), + Miner: addr, + EPostProof: types.EPostProof{ + Proof: []byte("election post proof proof"), + }, Ticket: &types.Ticket{ VRFProof: []byte(fmt.Sprintf("====%d=====", ticketNonce)), }, diff --git a/extern/go-bls-sigs b/extern/go-bls-sigs index 98479d3c7..1c177e24f 160000 --- a/extern/go-bls-sigs +++ b/extern/go-bls-sigs @@ -1 +1 @@ -Subproject commit 98479d3c79620f18783da0c2c6a15f8b8eb4fa2e +Subproject commit 1c177e24fd71669a1c8cc2e0ce4ba6e247e34d77 diff --git a/extern/go-sectorbuilder b/extern/go-sectorbuilder index 40278d4a6..33fb89c5e 160000 --- a/extern/go-sectorbuilder +++ b/extern/go-sectorbuilder @@ -1 +1 @@ -Subproject commit 40278d4a6623e4c81003e20444871c9362bedd61 +Subproject commit 33fb89c5efe02a250508e95114836bd13dd3067e diff --git a/gen/main.go b/gen/main.go index c74a10f0f..48668d6da 100644 --- a/gen/main.go +++ b/gen/main.go @@ -20,6 +20,8 @@ func main() { err := gen.WriteTupleEncodersToFile("./chain/types/cbor_gen.go", "types", types.BlockHeader{}, types.Ticket{}, + types.EPostProof{}, + types.EPostTicket{}, types.Message{}, types.SignedMessage{}, types.MsgMeta{}, diff --git a/lib/sectorbuilder/sectorbuilder.go b/lib/sectorbuilder/sectorbuilder.go index 4383ddb57..3f3eeac2d 100644 --- a/lib/sectorbuilder/sectorbuilder.go +++ b/lib/sectorbuilder/sectorbuilder.go @@ -47,6 +47,8 @@ type PublicPieceInfo = sectorbuilder.PublicPieceInfo type RawSealPreCommitOutput = sectorbuilder.RawSealPreCommitOutput +type EPostCandidate = sectorbuilder.Candidate + const CommLen = sectorbuilder.CommitmentBytesLen type SectorBuilder struct { @@ -333,16 +335,36 @@ func (sb *SectorBuilder) GetAllStagedSectors() ([]uint64, error) { return out, nil } +/* func (sb *SectorBuilder) GeneratePoSt(sectorInfo SortedSectorInfo, challengeSeed [CommLen]byte, faults []uint64) ([]byte, error) { // Wait, this is a blocking method with no way of interrupting it? // does it checkpoint itself? return sectorbuilder.GeneratePoSt(sb.handle, sectorInfo, challengeSeed, faults) } +*/ func (sb *SectorBuilder) SectorSize() uint64 { return sb.ssize } +func (sb *SectorBuilder) ComputeElectionPoSt(sectorInfo SortedSectorInfo, challengeSeed []byte, winners []EPostCandidate) ([]byte, error) { + if len(challengeSeed) != CommLen { + return nil, xerrors.Errorf("given challenge seed was the wrong length: %d != %d", len(challengeSeed), CommLen) + } + var cseed [CommLen]byte + copy(cseed[:], challengeSeed) + + return sectorbuilder.GeneratePoSt(sb.handle, sectorInfo, cseed, winners) +} + +func (sb *SectorBuilder) GenerateEPostCandidates(sectorInfo SortedSectorInfo, challengeSeed [CommLen]byte, faults []uint64) ([]EPostCandidate, error) { + return sectorbuilder.GenerateCandidates(sb.handle, sectorInfo, challengeSeed, faults) +} + +func (sb *SectorBuilder) GenerateFallbackPoSt(sectorInfo SortedSectorInfo, challengeSeed [CommLen]byte, faults []uint64) ([]byte, error) { + panic("NYI") +} + var UserBytesForSectorSize = sectorbuilder.GetMaxUserBytesPerStagedSector func VerifySeal(sectorSize uint64, commR, commD []byte, proverID address.Address, ticket []byte, seed []byte, sectorID uint64, proof []byte) (bool, error) { @@ -360,10 +382,14 @@ func NewSortedSectorInfo(sectors []SectorInfo) SortedSectorInfo { return sectorbuilder.NewSortedSectorInfo(sectors...) } -func VerifyPost(ctx context.Context, sectorSize uint64, sectorInfo SortedSectorInfo, challengeSeed [CommLen]byte, proof []byte, faults []uint64) (bool, error) { +func VerifyPost(ctx context.Context, sectorSize uint64, sectorInfo SortedSectorInfo, challengeSeed []byte, proof []byte, winners []EPostCandidate, proverID address.Address) (bool, error) { + var challengeSeeda [CommLen]byte + copy(challengeSeeda[:], challengeSeed) + _, span := trace.StartSpan(ctx, "VerifyPoSt") defer span.End() - return sectorbuilder.VerifyPoSt(sectorSize, sectorInfo, challengeSeed, proof, faults) + prover := addressToProverID(proverID) + return sectorbuilder.VerifyPoSt(sectorSize, sectorInfo, challengeSeeda, proof, winners, prover) } func GeneratePieceCommitment(piece io.Reader, pieceSize uint64) (commP [CommLen]byte, err error) { diff --git a/miner/miner.go b/miner/miner.go index 1a394da2b..47958bab1 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -47,6 +47,8 @@ func NewMiner(api api) *Miner { type Miner struct { api api + epp gen.ElectionPoStProver + lk sync.Mutex addresses []address.Address stop chan struct{} @@ -248,12 +250,12 @@ func (m *Miner) GetBestMiningCandidate(ctx context.Context) (*MiningBase, error) func (m *Miner) mineOne(ctx context.Context, addr address.Address, base *MiningBase) (*types.BlockMsg, error) { log.Debugw("attempting to mine a block", "tipset", types.LogCids(base.ts.Cids())) - ticket, err := m.scratchTicket(ctx, addr, base) + ticket, err := m.computeTicket(ctx, addr, base) if err != nil { return nil, errors.Wrap(err, "scratching ticket failed") } - win, proof, err := gen.IsRoundWinner(ctx, base.ts, int64(base.ts.Height()+base.nullRounds+1), addr, &m.api) + win, proof, err := gen.IsRoundWinner(ctx, base.ts, int64(base.ts.Height()+base.nullRounds+1), addr, m.epp, &m.api) if err != nil { return nil, errors.Wrap(err, "failed to check if we win next round") } @@ -278,7 +280,7 @@ func (m *Miner) computeVRF(ctx context.Context, addr address.Address, input []by return nil, err } - return gen.ComputeVRF(ctx, m.api.WalletSign, w, input) + return gen.ComputeVRF(ctx, m.api.WalletSign, w, addr, gen.DSepTicket, input) } func (m *Miner) getMinerWorker(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { @@ -303,10 +305,9 @@ func (m *Miner) getMinerWorker(ctx context.Context, addr address.Address, ts *ty return w, nil } -func (m *Miner) scratchTicket(ctx context.Context, addr address.Address, base *MiningBase) (*types.Ticket, error) { - round := base.ts.Height() + base.nullRounds + 1 +func (m *Miner) computeTicket(ctx context.Context, addr address.Address, base *MiningBase) (*types.Ticket, error) { - vrfBase := gen.TicketHash(base.ts.MinTicket(), round) + vrfBase := gen.TicketHash(base.ts.MinTicket(), addr) vrfOut, err := m.computeVRF(ctx, addr, vrfBase) if err != nil { @@ -318,7 +319,7 @@ func (m *Miner) scratchTicket(ctx context.Context, addr address.Address, base *M }, nil } -func (m *Miner) createBlock(base *MiningBase, addr address.Address, ticket *types.Ticket, proof types.ElectionProof) (*types.BlockMsg, error) { +func (m *Miner) createBlock(base *MiningBase, addr address.Address, ticket *types.Ticket, proof *types.EPostProof) (*types.BlockMsg, error) { pending, err := m.api.MpoolPending(context.TODO(), base.ts) if err != nil { diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 38d9490b7..844220475 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -177,7 +177,7 @@ func (a *StateAPI) StateReadState(ctx context.Context, act *types.Actor, ts *typ } // This is on StateAPI because miner.Miner requires this, and MinerAPI requires miner.Miner -func (a *StateAPI) MinerCreateBlock(ctx context.Context, addr address.Address, parents *types.TipSet, ticket *types.Ticket, proof types.ElectionProof, msgs []*types.SignedMessage, height, ts uint64) (*types.BlockMsg, error) { +func (a *StateAPI) MinerCreateBlock(ctx context.Context, addr address.Address, parents *types.TipSet, ticket *types.Ticket, proof *types.EPostProof, msgs []*types.SignedMessage, height, ts uint64) (*types.BlockMsg, error) { fblk, err := gen.MinerCreateBlock(ctx, a.StateManager, a.Wallet, addr, parents, ticket, proof, msgs, height, ts) if err != nil { return nil, err diff --git a/storage/post.go b/storage/post.go index be2d06eac..4f73b8af2 100644 --- a/storage/post.go +++ b/storage/post.go @@ -193,7 +193,7 @@ func (p *post) runPost(ctx context.Context) error { var seed [32]byte copy(seed[:], p.r) - proof, err := p.m.sb.GeneratePoSt(p.sortedSectorInfo(), seed, faults) + proof, err := p.m.sb.GenerateFallbackPoSt(p.sortedSectorInfo(), seed, faults) if err != nil { return xerrors.Errorf("running post failed: %w", err) } @@ -209,8 +209,9 @@ func (p *post) commitPost(ctx context.Context) error { ctx, span := trace.StartSpan(ctx, "storage.commitPost") defer span.End() + panic("NYI") params := &actors.SubmitPoStParams{ - Proof: p.proof, + //Proof: p.proof, DoneSet: types.BitFieldFromSet(nil), }