Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor the miner #1545

Merged
merged 98 commits into from
May 28, 2021
Merged

Refactor the miner #1545

merged 98 commits into from
May 28, 2021

Conversation

trianglesphere
Copy link
Contributor

@trianglesphere trianglesphere commented May 12, 2021

Description

This refactors the miner with a couple goals

  1. Simplify the block construction logic.
  2. Enable pre-construciton / pre-sharing of blocks in the miner
  3. Fix the pending state bug

I worked towards these goals by

  • Reducing the number of go-routines passing work to each other. There is now 1 main loop goroutine.
  • Make the block creation process as linear as possible (and use contexts to cancel instead of global interrupts).
    • Each step in the block production cycle is now done in miner/block.go
    • There is no longer a shared "current environment" for the in flight block, but a new environment is made for each request (which is the blockState struct).
    • Block production is cancelable with contexts instead of global interrupts
    • I have removed as much blocking as possible from engine.Prepare
      • It no longer sleeps until the block time - that is done in the worker
      • It does not wait until the next sequence for nodes that are not validating

Other changes

  • Removes proof of work specific miner interfaces.
  • Removes miner/unconfirmed.go
  • Includes some upstream changes made to miner/miner.go to simplify the start up logic.

Tested

Unit tests still pass.

Tested end to end on trianglesphere/miner_refactor_v2
TODO: test that this fixes the pending bug.

Related issues

Backwards compatibility

Yes

// addParentSeal blocks for up to 500ms waiting for the core to reach the target sequence.
// Prepare is called from non-validators, so don't bother with the parent seal unless this
// block is to be proposed instead of for the local state.
if sb.IsValidating() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is awckward...

we have a method call addParentSeal() that does a sleep. As a reader, why would adding a seal implies sleeping?

And then i have a Prepare() than ask if we are validating...

I know this is not from this PR, and the ware only bringing up the awckarness to the surface. But i wonder if there's a clearer way to express this.

@@ -223,6 +223,7 @@ func makeBlockWithoutSeal(chain *core.BlockChain, engine *Backend, parent *types
// The worker that calls Prepare is the one filling the Coinbase
header.Coinbase = engine.address
engine.Prepare(chain, header)
time.Sleep(time.Until(time.Unix(int64(header.Time), 0)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to sleep? would be ncie if we didn't as i've seen we are not really testing this behaviuor, but just need to create blocks on the test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests have issues if we don't sleep at all, but it's definitely possible to sleep for less time.

miner/miner.go Outdated Show resolved Hide resolved
miner/worker.go Show resolved Hide resolved
miner/worker.go Show resolved Hide resolved
miner/worker.go Outdated Show resolved Hide resolved
miner/worker.go Show resolved Hide resolved
}

// prepareBlock intializes a new blockState that is ready to have transaction included to.
func prepareBlock(w *worker) (*blockState, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would say this is cheating: so, to avoid having a worker method in another file, you make it a standalone function...

but you access worker state is if where your private state.

for example, using the Lock to access internal state. This can be nasty, since the whole idea of having an object with "private state" is that you don't need to worry about anyone else when modifying it... that's encapsulation. But we don't have any of that here.

I can look to the other side for now, if we want to get this to master, but it is worth mentioning nonetheless

cpy[i] = new(types.Log)
*cpy[i] = *l
}
w.pendingLogsFeed.Send(cpy)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to me it's ackward that we are accessing an internal feed object on the worker here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea. This is the same thing as above that the miner used to be so entangled in the block production that making it only depend on specific fields is a pain.

}
}
recommit = time.Duration(int64(next))
w.updatePendingBlock(b)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asking again, does it make sense to have a pending block with no transactions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure that the pending block is the latest head block

}
for {
select {
case <-w.startCh:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we call start() twice that means we will cancel current work and start again, feature or bug?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. Unintended consequence that doesn't really matter. We want to cancel the pending block and start proposing blocks on start. It would be possible to gate sending to the start channel on the current miner state, but it doesn't seem like a big issue.


var coalescedLogs []*types.Log
txsCh := make(chan core.NewTxsEvent, txChanSize)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we try to get txpool configured size to set this size?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but this isn't regressing.

* Remove unused miner flags and configuration options

* Remove threads flag. Mark GasPrice as deprecated

- Removes MinerThreads flags, it was not used.
- Marks GasPrice flag as deprecated
- Maps miner.Start/Stop method on the web3js api

* pr comment
Copy link
Contributor

@mcortesi mcortesi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Great work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RPC "pending" block can be behind "latest" block
4 participants