From 7723be709f08ff47f75c08f3df1a42704b0a0d7e Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 22 Dec 2021 09:26:11 +0300 Subject: [PATCH] feat(ethash): flashbots_getWork RPC with profit --- cmd/evm/internal/t8ntool/block.go | 2 +- consensus/clique/clique.go | 2 +- consensus/consensus.go | 2 +- consensus/ethash/api.go | 7 ++++-- consensus/ethash/ethash.go | 6 +++++ consensus/ethash/ethash_test.go | 6 ++--- consensus/ethash/flashbots_api.go | 38 +++++++++++++++++++++++++++++++ consensus/ethash/sealer.go | 26 +++++++++++++-------- miner/worker.go | 2 +- 9 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 consensus/ethash/flashbots_api.go diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go index d4edd33bdeb7..ceb2388cd468 100644 --- a/cmd/evm/internal/t8ntool/block.go +++ b/cmd/evm/internal/t8ntool/block.go @@ -188,7 +188,7 @@ func (i *bbInput) sealEthash(block *types.Block) (*types.Block, error) { // If the testmode is used, the sealer will return quickly, and complain // "Sealing result is not read by miner" if it cannot write the result. results := make(chan *types.Block, 1) - if err := engine.Seal(nil, block, results, nil); err != nil { + if err := engine.Seal(nil, block, nil, results, nil); err != nil { panic(fmt.Sprintf("failed to seal block: %v", err)) } found := <-results diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 38597e15267a..6549d5f53956 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -590,7 +590,7 @@ func (c *Clique) Authorize(signer common.Address, signFn SignerFn) { // Seal implements consensus.Engine, attempting to create a sealed block using // the local signing credentials. -func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { +func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, profit *big.Int, results chan<- *types.Block, stop <-chan struct{}) error { header := block.Header() // Sealing the genesis block is not supported diff --git a/consensus/consensus.go b/consensus/consensus.go index 2a5aac945d9d..6039698140d8 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -102,7 +102,7 @@ type Engine interface { // // Note, the method returns immediately and will send the result async. More // than one result may also be returned depending on the consensus algorithm. - Seal(chain ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error + Seal(chain ChainHeaderReader, block *types.Block, profit *big.Int, results chan<- *types.Block, stop <-chan struct{}) error // SealHash returns the hash of a block prior to it being sealed. SealHash(header *types.Header) common.Hash diff --git a/consensus/ethash/api.go b/consensus/ethash/api.go index f4d3802e0b37..8aece9c7bb89 100644 --- a/consensus/ethash/api.go +++ b/consensus/ethash/api.go @@ -44,7 +44,7 @@ func (api *API) GetWork() ([4]string, error) { } var ( - workCh = make(chan [4]string, 1) + workCh = make(chan [5]string, 1) errc = make(chan error, 1) ) select { @@ -53,7 +53,10 @@ func (api *API) GetWork() ([4]string, error) { return [4]string{}, errEthashStopped } select { - case work := <-workCh: + case fullWork := <-workCh: + var work [4]string + copy(work[:], fullWork[:4]) + return work, nil case err := <-errc: return [4]string{}, err diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index 4e33d99c8dde..ad8dd1c34760 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -683,6 +683,12 @@ func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API { Service: &API{ethash}, Public: true, }, + { + Namespace: "flashbots", + Version: "1.0", + Service: &FlashbotsAPI{ethash}, + Public: true, + }, } } diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go index 382eefeecf12..0b1c40572611 100644 --- a/consensus/ethash/ethash_test.go +++ b/consensus/ethash/ethash_test.go @@ -38,7 +38,7 @@ func TestTestMode(t *testing.T) { defer ethash.Close() results := make(chan *types.Block) - err := ethash.Seal(nil, types.NewBlockWithHeader(header), results, nil) + err := ethash.Seal(nil, types.NewBlockWithHeader(header), nil, results, nil) if err != nil { t.Fatalf("failed to seal block: %v", err) } @@ -111,7 +111,7 @@ func TestRemoteSealer(t *testing.T) { // Push new work. results := make(chan *types.Block) - ethash.Seal(nil, block, results, nil) + ethash.Seal(nil, block, nil, results, nil) var ( work [4]string @@ -128,7 +128,7 @@ func TestRemoteSealer(t *testing.T) { header = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)} block = types.NewBlockWithHeader(header) sealhash = ethash.SealHash(header) - ethash.Seal(nil, block, results, nil) + ethash.Seal(nil, block, nil, results, nil) if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() { t.Error("expect to return the latest pushed work") diff --git a/consensus/ethash/flashbots_api.go b/consensus/ethash/flashbots_api.go new file mode 100644 index 000000000000..527d2a44352e --- /dev/null +++ b/consensus/ethash/flashbots_api.go @@ -0,0 +1,38 @@ +package ethash + +import "errors" + +// FlashbotsAPI exposes Flashbots related methods for the RPC interface. +type FlashbotsAPI struct { + ethash *Ethash +} + +// GetWork returns a work package for external miner. +// +// The work package consists of 5 strings: +// result[0] - 32 bytes hex encoded current block header pow-hash +// result[1] - 32 bytes hex encoded seed hash used for DAG +// result[2] - 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty +// result[3] - hex encoded block number +// result[4] - hex encoded profit generated from this block +func (api *FlashbotsAPI) GetWork() ([5]string, error) { + if api.ethash.remote == nil { + return [5]string{}, errors.New("not supported") + } + + var ( + workCh = make(chan [5]string, 1) + errc = make(chan error, 1) + ) + select { + case api.ethash.remote.fetchWorkCh <- &sealWork{errc: errc, res: workCh}: + case <-api.ethash.remote.exitCh: + return [5]string{}, errEthashStopped + } + select { + case work := <-workCh: + return work, nil + case err := <-errc: + return [5]string{}, err + } +} diff --git a/consensus/ethash/sealer.go b/consensus/ethash/sealer.go index 6fa60ef6a8bb..d2b9253e5c34 100644 --- a/consensus/ethash/sealer.go +++ b/consensus/ethash/sealer.go @@ -48,7 +48,7 @@ var ( // Seal implements consensus.Engine, attempting to find a nonce that satisfies // the block's difficulty requirements. -func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { +func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, profit *big.Int, results chan<- *types.Block, stop <-chan struct{}) error { // If we're running a fake PoW, simply return a 0 nonce immediately if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake { header := block.Header() @@ -62,7 +62,7 @@ func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block } // If we're running a shared PoW, delegate sealing to it if ethash.shared != nil { - return ethash.shared.Seal(chain, block, results, stop) + return ethash.shared.Seal(chain, block, profit, results, stop) } // Create a runner and the multiple search threads it directs abort := make(chan struct{}) @@ -86,7 +86,7 @@ func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block } // Push new work to remote sealer if ethash.remote != nil { - ethash.remote.workCh <- &sealTask{block: block, results: results} + ethash.remote.workCh <- &sealTask{block: block, profit: profit, results: results} } var ( pend sync.WaitGroup @@ -117,7 +117,7 @@ func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block case <-ethash.update: // Thread count was changed on user request, restart close(abort) - if err := ethash.Seal(chain, block, results, stop); err != nil { + if err := ethash.Seal(chain, block, profit, results, stop); err != nil { ethash.config.Log.Error("Failed to restart sealing after update", "err", err) } } @@ -194,7 +194,7 @@ type remoteSealer struct { works map[common.Hash]*types.Block rates map[common.Hash]hashrate currentBlock *types.Block - currentWork [4]string + currentWork [5]string notifyCtx context.Context cancelNotify context.CancelFunc // cancels all notification requests reqWG sync.WaitGroup // tracks notification request goroutines @@ -215,6 +215,7 @@ type remoteSealer struct { // sealTask wraps a seal block with relative result channel for remote sealer thread. type sealTask struct { block *types.Block + profit *big.Int results chan<- *types.Block } @@ -239,7 +240,7 @@ type hashrate struct { // sealWork wraps a seal work package for remote sealer. type sealWork struct { errc chan error - res chan [4]string + res chan [5]string } func startRemoteSealer(ethash *Ethash, urls []string, noverify bool) *remoteSealer { @@ -281,7 +282,7 @@ func (s *remoteSealer) loop() { // Update current work with new received block. // Note same work can be past twice, happens when changing CPU threads. s.results = work.results - s.makeWork(work.block) + s.makeWork(work.block, work.profit) s.notifyWork() case work := <-s.fetchWorkCh: @@ -338,18 +339,23 @@ func (s *remoteSealer) loop() { // makeWork creates a work package for external miner. // -// The work package consists of 3 strings: +// The work package consists of 5 strings: // result[0], 32 bytes hex encoded current block header pow-hash // result[1], 32 bytes hex encoded seed hash used for DAG // result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty // result[3], hex encoded block number -func (s *remoteSealer) makeWork(block *types.Block) { +// result[4], hex encoded profit generated from this block, if present +func (s *remoteSealer) makeWork(block *types.Block, profit *big.Int) { hash := s.ethash.SealHash(block.Header()) s.currentWork[0] = hash.Hex() s.currentWork[1] = common.BytesToHash(SeedHash(block.NumberU64())).Hex() s.currentWork[2] = common.BytesToHash(new(big.Int).Div(two256, block.Difficulty()).Bytes()).Hex() s.currentWork[3] = hexutil.EncodeBig(block.Number()) + if profit != nil { + s.currentWork[4] = hexutil.EncodeBig(profit) + } + // Trace the seal work fetched by remote sealer. s.currentBlock = block s.works[hash] = block @@ -375,7 +381,7 @@ func (s *remoteSealer) notifyWork() { } } -func (s *remoteSealer) sendNotification(ctx context.Context, url string, json []byte, work [4]string) { +func (s *remoteSealer) sendNotification(ctx context.Context, url string, json []byte, work [5]string) { defer s.reqWG.Done() req, err := http.NewRequest("POST", url, bytes.NewReader(json)) diff --git a/miner/worker.go b/miner/worker.go index 0b70e0d07fac..ce1964701b3e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -641,7 +641,7 @@ func (w *worker) taskLoop() { w.pendingTasks[sealHash] = task w.pendingMu.Unlock() - if err := w.engine.Seal(w.chain, task.block, w.resultCh, stopCh); err != nil { + if err := w.engine.Seal(w.chain, task.block, task.profit, w.resultCh, stopCh); err != nil { log.Warn("Block sealing failed", "err", err) w.pendingMu.Lock() delete(w.pendingTasks, sealHash)