Refactor mining logic to match specs ticket chain

This commit is contained in:
whyrusleeping 2019-11-19 09:53:00 -06:00
parent d680dab52e
commit f8eabd3db4
26 changed files with 166 additions and 216 deletions

View File

@ -23,7 +23,7 @@ type FullNode interface {
// First message is guaranteed to be of len == 1, and type == 'current'
ChainNotify(context.Context) (<-chan []*store.HeadChange, error)
ChainHead(context.Context) (*types.TipSet, error)
ChainGetRandomness(context.Context, types.TipSetKey, []*types.Ticket, int) ([]byte, error)
ChainGetRandomness(context.Context, types.TipSetKey, int64) ([]byte, error)
ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error)
ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error)
ChainGetBlockMessages(context.Context, cid.Cid) (*BlockMessages, error)
@ -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) (*types.BlockMsg, error)
MinerCreateBlock(context.Context, address.Address, *types.TipSet, *types.Ticket, types.ElectionProof, []*types.SignedMessage, uint64, uint64) (*types.BlockMsg, error)
// // UX ?

View File

@ -37,19 +37,19 @@ type FullNodeStruct struct {
CommonStruct
Internal struct {
ChainNotify func(context.Context) (<-chan []*store.HeadChange, error) `perm:"read"`
ChainHead func(context.Context) (*types.TipSet, error) `perm:"read"`
ChainGetRandomness func(context.Context, types.TipSetKey, []*types.Ticket, int) ([]byte, error) `perm:"read"`
ChainGetBlock func(context.Context, cid.Cid) (*types.BlockHeader, error) `perm:"read"`
ChainGetTipSet func(context.Context, types.TipSetKey) (*types.TipSet, error) `perm:"read"`
ChainGetBlockMessages func(context.Context, cid.Cid) (*BlockMessages, error) `perm:"read"`
ChainGetParentReceipts func(context.Context, cid.Cid) ([]*types.MessageReceipt, error) `perm:"read"`
ChainGetParentMessages func(context.Context, cid.Cid) ([]Message, error) `perm:"read"`
ChainGetTipSetByHeight func(context.Context, uint64, *types.TipSet) (*types.TipSet, error) `perm:"read"`
ChainReadObj func(context.Context, cid.Cid) ([]byte, error) `perm:"read"`
ChainSetHead func(context.Context, *types.TipSet) error `perm:"admin"`
ChainGetGenesis func(context.Context) (*types.TipSet, error) `perm:"read"`
ChainTipSetWeight func(context.Context, *types.TipSet) (types.BigInt, error) `perm:"read"`
ChainNotify func(context.Context) (<-chan []*store.HeadChange, error) `perm:"read"`
ChainHead func(context.Context) (*types.TipSet, error) `perm:"read"`
ChainGetRandomness func(context.Context, types.TipSetKey, int64) ([]byte, error) `perm:"read"`
ChainGetBlock func(context.Context, cid.Cid) (*types.BlockHeader, error) `perm:"read"`
ChainGetTipSet func(context.Context, types.TipSetKey) (*types.TipSet, error) `perm:"read"`
ChainGetBlockMessages func(context.Context, cid.Cid) (*BlockMessages, error) `perm:"read"`
ChainGetParentReceipts func(context.Context, cid.Cid) ([]*types.MessageReceipt, error) `perm:"read"`
ChainGetParentMessages func(context.Context, cid.Cid) ([]Message, error) `perm:"read"`
ChainGetTipSetByHeight func(context.Context, uint64, *types.TipSet) (*types.TipSet, error) `perm:"read"`
ChainReadObj func(context.Context, cid.Cid) ([]byte, error) `perm:"read"`
ChainSetHead func(context.Context, *types.TipSet) error `perm:"admin"`
ChainGetGenesis func(context.Context) (*types.TipSet, error) `perm:"read"`
ChainTipSetWeight func(context.Context, *types.TipSet) (types.BigInt, error) `perm:"read"`
SyncState func(context.Context) (*SyncState, error) `perm:"read"`
SyncSubmitBlock func(ctx context.Context, blk *types.BlockMsg) error `perm:"write"`
@ -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) (*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.ElectionProof, []*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,16 +237,16 @@ 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, tickets []*types.Ticket, eproof types.ElectionProof, msgs []*types.SignedMessage, ts uint64) (*types.BlockMsg, error) {
return c.Internal.MinerCreateBlock(ctx, addr, base, tickets, eproof, msgs, ts)
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) {
return c.Internal.MinerCreateBlock(ctx, addr, base, ticket, eproof, msgs, height, ts)
}
func (c *FullNodeStruct) ChainHead(ctx context.Context) (*types.TipSet, error) {
return c.Internal.ChainHead(ctx)
}
func (c *FullNodeStruct) ChainGetRandomness(ctx context.Context, pts types.TipSetKey, ticks []*types.Ticket, lb int) ([]byte, error) {
return c.Internal.ChainGetRandomness(ctx, pts, ticks, lb)
func (c *FullNodeStruct) ChainGetRandomness(ctx context.Context, pts types.TipSetKey, round int64) ([]byte, error) {
return c.Internal.ChainGetRandomness(ctx, pts, round)
}
func (c *FullNodeStruct) ChainGetTipSetByHeight(ctx context.Context, h uint64, ts *types.TipSet) (*types.TipSet, error) {

View File

@ -5,7 +5,7 @@ import (
"io"
"github.com/filecoin-project/lotus/chain/types"
cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-cid"
cbg "github.com/whyrusleeping/cbor-gen"
xerrors "golang.org/x/xerrors"
)

View File

@ -52,7 +52,7 @@ func makeTs(t *testing.T, h uint64, msgcid cid.Cid) *types.TipSet {
Height: h,
Miner: a,
Tickets: []*types.Ticket{{[]byte{byte(h % 2)}}},
Ticket: &types.Ticket{[]byte{byte(h % 2)}},
ParentStateRoot: dummyCid,
Messages: msgcid,
@ -65,7 +65,7 @@ func makeTs(t *testing.T, h uint64, msgcid cid.Cid) *types.TipSet {
Height: h,
Miner: b,
Tickets: []*types.Ticket{{[]byte{byte((h + 1) % 2)}}},
Ticket: &types.Ticket{[]byte{byte((h + 1) % 2)}},
ParentStateRoot: dummyCid,
Messages: msgcid,

View File

@ -3,6 +3,8 @@ package gen
import (
"bytes"
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"sync/atomic"
@ -46,7 +48,7 @@ type ChainGen struct {
genesis *types.BlockHeader
CurTipset *store.FullTipSet
Timestamper func(*types.TipSet, int) uint64
Timestamper func(*types.TipSet, uint64) uint64
w *wallet.Wallet
@ -195,14 +197,9 @@ func (cg *ChainGen) GenesisCar() ([]byte, error) {
return out.Bytes(), nil
}
func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m address.Address, ticks []*types.Ticket) (types.ElectionProof, *types.Ticket, error) {
func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m address.Address, round int64) (types.ElectionProof, *types.Ticket, error) {
var lastTicket *types.Ticket
if len(ticks) == 0 {
lastTicket = pts.MinTicket()
} else {
lastTicket = ticks[len(ticks)-1]
}
lastTicket := pts.MinTicket()
st := pts.ParentState()
@ -211,7 +208,8 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add
return nil, nil, xerrors.Errorf("get miner worker: %w", err)
}
vrfout, err := ComputeVRF(ctx, cg.w.Sign, worker, lastTicket.VRFProof)
vrfBase := TicketHash(lastTicket, uint64(round))
vrfout, err := ComputeVRF(ctx, cg.w.Sign, worker, vrfBase)
if err != nil {
return nil, nil, xerrors.Errorf("compute VRF: %w", err)
}
@ -220,7 +218,7 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add
VRFProof: vrfout,
}
win, eproof, err := IsRoundWinner(ctx, pts, append(ticks, tick), m, &mca{w: cg.w, sm: cg.sm})
win, eproof, err := IsRoundWinner(ctx, pts, round, m, &mca{w: cg.w, sm: cg.sm})
if err != nil {
return nil, nil, xerrors.Errorf("checking round winner failed: %w", err)
}
@ -248,23 +246,21 @@ func (cg *ChainGen) NextTipSet() (*MinedTipSet, error) {
func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Address) (*MinedTipSet, error) {
var blks []*types.FullBlock
ticketSets := make([][]*types.Ticket, len(miners))
msgs, err := cg.getRandomMessages()
if err != nil {
return nil, xerrors.Errorf("get random messages: %w", err)
}
for len(blks) == 0 {
for i, m := range miners {
proof, t, err := cg.nextBlockProof(context.TODO(), base, m, ticketSets[i])
for round := int64(base.Height() + 1); len(blks) == 0; round++ {
for _, m := range miners {
proof, t, err := cg.nextBlockProof(context.TODO(), base, m, round)
if err != nil {
return nil, xerrors.Errorf("next block proof: %w", err)
}
ticketSets[i] = append(ticketSets[i], t)
if proof != nil {
fblk, err := cg.makeBlock(base, m, proof, ticketSets[i], msgs)
fblk, err := cg.makeBlock(base, m, proof, t, uint64(round), msgs)
if err != nil {
return nil, xerrors.Errorf("making a block for next tipset failed: %w", err)
}
@ -286,16 +282,16 @@ func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Ad
}, nil
}
func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, eproof types.ElectionProof, tickets []*types.Ticket, msgs []*types.SignedMessage) (*types.FullBlock, error) {
func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, eproof types.ElectionProof, ticket *types.Ticket, height uint64, msgs []*types.SignedMessage) (*types.FullBlock, error) {
var ts uint64
if cg.Timestamper != nil {
ts = cg.Timestamper(parents, len(tickets))
ts = cg.Timestamper(parents, height-parents.Height())
} else {
ts = parents.MinTimestamp() + (uint64(len(tickets)) * build.BlockDelay)
ts = parents.MinTimestamp() + ((height - parents.Height()) * build.BlockDelay)
}
fblk, err := MinerCreateBlock(context.TODO(), cg.sm, cg.w, m, parents, tickets, eproof, msgs, ts)
fblk, err := MinerCreateBlock(context.TODO(), cg.sm, cg.w, m, parents, ticket, eproof, msgs, height, ts)
if err != nil {
return nil, err
}
@ -354,7 +350,7 @@ func (cg *ChainGen) YieldRepo() (repo.Repo, error) {
}
type MiningCheckAPI interface {
ChainGetRandomness(context.Context, types.TipSetKey, []*types.Ticket, int) ([]byte, error)
ChainGetRandomness(context.Context, types.TipSetKey, int64) ([]byte, error)
StateMinerPower(context.Context, address.Address, *types.TipSet) (api.MinerPower, error)
@ -368,8 +364,8 @@ type mca struct {
sm *stmgr.StateManager
}
func (mca mca) ChainGetRandomness(ctx context.Context, pts types.TipSetKey, ticks []*types.Ticket, lb int) ([]byte, error) {
return mca.sm.ChainStore().GetRandomness(ctx, pts.Cids(), ticks, int64(lb))
func (mca mca) ChainGetRandomness(ctx context.Context, pts types.TipSetKey, lb int64) ([]byte, error) {
return mca.sm.ChainStore().GetRandomness(ctx, pts.Cids(), int64(lb))
}
func (mca mca) StateMinerPower(ctx context.Context, maddr address.Address, ts *types.TipSet) (api.MinerPower, error) {
@ -392,8 +388,8 @@ func (mca mca) WalletSign(ctx context.Context, a address.Address, v []byte) (*ty
return mca.w.Sign(ctx, a, v)
}
func IsRoundWinner(ctx context.Context, ts *types.TipSet, ticks []*types.Ticket, miner address.Address, a MiningCheckAPI) (bool, types.ElectionProof, error) {
r, err := a.ChainGetRandomness(ctx, ts.Key(), ticks, build.EcRandomnessLookback)
func IsRoundWinner(ctx context.Context, ts *types.TipSet, round int64, miner address.Address, a MiningCheckAPI) (bool, types.ElectionProof, error) {
r, err := a.ChainGetRandomness(ctx, ts.Key(), round-build.EcRandomnessLookback)
if err != nil {
return false, nil, xerrors.Errorf("chain get randomness: %w", err)
}
@ -430,3 +426,12 @@ func ComputeVRF(ctx context.Context, sign SignFunc, w address.Address, input []b
return sig.Data, nil
}
func TicketHash(t *types.Ticket, round uint64) []byte {
h := sha256.New()
h.Write(t.VRFProof)
var roundbuf [8]byte
binary.LittleEndian.PutUint64(roundbuf[:], round)
h.Write(roundbuf[:])
return h.Sum(nil)
}

View File

@ -12,18 +12,12 @@ func testGeneration(t testing.TB, n int, msgs int) {
g.msgsPerBlock = msgs
var height int
for i := 0; i < n; i++ {
mts, err := g.NextTipSet()
if err != nil {
t.Fatalf("error at H:%d, %s", i, err)
}
ts := mts.TipSet.TipSet()
if ts.Height() != uint64(height+len(ts.Blocks()[0].Tickets)) {
t.Fatal("wrong height", ts.Height(), i, len(ts.Blocks()[0].Tickets), len(ts.Blocks()))
}
height += len(ts.Blocks()[0].Tickets)
_ = mts
}
}

View File

@ -18,14 +18,12 @@ 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, tickets []*types.Ticket, proof types.ElectionProof, msgs []*types.SignedMessage, 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.ElectionProof, 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)
}
height := parents.Height() + uint64(len(tickets))
worker, err := stmgr.GetMinerWorkerRaw(ctx, sm, st, miner)
if err != nil {
return nil, xerrors.Errorf("failed to get miner worker: %w", err)
@ -34,7 +32,7 @@ func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wal
next := &types.BlockHeader{
Miner: miner,
Parents: parents.Cids(),
Tickets: tickets,
Ticket: ticket,
Height: height,
Timestamp: timestamp,
ElectionProof: proof,

View File

@ -384,7 +384,7 @@ func MakeGenesisBlock(bs bstore.Blockstore, balances map[address.Address]types.B
b := &types.BlockHeader{
Miner: actors.InitAddress,
Tickets: []*types.Ticket{genesisticket},
Ticket: genesisticket,
ElectionProof: []byte("the Genesis block"),
Parents: []cid.Cid{},
Height: 0,

View File

@ -68,7 +68,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.
state := ts.ParentState()
r := store.NewChainRand(sm.cs, ts.Cids(), ts.Height(), nil)
r := store.NewChainRand(sm.cs, ts.Cids(), ts.Height())
return sm.CallRaw(ctx, msg, state, r, ts.Height())
}

View File

@ -102,7 +102,7 @@ func (sm *StateManager) computeTipSetState(ctx context.Context, blks []*types.Bl
cids[i] = v.Cid()
}
r := store.NewChainRand(sm.cs, cids, blks[0].Height, nil)
r := store.NewChainRand(sm.cs, cids, blks[0].Height)
vmi, err := vm.NewVM(pstate, blks[0].Height, r, address.Undef, sm.cs.Blockstore())
if err != nil {

View File

@ -3,8 +3,8 @@ package store
import (
"context"
"crypto/sha256"
"encoding/binary"
"encoding/json"
"fmt"
"sync"
"github.com/filecoin-project/lotus/build"
@ -12,7 +12,6 @@ import (
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/vm"
"go.opencensus.io/trace"
"go.uber.org/zap"
amt "github.com/filecoin-project/go-amt-ipld"
"github.com/filecoin-project/lotus/chain/types"
@ -771,24 +770,21 @@ func (cs *ChainStore) TryFillTipSet(ts *types.TipSet) (*FullTipSet, error) {
return NewFullTipSet(out), nil
}
func (cs *ChainStore) GetRandomness(ctx context.Context, blks []cid.Cid, tickets []*types.Ticket, lb int64) ([]byte, error) {
func ticketHash(t *types.Ticket, round int64) []byte {
h := sha256.New()
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], uint64(round))
h.Write(t.VRFProof)
h.Write(buf[:])
return h.Sum(nil)
}
func (cs *ChainStore) GetRandomness(ctx context.Context, blks []cid.Cid, round int64) ([]byte, error) {
ctx, span := trace.StartSpan(ctx, "store.GetRandomness")
defer span.End()
span.AddAttributes(trace.Int64Attribute("lb", lb))
if lb < 0 {
return nil, fmt.Errorf("negative lookback parameters are not valid (got %d)", lb)
}
lt := int64(len(tickets))
if lb < lt {
log.Desugar().Warn("self sampling randomness. this should be extremely rare, if you see this often it may be a bug", zap.Stack("stacktrace"))
t := tickets[lt-(1+lb)]
return t.VRFProof, nil
}
nv := lb - lt
span.AddAttributes(trace.Int64Attribute("round", round))
for {
nts, err := cs.LoadTipSet(blks)
@ -797,26 +793,21 @@ func (cs *ChainStore) GetRandomness(ctx context.Context, blks []cid.Cid, tickets
}
mtb := nts.MinTicketBlock()
lt := int64(len(mtb.Tickets))
if nv < lt {
t := mtb.Tickets[lt-(1+nv)]
return t.VRFProof, nil
}
nv -= lt
if int64(nts.Height()) <= round {
return ticketHash(nts.MinTicketBlock().Ticket, round), nil
}
// special case for lookback behind genesis block
// TODO(spec): this is not in the spec, need to sync that
if mtb.Height == 0 {
t := mtb.Tickets[0]
// round is negative
thash := ticketHash(mtb.Ticket, round*-1)
rval := t.VRFProof
for i := int64(0); i < nv; i++ {
h := sha256.Sum256(rval)
rval = h[:]
}
return rval, nil
// for negative lookbacks, just use the hash of the positive tickethash value
h := sha256.Sum256(thash)
return h[:], nil
}
blks = mtb.Parents
@ -837,36 +828,33 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h uint64, ts *types
}
for {
mtb := ts.MinTicketBlock()
if h >= ts.Height()-uint64(len(mtb.Tickets)) {
return ts, nil
}
pts, err := cs.LoadTipSet(ts.Parents())
if err != nil {
return nil, err
}
if h > pts.Height() {
return ts, nil
}
ts = pts
}
}
type chainRand struct {
cs *ChainStore
blks []cid.Cid
bh uint64
tickets []*types.Ticket
cs *ChainStore
blks []cid.Cid
bh uint64
}
func NewChainRand(cs *ChainStore, blks []cid.Cid, bheight uint64, tickets []*types.Ticket) vm.Rand {
func NewChainRand(cs *ChainStore, blks []cid.Cid, bheight uint64) vm.Rand {
return &chainRand{
cs: cs,
blks: blks,
bh: bheight,
tickets: tickets,
cs: cs,
blks: blks,
bh: bheight,
}
}
func (cr *chainRand) GetRandomness(ctx context.Context, h int64) ([]byte, error) {
lb := (int64(cr.bh) + int64(len(cr.tickets))) - h
return cr.cs.GetRandomness(ctx, cr.blks, cr.tickets, lb)
func (cr *chainRand) GetRandomness(ctx context.Context, round int64) ([]byte, error) {
return cr.cs.GetRandomness(ctx, cr.blks, round)
}

View File

@ -2,12 +2,13 @@ package store
import (
"context"
"math/big"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"golang.org/x/xerrors"
"math/big"
)
var zero = types.NewInt(0)
@ -57,7 +58,7 @@ func (cs *ChainStore) Weight(ctx context.Context, ts *types.TipSet) (types.BigIn
func (cs *ChainStore) call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) {
bstate := ts.ParentState()
r := NewChainRand(cs, ts.Cids(), ts.Height(), nil)
r := NewChainRand(cs, ts.Cids(), ts.Height())
vmi, err := vm.NewVM(bstate, ts.Height(), r, actors.NetworkAddress, cs.bs)
if err != nil {

View File

@ -27,6 +27,7 @@ import (
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/address"
"github.com/filecoin-project/lotus/chain/blocksync"
"github.com/filecoin-project/lotus/chain/gen"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
@ -466,30 +467,22 @@ func (syncer *Syncer) minerIsValid(ctx context.Context, maddr address.Address, b
return nil
}
func (syncer *Syncer) validateTickets(ctx context.Context, mworker address.Address, tickets []*types.Ticket, base *types.TipSet) error {
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()
span.AddAttributes(trace.Int64Attribute("tickets", int64(len(tickets))))
if len(tickets) == 0 {
return xerrors.Errorf("block had no tickets")
sig := &types.Signature{
Type: types.KTBLS,
Data: ticket.VRFProof,
}
cur := base.MinTicket()
for i := 0; i < len(tickets); i++ {
next := tickets[i]
vrfBase := gen.TicketHash(base.MinTicket(), round)
sig := &types.Signature{
Type: types.KTBLS,
Data: next.VRFProof,
}
log.Infof("about to verify ticket: %x %d", base.MinTicket().VRFProof, round)
// TODO: ticket signatures should also include miner address
if err := sig.Verify(mworker, cur.VRFProof); err != nil {
return xerrors.Errorf("invalid ticket, VRFProof invalid: %w", err)
}
cur = next
// 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
@ -514,9 +507,9 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err
return xerrors.Errorf("block was from the future")
}
if h.Timestamp < baseTs.MinTimestamp()+uint64(build.BlockDelay*len(h.Tickets)) {
log.Warn("timestamp funtimes: ", h.Timestamp, baseTs.MinTimestamp(), len(h.Tickets))
return xerrors.Errorf("block was generated too soon (h.ts:%d < base.mints:%d + BLOCK_DELAY:%d * tkts.len:%d)", h.Timestamp, baseTs.MinTimestamp(), build.BlockDelay, len(h.Tickets))
if h.Timestamp < baseTs.MinTimestamp()+(build.BlockDelay*(h.Height-baseTs.Height())) {
log.Warn("timestamp funtimes: ", h.Timestamp, baseTs.MinTimestamp(), h.Height, baseTs.Height())
return xerrors.Errorf("block was generated too soon (h.ts:%d < base.mints:%d + BLOCK_DELAY:%d * deltaH:%d)", h.Timestamp, baseTs.MinTimestamp(), build.BlockDelay, h.Height-baseTs.Height())
}
winnerCheck := async.Err(func() error {
@ -583,14 +576,14 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err
})
tktsCheck := async.Err(func() error {
if err := syncer.validateTickets(ctx, waddr, h.Tickets, baseTs); err != nil {
if err := syncer.validateTicket(ctx, waddr, h.Ticket, baseTs, h.Height); 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(), h.Tickets, build.EcRandomnessLookback)
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)
}

View File

@ -390,7 +390,7 @@ func TestSyncBadTimestamp(t *testing.T) {
tu.waitUntilSync(0, client)
base := tu.g.CurTipset
tu.g.Timestamper = func(pts *types.TipSet, tl int) uint64 {
tu.g.Timestamper = func(pts *types.TipSet, tl uint64) uint64 {
return pts.MinTimestamp() + (build.BlockDelay / 2)
}

View File

@ -25,7 +25,7 @@ type ElectionProof []byte
type BlockHeader struct {
Miner address.Address
Tickets []*Ticket
Ticket *Ticket
ElectionProof []byte
@ -91,7 +91,7 @@ func (blk *BlockHeader) Serialize() ([]byte, error) {
}
func (blk *BlockHeader) LastTicket() *Ticket {
return blk.Tickets[len(blk.Tickets)-1]
return blk.Ticket
}
func (blk *BlockHeader) SigningBytes() ([]byte, error) {

View File

@ -25,10 +25,8 @@ func testBlockHeader(t testing.TB) *BlockHeader {
return &BlockHeader{
Miner: addr,
ElectionProof: []byte("cats won the election"),
Tickets: []*Ticket{
&Ticket{
VRFProof: []byte("vrf proof0000000vrf proof0000000"),
},
Ticket: &Ticket{
VRFProof: []byte("vrf proof0000000vrf proof0000000"),
},
Parents: []cid.Cid{c, c},
ParentMessageReceipts: c,

View File

@ -5,7 +5,7 @@ import (
"io"
"math"
cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-cid"
cbg "github.com/whyrusleeping/cbor-gen"
xerrors "golang.org/x/xerrors"
)
@ -28,15 +28,10 @@ func (t *BlockHeader) MarshalCBOR(w io.Writer) error {
return err
}
// t.t.Tickets ([]*types.Ticket) (slice)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.Tickets)))); err != nil {
// t.t.Ticket (types.Ticket) (struct)
if err := t.Ticket.MarshalCBOR(w); err != nil {
return err
}
for _, v := range t.Tickets {
if err := v.MarshalCBOR(w); err != nil {
return err
}
}
// t.t.ElectionProof ([]uint8) (slice)
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajByteString, uint64(len(t.ElectionProof)))); err != nil {
@ -125,32 +120,27 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error {
}
}
// t.t.Tickets ([]*types.Ticket) (slice)
// t.t.Ticket (types.Ticket) (struct)
maj, extra, err = cbg.CborReadHeader(br)
if err != nil {
return err
}
if extra > 8192 {
return fmt.Errorf("t.Tickets: array too large (%d)", extra)
}
{
if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}
if extra > 0 {
t.Tickets = make([]*Ticket, extra)
}
for i := 0; i < int(extra); i++ {
var v Ticket
if err := v.UnmarshalCBOR(br); err != nil {
pb, err := br.PeekByte()
if err != nil {
return err
}
if pb == cbg.CborNull[0] {
var nbuf [1]byte
if _, err := br.Read(nbuf[:]); err != nil {
return err
}
} else {
t.Ticket = new(Ticket)
if err := t.Ticket.UnmarshalCBOR(br); err != nil {
return err
}
}
t.Tickets[i] = &v
}
// t.t.ElectionProof ([]uint8) (slice)
maj, extra, err = cbg.CborReadHeader(br)

View File

@ -165,8 +165,7 @@ func (t *Ticket) Less(o *Ticket) bool {
}
func (ts *TipSet) MinTicket() *Ticket {
b := ts.MinTicketBlock()
return b.Tickets[len(b.Tickets)-1]
return ts.MinTicketBlock().Ticket
}
func (ts *TipSet) MinTimestamp() uint64 {

View File

@ -161,8 +161,8 @@ eventLoop:
log.Errorf("failed to get best mining candidate: %s", err)
continue
}
if base.ts.Equals(lastBase.ts) && len(lastBase.tickets) == len(base.tickets) {
log.Errorf("BestMiningCandidate from the previous round: %s (tkts:%d)", lastBase.ts.Cids(), len(lastBase.tickets))
if base.ts.Equals(lastBase.ts) && lastBase.nullRounds == base.nullRounds {
log.Errorf("BestMiningCandidate from the previous round: %s (nulls:%d)", lastBase.ts.Cids(), lastBase.nullRounds)
time.Sleep(build.BlockDelay * time.Second)
continue
}
@ -205,15 +205,15 @@ eventLoop:
}
}
} else {
nextRound := time.Unix(int64(base.ts.MinTimestamp()+uint64(build.BlockDelay*len(base.tickets))), 0)
nextRound := time.Unix(int64(base.ts.MinTimestamp()+uint64(build.BlockDelay*base.nullRounds)), 0)
time.Sleep(time.Until(nextRound))
}
}
}
type MiningBase struct {
ts *types.TipSet
tickets []*types.Ticket
ts *types.TipSet
nullRounds uint64
}
func (m *Miner) GetBestMiningCandidate(ctx context.Context) (*MiningBase, error) {
@ -253,13 +253,13 @@ func (m *Miner) mineOne(ctx context.Context, addr address.Address, base *MiningB
return nil, errors.Wrap(err, "scratching ticket failed")
}
win, proof, err := gen.IsRoundWinner(ctx, base.ts, append(base.tickets, ticket), addr, &m.api)
win, proof, err := gen.IsRoundWinner(ctx, base.ts, int64(base.ts.Height()+base.nullRounds+1), addr, &m.api)
if err != nil {
return nil, errors.Wrap(err, "failed to check if we win next round")
}
if !win {
m.submitNullTicket(base, ticket)
base.nullRounds++
return nil, nil
}
@ -272,11 +272,6 @@ func (m *Miner) mineOne(ctx context.Context, addr address.Address, base *MiningB
return b, nil
}
func (m *Miner) submitNullTicket(base *MiningBase, ticket *types.Ticket) {
base.tickets = append(base.tickets, ticket)
m.lastWork = base
}
func (m *Miner) computeVRF(ctx context.Context, addr address.Address, input []byte) ([]byte, error) {
w, err := m.getMinerWorker(ctx, addr, nil)
if err != nil {
@ -309,14 +304,11 @@ func (m *Miner) getMinerWorker(ctx context.Context, addr address.Address, ts *ty
}
func (m *Miner) scratchTicket(ctx context.Context, addr address.Address, base *MiningBase) (*types.Ticket, error) {
var lastTicket *types.Ticket
if len(base.tickets) > 0 {
lastTicket = base.tickets[len(base.tickets)-1]
} else {
lastTicket = base.ts.MinTicket()
}
round := base.ts.Height() + base.nullRounds
vrfOut, err := m.computeVRF(ctx, addr, lastTicket.VRFProof)
vrfBase := gen.TicketHash(base.ts.MinTicket(), round)
vrfOut, err := m.computeVRF(ctx, addr, vrfBase)
if err != nil {
return nil, err
}
@ -338,10 +330,12 @@ func (m *Miner) createBlock(base *MiningBase, addr address.Address, ticket *type
return nil, xerrors.Errorf("message filtering failed: %w", err)
}
uts := base.ts.MinTimestamp() + uint64(build.BlockDelay*(len(base.tickets)+1))
uts := base.ts.MinTimestamp() + uint64(build.BlockDelay*(base.nullRounds+1))
nheight := base.ts.Height() + base.nullRounds + 1
// why even return this? that api call could just submit it for us
return m.api.MinerCreateBlock(context.TODO(), addr, base.ts, append(base.tickets, ticket), proof, msgs, uint64(uts))
return m.api.MinerCreateBlock(context.TODO(), addr, base.ts, ticket, proof, msgs, nheight, uint64(uts))
}
type actorLookup func(context.Context, address.Address, *types.TipSet) (*types.Actor, error)

View File

@ -28,8 +28,8 @@ func (a *ChainAPI) ChainHead(context.Context) (*types.TipSet, error) {
return a.Chain.GetHeaviestTipSet(), nil
}
func (a *ChainAPI) ChainGetRandomness(ctx context.Context, pts types.TipSetKey, tickets []*types.Ticket, lb int) ([]byte, error) {
return a.Chain.GetRandomness(ctx, pts.Cids(), tickets, int64(lb))
func (a *ChainAPI) ChainGetRandomness(ctx context.Context, pts types.TipSetKey, round int64) ([]byte, error) {
return a.Chain.GetRandomness(ctx, pts.Cids(), round)
}
func (a *ChainAPI) ChainGetBlock(ctx context.Context, msg cid.Cid) (*types.BlockHeader, error) {

View File

@ -177,8 +177,8 @@ 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, tickets []*types.Ticket, proof types.ElectionProof, msgs []*types.SignedMessage, ts uint64) (*types.BlockMsg, error) {
fblk, err := gen.MinerCreateBlock(ctx, a.StateManager, a.Wallet, addr, parents, tickets, proof, msgs, ts)
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) {
fblk, err := gen.MinerCreateBlock(ctx, a.StateManager, a.Wallet, addr, parents, ticket, proof, msgs, height, ts)
if err != nil {
return nil, err
}

View File

@ -221,7 +221,7 @@ func SealTicketGen(api api.FullNode) storage.TicketFn {
return nil, xerrors.Errorf("getting head ts for SealTicket failed: %w", err)
}
r, err := api.ChainGetRandomness(ctx, ts.Key(), nil, build.SealRandomnessLookback)
r, err := api.ChainGetRandomness(ctx, ts.Key(), int64(ts.Height())-build.SealRandomnessLookback)
if err != nil {
return nil, xerrors.Errorf("getting randomness for SealTicket failed: %w", err)
}

View File

@ -64,7 +64,7 @@ type storageMinerApi interface {
ChainHead(context.Context) (*types.TipSet, error)
ChainNotify(context.Context) (<-chan []*store.HeadChange, error)
ChainGetRandomness(context.Context, types.TipSetKey, []*types.Ticket, int) ([]byte, error)
ChainGetRandomness(context.Context, types.TipSetKey, int64) ([]byte, error)
ChainGetTipSetByHeight(context.Context, uint64, *types.TipSet) (*types.TipSet, error)
ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error)

View File

@ -154,9 +154,8 @@ func (p *post) preparePost(ctx context.Context) error {
p.sset = sset
// Compute how many blocks back we have to look from the given tipset for the PoSt challenge
challengeLookback := int((int64(p.ts.Height()) - int64(p.ppe)) + int64(build.PoStChallangeTime) + int64(build.PoStRandomnessLookback))
r, err := p.m.api.ChainGetRandomness(ctx, p.ts.Key(), nil, challengeLookback)
challengeRound := int64(p.ppe) - int64(build.PoStChallangeTime+build.PoStRandomnessLookback)
r, err := p.m.api.ChainGetRandomness(ctx, p.ts.Key(), challengeRound)
if err != nil {
return xerrors.Errorf("failed to get chain randomness for post (ts=%d; ppe=%d): %w", p.ts.Height(), p.ppe, err)
}

View File

@ -145,7 +145,7 @@ func (m *Miner) preCommitted(ctx context.Context, sector SectorInfo) (func(*Sect
log.Infof("precommit for sector %d made it on chain, will start proof computation at height %d", sector.SectorID, randHeight)
err = m.events.ChainAt(func(ctx context.Context, ts *types.TipSet, curH uint64) error {
rand, err := m.api.ChainGetRandomness(ctx, ts.Key(), nil, int(ts.Height()-randHeight))
rand, err := m.api.ChainGetRandomness(ctx, ts.Key(), int64(randHeight))
if err != nil {
return xerrors.Errorf("failed to get randomness for computing seal proof: %w", err)
}

View File

@ -134,12 +134,8 @@ func loadTipsets(ctx context.Context, api api.FullNode, curr *types.TipSet, lowe
log.Printf("Walking back { height:%d }", curr.Height())
tipsets = append(tipsets, curr)
ph := ParentTipsetHeight(curr)
if ph == 0 {
break
}
prev, err := api.ChainGetTipSetByHeight(ctx, ph, curr)
tsk := types.NewTipSetKey(curr.Parents()...)
prev, err := api.ChainGetTipSet(ctx, tsk)
if err != nil {
return tipsets, err
}
@ -154,11 +150,6 @@ func loadTipsets(ctx context.Context, api api.FullNode, curr *types.TipSet, lowe
return tipsets, nil
}
func ParentTipsetHeight(tipset *types.TipSet) uint64 {
mtb := tipset.MinTicketBlock()
return tipset.Height() - uint64(len(mtb.Tickets)) - 1
}
func GetFullNodeAPI(repo string) (api.FullNode, jsonrpc.ClientCloser, error) {
addr, headers, err := getAPI(repo)
if err != nil {