From 87efc9d5360dcd3e49a86133dedcbd2c0eac3f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 30 Jun 2020 11:26:27 +0100 Subject: [PATCH] add godocs to miner objects. --- miner/miner.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/miner/miner.go b/miner/miner.go index efdbe489652..eb7dd95f54d 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -37,7 +37,13 @@ const ( evtTypeBlockMined = iota ) -// returns a callback reporting whether we mined a blocks in this round +// waitFunc is expected to pace block mining at the configured network rate. +// +// baseTime is the timestamp of the mining base, i.e. the timestamp +// of the tipset we're planning to construct upon. +// +// Upon each mining loop iteration, the returned callback is called reporting +// whether we mined a block in this round or not. type waitFunc func(ctx context.Context, baseTime uint64) (func(bool, abi.ChainEpoch, error), abi.ChainEpoch, error) func randTimeOffset(width time.Duration) time.Duration { @@ -48,6 +54,8 @@ func randTimeOffset(width time.Duration) time.Duration { return val - (width / 2) } +// NewMiner instantiates a miner with a concrete WinningPoStProver and a miner +// address (which can be different from the worker's address). func NewMiner(api api.FullNode, epp gen.WinningPoStProver, addr address.Address, sf *slashfilter.SlashFilter, j journal.Journal) *Miner { arc, err := lru.NewARC(10000) if err != nil { @@ -59,7 +67,16 @@ func NewMiner(api api.FullNode, epp gen.WinningPoStProver, addr address.Address, epp: epp, address: addr, waitFunc: func(ctx context.Context, baseTime uint64) (func(bool, abi.ChainEpoch, error), abi.ChainEpoch, error) { - // Wait around for half the block time in case other parents come in + // wait around for half the block time in case other parents come in + // + // if we're mining a block in the past via catch-up/rush mining, + // such as when recovering from a network halt, this sleep will be + // for a negative duration, and therefore **will return + // immediately**. + // + // the result is that we WILL NOT wait, therefore fast-forwarding + // and thus healing the chain by backfilling it with null rounds + // rapidly. deadline := baseTime + build.PropagationDelaySecs baseT := time.Unix(int64(deadline), 0) @@ -79,6 +96,9 @@ func NewMiner(api api.FullNode, epp gen.WinningPoStProver, addr address.Address, } } +// Miner encapsulates the mining processes of the system. +// +// Refer to the godocs on mineOne and mine methods for more detail. type Miner struct { api api.FullNode @@ -91,15 +111,20 @@ type Miner struct { waitFunc waitFunc + // lastWork holds the last MiningBase we built upon. lastWork *MiningBase - sf *slashfilter.SlashFilter + sf *slashfilter.SlashFilter + // minedBlockHeights is a safeguard that caches the last heights we mined. + // It is consulted before publishing a newly mined block, for a sanity check + // intended to avoid slashings in case of a bug. minedBlockHeights *lru.ARCCache evtTypes [1]journal.EventType journal journal.Journal } +// Address returns the address of the miner. func (m *Miner) Address() address.Address { m.lk.Lock() defer m.lk.Unlock() @@ -107,7 +132,9 @@ func (m *Miner) Address() address.Address { return m.address } -func (m *Miner) Start(ctx context.Context) error { +// Start starts the mining operation. It spawns a goroutine and returns +// immediately. Start is not idempotent. +func (m *Miner) Start(_ context.Context) error { m.lk.Lock() defer m.lk.Unlock() if m.stop != nil { @@ -118,6 +145,8 @@ func (m *Miner) Start(ctx context.Context) error { return nil } +// Stop stops the mining operation. It is not idempotent, and multiple adjacent +// calls to Stop will fail. func (m *Miner) Stop(ctx context.Context) error { m.lk.Lock() @@ -145,6 +174,28 @@ func (m *Miner) niceSleep(d time.Duration) bool { } } +// mine runs the mining loop. It performs the following: +// +// 1. Queries our current best currently-known mining candidate (tipset to +// build upon). +// 2. Waits until the propagation delay of the network has elapsed (currently +// 6 seconds). The waiting is done relative to the timestamp of the best +// candidate, which means that if it's way in the past, we won't wait at +// all (e.g. in catch-up or rush mining). +// 3. After the wait, we query our best mining candidate. This will be the one +// we'll work with. +// 4. Sanity check that we _actually_ have a new mining base to mine on. If +// not, wait one epoch + propagation delay, and go back to the top. +// 5. We attempt to mine a block, by calling mineOne (refer to godocs). This +// method will either return a block if we were eligible to mine, or nil +// if we weren't. +// 6a. If we mined a block, we update our state and push it out to the network +// via gossipsub. +// 6b. If we didn't mine a block, we consider this to be a nil round on top of +// the mining base we selected. If other miner or miners on the network +// were eligible to mine, we will receive their blocks via gossipsub and +// we will select that tipset on the next iteration of the loop, thus +// discarding our null round. func (m *Miner) mine(ctx context.Context) { ctx, span := trace.StartSpan(ctx, "/mine") defer span.End() @@ -305,11 +356,19 @@ minerLoop: } } +// MiningBase is the tipset on top of which we plan to construct our next block. +// Refer to godocs on GetBestMiningCandidate. type MiningBase struct { TipSet *types.TipSet NullRounds abi.ChainEpoch } +// GetBestMiningCandidate implements the fork choice rule from a miner's +// perspective. +// +// It obtains the current chain head (HEAD), and compares it to the last tipset +// we selected as our mining base (LAST). If HEAD's weight is larger than +// LAST's weight, it selects HEAD to build on. Else, it selects LAST. func (m *Miner) GetBestMiningCandidate(ctx context.Context) (*MiningBase, error) { m.lk.Lock() defer m.lk.Unlock()