diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 3e03cd97f..3a6c937d6 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -15,6 +15,7 @@ import ( "github.com/filecoin-project/go-lotus/chain/store" "github.com/filecoin-project/go-lotus/chain/types" "github.com/filecoin-project/go-lotus/chain/wallet" + "github.com/filecoin-project/go-lotus/lib/vdf" "github.com/filecoin-project/go-lotus/node/repo" block "github.com/ipfs/go-block-format" @@ -134,6 +135,10 @@ func NewGenerator() (*ChainGen, error) { return nil, xerrors.Errorf("set genesis failed: %w", err) } + if minercfg.MinerAddr == address.Undef { + return nil, xerrors.Errorf("MakeGenesisBlock failed to set miner address") + } + gen := &ChainGen{ bs: bs, cs: cs, @@ -174,11 +179,19 @@ func (cg *ChainGen) GenesisCar() ([]byte, error) { } func (cg *ChainGen) nextBlockProof() (address.Address, types.ElectionProof, []*types.Ticket, error) { - tick := &types.Ticket{ - VRFProof: []byte("im a ticket, promise"), - VDFProof: []byte("vdf proof"), - VDFResult: []byte("verifiable and delayed"), + vrf := []byte("veee arrr efff") + + out, proof, err := vdf.Run(vrf) + if err != nil { + return address.Undef, nil, nil, err } + + tick := &types.Ticket{ + VRFProof: vrf, + VDFProof: proof, + VDFResult: out, + } + return cg.miner, []byte("cat in a box"), []*types.Ticket{tick}, nil } diff --git a/chain/sync.go b/chain/sync.go index d1215eba6..35f0e5aed 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -8,9 +8,11 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-lotus/chain/actors" + "github.com/filecoin-project/go-lotus/chain/address" "github.com/filecoin-project/go-lotus/chain/store" "github.com/filecoin-project/go-lotus/chain/types" "github.com/filecoin-project/go-lotus/chain/vm" + "github.com/filecoin-project/go-lotus/lib/vdf" "github.com/ipfs/go-cid" dstore "github.com/ipfs/go-datastore" @@ -287,7 +289,7 @@ func (syncer *Syncer) Sync(maybeHead *store.FullTipSet) error { } if err := syncer.collectChain(maybeHead); err != nil { - return err + return xerrors.Errorf("collectChain failed: %w", err) } if err := syncer.store.PutTipSet(maybeHead); err != nil { @@ -311,6 +313,89 @@ func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet) return nil } +func (syncer *Syncer) minerIsValid(ctx context.Context, maddr address.Address, baseTs *types.TipSet) error { + var err error + enc, err := actors.SerializeParams(&actors.IsMinerParam{Addr: maddr}) + if err != nil { + return err + } + + ret, err := vm.Call(ctx, syncer.store, &types.Message{ + To: actors.StorageMarketAddress, + From: maddr, + Method: actors.SMAMethods.IsMiner, + Params: enc, + }, baseTs) + if err != nil { + return xerrors.Errorf("checking if block miner is valid failed: %w", err) + } + + if ret.ExitCode != 0 { + return xerrors.Errorf("StorageMarket.IsMiner check failed (exit code %d)", ret.ExitCode) + } + + // TODO: ensure the miner is currently not late on their PoSt submission (this hasnt landed in the spec yet) + + return nil +} + +func (syncer *Syncer) validateTickets(ctx context.Context, mworker address.Address, tickets []*types.Ticket, base *types.TipSet) error { + if len(tickets) == 0 { + return xerrors.Errorf("block had no tickets") + } + + cur := base.MinTicket() + for i := 0; i < len(tickets); i++ { + next := tickets[i] + + sig := &types.Signature{ + Type: types.KTBLS, + Data: next.VRFProof, + } + + log.Infof("About to verify signature: ", sig.Data, mworker, cur.VDFResult) + if err := sig.Verify(mworker, cur.VDFResult); err != nil { + return xerrors.Errorf("invalid ticket, VRFProof invalid: %w", err) + } + + // now verify the VDF + if err := vdf.Verify(next.VRFProof, next.VDFResult, next.VDFProof); err != nil { + return xerrors.Errorf("ticket %d had invalid VDF: %w", err) + } + + cur = next + } + + return nil +} + +func getMinerWorker(ctx context.Context, cs *store.ChainStore, st cid.Cid, maddr address.Address) (address.Address, error) { + recp, err := vm.CallRaw(ctx, cs, &types.Message{ + To: maddr, + From: maddr, + Method: actors.MAMethods.GetWorkerAddr, + }, st, 0) + if err != nil { + return address.Undef, xerrors.Errorf("callRaw failed: %w", err) + } + + if recp.ExitCode != 0 { + return address.Undef, xerrors.Errorf("getting miner worker addr failed (exit code %d)", recp.ExitCode) + } + + worker, err := address.NewFromBytes(recp.Return) + if err != nil { + return address.Undef, err + } + + if worker.Protocol() == address.ID { + return address.Undef, xerrors.Errorf("need to resolve worker address to a pubkeyaddr") + } + + return worker, nil +} + +// Should match up with 'Semantical Validation' in validation.md in the spec func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) error { h := b.Header stateroot, err := syncer.store.TipSetState(h.Parents) @@ -318,12 +403,25 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err return xerrors.Errorf("get tipsetstate(%d, %s) failed: %w", h.Height, h.Parents, err) } - baseTs, err := syncer.store.LoadTipSet(b.Header.Parents) + baseTs, err := syncer.store.LoadTipSet(h.Parents) if err != nil { return err } - vmi, err := vm.NewVM(stateroot, b.Header.Height, b.Header.Miner, syncer.store) + if err := syncer.minerIsValid(ctx, h.Miner, baseTs); err != nil { + return xerrors.Errorf("minerIsValid failed: %w", err) + } + + waddr, err := getMinerWorker(ctx, syncer.store, stateroot, h.Miner) + if err != nil { + return xerrors.Errorf("getMinerWorker failed: %w", err) + } + + if err := syncer.validateTickets(ctx, waddr, h.Tickets, baseTs); err != nil { + return xerrors.Errorf("validating block tickets failed: %w", err) + } + + vmi, err := vm.NewVM(stateroot, h.Height, h.Miner, syncer.store) if err != nil { return err } @@ -549,7 +647,7 @@ func (syncer *Syncer) collectChain(fts *store.FullTipSet) error { } if err := syncer.syncMessagesAndCheckState(headers); err != nil { - return err + return xerrors.Errorf("collectChain syncMessages: %w", err) } return nil diff --git a/chain/types/tipset.go b/chain/types/tipset.go index e4b8945c6..e42cca01c 100644 --- a/chain/types/tipset.go +++ b/chain/types/tipset.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "encoding/json" "fmt" @@ -89,3 +90,18 @@ func (ts *TipSet) Equals(ots *TipSet) bool { return true } + +func (ts *TipSet) MinTicket() *Ticket { + if len(ts.Blocks()) == 0 { + panic("tipset has no blocks!") + } + var minTicket *Ticket + for _, b := range ts.Blocks() { + lastTicket := b.Tickets[len(b.Tickets)-1] + if minTicket == nil || bytes.Compare(lastTicket.VDFResult, minTicket.VDFResult) < 0 { + minTicket = lastTicket + } + } + + return minTicket +} diff --git a/chain/vm/call.go b/chain/vm/call.go index 920ae2505..d85f34145 100644 --- a/chain/vm/call.go +++ b/chain/vm/call.go @@ -6,19 +6,12 @@ import ( "github.com/filecoin-project/go-lotus/chain/actors" "github.com/filecoin-project/go-lotus/chain/store" "github.com/filecoin-project/go-lotus/chain/types" + cid "github.com/ipfs/go-cid" "golang.org/x/xerrors" ) -func Call(ctx context.Context, cs *store.ChainStore, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) { - if ts == nil { - ts = cs.GetHeaviestTipSet() - } - state, err := cs.TipSetState(ts.Cids()) - if err != nil { - return nil, err - } - - vmi, err := NewVM(state, ts.Height(), ts.Blocks()[0].Miner, cs) +func CallRaw(ctx context.Context, cs *store.ChainStore, msg *types.Message, bstate cid.Cid, bheight uint64) (*types.MessageReceipt, error) { + vmi, err := NewVM(bstate, bheight, actors.NetworkAddress, cs) if err != nil { return nil, xerrors.Errorf("failed to set up vm: %w", err) } @@ -56,4 +49,17 @@ func Call(ctx context.Context, cs *store.ChainStore, msg *types.Message, ts *typ log.Warnf("chain call failed: %s", ret.ActorErr) } return &ret.MessageReceipt, nil + +} + +func Call(ctx context.Context, cs *store.ChainStore, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) { + if ts == nil { + ts = cs.GetHeaviestTipSet() + } + state, err := cs.TipSetState(ts.Cids()) + if err != nil { + return nil, err + } + + return CallRaw(ctx, cs, msg, state, ts.Height()) } diff --git a/lib/vdf/vdf.go b/lib/vdf/vdf.go new file mode 100644 index 000000000..98590be8e --- /dev/null +++ b/lib/vdf/vdf.go @@ -0,0 +1,28 @@ +package vdf + +import ( + "bytes" + "crypto/sha256" + "fmt" +) + +func Run(input []byte) ([]byte, []byte, error) { + h := sha256.Sum256(input) + // TODO: THIS IS A FAKE VDF. THE SPEC IS UNCLEAR ON WHAT TO REALLY DO HERE + return h[:], []byte("proof"), nil +} + +func Verify(input []byte, out []byte, proof []byte) error { + // this is a fake VDF + h := sha256.Sum256(input) + + if !bytes.Equal(h[:], out) { + return fmt.Errorf("vdf output incorrect") + } + + if !bytes.Equal(proof, []byte("proof")) { + return fmt.Errorf("vdf proof failed to validate") + } + + return nil +} diff --git a/miner/miner.go b/miner/miner.go index ff926c97b..20d217f6a 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -1,7 +1,6 @@ package miner import ( - "bytes" "context" "crypto/sha256" "fmt" @@ -17,6 +16,7 @@ import ( "github.com/filecoin-project/go-lotus/chain/actors" "github.com/filecoin-project/go-lotus/chain/address" "github.com/filecoin-project/go-lotus/chain/types" + "github.com/filecoin-project/go-lotus/lib/vdf" ) var log = logging.Logger("miner") @@ -271,24 +271,7 @@ func (m *Miner) runVDF(ctx context.Context, input []byte) ([]byte, []byte, error case <-time.After(m.Delay): } - h := sha256.Sum256(input) - // TODO: THIS IS A FAKE VDF. THE SPEC IS UNCLEAR ON WHAT TO REALLY DO HERE - return h[:], []byte("proof"), nil -} - -func minTicket(ts *types.TipSet) *types.Ticket { - if len(ts.Blocks()) == 0 { - panic("tipset has no blocks!") - } - var minTicket *types.Ticket - for _, b := range ts.Blocks() { - lastTicket := b.Tickets[len(b.Tickets)-1] - if minTicket == nil || bytes.Compare(lastTicket.VDFResult, minTicket.VDFResult) < 0 { - minTicket = lastTicket - } - } - - return minTicket + return vdf.Run(input) } func (m *Miner) scratchTicket(ctx context.Context, base *MiningBase) (*types.Ticket, error) { @@ -296,7 +279,7 @@ func (m *Miner) scratchTicket(ctx context.Context, base *MiningBase) (*types.Tic if len(base.tickets) > 0 { lastTicket = base.tickets[len(base.tickets)-1] } else { - lastTicket = minTicket(base.ts) + lastTicket = base.ts.MinTicket() } vrfOut, err := m.computeVRF(ctx, lastTicket.VDFResult)