From eb4d322470bdccf2040e1c70f29f3f1c7a600565 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 28 Apr 2022 10:56:40 +0200
Subject: [PATCH 01/83] Implement geth-embedded builder API
---
README.md | 392 +++---------------------------
README.original.md | 379 +++++++++++++++++++++++++++++
builder/backend.go | 436 ++++++++++++++++++++++++++++++++++
builder/backend_test.go | 233 ++++++++++++++++++
builder/beacon_client.go | 200 ++++++++++++++++
builder/beacon_client_test.go | 268 +++++++++++++++++++++
builder/index.go | 90 +++++++
builder/service.go | 108 +++++++++
builder/validator.go | 49 ++++
cmd/geth/config.go | 14 +-
cmd/geth/main.go | 11 +
cmd/utils/flags.go | 50 +++-
core/beacon/types.go | 1 +
core/types/block.go | 2 +
eth/backend.go | 25 ++
eth/catalyst/api.go | 16 +-
eth/catalyst/api_test.go | 4 +-
eth/catalyst/queue.go | 9 +-
go.mod | 51 ++--
go.sum | 169 +++++++------
miner/miner.go | 8 +-
miner/stress/beacon/main.go | 11 +-
miner/worker.go | 21 +-
miner/worker_test.go | 4 +-
24 files changed, 2060 insertions(+), 491 deletions(-)
create mode 100644 README.original.md
create mode 100644 builder/backend.go
create mode 100644 builder/backend_test.go
create mode 100644 builder/beacon_client.go
create mode 100644 builder/beacon_client_test.go
create mode 100644 builder/index.go
create mode 100644 builder/service.go
create mode 100644 builder/validator.go
diff --git a/README.md b/README.md
index b20eb5b748..f831771269 100644
--- a/README.md
+++ b/README.md
@@ -1,379 +1,43 @@
-## Go Ethereum
+[geth readme](README.original.md)
-Official Golang implementation of the Ethereum protocol.
+# Builder API
-[](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc)
-[](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
-[](https://travis-ci.com/ethereum/go-ethereum)
-[](https://discord.gg/nthXNEv)
+Builder API implementing [builder spec](https://github.com/ethereum/builder-specs), making geth into a standalone block builder.
-Automated builds are available for stable releases and the unstable master branch. Binary
-archives are published at https://geth.ethereum.org/downloads/.
+Run on your favorite network, including Kiln and local devnet.
-## Building the source
+Requires forkchoice update to be sent for block building, on public testnets run beacon node modified to send forkchoice update on every slot [example modified beacon client (lighthouse)](https://github.com/flashbots/lighthouse)
-For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/install-and-build/installing-geth).
+Test with [mev-boost](https://github.com/flashbots/mev-boost) and [mev-boost test cli](https://github.com/flashbots/mev-boost/tree/main/cmd/test-cli).
-Building `geth` requires both a Go (version 1.16 or later) and a C compiler. You can install
-them using your favourite package manager. Once the dependencies are installed, run
+Provides summary page at the listening address' root (http://localhost:28545 by default).
-```shell
-make geth
-```
-
-or, to build the full suite of utilities:
-
-```shell
-make all
-```
-
-## Executables
-
-The go-ethereum project comes with several wrappers/executables found in the `cmd`
-directory.
-
-| Command | Description |
-| :-----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI page](https://geth.ethereum.org/docs/interface/command-line-options) for command line options. |
-| `clef` | Stand-alone signing tool, which can be used as a backend signer for `geth`. |
-| `devp2p` | Utilities to interact with nodes on the networking layer, without running a full blockchain. |
-| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://docs.soliditylang.org/en/develop/abi-spec.html) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://geth.ethereum.org/docs/dapp/native-bindings) page for details. |
-| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
-| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). |
-| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
-| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
-
-## Running `geth`
-
-Going through all the possible command line flags is out of scope here (please consult our
-[CLI Wiki page](https://geth.ethereum.org/docs/interface/command-line-options)),
-but we've enumerated a few common parameter combos to get you up to speed quickly
-on how you can run your own `geth` instance.
-
-### Hardware Requirements
-
-Minimum:
-
-* CPU with 2+ cores
-* 4GB RAM
-* 1TB free storage space to sync the Mainnet
-* 8 MBit/sec download Internet service
-
-Recommended:
-
-* Fast CPU with 4+ cores
-* 16GB+ RAM
-* High Performance SSD with at least 1TB free space
-* 25+ MBit/sec download Internet service
-
-### Full node on the main Ethereum network
-
-By far the most common scenario is people wanting to simply interact with the Ethereum
-network: create accounts; transfer funds; deploy and interact with contracts. For this
-particular use-case the user doesn't care about years-old historical data, so we can
-sync quickly to the current state of the network. To do so:
-
-```shell
-$ geth console
-```
-
-This command will:
- * Start `geth` in snap sync mode (default, can be changed with the `--syncmode` flag),
- causing it to download more data in exchange for avoiding processing the entire history
- of the Ethereum network, which is very CPU intensive.
- * Start up `geth`'s built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interface/javascript-console),
- (via the trailing `console` subcommand) through which you can interact using [`web3` methods](https://github.com/ChainSafe/web3.js/blob/0.20.7/DOCUMENTATION.md)
- (note: the `web3` version bundled within `geth` is very old, and not up to date with official docs),
- as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/rpc/server).
- This tool is optional and if you leave it out you can always attach to an already running
- `geth` instance with `geth attach`.
-
-### A Full node on the Görli test network
-
-Transitioning towards developers, if you'd like to play around with creating Ethereum
-contracts, you almost certainly would like to do that without any real money involved until
-you get the hang of the entire system. In other words, instead of attaching to the main
-network, you want to join the **test** network with your node, which is fully equivalent to
-the main network, but with play-Ether only.
-
-```shell
-$ geth --goerli console
-```
-
-The `console` subcommand has the exact same meaning as above and they are equally
-useful on the testnet too. Please, see above for their explanations if you've skipped here.
-
-Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a bit:
-
- * Instead of connecting the main Ethereum network, the client will connect to the Görli
- test network, which uses different P2P bootnodes, different network IDs and genesis
- states.
- * Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth`
- will nest itself one level deeper into a `goerli` subfolder (`~/.ethereum/goerli` on
- Linux). Note, on OSX and Linux this also means that attaching to a running testnet node
- requires the use of a custom endpoint since `geth attach` will try to attach to a
- production node endpoint by default, e.g.,
- `geth attach /goerli/geth.ipc`. Windows users are not affected by
- this.
-
-*Note: Although there are some internal protective measures to prevent transactions from
-crossing over between the main network and test network, you should make sure to always
-use separate accounts for play-money and real-money. Unless you manually move
-accounts, `geth` will by default correctly separate the two networks and will not make any
-accounts available between them.*
-
-### Full node on the Rinkeby test network
-
-Go Ethereum also supports connecting to the older proof-of-authority based test network
-called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community.
-
-```shell
-$ geth --rinkeby console
-```
-
-### Full node on the Ropsten test network
-
-In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The
-Ropsten test network is based on the Ethash proof-of-work consensus algorithm. As such,
-it has certain extra overhead and is more susceptible to reorganization attacks due to the
-network's low difficulty/security.
+## How it works
-```shell
-$ geth --ropsten console
-```
-
-*Note: Older Geth configurations store the Ropsten database in the `testnet` subdirectory.*
+Builder API has two hooks into geth:
+* On forkchoice update, changing the payload attributes feeRecipient to the one registered for next slot's validator
+* On new sealed block, consuming the block as the next slot's proposed payload
-### Configuration
+## Limitations
-As an alternative to passing the numerous flags to the `geth` binary, you can also pass a
-configuration file via:
+* Blocks are only built on forkchoice update call from beacon node
+* Only works post-Bellatrix, fork version is static
+* Does not accept external blocks
+* Does not have payload cache, only the latest block is available
-```shell
-$ geth --config /path/to/your_config.toml
-```
+## Usage
-To get an idea how the file should look like you can use the `dumpconfig` subcommand to
-export your existing configuration:
+Configure geth for your network, it will become the block builder.
-```shell
-$ geth --your-favourite-flags dumpconfig
+Builder API options:
```
-
-*Note: This works only with `geth` v1.6.0 and above.*
-
-#### Docker quick start
-
-One of the quickest ways to get Ethereum up and running on your machine is by using
-Docker:
-
-```shell
-docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \
- -p 8545:8545 -p 30303:30303 \
- ethereum/client-go
+$ geth --help
+BUILDER API OPTIONS:
+ --builder.validator_checks Enable the validator checks
+ --builder.secret_key value Builder API key used for signing headers (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_SECRET_KEY]
+ --builder.listen_addr value Listening address for builder endpoint (default: ":28545") [$BUILDER_LISTEN_ADDR]
+ --builder.genesis_fork_version value Gensis fork version (default: "0x02000000") [$BUILDER_GENESIS_FORK_VERSION]
+ --builder.bellatrix_fork_version value Bellatrix fork version (default: "0x02000000") [$BUILDER_BELLATRIX_FORK_VERSION]
+ --builder.genesis_validators_root value Genesis validators root of the network (static). For kiln use 0x99b09fcd43e5905236c370f184056bec6e6638cfc31a323b304fc4aa789cb4ad (default: "0x0000000000000000000000000000000000000000000000000000000000000000") [$BUILDER_GENESIS_VALIDATORS_ROOT]
+ --builder.beacon_endpoint value Beacon endpoint to connect to for beacon chain data (default: "http://127.0.0.1:5052") [$BUILDER_BEACON_ENDPOINT]
```
-
-This will start `geth` in snap-sync mode with a DB memory allowance of 1GB just as the
-above command does. It will also create a persistent volume in your home directory for
-saving your blockchain as well as map the default ports. There is also an `alpine` tag
-available for a slim version of the image.
-
-Do not forget `--http.addr 0.0.0.0`, if you want to access RPC from other containers
-and/or hosts. By default, `geth` binds to the local interface and RPC endpoints are not
-accessible from the outside.
-
-### Programmatically interfacing `geth` nodes
-
-As a developer, sooner rather than later you'll want to start interacting with `geth` and the
-Ethereum network via your own programs and not manually through the console. To aid
-this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://ethereum.github.io/execution-apis/api-documentation/)
-and [`geth` specific APIs](https://geth.ethereum.org/docs/rpc/server)).
-These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based
-platforms, and named pipes on Windows).
-
-The IPC interface is enabled by default and exposes all the APIs supported by `geth`,
-whereas the HTTP and WS interfaces need to manually be enabled and only expose a
-subset of APIs due to security reasons. These can be turned on/off and configured as
-you'd expect.
-
-HTTP based JSON-RPC API options:
-
- * `--http` Enable the HTTP-RPC server
- * `--http.addr` HTTP-RPC server listening interface (default: `localhost`)
- * `--http.port` HTTP-RPC server listening port (default: `8545`)
- * `--http.api` API's offered over the HTTP-RPC interface (default: `eth,net,web3`)
- * `--http.corsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced)
- * `--ws` Enable the WS-RPC server
- * `--ws.addr` WS-RPC server listening interface (default: `localhost`)
- * `--ws.port` WS-RPC server listening port (default: `8546`)
- * `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`)
- * `--ws.origins` Origins from which to accept websockets requests
- * `--ipcdisable` Disable the IPC-RPC server
- * `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,txpool,web3`)
- * `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it)
-
-You'll need to use your own programming environments' capabilities (libraries, tools, etc) to
-connect via HTTP, WS or IPC to a `geth` node configured with the above flags and you'll
-need to speak [JSON-RPC](https://www.jsonrpc.org/specification) on all transports. You
-can reuse the same connection for multiple requests!
-
-**Note: Please understand the security implications of opening up an HTTP/WS based
-transport before doing so! Hackers on the internet are actively trying to subvert
-Ethereum nodes with exposed APIs! Further, all browser tabs can access locally
-running web servers, so malicious web pages could try to subvert locally available
-APIs!**
-
-### Operating a private network
-
-Maintaining your own private network is more involved as a lot of configurations taken for
-granted in the official networks need to be manually set up.
-
-#### Defining the private genesis state
-
-First, you'll need to create the genesis state of your networks, which all nodes need to be
-aware of and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`):
-
-```json
-{
- "config": {
- "chainId": ,
- "homesteadBlock": 0,
- "eip150Block": 0,
- "eip155Block": 0,
- "eip158Block": 0,
- "byzantiumBlock": 0,
- "constantinopleBlock": 0,
- "petersburgBlock": 0,
- "istanbulBlock": 0,
- "berlinBlock": 0,
- "londonBlock": 0
- },
- "alloc": {},
- "coinbase": "0x0000000000000000000000000000000000000000",
- "difficulty": "0x20000",
- "extraData": "",
- "gasLimit": "0x2fefd8",
- "nonce": "0x0000000000000042",
- "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "timestamp": "0x00"
-}
-```
-
-The above fields should be fine for most purposes, although we'd recommend changing
-the `nonce` to some random value so you prevent unknown remote nodes from being able
-to connect to you. If you'd like to pre-fund some accounts for easier testing, create
-the accounts and populate the `alloc` field with their addresses.
-
-```json
-"alloc": {
- "0x0000000000000000000000000000000000000001": {
- "balance": "111111111"
- },
- "0x0000000000000000000000000000000000000002": {
- "balance": "222222222"
- }
-}
-```
-
-With the genesis state defined in the above JSON file, you'll need to initialize **every**
-`geth` node with it prior to starting it up to ensure all blockchain parameters are correctly
-set:
-
-```shell
-$ geth init path/to/genesis.json
-```
-
-#### Creating the rendezvous point
-
-With all nodes that you want to run initialized to the desired genesis state, you'll need to
-start a bootstrap node that others can use to find each other in your network and/or over
-the internet. The clean way is to configure and run a dedicated bootnode:
-
-```shell
-$ bootnode --genkey=boot.key
-$ bootnode --nodekey=boot.key
-```
-
-With the bootnode online, it will display an [`enode` URL](https://ethereum.org/en/developers/docs/networking-layer/network-addresses/#enode)
-that other nodes can use to connect to it and exchange peer information. Make sure to
-replace the displayed IP address information (most probably `[::]`) with your externally
-accessible IP to get the actual `enode` URL.
-
-*Note: You could also use a full-fledged `geth` node as a bootnode, but it's the less
-recommended way.*
-
-#### Starting up your member nodes
-
-With the bootnode operational and externally reachable (you can try
-`telnet ` to ensure it's indeed reachable), start every subsequent `geth`
-node pointed to the bootnode for peer discovery via the `--bootnodes` flag. It will
-probably also be desirable to keep the data directory of your private network separated, so
-do also specify a custom `--datadir` flag.
-
-```shell
-$ geth --datadir=path/to/custom/data/folder --bootnodes=
-```
-
-*Note: Since your network will be completely cut off from the main and test networks, you'll
-also need to configure a miner to process transactions and create new blocks for you.*
-
-#### Running a private miner
-
-Mining on the public Ethereum network is a complex task as it's only feasible using GPUs,
-requiring an OpenCL or CUDA enabled `ethminer` instance. For information on such a
-setup, please consult the [EtherMining subreddit](https://www.reddit.com/r/EtherMining/)
-and the [ethminer](https://github.com/ethereum-mining/ethminer) repository.
-
-In a private network setting, however a single CPU miner instance is more than enough for
-practical purposes as it can produce a stable stream of blocks at the correct intervals
-without needing heavy resources (consider running on a single thread, no need for multiple
-ones either). To start a `geth` instance for mining, run it with all your usual flags, extended
-by:
-
-```shell
-$ geth --mine --miner.threads=1 --miner.etherbase=0x0000000000000000000000000000000000000000
-```
-
-Which will start mining blocks and transactions on a single CPU thread, crediting all
-proceedings to the account specified by `--miner.etherbase`. You can further tune the mining
-by changing the default gas limit blocks converge to (`--miner.targetgaslimit`) and the price
-transactions are accepted at (`--miner.gasprice`).
-
-## Contribution
-
-Thank you for considering to help out with the source code! We welcome contributions
-from anyone on the internet, and are grateful for even the smallest of fixes!
-
-If you'd like to contribute to go-ethereum, please fork, fix, commit and send a pull request
-for the maintainers to review and merge into the main code base. If you wish to submit
-more complex changes though, please check up with the core devs first on [our Discord Server](https://discord.gg/invite/nthXNEv)
-to ensure those changes are in line with the general philosophy of the project and/or get
-some early feedback which can make both your efforts much lighter as well as our review
-and merge procedures quick and simple.
-
-Please make sure your contributions adhere to our coding guidelines:
-
- * Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting)
- guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
- * Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary)
- guidelines.
- * Pull requests need to be based on and opened against the `master` branch.
- * Commit messages should be prefixed with the package(s) they modify.
- * E.g. "eth, rpc: make trace configs optional"
-
-Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide)
-for more details on configuring your environment, managing project dependencies, and
-testing procedures.
-
-## License
-
-The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the
-[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html),
-also included in our repository in the `COPYING.LESSER` file.
-
-The go-ethereum binaries (i.e. all code inside of the `cmd` directory) is licensed under the
-[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also
-included in our repository in the `COPYING` file.
diff --git a/README.original.md b/README.original.md
new file mode 100644
index 0000000000..b20eb5b748
--- /dev/null
+++ b/README.original.md
@@ -0,0 +1,379 @@
+## Go Ethereum
+
+Official Golang implementation of the Ethereum protocol.
+
+[](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc)
+[](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
+[](https://travis-ci.com/ethereum/go-ethereum)
+[](https://discord.gg/nthXNEv)
+
+Automated builds are available for stable releases and the unstable master branch. Binary
+archives are published at https://geth.ethereum.org/downloads/.
+
+## Building the source
+
+For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/install-and-build/installing-geth).
+
+Building `geth` requires both a Go (version 1.16 or later) and a C compiler. You can install
+them using your favourite package manager. Once the dependencies are installed, run
+
+```shell
+make geth
+```
+
+or, to build the full suite of utilities:
+
+```shell
+make all
+```
+
+## Executables
+
+The go-ethereum project comes with several wrappers/executables found in the `cmd`
+directory.
+
+| Command | Description |
+| :-----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI page](https://geth.ethereum.org/docs/interface/command-line-options) for command line options. |
+| `clef` | Stand-alone signing tool, which can be used as a backend signer for `geth`. |
+| `devp2p` | Utilities to interact with nodes on the networking layer, without running a full blockchain. |
+| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://docs.soliditylang.org/en/develop/abi-spec.html) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://geth.ethereum.org/docs/dapp/native-bindings) page for details. |
+| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
+| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). |
+| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
+| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
+
+## Running `geth`
+
+Going through all the possible command line flags is out of scope here (please consult our
+[CLI Wiki page](https://geth.ethereum.org/docs/interface/command-line-options)),
+but we've enumerated a few common parameter combos to get you up to speed quickly
+on how you can run your own `geth` instance.
+
+### Hardware Requirements
+
+Minimum:
+
+* CPU with 2+ cores
+* 4GB RAM
+* 1TB free storage space to sync the Mainnet
+* 8 MBit/sec download Internet service
+
+Recommended:
+
+* Fast CPU with 4+ cores
+* 16GB+ RAM
+* High Performance SSD with at least 1TB free space
+* 25+ MBit/sec download Internet service
+
+### Full node on the main Ethereum network
+
+By far the most common scenario is people wanting to simply interact with the Ethereum
+network: create accounts; transfer funds; deploy and interact with contracts. For this
+particular use-case the user doesn't care about years-old historical data, so we can
+sync quickly to the current state of the network. To do so:
+
+```shell
+$ geth console
+```
+
+This command will:
+ * Start `geth` in snap sync mode (default, can be changed with the `--syncmode` flag),
+ causing it to download more data in exchange for avoiding processing the entire history
+ of the Ethereum network, which is very CPU intensive.
+ * Start up `geth`'s built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interface/javascript-console),
+ (via the trailing `console` subcommand) through which you can interact using [`web3` methods](https://github.com/ChainSafe/web3.js/blob/0.20.7/DOCUMENTATION.md)
+ (note: the `web3` version bundled within `geth` is very old, and not up to date with official docs),
+ as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/rpc/server).
+ This tool is optional and if you leave it out you can always attach to an already running
+ `geth` instance with `geth attach`.
+
+### A Full node on the Görli test network
+
+Transitioning towards developers, if you'd like to play around with creating Ethereum
+contracts, you almost certainly would like to do that without any real money involved until
+you get the hang of the entire system. In other words, instead of attaching to the main
+network, you want to join the **test** network with your node, which is fully equivalent to
+the main network, but with play-Ether only.
+
+```shell
+$ geth --goerli console
+```
+
+The `console` subcommand has the exact same meaning as above and they are equally
+useful on the testnet too. Please, see above for their explanations if you've skipped here.
+
+Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a bit:
+
+ * Instead of connecting the main Ethereum network, the client will connect to the Görli
+ test network, which uses different P2P bootnodes, different network IDs and genesis
+ states.
+ * Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth`
+ will nest itself one level deeper into a `goerli` subfolder (`~/.ethereum/goerli` on
+ Linux). Note, on OSX and Linux this also means that attaching to a running testnet node
+ requires the use of a custom endpoint since `geth attach` will try to attach to a
+ production node endpoint by default, e.g.,
+ `geth attach /goerli/geth.ipc`. Windows users are not affected by
+ this.
+
+*Note: Although there are some internal protective measures to prevent transactions from
+crossing over between the main network and test network, you should make sure to always
+use separate accounts for play-money and real-money. Unless you manually move
+accounts, `geth` will by default correctly separate the two networks and will not make any
+accounts available between them.*
+
+### Full node on the Rinkeby test network
+
+Go Ethereum also supports connecting to the older proof-of-authority based test network
+called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community.
+
+```shell
+$ geth --rinkeby console
+```
+
+### Full node on the Ropsten test network
+
+In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The
+Ropsten test network is based on the Ethash proof-of-work consensus algorithm. As such,
+it has certain extra overhead and is more susceptible to reorganization attacks due to the
+network's low difficulty/security.
+
+```shell
+$ geth --ropsten console
+```
+
+*Note: Older Geth configurations store the Ropsten database in the `testnet` subdirectory.*
+
+### Configuration
+
+As an alternative to passing the numerous flags to the `geth` binary, you can also pass a
+configuration file via:
+
+```shell
+$ geth --config /path/to/your_config.toml
+```
+
+To get an idea how the file should look like you can use the `dumpconfig` subcommand to
+export your existing configuration:
+
+```shell
+$ geth --your-favourite-flags dumpconfig
+```
+
+*Note: This works only with `geth` v1.6.0 and above.*
+
+#### Docker quick start
+
+One of the quickest ways to get Ethereum up and running on your machine is by using
+Docker:
+
+```shell
+docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \
+ -p 8545:8545 -p 30303:30303 \
+ ethereum/client-go
+```
+
+This will start `geth` in snap-sync mode with a DB memory allowance of 1GB just as the
+above command does. It will also create a persistent volume in your home directory for
+saving your blockchain as well as map the default ports. There is also an `alpine` tag
+available for a slim version of the image.
+
+Do not forget `--http.addr 0.0.0.0`, if you want to access RPC from other containers
+and/or hosts. By default, `geth` binds to the local interface and RPC endpoints are not
+accessible from the outside.
+
+### Programmatically interfacing `geth` nodes
+
+As a developer, sooner rather than later you'll want to start interacting with `geth` and the
+Ethereum network via your own programs and not manually through the console. To aid
+this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://ethereum.github.io/execution-apis/api-documentation/)
+and [`geth` specific APIs](https://geth.ethereum.org/docs/rpc/server)).
+These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based
+platforms, and named pipes on Windows).
+
+The IPC interface is enabled by default and exposes all the APIs supported by `geth`,
+whereas the HTTP and WS interfaces need to manually be enabled and only expose a
+subset of APIs due to security reasons. These can be turned on/off and configured as
+you'd expect.
+
+HTTP based JSON-RPC API options:
+
+ * `--http` Enable the HTTP-RPC server
+ * `--http.addr` HTTP-RPC server listening interface (default: `localhost`)
+ * `--http.port` HTTP-RPC server listening port (default: `8545`)
+ * `--http.api` API's offered over the HTTP-RPC interface (default: `eth,net,web3`)
+ * `--http.corsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced)
+ * `--ws` Enable the WS-RPC server
+ * `--ws.addr` WS-RPC server listening interface (default: `localhost`)
+ * `--ws.port` WS-RPC server listening port (default: `8546`)
+ * `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`)
+ * `--ws.origins` Origins from which to accept websockets requests
+ * `--ipcdisable` Disable the IPC-RPC server
+ * `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,txpool,web3`)
+ * `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it)
+
+You'll need to use your own programming environments' capabilities (libraries, tools, etc) to
+connect via HTTP, WS or IPC to a `geth` node configured with the above flags and you'll
+need to speak [JSON-RPC](https://www.jsonrpc.org/specification) on all transports. You
+can reuse the same connection for multiple requests!
+
+**Note: Please understand the security implications of opening up an HTTP/WS based
+transport before doing so! Hackers on the internet are actively trying to subvert
+Ethereum nodes with exposed APIs! Further, all browser tabs can access locally
+running web servers, so malicious web pages could try to subvert locally available
+APIs!**
+
+### Operating a private network
+
+Maintaining your own private network is more involved as a lot of configurations taken for
+granted in the official networks need to be manually set up.
+
+#### Defining the private genesis state
+
+First, you'll need to create the genesis state of your networks, which all nodes need to be
+aware of and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`):
+
+```json
+{
+ "config": {
+ "chainId": ,
+ "homesteadBlock": 0,
+ "eip150Block": 0,
+ "eip155Block": 0,
+ "eip158Block": 0,
+ "byzantiumBlock": 0,
+ "constantinopleBlock": 0,
+ "petersburgBlock": 0,
+ "istanbulBlock": 0,
+ "berlinBlock": 0,
+ "londonBlock": 0
+ },
+ "alloc": {},
+ "coinbase": "0x0000000000000000000000000000000000000000",
+ "difficulty": "0x20000",
+ "extraData": "",
+ "gasLimit": "0x2fefd8",
+ "nonce": "0x0000000000000042",
+ "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "timestamp": "0x00"
+}
+```
+
+The above fields should be fine for most purposes, although we'd recommend changing
+the `nonce` to some random value so you prevent unknown remote nodes from being able
+to connect to you. If you'd like to pre-fund some accounts for easier testing, create
+the accounts and populate the `alloc` field with their addresses.
+
+```json
+"alloc": {
+ "0x0000000000000000000000000000000000000001": {
+ "balance": "111111111"
+ },
+ "0x0000000000000000000000000000000000000002": {
+ "balance": "222222222"
+ }
+}
+```
+
+With the genesis state defined in the above JSON file, you'll need to initialize **every**
+`geth` node with it prior to starting it up to ensure all blockchain parameters are correctly
+set:
+
+```shell
+$ geth init path/to/genesis.json
+```
+
+#### Creating the rendezvous point
+
+With all nodes that you want to run initialized to the desired genesis state, you'll need to
+start a bootstrap node that others can use to find each other in your network and/or over
+the internet. The clean way is to configure and run a dedicated bootnode:
+
+```shell
+$ bootnode --genkey=boot.key
+$ bootnode --nodekey=boot.key
+```
+
+With the bootnode online, it will display an [`enode` URL](https://ethereum.org/en/developers/docs/networking-layer/network-addresses/#enode)
+that other nodes can use to connect to it and exchange peer information. Make sure to
+replace the displayed IP address information (most probably `[::]`) with your externally
+accessible IP to get the actual `enode` URL.
+
+*Note: You could also use a full-fledged `geth` node as a bootnode, but it's the less
+recommended way.*
+
+#### Starting up your member nodes
+
+With the bootnode operational and externally reachable (you can try
+`telnet ` to ensure it's indeed reachable), start every subsequent `geth`
+node pointed to the bootnode for peer discovery via the `--bootnodes` flag. It will
+probably also be desirable to keep the data directory of your private network separated, so
+do also specify a custom `--datadir` flag.
+
+```shell
+$ geth --datadir=path/to/custom/data/folder --bootnodes=
+```
+
+*Note: Since your network will be completely cut off from the main and test networks, you'll
+also need to configure a miner to process transactions and create new blocks for you.*
+
+#### Running a private miner
+
+Mining on the public Ethereum network is a complex task as it's only feasible using GPUs,
+requiring an OpenCL or CUDA enabled `ethminer` instance. For information on such a
+setup, please consult the [EtherMining subreddit](https://www.reddit.com/r/EtherMining/)
+and the [ethminer](https://github.com/ethereum-mining/ethminer) repository.
+
+In a private network setting, however a single CPU miner instance is more than enough for
+practical purposes as it can produce a stable stream of blocks at the correct intervals
+without needing heavy resources (consider running on a single thread, no need for multiple
+ones either). To start a `geth` instance for mining, run it with all your usual flags, extended
+by:
+
+```shell
+$ geth --mine --miner.threads=1 --miner.etherbase=0x0000000000000000000000000000000000000000
+```
+
+Which will start mining blocks and transactions on a single CPU thread, crediting all
+proceedings to the account specified by `--miner.etherbase`. You can further tune the mining
+by changing the default gas limit blocks converge to (`--miner.targetgaslimit`) and the price
+transactions are accepted at (`--miner.gasprice`).
+
+## Contribution
+
+Thank you for considering to help out with the source code! We welcome contributions
+from anyone on the internet, and are grateful for even the smallest of fixes!
+
+If you'd like to contribute to go-ethereum, please fork, fix, commit and send a pull request
+for the maintainers to review and merge into the main code base. If you wish to submit
+more complex changes though, please check up with the core devs first on [our Discord Server](https://discord.gg/invite/nthXNEv)
+to ensure those changes are in line with the general philosophy of the project and/or get
+some early feedback which can make both your efforts much lighter as well as our review
+and merge procedures quick and simple.
+
+Please make sure your contributions adhere to our coding guidelines:
+
+ * Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting)
+ guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+ * Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary)
+ guidelines.
+ * Pull requests need to be based on and opened against the `master` branch.
+ * Commit messages should be prefixed with the package(s) they modify.
+ * E.g. "eth, rpc: make trace configs optional"
+
+Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide)
+for more details on configuring your environment, managing project dependencies, and
+testing procedures.
+
+## License
+
+The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the
+[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html),
+also included in our repository in the `COPYING.LESSER` file.
+
+The go-ethereum binaries (i.e. all code inside of the `cmd` directory) is licensed under the
+[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also
+included in our repository in the `COPYING` file.
diff --git a/builder/backend.go b/builder/backend.go
new file mode 100644
index 0000000000..5473e280dc
--- /dev/null
+++ b/builder/backend.go
@@ -0,0 +1,436 @@
+package builder
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "math/big"
+ "net/http"
+ _ "os"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/beacon"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/trie"
+ "github.com/gorilla/mux"
+
+ "github.com/flashbots/go-boost-utils/bls"
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+)
+
+type PubkeyHex string
+
+type ValidatorData struct {
+ FeeRecipient boostTypes.Address `json:"feeRecipient"`
+ GasLimit uint64 `json:"gasLimit"`
+ Timestamp uint64 `json:"timestamp"`
+}
+
+type IBeaconClient interface {
+ isValidator(pubkey PubkeyHex) bool
+ getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error)
+ onForkchoiceUpdate() (PubkeyHex, error)
+}
+
+type Backend struct {
+ beaconClient IBeaconClient
+
+ builderSecretKey *bls.SecretKey
+ builderPublicKey boostTypes.PublicKey
+ serializedBuilderPoolPubkey hexutil.Bytes
+ builderSigningDomain boostTypes.Domain
+ proposerSigningDomain boostTypes.Domain
+ enableBeaconChecks bool
+
+ validatorsLock sync.RWMutex
+ validators map[PubkeyHex]ValidatorData
+
+ bestDataLock sync.Mutex
+ bestHeader *boostTypes.ExecutionPayloadHeader
+ bestPayload *boostTypes.ExecutionPayload
+ profit *big.Int
+
+ indexTemplate *template.Template
+}
+
+func NewBackend(sk *bls.SecretKey, bc IBeaconClient, builderSigningDomain boostTypes.Domain, proposerSigningDomain boostTypes.Domain, enableBeaconChecks bool) *Backend {
+ pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
+ pk := boostTypes.PublicKey{}
+ pk.FromSlice(pkBytes)
+
+ _, err := bc.onForkchoiceUpdate()
+ if err != nil {
+ log.Error("could not initialize beacon client", "err", err)
+ }
+
+ indexTemplate, err := parseIndexTemplate()
+ if err != nil {
+ log.Error("could not parse index template", "err", err)
+ indexTemplate = nil
+ }
+ return &Backend{
+ beaconClient: bc,
+ builderSecretKey: sk,
+ builderPublicKey: pk,
+ serializedBuilderPoolPubkey: pkBytes,
+
+ builderSigningDomain: builderSigningDomain,
+ proposerSigningDomain: proposerSigningDomain,
+ enableBeaconChecks: enableBeaconChecks,
+ validators: make(map[PubkeyHex]ValidatorData),
+ indexTemplate: indexTemplate,
+ }
+}
+
+func (b *Backend) handleIndex(w http.ResponseWriter, req *http.Request) {
+ if b.indexTemplate == nil {
+ http.Error(w, "not available", http.StatusInternalServerError)
+ }
+
+ b.validatorsLock.RLock()
+ noValidators := len(b.validators)
+ b.validatorsLock.RUnlock()
+
+ header := b.bestHeader
+ headerData, err := json.MarshalIndent(header, "", " ")
+ if err != nil {
+ headerData = []byte{}
+ }
+
+ payload := b.bestPayload
+ payloadData, err := json.MarshalIndent(payload, "", " ")
+ if err != nil {
+ payloadData = []byte{}
+ }
+
+ statusData := struct {
+ NoValidators int
+ Header string
+ Blocks string
+ }{noValidators, string(headerData), string(payloadData)}
+
+ if err := b.indexTemplate.Execute(w, statusData); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
+
+func (b *Backend) handleStatus(w http.ResponseWriter, req *http.Request) {
+ w.WriteHeader(http.StatusOK)
+}
+
+type httpErrorResp struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+}
+
+func respondError(w http.ResponseWriter, code int, message string) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ if err := json.NewEncoder(w).Encode(httpErrorResp{code, message}); err != nil {
+ http.Error(w, message, code)
+ }
+}
+
+func (b *Backend) handleRegisterValidator(w http.ResponseWriter, req *http.Request) {
+ payload := []boostTypes.SignedValidatorRegistration{}
+ if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
+ log.Error("could not decode payload", "err", err)
+ respondError(w, http.StatusBadRequest, "invalid payload")
+ return
+ }
+
+ for _, registerRequest := range payload {
+ if len(registerRequest.Message.Pubkey) != 48 {
+ respondError(w, http.StatusBadRequest, "invalid pubkey")
+ return
+ }
+
+ if len(registerRequest.Signature) != 96 {
+ respondError(w, http.StatusBadRequest, "invalid signature")
+ return
+ }
+
+ ok, err := boostTypes.VerifySignature(registerRequest.Message, b.builderSigningDomain, registerRequest.Message.Pubkey[:], registerRequest.Signature[:])
+ if !ok || err != nil {
+ log.Error("error verifying signature", "err", err)
+ respondError(w, http.StatusBadRequest, "invalid signature")
+ return
+ }
+
+ // Do not check timestamp before signature, as it would leak validator data
+ if registerRequest.Message.Timestamp > uint64(time.Now().Add(10*time.Second).Unix()) {
+ respondError(w, http.StatusBadRequest, "invalid payload")
+ return
+ }
+ }
+
+ for _, registerRequest := range payload {
+ pubkeyHex := PubkeyHex(registerRequest.Message.Pubkey.String())
+ if !b.beaconClient.isValidator(pubkeyHex) {
+ respondError(w, http.StatusBadRequest, "not a validator")
+ return
+ }
+ }
+
+ b.validatorsLock.Lock()
+ defer b.validatorsLock.Unlock()
+
+ for _, registerRequest := range payload {
+ pubkeyHex := PubkeyHex(registerRequest.Message.Pubkey.String())
+ if previousValidatorData, ok := b.validators[pubkeyHex]; ok {
+ if registerRequest.Message.Timestamp <= previousValidatorData.Timestamp {
+ respondError(w, http.StatusBadRequest, "invalid timestamp")
+ return
+ }
+ }
+ }
+
+ for _, registerRequest := range payload {
+ pubkeyHex := PubkeyHex(registerRequest.Message.Pubkey.String())
+ b.validators[pubkeyHex] = ValidatorData{
+ FeeRecipient: registerRequest.Message.FeeRecipient,
+ GasLimit: registerRequest.Message.GasLimit,
+ Timestamp: registerRequest.Message.Timestamp,
+ }
+
+ log.Info("registered validator", "pubkey", pubkeyHex, "data", b.validators[pubkeyHex])
+ }
+
+ w.WriteHeader(http.StatusOK)
+}
+
+func (b *Backend) handleGetHeader(w http.ResponseWriter, req *http.Request) {
+ vars := mux.Vars(req)
+ slot, err := strconv.Atoi(vars["slot"])
+ if err != nil {
+ respondError(w, http.StatusBadRequest, "incorrect slot")
+ return
+ }
+ parentHashHex := vars["parent_hash"]
+ pubkeyHex := PubkeyHex(vars["pubkey"])
+
+ b.validatorsLock.RLock()
+ if _, ok := b.validators[pubkeyHex]; !ok {
+ log.Error("missing validator", "validators", b.validators, "provided", pubkeyHex)
+ b.validatorsLock.RUnlock()
+ respondError(w, http.StatusBadRequest, "unknown validator")
+ return
+ }
+ b.validatorsLock.RUnlock()
+
+ // Do not validate slot separately, it will create a race between slot update and proposer key
+ if nextSlotProposer, err := b.beaconClient.getProposerForNextSlot(uint64(slot)); err != nil || nextSlotProposer != pubkeyHex {
+ log.Error("getHeader requested for public key other than next slots proposer", "requested", pubkeyHex, "expected", nextSlotProposer)
+ if b.enableBeaconChecks {
+ respondError(w, http.StatusBadRequest, "unknown validator")
+ return
+ }
+ }
+
+ b.bestDataLock.Lock()
+ bestHeader := b.bestHeader
+ profit := b.profit
+ b.bestDataLock.Unlock()
+
+ if bestHeader == nil || bestHeader.ParentHash.String() != parentHashHex {
+ respondError(w, http.StatusBadRequest, "unknown payload")
+ return
+ }
+
+ bid := boostTypes.BuilderBid{
+ Header: bestHeader,
+ Value: [32]byte(common.BytesToHash(profit.Bytes())),
+ Pubkey: b.builderPublicKey,
+ }
+ signature, err := boostTypes.SignMessage(&bid, b.builderSigningDomain, b.builderSecretKey)
+ if err != nil {
+ respondError(w, http.StatusInternalServerError, "internal server error")
+ return
+ }
+
+ response := &boostTypes.GetHeaderResponse{
+ Version: "bellatrix",
+ Data: &boostTypes.SignedBuilderBid{Message: &bid, Signature: signature},
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(response); err != nil {
+ respondError(w, http.StatusInternalServerError, "internal server error")
+ return
+ }
+}
+
+func (b *Backend) handleGetPayload(w http.ResponseWriter, req *http.Request) {
+ payload := new(boostTypes.SignedBlindedBeaconBlock)
+ if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
+ respondError(w, http.StatusBadRequest, "invalid payload")
+ return
+ }
+
+ if len(payload.Signature) != 96 {
+ respondError(w, http.StatusBadRequest, "invalid signature")
+ return
+ }
+
+ nextSlotProposerPubkeyHex, err := b.beaconClient.getProposerForNextSlot(payload.Message.Slot)
+ if err != nil {
+ if b.enableBeaconChecks {
+ respondError(w, http.StatusBadRequest, "unknown validator")
+ return
+ }
+ }
+
+ nextSlotProposerPubkeyBytes, err := hexutil.Decode(string(nextSlotProposerPubkeyHex))
+ if err != nil {
+ if b.enableBeaconChecks {
+ respondError(w, http.StatusBadRequest, "unknown validator")
+ return
+ }
+ }
+
+ ok, err := boostTypes.VerifySignature(payload.Message, b.proposerSigningDomain, nextSlotProposerPubkeyBytes[:], payload.Signature[:])
+ if !ok || err != nil {
+ if b.enableBeaconChecks {
+ respondError(w, http.StatusBadRequest, "invalid signature")
+ return
+ }
+ }
+
+ b.bestDataLock.Lock()
+ bestHeader := b.bestHeader
+ bestPayload := b.bestPayload
+ b.bestDataLock.Unlock()
+
+ if bestHeader == nil || bestPayload == nil {
+ respondError(w, http.StatusInternalServerError, "no payloads")
+ return
+ }
+
+ if !ExecutionPayloadHeaderEqual(bestHeader, payload.Message.Body.ExecutionPayloadHeader) {
+ respondError(w, http.StatusBadRequest, "unknown payload")
+ return
+ }
+
+ response := boostTypes.GetPayloadResponse{
+ Version: "bellatrix",
+ Data: bestPayload,
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(response); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ respondError(w, http.StatusInternalServerError, "internal server error")
+ return
+ }
+}
+
+func (b *Backend) onForkchoice(payloadAttributes *beacon.PayloadAttributesV1) {
+ dataJson, err := json.Marshal(payloadAttributes)
+ if err == nil {
+ log.Info("FCU", "data", string(dataJson))
+ }
+ // if payloadAttributes.SuggestedFeeRecipient == common.Address{}
+ pubkeyHex, err := b.beaconClient.onForkchoiceUpdate()
+ if err != nil {
+ return
+ }
+
+ if payloadAttributes != nil {
+ b.validatorsLock.RLock()
+ vd, found := b.validators[pubkeyHex]
+ if found {
+ payloadAttributes.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
+ payloadAttributes.GasLimit = vd.GasLimit
+ }
+ b.validatorsLock.RUnlock()
+ }
+}
+
+func (b *Backend) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Block) {
+ dataJson, err := json.Marshal(data)
+ if err == nil {
+ log.Info("newSealedBlock", "data", string(dataJson))
+ }
+ payload := executableDataToExecutionPayload(data)
+ payloadHeader, err := payloadToPayloadHeader(payload)
+ if err != nil {
+ log.Error("could not convert payload to header", "err", err)
+ return
+ }
+
+ b.bestDataLock.Lock()
+ b.bestHeader = payloadHeader
+ b.bestPayload = payload
+ b.profit = new(big.Int).Set(block.Profit)
+ b.bestDataLock.Unlock()
+}
+
+func decodeTransactions(enc []hexutil.Bytes) ([]*types.Transaction, error) {
+ var txs = make([]*types.Transaction, len(enc))
+ for i, encTx := range enc {
+ var tx types.Transaction
+ if err := tx.UnmarshalBinary(encTx); err != nil {
+ return nil, fmt.Errorf("invalid transaction %d: %v", i, err)
+ }
+ txs[i] = &tx
+ }
+ return txs, nil
+}
+
+func payloadToPayloadHeader(p *boostTypes.ExecutionPayload) (*boostTypes.ExecutionPayloadHeader, error) {
+ txs, err := decodeTransactions(p.Transactions)
+ if err != nil {
+ return nil, err
+ }
+ return &boostTypes.ExecutionPayloadHeader{
+ ParentHash: p.ParentHash,
+ FeeRecipient: p.FeeRecipient,
+ StateRoot: p.StateRoot,
+ ReceiptsRoot: p.ReceiptsRoot,
+ LogsBloom: p.LogsBloom,
+ Random: p.Random,
+ BlockNumber: p.BlockNumber,
+ GasLimit: p.GasLimit,
+ GasUsed: p.GasUsed,
+ Timestamp: p.Timestamp,
+ ExtraData: []byte(p.ExtraData),
+ BaseFeePerGas: p.BaseFeePerGas,
+ BlockHash: p.BlockHash,
+ TransactionsRoot: [32]byte(types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil))),
+ }, nil
+}
+
+func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) *boostTypes.ExecutionPayload {
+ transactionData := make([]hexutil.Bytes, len(data.Transactions))
+ for i, tx := range data.Transactions {
+ transactionData[i] = hexutil.Bytes(tx)
+ }
+ logsBloom := boostTypes.Bloom{}
+ logsBloom.FromSlice(data.LogsBloom)
+ return &boostTypes.ExecutionPayload{
+ ParentHash: [32]byte(data.ParentHash),
+ FeeRecipient: [20]byte(data.FeeRecipient),
+ StateRoot: [32]byte(data.StateRoot),
+ ReceiptsRoot: [32]byte(data.ReceiptsRoot),
+ LogsBloom: logsBloom,
+ Random: [32]byte(data.Random),
+ BlockNumber: data.Number,
+ GasLimit: data.GasLimit,
+ GasUsed: data.GasUsed,
+ Timestamp: data.Timestamp,
+ ExtraData: data.ExtraData,
+ BaseFeePerGas: [32]byte(common.BytesToHash(data.BaseFeePerGas.Bytes())),
+ BlockHash: [32]byte(data.BlockHash),
+ Transactions: transactionData,
+ }
+}
+
+func ExecutionPayloadHeaderEqual(l *boostTypes.ExecutionPayloadHeader, r *boostTypes.ExecutionPayloadHeader) bool {
+ return l.ParentHash == r.ParentHash && l.FeeRecipient == r.FeeRecipient && l.StateRoot == r.StateRoot && l.ReceiptsRoot == r.ReceiptsRoot && l.LogsBloom == r.LogsBloom && l.Random == r.Random && l.BlockNumber == r.BlockNumber && l.GasLimit == r.GasLimit && l.GasUsed == r.GasUsed && l.Timestamp == r.Timestamp && l.BaseFeePerGas == r.BaseFeePerGas && bytes.Equal(l.ExtraData, r.ExtraData) && l.BlockHash == r.BlockHash && l.TransactionsRoot == r.TransactionsRoot
+}
diff --git a/builder/backend_test.go b/builder/backend_test.go
new file mode 100644
index 0000000000..dbd56d7c35
--- /dev/null
+++ b/builder/backend_test.go
@@ -0,0 +1,233 @@
+package builder
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "math/big"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/beacon"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/flashbots/go-boost-utils/bls"
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+ "github.com/stretchr/testify/require"
+)
+
+func newTestBackend(t *testing.T) (*Backend, *ValidatorPrivateData) {
+ validator := NewRandomValidator()
+ sk, _ := bls.GenerateRandomSecretKey()
+ bDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeAppBuilder, [4]byte{0x02, 0x0, 0x0, 0x0}, boostTypes.Hash{})
+ genesisValidatorsRoot := boostTypes.Hash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"))
+ cDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeBeaconProposer, [4]byte{0x02, 0x0, 0x0, 0x0}, genesisValidatorsRoot)
+ backend := NewBackend(sk, &testBeaconClient{validator}, bDomain, cDomain, true)
+ // service := NewService("127.0.0.1:31545", backend)
+
+ return backend, validator
+}
+
+func testRequest(t *testing.T, backend *Backend, method string, path string, payload any) *httptest.ResponseRecorder {
+ var req *http.Request
+ var err error
+
+ if payload == nil {
+ req, err = http.NewRequest(method, path, nil)
+ } else {
+ payloadBytes, err2 := json.Marshal(payload)
+ require.NoError(t, err2)
+ req, err = http.NewRequest(method, path, bytes.NewReader(payloadBytes))
+ }
+
+ require.NoError(t, err)
+ rr := httptest.NewRecorder()
+ getRouter(backend).ServeHTTP(rr, req)
+ return rr
+}
+
+func TestValidatorRegistration(t *testing.T) {
+ backend, _ := newTestBackend(t)
+ log.Error("rsk", "sk", hexutil.Encode(backend.builderSecretKey.Serialize()))
+
+ v := NewRandomValidator()
+ payload, err := prepareRegistrationMessage(t, backend.builderSigningDomain, v)
+ require.NoError(t, err)
+
+ rr := testRequest(t, backend, "POST", "/eth/v1/builder/validators", payload)
+ require.Equal(t, http.StatusOK, rr.Code)
+ require.Contains(t, backend.validators, PubkeyHex(v.Pk.String()))
+ require.Equal(t, ValidatorData{FeeRecipient: payload[0].Message.FeeRecipient, GasLimit: payload[0].Message.GasLimit, Timestamp: payload[0].Message.Timestamp}, backend.validators[PubkeyHex(v.Pk.String())])
+
+ rr = testRequest(t, backend, "POST", "/eth/v1/builder/validators", payload)
+ require.Equal(t, http.StatusBadRequest, rr.Code)
+ require.Equal(t, `{"code":400,"message":"invalid timestamp"}`+"\n", rr.Body.String())
+
+ payload[0].Message.Timestamp += 1
+ // Invalid signature
+ payload[0].Signature[len(payload[0].Signature)-1] = 0x00
+ rr = testRequest(t, backend, "POST", "/eth/v1/builder/validators", payload)
+ require.Equal(t, http.StatusBadRequest, rr.Code)
+ require.Equal(t, `{"code":400,"message":"invalid signature"}`+"\n", rr.Body.String())
+
+ // TODO: cover all errors
+}
+
+func prepareRegistrationMessage(t *testing.T, domain boostTypes.Domain, v *ValidatorPrivateData) ([]boostTypes.SignedValidatorRegistration, error) {
+ var pubkey boostTypes.PublicKey
+ pubkey.FromSlice(v.Pk)
+ require.Equal(t, []byte(v.Pk), pubkey[:])
+
+ msg := boostTypes.RegisterValidatorRequestMessage{
+ FeeRecipient: boostTypes.Address{0x42},
+ GasLimit: 15_000_000,
+ Timestamp: uint64(time.Now().Unix()),
+ Pubkey: pubkey,
+ }
+
+ signature, err := v.Sign(&msg, domain)
+ require.NoError(t, err)
+
+ return []boostTypes.SignedValidatorRegistration{{
+ Message: &msg,
+ Signature: signature,
+ }}, nil
+}
+
+func registerValidator(t *testing.T, v *ValidatorPrivateData, backend *Backend) {
+ payload, err := prepareRegistrationMessage(t, backend.builderSigningDomain, v)
+ require.NoError(t, err)
+
+ log.Info("Registering", "payload", payload[0].Message)
+ rr := testRequest(t, backend, "POST", "/eth/v1/builder/validators", payload)
+ require.Equal(t, http.StatusOK, rr.Code)
+ require.Contains(t, backend.validators, PubkeyHex(v.Pk.String()))
+ require.Equal(t, ValidatorData{FeeRecipient: payload[0].Message.FeeRecipient, GasLimit: payload[0].Message.GasLimit, Timestamp: payload[0].Message.Timestamp}, backend.validators[PubkeyHex(v.Pk.String())])
+}
+
+func TestGetHeader(t *testing.T) {
+ backend, validator := newTestBackend(t)
+
+ forkchoiceData := &beacon.ExecutableDataV1{
+ ParentHash: common.HexToHash("0xafafafa"),
+ FeeRecipient: common.Address{0x01},
+ BlockHash: common.HexToHash("0xbfbfbfb"),
+ BaseFeePerGas: big.NewInt(12),
+ ExtraData: []byte{},
+ }
+ forkchoiceBlock := &types.Block{
+ Profit: big.NewInt(10),
+ }
+
+ path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
+ rr := testRequest(t, backend, "GET", path, nil)
+ require.Equal(t, `{"code":400,"message":"unknown validator"}`+"\n", rr.Body.String())
+
+ registerValidator(t, validator, backend)
+
+ rr = testRequest(t, backend, "GET", path, nil)
+ require.Equal(t, `{"code":400,"message":"unknown payload"}`+"\n", rr.Body.String())
+
+ path = fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), NewRandomValidator().Pk.String())
+ rr = testRequest(t, backend, "GET", path, nil)
+ require.Equal(t, `{"code":400,"message":"unknown validator"}`+"\n", rr.Body.String())
+
+ backend.newSealedBlock(forkchoiceData, forkchoiceBlock)
+
+ path = fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
+ rr = testRequest(t, backend, "GET", path, nil)
+ require.Equal(t, http.StatusOK, rr.Code)
+
+ bid := new(boostTypes.GetHeaderResponse)
+ err := json.Unmarshal(rr.Body.Bytes(), bid)
+ require.NoError(t, err)
+
+ expectedHeader, err := payloadToPayloadHeader(executableDataToExecutionPayload(forkchoiceData))
+ require.NoError(t, err)
+ require.EqualValues(t, &boostTypes.BuilderBid{
+ Header: expectedHeader,
+ Value: [32]byte(common.BytesToHash(forkchoiceBlock.Profit.Bytes())),
+ Pubkey: backend.builderPublicKey,
+ }, bid.Data.Message)
+
+ require.Equal(t, forkchoiceData.ParentHash.Bytes(), bid.Data.Message.Header.ParentHash[:], "didn't build on expected parent")
+ ok, err := boostTypes.VerifySignature(bid.Data.Message, backend.builderSigningDomain, backend.builderPublicKey[:], bid.Data.Signature[:])
+
+ require.NoError(t, err)
+ require.True(t, ok)
+}
+
+func TestGetPayload(t *testing.T) {
+ backend, validator := newTestBackend(t)
+
+ forkchoiceData := &beacon.ExecutableDataV1{
+ ParentHash: common.HexToHash("0xafafafa"),
+ FeeRecipient: common.Address{0x01},
+ BlockHash: common.HexToHash("0xbfbfbfb"),
+ BaseFeePerGas: big.NewInt(12),
+ ExtraData: []byte{},
+ }
+ forkchoiceBlock := &types.Block{
+ Profit: big.NewInt(10),
+ }
+
+ registerValidator(t, validator, backend)
+ backend.newSealedBlock(forkchoiceData, forkchoiceBlock)
+
+ path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
+ rr := testRequest(t, backend, "GET", path, nil)
+ require.Equal(t, http.StatusOK, rr.Code)
+
+ bid := new(boostTypes.GetHeaderResponse)
+ err := json.Unmarshal(rr.Body.Bytes(), bid)
+ require.NoError(t, err)
+
+ // Create request payload
+ msg := &boostTypes.BlindedBeaconBlock{
+ Slot: 1,
+ ProposerIndex: 2,
+ ParentRoot: boostTypes.Root{0x03},
+ StateRoot: boostTypes.Root{0x04},
+ Body: &boostTypes.BlindedBeaconBlockBody{
+ Eth1Data: &boostTypes.Eth1Data{
+ DepositRoot: boostTypes.Root{0x05},
+ DepositCount: 5,
+ BlockHash: boostTypes.Hash{0x06},
+ },
+ SyncAggregate: &boostTypes.SyncAggregate{
+ CommitteeBits: boostTypes.CommitteeBits{0x07},
+ CommitteeSignature: boostTypes.Signature{0x08},
+ },
+ ExecutionPayloadHeader: bid.Data.Message.Header,
+ },
+ }
+
+ // TODO: test wrong signing domain
+ signature, err := validator.Sign(msg, backend.proposerSigningDomain)
+ require.NoError(t, err)
+
+ // Call getPayload with invalid signature
+ rr = testRequest(t, backend, "POST", "/eth/v1/builder/blinded_blocks", boostTypes.SignedBlindedBeaconBlock{
+ Message: msg,
+ Signature: boostTypes.Signature{0x09},
+ })
+ require.Equal(t, http.StatusBadRequest, rr.Code)
+ require.Equal(t, `{"code":400,"message":"invalid signature"}`+"\n", rr.Body.String())
+
+ // Call getPayload with correct signature
+ rr = testRequest(t, backend, "POST", "/eth/v1/builder/blinded_blocks", boostTypes.SignedBlindedBeaconBlock{
+ Message: msg,
+ Signature: signature,
+ })
+
+ // Verify getPayload response
+ require.Equal(t, http.StatusOK, rr.Code)
+ getPayloadResponse := new(boostTypes.GetPayloadResponse)
+ err = json.Unmarshal(rr.Body.Bytes(), getPayloadResponse)
+ require.NoError(t, err)
+ require.Equal(t, bid.Data.Message.Header.BlockHash, getPayloadResponse.Data.BlockHash)
+}
diff --git a/builder/beacon_client.go b/builder/beacon_client.go
new file mode 100644
index 0000000000..62138c9b05
--- /dev/null
+++ b/builder/beacon_client.go
@@ -0,0 +1,200 @@
+package builder
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+type testBeaconClient struct {
+ validator *ValidatorPrivateData
+}
+
+func (b *testBeaconClient) isValidator(pubkey PubkeyHex) bool {
+ return true
+}
+func (b *testBeaconClient) getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error) {
+ return PubkeyHex(hexutil.Encode(b.validator.Pk)), nil
+}
+func (b *testBeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
+ return PubkeyHex(hexutil.Encode(b.validator.Pk)), nil
+}
+
+type BeaconClient struct {
+ endpoint string
+
+ mu sync.Mutex
+ currentEpoch uint64
+ currentSlot uint64
+ nextSlotProposer PubkeyHex
+ slotProposerMap map[uint64]PubkeyHex
+}
+
+func NewBeaconClient(endpoint string) *BeaconClient {
+ return &BeaconClient{
+ endpoint: endpoint,
+ slotProposerMap: make(map[uint64]PubkeyHex),
+ }
+}
+
+func (b *BeaconClient) isValidator(pubkey PubkeyHex) bool {
+ return true
+}
+
+func (b *BeaconClient) getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error) {
+ /* Only returns proposer if requestedSlot is currentSlot + 1, would be a race otherwise */
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ if b.currentSlot+1 != requestedSlot {
+ return PubkeyHex(""), errors.New("slot out of sync")
+ }
+ return b.nextSlotProposer, nil
+}
+
+/* Returns next slot's proposer pubkey */
+// TODO: what happens if no block for previous slot - should still get next slot
+func (b *BeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ currentSlot, err := fetchCurrentSlot(b.endpoint)
+ if err != nil {
+ return PubkeyHex(""), err
+ }
+
+ nextSlot := currentSlot + 1
+
+ b.currentSlot = currentSlot
+ nextSlotEpoch := nextSlot / 32
+
+ if nextSlotEpoch != b.currentEpoch {
+ // TODO: this should be prepared in advance, possibly just fetch for next epoch in advance
+ slotProposerMap, err := fetchEpochProposersMap(b.endpoint, nextSlotEpoch)
+ if err != nil {
+ return PubkeyHex(""), err
+ }
+
+ b.currentEpoch = nextSlotEpoch
+ b.slotProposerMap = slotProposerMap
+ }
+
+ nextSlotProposer, found := b.slotProposerMap[nextSlot]
+ if !found {
+ log.Error("inconsistent proposer mapping", "currentSlot", currentSlot, "slotProposerMap", b.slotProposerMap)
+ return PubkeyHex(""), errors.New("inconsistent proposer mapping")
+ }
+ b.nextSlotProposer = nextSlotProposer
+ return nextSlotProposer, nil
+}
+
+func fetchCurrentSlot(endpoint string) (uint64, error) {
+ headerRes := &struct {
+ Data []struct {
+ Root common.Hash `json:"root"`
+ Canonical bool `json:"canonical"`
+ Header struct {
+ Message struct {
+ Slot string `json:"slot"`
+ ProposerIndex string `json:"proposer_index"`
+ ParentRoot common.Hash `json:"parent_root"`
+ StateRoot common.Hash `json:"state_root"`
+ BodyRoot common.Hash `json:"body_root"`
+ } `json:"message"`
+ Signature hexutil.Bytes `json:"signature"`
+ } `json:"header"`
+ } `json:"data"`
+ }{}
+
+ err := fetchBeacon(endpoint+"/eth/v1/beacon/headers", headerRes)
+ if err != nil {
+ return uint64(0), err
+ }
+
+ if len(headerRes.Data) != 1 {
+ return uint64(0), errors.New("invalid response")
+ }
+
+ slot, err := strconv.Atoi(headerRes.Data[0].Header.Message.Slot)
+ if err != nil {
+ log.Error("could not parse slot", "Slot", headerRes.Data[0].Header.Message.Slot, "err", err)
+ return uint64(0), errors.New("invalid response")
+ }
+ return uint64(slot), nil
+}
+
+func fetchEpochProposersMap(endpoint string, epoch uint64) (map[uint64]PubkeyHex, error) {
+ proposerDutiesResponse := &struct {
+ Data []struct {
+ PubkeyHex string `json:"pubkey"`
+ Slot string `json:"slot"`
+ } `json:"data"`
+ }{}
+
+ err := fetchBeacon(fmt.Sprintf("%s/eth/v1/validator/duties/proposer/%d", endpoint, epoch), proposerDutiesResponse)
+ if err != nil {
+ return nil, err
+ }
+
+ proposersMap := make(map[uint64]PubkeyHex)
+ for _, proposerDuty := range proposerDutiesResponse.Data {
+ slot, err := strconv.Atoi(proposerDuty.Slot)
+ if err != nil {
+ log.Error("could not parse slot", "Slot", proposerDuty.Slot, "err", err)
+ continue
+ }
+ proposersMap[uint64(slot)] = PubkeyHex(proposerDuty.PubkeyHex)
+ }
+ return proposersMap, nil
+}
+
+func fetchBeacon(url string, dst any) error {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ log.Error("invalid request", "url", url, "err", err)
+ return err
+ }
+ req.Header.Set("accept", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ log.Error("client refused", "url", url, "err", err)
+ return err
+ }
+ defer resp.Body.Close()
+
+ bodyBytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ log.Error("could not read response body", "url", url, "err", err)
+ return err
+ }
+
+ if resp.StatusCode >= 300 {
+ ec := &struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ }{}
+ if err = json.Unmarshal(bodyBytes, ec); err != nil {
+ log.Error("Couldn't unmarshal error from beacon node", "url", url, "body", string(bodyBytes))
+ return errors.New("could not unmarshal error response from beacon node")
+ }
+ return errors.New(ec.Message)
+ }
+
+ err = json.Unmarshal(bodyBytes, dst)
+ if err != nil {
+ log.Error("could not unmarshal response", "url", url, "resp", string(bodyBytes), "dst", dst, "err", err)
+ return err
+ }
+
+ log.Info("fetched", "url", url, "res", dst)
+ return nil
+}
diff --git a/builder/beacon_client_test.go b/builder/beacon_client_test.go
new file mode 100644
index 0000000000..c4cb9d6814
--- /dev/null
+++ b/builder/beacon_client_test.go
@@ -0,0 +1,268 @@
+package builder
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strconv"
+ "testing"
+
+ "github.com/gorilla/mux"
+ "github.com/stretchr/testify/require"
+)
+
+type mockBeaconNode struct {
+ srv *httptest.Server
+
+ proposerDuties map[int][]byte
+ forkResp map[int][]byte
+ headersCode int
+ headersResp []byte
+}
+
+func newMockBeaconNode() *mockBeaconNode {
+ r := mux.NewRouter()
+ srv := httptest.NewServer(r)
+
+ mbn := &mockBeaconNode{
+ srv: srv,
+
+ proposerDuties: make(map[int][]byte),
+ forkResp: make(map[int][]byte),
+ headersCode: 200,
+ headersResp: []byte{},
+ }
+
+ r.HandleFunc("/eth/v1/validator/duties/proposer/{epoch}", func(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ epochStr, ok := vars["epoch"]
+ if !ok {
+ http.Error(w, `{ "code": 400, "message": "Invalid epoch" }`, 400)
+ return
+ }
+ epoch, err := strconv.Atoi(epochStr)
+ if err != nil {
+ http.Error(w, `{ "code": 400, "message": "Invalid epoch" }`, 400)
+ return
+ }
+
+ resp, found := mbn.proposerDuties[epoch]
+ if !found {
+ http.Error(w, `{ "code": 400, "message": "Invalid epoch" }`, 400)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(resp)
+ })
+
+ r.HandleFunc("/eth/v1/beacon/headers", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(mbn.headersCode)
+ w.Write(mbn.headersResp)
+ })
+
+ return mbn
+}
+
+func TestFetchBeacon(t *testing.T) {
+ mbn := newMockBeaconNode()
+ defer mbn.srv.Close()
+
+ mbn.headersCode = 200
+ mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "10", "proposer_index": "1" } } } ] }`)
+
+ // Green path
+ headersResp := struct {
+ Data []struct {
+ Header struct {
+ Message struct {
+ Slot string `json:"slot"`
+ } `json:"message"`
+ } `json:"header"`
+ } `json:"data"`
+ }{}
+ err := fetchBeacon(mbn.srv.URL+"/eth/v1/beacon/headers", &headersResp)
+ require.NoError(t, err)
+ require.Equal(t, "10", headersResp.Data[0].Header.Message.Slot)
+
+ // Wrong dst
+ wrongForkResp := struct {
+ Data []struct {
+ Slot string `json:"slot"`
+ }
+ }{}
+ err = fetchBeacon(mbn.srv.URL+"/eth/v1/beacon/headers", &wrongForkResp)
+ require.NoError(t, err)
+ require.Equal(t, wrongForkResp.Data[0].Slot, "")
+
+ mbn.headersCode = 400
+ mbn.headersResp = []byte(`{ "code": 400, "message": "Invalid call" }`)
+ err = fetchBeacon(mbn.srv.URL+"/eth/v1/beacon/headers", &headersResp)
+ require.EqualError(t, err, "Invalid call")
+}
+
+func TestFetchCurrentSlot(t *testing.T) {
+ mbn := newMockBeaconNode()
+ defer mbn.srv.Close()
+
+ mbn.headersResp = []byte(`{
+ "execution_optimistic": false,
+ "data": [
+ {
+ "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
+ "canonical": true,
+ "header": {
+ "message": {
+ "slot": "101",
+ "proposer_index": "1",
+ "parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
+ "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
+ "body_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
+ },
+ "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
+ }
+ }
+ ]
+}`)
+
+ slot, err := fetchCurrentSlot(mbn.srv.URL)
+ require.NoError(t, err)
+ require.Equal(t, uint64(101), slot)
+
+ mbn.headersResp = []byte(`{
+ "execution_optimistic": false,
+ "data": [
+ {
+ "header": {
+ "message": {
+ "slot": "xxx"
+ }
+ }
+ }
+ ]
+}`)
+
+ slot, err = fetchCurrentSlot(mbn.srv.URL)
+ require.EqualError(t, err, "invalid response")
+ require.Equal(t, uint64(0), slot)
+}
+
+func TestFetchEpochProposersMap(t *testing.T) {
+ mbn := newMockBeaconNode()
+ defer mbn.srv.Close()
+
+ mbn.proposerDuties[10] = []byte(`{
+ "dependent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
+ "execution_optimistic": false,
+ "data": [
+ {
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
+ "validator_index": "1",
+ "slot": "1"
+ },
+ {
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b",
+ "validator_index": "2",
+ "slot": "2"
+ }
+ ]
+}`)
+
+ proposersMap, err := fetchEpochProposersMap(mbn.srv.URL, 10)
+ require.NoError(t, err)
+ require.Equal(t, 2, len(proposersMap))
+ require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"), proposersMap[1])
+ require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), proposersMap[2])
+}
+
+func TestOnForkchoiceUpdate(t *testing.T) {
+ mbn := newMockBeaconNode()
+ defer mbn.srv.Close()
+
+ mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "31", "proposer_index": "1" } } } ] }`)
+
+ mbn.proposerDuties[1] = []byte(`{
+ "dependent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
+ "execution_optimistic": false,
+ "data": [
+ {
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
+ "validator_index": "1",
+ "slot": "31"
+ },
+ {
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b",
+ "validator_index": "2",
+ "slot": "32"
+ },
+ {
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74c",
+ "validator_index": "3",
+ "slot": "33"
+ }
+ ]
+}`)
+
+ bc := NewBeaconClient(mbn.srv.URL)
+ pubkeyHex, err := bc.onForkchoiceUpdate()
+ require.NoError(t, err)
+ require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
+
+ pubkeyHex, err = bc.getProposerForNextSlot(32)
+ require.NoError(t, err)
+ require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
+
+ pubkeyHex, err = bc.getProposerForNextSlot(31)
+ require.EqualError(t, err, "slot out of sync")
+
+ pubkeyHex, err = bc.getProposerForNextSlot(33)
+ require.EqualError(t, err, "slot out of sync")
+
+ mbn.headersCode = 404
+ mbn.headersResp = []byte(`{ "code": 404, "message": "State not found" }`)
+
+ pubkeyHex, err = NewBeaconClient(mbn.srv.URL).onForkchoiceUpdate()
+ require.EqualError(t, err, "State not found")
+ require.Equal(t, PubkeyHex(""), pubkeyHex)
+
+ // Check that client does not fetch new proposers if epoch did not change
+ mbn.headersCode = 200
+ mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "31", "proposer_index": "1" } } } ] }`)
+ mbn.proposerDuties[1] = []byte(`{
+ "data": [
+ {
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74d",
+ "validator_index": "4",
+ "slot": "32"
+ }
+ ]
+}`)
+
+ pubkeyHex, err = bc.onForkchoiceUpdate()
+ require.NoError(t, err, "")
+ require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
+
+ mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "63", "proposer_index": "1" } } } ] }`)
+ mbn.proposerDuties[2] = []byte(`{
+ "data": [
+ {
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74d",
+ "validator_index": "4",
+ "slot": "64"
+ }
+ ]
+}`)
+
+ pubkeyHex, err = bc.onForkchoiceUpdate()
+ require.NoError(t, err, "")
+ require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74d"), pubkeyHex)
+
+ pubkeyHex, err = bc.getProposerForNextSlot(64)
+ require.NoError(t, err)
+ require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74d"), pubkeyHex)
+
+ // Check proposers map error is routed out
+ mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "65", "proposer_index": "1" } } } ] }`)
+ pubkeyHex, err = bc.onForkchoiceUpdate()
+ require.EqualError(t, err, "inconsistent proposer mapping")
+}
diff --git a/builder/index.go b/builder/index.go
new file mode 100644
index 0000000000..6e1e02e4db
--- /dev/null
+++ b/builder/index.go
@@ -0,0 +1,90 @@
+package builder
+
+import (
+ "html/template"
+)
+
+func parseIndexTemplate() (*template.Template, error) {
+ return template.New("index").Parse(`
+
+
+
+
+
+
+
+ Boost Block Builder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Boost Block Builder
+
+
+
+
+
+
+
+
+
+
+
+ Registered Validators: {{ .NoValidators }}
+
+
+
+
+
+
+
+ Best Header
+
+
{{ .Header }}
+
+
+
+
+
+
+ Best Payload
+
+
{{ .Blocks }}
+
+
+
+
+
+
+
+`)
+}
diff --git a/builder/service.go b/builder/service.go
new file mode 100644
index 0000000000..2b47787174
--- /dev/null
+++ b/builder/service.go
@@ -0,0 +1,108 @@
+package builder
+
+import (
+ "errors"
+ "net/http"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/gorilla/mux"
+
+ "github.com/flashbots/go-boost-utils/bls"
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+
+ "github.com/flashbots/go-utils/httplogger"
+)
+
+const (
+ _PathStatus = "/eth/v1/builder/status"
+ _PathRegisterValidator = "/eth/v1/builder/validators"
+ _PathGetHeader = "/eth/v1/builder/header/{slot:[0-9]+}/{parent_hash:0x[a-fA-F0-9]+}/{pubkey:0x[a-fA-F0-9]+}"
+ _PathGetPayload = "/eth/v1/builder/blinded_blocks"
+)
+
+type Service struct {
+ srv *http.Server
+}
+
+func (s *Service) Start() {
+ log.Info("Service started")
+ go s.srv.ListenAndServe()
+}
+
+func getRouter(backend *Backend) http.Handler {
+ router := mux.NewRouter()
+
+ // Add routes
+ router.HandleFunc("/", backend.handleIndex).Methods(http.MethodGet)
+ router.HandleFunc(_PathStatus, backend.handleStatus).Methods(http.MethodGet)
+ router.HandleFunc(_PathRegisterValidator, backend.handleRegisterValidator).Methods(http.MethodPost)
+ router.HandleFunc(_PathGetHeader, backend.handleGetHeader).Methods(http.MethodGet)
+ router.HandleFunc(_PathGetPayload, backend.handleGetPayload).Methods(http.MethodPost)
+
+ // Add logging and return router
+ loggedRouter := httplogger.LoggingMiddleware(router)
+ return loggedRouter
+}
+
+func NewService(listenAddr string, backend *Backend) *Service {
+ return &Service{
+ srv: &http.Server{
+ Addr: listenAddr,
+ Handler: getRouter(backend),
+ /*
+ ReadTimeout:
+ ReadHeaderTimeout:
+ WriteTimeout:
+ IdleTimeout:
+ */
+ },
+ }
+}
+
+type BuilderConfig struct {
+ EnableValidatorChecks bool
+ SecretKey string
+ ListenAddr string
+ GenesisForkVersion string
+ BellatrixForkVersion string
+ GenesisValidatorsRoot string
+ BeaconEndpoint string
+}
+
+func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error {
+ envSkBytes, err := hexutil.Decode(cfg.SecretKey)
+ if err != nil {
+ return errors.New("incorrect builder API secret key provided")
+ }
+
+ sk, err := bls.SecretKeyFromBytes(envSkBytes[:])
+ if err != nil {
+ return errors.New("incorrect builder API secret key provided")
+ }
+
+ genesisForkVersionBytes, err := hexutil.Decode(cfg.GenesisForkVersion)
+ var genesisForkVersion [4]byte
+ copy(genesisForkVersion[:], genesisForkVersionBytes[:4])
+ builderSigningDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeAppBuilder, genesisForkVersion, boostTypes.Root{})
+
+ genesisValidatorsRoot := boostTypes.Root(common.HexToHash(cfg.GenesisValidatorsRoot))
+ bellatrixForkVersionBytes, err := hexutil.Decode(cfg.BellatrixForkVersion)
+ var bellatrixForkVersion [4]byte
+ copy(bellatrixForkVersion[:], bellatrixForkVersionBytes[:4])
+ proposerSigningDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeBeaconProposer, bellatrixForkVersion, genesisValidatorsRoot)
+
+ var beaconClient IBeaconClient
+ beaconClient = NewBeaconClient(cfg.BeaconEndpoint)
+
+ builderBackend := NewBackend(sk, beaconClient, builderSigningDomain, proposerSigningDomain, cfg.EnableValidatorChecks)
+ builderService := NewService(cfg.ListenAddr, builderBackend)
+ builderService.Start()
+
+ backend.SetSealedBlockHook(builderBackend.newSealedBlock)
+ backend.SetForkchoiceHook(builderBackend.onForkchoice)
+ return nil
+}
diff --git a/builder/validator.go b/builder/validator.go
new file mode 100644
index 0000000000..b1554a4f95
--- /dev/null
+++ b/builder/validator.go
@@ -0,0 +1,49 @@
+package builder
+
+import (
+ "time"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+
+ "github.com/flashbots/go-boost-utils/bls"
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+)
+
+type ValidatorPrivateData struct {
+ sk *bls.SecretKey
+ Pk hexutil.Bytes
+}
+
+func NewRandomValidator() *ValidatorPrivateData {
+ sk, pk, err := bls.GenerateNewKeypair()
+ if err != nil {
+ return nil
+ }
+ return &ValidatorPrivateData{sk, pk.Compress()}
+}
+
+func (v *ValidatorPrivateData) Sign(msg boostTypes.HashTreeRoot, d boostTypes.Domain) (boostTypes.Signature, error) {
+ return boostTypes.SignMessage(msg, d, v.sk)
+}
+
+func (v *ValidatorPrivateData) PrepareRegistrationMessage(feeRecipientHex string) (boostTypes.SignedValidatorRegistration, error) {
+ address, err := boostTypes.HexToAddress(feeRecipientHex)
+ if err != nil {
+ return boostTypes.SignedValidatorRegistration{}, err
+ }
+
+ pubkey := boostTypes.PublicKey{}
+ pubkey.FromSlice(v.Pk)
+
+ msg := &boostTypes.RegisterValidatorRequestMessage{
+ FeeRecipient: address,
+ GasLimit: 1000,
+ Timestamp: uint64(time.Now().UnixMilli()),
+ Pubkey: pubkey,
+ }
+ signature, err := v.Sign(msg, boostTypes.DomainBuilder)
+ if err != nil {
+ return boostTypes.SignedValidatorRegistration{}, err
+ }
+ return boostTypes.SignedValidatorRegistration{msg, signature}, nil
+}
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 30565fda61..1fe5e8ed27 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
"github.com/ethereum/go-ethereum/accounts/usbwallet"
+ builder "github.com/ethereum/go-ethereum/builder"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/eth/ethconfig"
@@ -159,12 +160,23 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
if ctx.IsSet(utils.OverrideTerminalTotalDifficulty.Name) {
cfg.Eth.OverrideTerminalTotalDifficulty = flags.GlobalBig(ctx, utils.OverrideTerminalTotalDifficulty.Name)
}
+
if ctx.IsSet(utils.OverrideTerminalTotalDifficultyPassed.Name) {
override := ctx.Bool(utils.OverrideTerminalTotalDifficultyPassed.Name)
cfg.Eth.OverrideTerminalTotalDifficultyPassed = &override
}
- backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
+ bpConfig := &builder.BuilderConfig{
+ EnableValidatorChecks: ctx.IsSet(utils.BuilderEnableValidatorChecks.Name),
+ SecretKey: ctx.String(utils.BuilderSecretKey.Name),
+ ListenAddr: ctx.String(utils.BuilderListenAddr.Name),
+ GenesisForkVersion: ctx.String(utils.BuilderGenesisForkVersion.Name),
+ BellatrixForkVersion: ctx.String(utils.BuilderBellatrixForkVersion.Name),
+ GenesisValidatorsRoot: ctx.String(utils.BuilderGenesisValidatorsRoot.Name),
+ BeaconEndpoint: ctx.String(utils.BuilderBeaconEndpoint.Name),
+ }
+
+ backend, eth := utils.RegisterEthService(stack, &cfg.Eth, bpConfig)
// Warn users to migrate if they have a legacy freezer format.
if eth != nil && !ctx.IsSet(utils.IgnoreLegacyReceiptsFlag.Name) {
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index b9e3ed31e8..ab159b91de 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -157,6 +157,16 @@ var (
configFileFlag,
}, utils.NetworkFlags, utils.DatabasePathFlags)
+ builderApiFlags = []cli.Flag{
+ utils.BuilderEnableValidatorChecks,
+ utils.BuilderSecretKey,
+ utils.BuilderListenAddr,
+ utils.BuilderGenesisForkVersion,
+ utils.BuilderBellatrixForkVersion,
+ utils.BuilderGenesisValidatorsRoot,
+ utils.BuilderBeaconEndpoint,
+ }
+
rpcFlags = []cli.Flag{
utils.HTTPEnabledFlag,
utils.HTTPListenAddrFlag,
@@ -247,6 +257,7 @@ func init() {
app.Flags = flags.Merge(
nodeFlags,
rpcFlags,
+ builderApiFlags,
consoleFlags,
debug.Flags,
metricsFlags,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 9e95193343..c76e2f37ee 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
+ builder "github.com/ethereum/go-ethereum/builder"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/fdlimit"
"github.com/ethereum/go-ethereum/consensus"
@@ -660,7 +661,47 @@ var (
Usage: "Geth will start up even if there are legacy receipts in freezer",
Category: flags.MiscCategory,
}
-
+ // Builder API settings
+ BuilderEnableValidatorChecks = &cli.BoolFlag{
+ Name: "builder.validator_checks",
+ Usage: "Enable the validator checks",
+ }
+ BuilderSecretKey = &cli.StringFlag{
+ Name: "builder.secret_key",
+ Usage: "Builder API key used for signing headers",
+ EnvVars: []string{"BUILDER_SECRET_KEY"},
+ Value: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11",
+ }
+ BuilderListenAddr = &cli.StringFlag{
+ Name: "builder.listen_addr",
+ Usage: "Listening address for builder endpoint",
+ EnvVars: []string{"BUILDER_LISTEN_ADDR"},
+ Value: ":28545",
+ }
+ BuilderGenesisForkVersion = &cli.StringFlag{
+ Name: "builder.genesis_fork_version",
+ Usage: "Gensis fork version. For kiln use 0x70000069",
+ EnvVars: []string{"BUILDER_GENESIS_FORK_VERSION"},
+ Value: "0x00000000",
+ }
+ BuilderBellatrixForkVersion = &cli.StringFlag{
+ Name: "builder.bellatrix_fork_version",
+ Usage: "Bellatrix fork version. For kiln use 0x70000071",
+ EnvVars: []string{"BUILDER_BELLATRIX_FORK_VERSION"},
+ Value: "0x02000000",
+ }
+ BuilderGenesisValidatorsRoot = &cli.StringFlag{
+ Name: "builder.genesis_validators_root",
+ Usage: "Genesis validators root of the network. For kiln use 0x99b09fcd43e5905236c370f184056bec6e6638cfc31a323b304fc4aa789cb4ad",
+ EnvVars: []string{"BUILDER_GENESIS_VALIDATORS_ROOT"},
+ Value: "0x0000000000000000000000000000000000000000000000000000000000000000",
+ }
+ BuilderBeaconEndpoint = &cli.StringFlag{
+ Name: "builder.beacon_endpoint",
+ Usage: "Beacon endpoint to connect to for beacon chain data",
+ EnvVars: []string{"BUILDER_BEACON_ENDPOINT"},
+ Value: "http://127.0.0.1:5052",
+ }
// RPC settings
IPCDisabledFlag = &cli.BoolFlag{
Name: "ipcdisable",
@@ -1987,7 +2028,7 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
// RegisterEthService adds an Ethereum client to the stack.
// The second return value is the full node instance, which may be nil if the
// node is running as a light client.
-func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
+func RegisterEthService(stack *node.Node, cfg *ethconfig.Config, bpCfg *builder.BuilderConfig) (ethapi.Backend, *eth.Ethereum) {
if cfg.SyncMode == downloader.LightSync {
backend, err := les.New(stack, cfg)
if err != nil {
@@ -2012,6 +2053,11 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend
if err := ethcatalyst.Register(stack, backend); err != nil {
Fatalf("Failed to register the Engine API service: %v", err)
}
+
+ if err := builder.Register(stack, backend, bpCfg); err != nil {
+ Fatalf("Failed to register the builder service: %v", err)
+ }
+
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
return backend.APIBackend, backend
}
diff --git a/core/beacon/types.go b/core/beacon/types.go
index e25d724c0d..d0e878bde1 100644
--- a/core/beacon/types.go
+++ b/core/beacon/types.go
@@ -33,6 +33,7 @@ type PayloadAttributesV1 struct {
Timestamp uint64 `json:"timestamp" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
+ GasLimit uint64
}
// JSON type overrides for PayloadAttributesV1.
diff --git a/core/types/block.go b/core/types/block.go
index 7525a88f5a..c5076f1142 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -168,6 +168,8 @@ type Block struct {
uncles []*Header
transactions Transactions
+ Profit *big.Int
+
// caches
hash atomic.Value
size atomic.Value
diff --git a/eth/backend.go b/eth/backend.go
index 7782076363..7da37eb5a1 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/core"
+ beaconTypes "github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/pruner"
@@ -85,6 +86,9 @@ type Ethereum struct {
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
closeBloomHandler chan struct{}
+ newSealedBlockHook func(*beaconTypes.ExecutableDataV1, *types.Block)
+ forkchoiceHook func(*beaconTypes.PayloadAttributesV1)
+
APIBackend *EthAPIBackend
miner *miner.Miner
@@ -353,6 +357,27 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) {
return common.Address{}, fmt.Errorf("etherbase must be explicitly specified")
}
+func (s *Ethereum) SetSealedBlockHook(newSealedBlockHook func(*beaconTypes.ExecutableDataV1, *types.Block)) {
+ s.newSealedBlockHook = newSealedBlockHook
+}
+
+func (s *Ethereum) NewSealedBlock(data *beaconTypes.ExecutableDataV1, block *types.Block) {
+ if s.newSealedBlockHook != nil {
+ s.newSealedBlockHook(data, block)
+ }
+}
+
+func (s *Ethereum) SetForkchoiceHook(forkchoiceHook func(*beaconTypes.PayloadAttributesV1)) {
+ s.forkchoiceHook = forkchoiceHook
+}
+
+func (s *Ethereum) ForkchoiceHook(payloadAttributes *beaconTypes.PayloadAttributesV1) {
+ // Possibly modifies payloadAttributes's fee recipient
+ if s.forkchoiceHook != nil {
+ s.forkchoiceHook(payloadAttributes)
+ }
+}
+
// isLocalBlock checks whether the specified block is mined
// by local miner accounts.
//
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index b159f34e64..cf748c5545 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -155,7 +155,11 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock()
- log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
+ log.Info("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
+
+ // Adjusts payload attributes to next slot's validator preferences
+ api.eth.ForkchoiceHook(payloadAttributes)
+
if update.HeadBlockHash == (common.Hash{}) {
log.Warn("Forkchoice requested update to zero hash")
return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
@@ -277,20 +281,24 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
// might replace it arbitrarily many times in between.
if payloadAttributes != nil {
// Create an empty block first which can be used as a fallback
- empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, true)
+ empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.GasLimit, payloadAttributes.Random, true)
if err != nil {
log.Error("Failed to create empty sealing payload", "err", err)
return valid(nil), beacon.InvalidPayloadAttributes.With(err)
}
// Send a request to generate a full block in the background.
// The result can be obtained via the returned channel.
- resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, false)
+ resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.GasLimit, payloadAttributes.Random, false)
if err != nil {
log.Error("Failed to create async sealing payload", "err", err)
return valid(nil), beacon.InvalidPayloadAttributes.With(err)
}
id := computePayloadId(update.HeadBlockHash, payloadAttributes)
- api.localBlocks.put(id, &payload{empty: empty, result: resCh})
+ resultPayload := &payload{empty: empty, result: resCh}
+ api.localBlocks.put(id, resultPayload)
+ go func() {
+ api.eth.NewSealedBlock(resultPayload.resolve())
+ }()
return valid(&id), nil
}
return valid(nil), nil
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
index 0d945993eb..68fc0a05ff 100644
--- a/eth/catalyst/api_test.go
+++ b/eth/catalyst/api_test.go
@@ -603,7 +603,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
}
func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) {
- block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, params.Random, false)
+ block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false)
if err != nil {
return nil, err
}
@@ -846,7 +846,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
Random: crypto.Keccak256Hash([]byte{byte(1)}),
SuggestedFeeRecipient: parent.Coinbase(),
}
- empty, err := api.eth.Miner().GetSealingBlockSync(parent.Hash(), params.Timestamp, params.SuggestedFeeRecipient, params.Random, true)
+ empty, err := api.eth.Miner().GetSealingBlockSync(parent.Hash(), params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, true)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err)
}
diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go
index ff8edc1201..2995ff4860 100644
--- a/eth/catalyst/queue.go
+++ b/eth/catalyst/queue.go
@@ -47,7 +47,7 @@ type payload struct {
// resolve extracts the generated full block from the given channel if possible
// or fallback to empty block as an alternative.
-func (req *payload) resolve() *beacon.ExecutableDataV1 {
+func (req *payload) resolve() (*beacon.ExecutableDataV1, *types.Block) {
// this function can be called concurrently, prevent any
// concurrency issue in the first place.
req.lock.Lock()
@@ -71,9 +71,9 @@ func (req *payload) resolve() *beacon.ExecutableDataV1 {
}
if req.block != nil {
- return beacon.BlockToExecutableData(req.block)
+ return beacon.BlockToExecutableData(req.block), req.block
}
- return beacon.BlockToExecutableData(req.empty)
+ return beacon.BlockToExecutableData(req.empty), req.empty
}
// payloadQueueItem represents an id->payload tuple to store until it's retrieved
@@ -120,7 +120,8 @@ func (q *payloadQueue) get(id beacon.PayloadID) *beacon.ExecutableDataV1 {
return nil // no more items
}
if item.id == id {
- return item.data.resolve()
+ data, _ := item.data.resolve()
+ return data
}
}
return nil
diff --git a/go.mod b/go.mod
index 4a769c7a2d..e14925890f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/ethereum/go-ethereum
-go 1.17
+go 1.18
require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0
@@ -9,8 +9,8 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.1.1
github.com/aws/aws-sdk-go-v2/credentials v1.1.1
github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1
- github.com/btcsuite/btcd/btcec/v2 v2.2.0
- github.com/cespare/cp v0.1.0
+ github.com/btcsuite/btcd/btcec/v2 v2.1.3
+ github.com/cespare/cp v1.1.1
github.com/cloudflare/cloudflare-go v0.14.0
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f
github.com/davecgh/go-spew v1.1.1
@@ -18,35 +18,38 @@ require (
github.com/docker/docker v1.6.2
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf
github.com/edsrzf/mmap-go v1.0.0
- github.com/fatih/color v1.7.0
+ github.com/fatih/color v1.9.0
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
- github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
+ github.com/flashbots/go-boost-utils v0.1.2
+ github.com/flashbots/go-utils v0.4.4
+ github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
github.com/go-stack/stack v1.8.0
github.com/golang-jwt/jwt/v4 v4.3.0
github.com/golang/protobuf v1.5.2
github.com/golang/snappy v0.0.4
- github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa
- github.com/google/uuid v1.2.0
- github.com/gorilla/websocket v1.4.2
+ github.com/google/gofuzz v1.2.0
+ github.com/google/uuid v1.3.0
+ github.com/gorilla/mux v1.8.0
+ github.com/gorilla/websocket v1.5.0
github.com/graph-gophers/graphql-go v1.3.0
github.com/hashicorp/go-bexpr v0.1.10
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/holiman/bloomfilter/v2 v2.0.3
github.com/holiman/uint256 v1.2.0
- github.com/huin/goupnp v1.0.3
+ github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204
github.com/influxdata/influxdb v1.8.3
github.com/influxdata/influxdb-client-go/v2 v2.4.0
github.com/jackpal/go-nat-pmp v1.0.2
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e
- github.com/julienschmidt/httprouter v1.2.0
+ github.com/julienschmidt/httprouter v1.3.0
github.com/karalabe/usb v0.0.2
- github.com/mattn/go-colorable v0.1.8
- github.com/mattn/go-isatty v0.0.12
+ github.com/mattn/go-colorable v0.1.9
+ github.com/mattn/go-isatty v0.0.14
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
github.com/olekukonko/tablewriter v0.0.5
- github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
- github.com/prometheus/tsdb v0.7.1
+ github.com/peterh/liner v1.2.0
+ github.com/prometheus/tsdb v0.10.0
github.com/rjeczalik/notify v0.9.1
github.com/rs/cors v1.7.0
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible
@@ -56,50 +59,54 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef
github.com/urfave/cli/v2 v2.10.2
- golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
+ golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.7
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
- golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023
+ golang.org/x/tools v0.1.10
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
+ gopkg.in/urfave/cli.v1 v1.20.0
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 // indirect
- github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
+ github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
+ github.com/allegro/bigcache v1.2.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.1.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.1.1 // indirect
github.com/aws/smithy-go v1.1.0 // indirect
- github.com/cespare/xxhash/v2 v2.1.1 // indirect
+ github.com/btcsuite/btcd v0.22.0-beta // indirect
+ github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
+ github.com/ferranbt/fastssz v0.1.1-0.20220303160658-88bb965b6747 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
- github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect
- github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
+ github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
+ github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/mitchellh/pointerstructure v1.2.0 // indirect
github.com/naoina/go-stringutil v0.1.0 // indirect
- github.com/opentracing/opentracing-go v1.1.0 // indirect
+ github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
- golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
+ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
google.golang.org/protobuf v1.26.0 // indirect
diff --git a/go.sum b/go.sum
index 4b27867fbc..0cdb466eed 100644
--- a/go.sum
+++ b/go.sum
@@ -25,19 +25,20 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSu
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 h1:Px2UA+2RvSSvv+RvJNuUB6n7rs5Wsel4dXLe90Um2n4=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
-github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
+github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
+github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
+github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
+github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
github.com/aws/aws-sdk-go-v2 v1.2.0 h1:BS+UYpbsElC82gB+2E2jiCBg36i8HlubTB/dO/moQ9c=
@@ -62,18 +63,29 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
-github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
-github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
-github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
-github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
+github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo=
+github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
+github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE=
+github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
+github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
+github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
+github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
+github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
+github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
+github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
+github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
+github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
-github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
-github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU=
+github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -89,6 +101,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
+github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -98,6 +111,7 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
+github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
@@ -119,21 +133,26 @@ github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/ferranbt/fastssz v0.1.1-0.20220303160658-88bb965b6747 h1:0w9ZW2gY6Ws640PkDsyB1CK1ndAPJJ0mJbsDojQFl5c=
+github.com/ferranbt/fastssz v0.1.1-0.20220303160658-88bb965b6747/go.mod h1:S8yiDeAXy8f88W4Ul+0dBMPx49S05byYbmZD6Uv94K4=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq6Q+cGWcGHz0FjGR3QqcInWcW0=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
+github.com/flashbots/go-boost-utils v0.1.2 h1:xcwO6rhLmdbZ+ttN8PjHQynqY1pm+RCA56eGP9wPp10=
+github.com/flashbots/go-boost-utils v0.1.2/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
+github.com/flashbots/go-utils v0.4.4 h1:J0LUifVEpVYE+ZbK/DeGay4E3B6+Yh8pKLgfv5A0Oq0=
+github.com/flashbots/go-utils v0.4.4/go.mod h1:weSbiNnH+xsmK8t3TDDJxluv4+qnwRWmKay2QQa8Yfc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
-github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc=
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8=
-github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
-github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
+github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
+github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -156,7 +175,6 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
@@ -183,6 +201,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -198,22 +217,22 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64=
-github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
-github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0=
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
@@ -227,11 +246,10 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
-github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
+github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204 h1:+EYBkW+dbi3F/atB+LSQZSWh7+HNrV3A/N0y6DSoy9k=
+github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY=
github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8=
@@ -251,36 +269,43 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U=
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
+github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0=
github.com/karalabe/usb v0.0.2 h1:M6QQBNxF+CQ8OFvxrT90BA0qBOXymndZnk5q235mFc4=
github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
+github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
@@ -293,20 +318,28 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
-github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
+github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
@@ -320,37 +353,30 @@ github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hz
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
-github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
-github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
-github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
-github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
-github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
-github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE=
github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
-github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
-github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
+github.com/peterh/liner v1.2.0 h1:w/UPXyl5GfahFxcTOz2j9wCIHNI+pUPr2laqpojKNCg=
+github.com/peterh/liner v1.2.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -365,17 +391,17 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic=
+github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4=
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -408,14 +434,8 @@ github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344 h1:m+8fKfQwCAy1QjzINvKe/pYtLjo2dl59x2w9YSEJxuY=
github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
-github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
-github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
-github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
-github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
-github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
-github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
@@ -434,25 +454,27 @@ github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6Ut
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
+golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -463,7 +485,6 @@ golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm0
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -483,9 +504,9 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4=
-golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -509,11 +530,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -533,7 +551,6 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -560,17 +577,13 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@@ -614,14 +627,12 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023 h1:0c3L82FDQ5rt1bjTBlchS8t6RQ6299/+5bWMnRLh+uI=
-golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
+golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -685,6 +696,8 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
+gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/miner/miner.go b/miner/miner.go
index 1e9607a76a..a51f49e82a 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -245,8 +245,8 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
// there is always a result that will be returned through the result channel.
// The difference is that if the execution fails, the returned result is nil
// and the concrete error is dropped silently.
-func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, noTxs bool) (chan *types.Block, error) {
- resCh, _, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, random, noTxs)
+func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (chan *types.Block, error) {
+ resCh, _, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs)
if err != nil {
return nil, err
}
@@ -256,8 +256,8 @@ func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, c
// GetSealingBlockSync creates a sealing block according to the given parameters.
// If the generation is failed or the underlying work is already closed, an error
// will be returned.
-func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, noTxs bool) (*types.Block, error) {
- resCh, errCh, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, random, noTxs)
+func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (*types.Block, error) {
+ resCh, errCh, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs)
if err != nil {
return nil, err
}
diff --git a/miner/stress/beacon/main.go b/miner/stress/beacon/main.go
index 88af84c7fc..b30365052d 100644
--- a/miner/stress/beacon/main.go
+++ b/miner/stress/beacon/main.go
@@ -141,9 +141,9 @@ func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode
}
}
-func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*beacon.ExecutableDataV1, error) {
+func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*beacon.ExecutableDataV1, *types.Block, error) {
if n.typ != eth2MiningNode {
- return nil, errors.New("invalid node type")
+ return nil, nil, errors.New("invalid node type")
}
timestamp := uint64(time.Now().Unix())
if timestamp <= parentTimestamp {
@@ -161,9 +161,10 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64)
}
payload, err := n.api.ForkchoiceUpdatedV1(fcState, &payloadAttribute)
if err != nil {
- return nil, err
+ return nil, nil, err
}
- return n.api.GetPayloadV1(*payload.PayloadID)
+ data, err := n.api.GetPayloadV1(*payload.PayloadID)
+ return data, nil, nil
}
func (n *ethNode) insertBlock(eb beacon.ExecutableDataV1) error {
@@ -358,7 +359,7 @@ func (mgr *nodeManager) run() {
if parentBlock.NumberU64() == 0 {
timestamp = uint64(time.Now().Unix()) - uint64(blockIntervalInt)
}
- ed, err := producers[0].assembleBlock(hash, timestamp)
+ ed, _, err := producers[0].assembleBlock(hash, timestamp)
if err != nil {
log.Error("Failed to assemble the block", "err", err)
continue
diff --git a/miner/worker.go b/miner/worker.go
index 93fb6288bb..be9e87fcb8 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -956,6 +956,7 @@ type generateParams struct {
forceTime bool // Flag whether the given timestamp is immutable or not
parentHash common.Hash // Parent block hash, empty means the latest chain head
coinbase common.Address // The fee recipient address for including transaction
+ gasLimit uint64 // The validator's requested gas limit target
random common.Hash // The randomness generated by beacon chain, empty before the merge
noUncle bool // Flag whether the uncle block inclusion is allowed
noExtra bool // Flag whether the extra field assignment is allowed
@@ -987,11 +988,15 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
timestamp = parent.Time() + 1
}
// Construct the sealing block header, set the extra field if it's allowed
+ gasTarget := genParams.gasLimit
+ if gasTarget == 0 {
+ gasTarget = w.config.GasCeil
+ }
num := parent.Number()
header := &types.Header{
ParentHash: parent.Hash(),
Number: num.Add(num, common.Big1),
- GasLimit: core.CalcGasLimit(parent.GasLimit(), w.config.GasCeil),
+ GasLimit: core.CalcGasLimit(parent.GasLimit(), gasTarget),
Time: timestamp,
Coinbase: genParams.coinbase,
}
@@ -1081,10 +1086,19 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
}
defer work.discard()
+ coinbaseBalanceBefore := work.state.GetBalance(params.coinbase)
+
if !params.noTxs {
w.fillTransactions(nil, work)
}
- return w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
+ block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
+ if err != nil {
+ return nil, err
+ }
+
+ coinbaseBalanceAfter := work.state.GetBalance(params.coinbase)
+ block.Profit = big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
+ return block, nil
}
// commitWork generates several new sealing tasks based on the parent block
@@ -1170,7 +1184,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
// getSealingBlock generates the sealing block based on the given parameters.
// The generation result will be passed back via the given channel no matter
// the generation itself succeeds or not.
-func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, noTxs bool) (chan *types.Block, chan error, error) {
+func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (chan *types.Block, chan error, error) {
var (
resCh = make(chan *types.Block, 1)
errCh = make(chan error, 1)
@@ -1181,6 +1195,7 @@ func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase
forceTime: true,
parentHash: parent,
coinbase: coinbase,
+ gasLimit: gasLimit,
random: random,
noUncle: true,
noExtra: true,
diff --git a/miner/worker_test.go b/miner/worker_test.go
index ec5ba67e1c..2b5af2ffc2 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -637,7 +637,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
// This API should work even when the automatic sealing is not enabled
for _, c := range cases {
- resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, false)
+ resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false)
block := <-resChan
err := <-errChan
if c.expectErr {
@@ -655,7 +655,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
// This API should work even when the automatic sealing is enabled
w.start()
for _, c := range cases {
- resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, false)
+ resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false)
block := <-resChan
err := <-errChan
if c.expectErr {
From 3c0d5f1881de1504eac9249e7dcdc26988ba91a9 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 2 Jun 2022 10:48:31 +0200
Subject: [PATCH 02/83] Adjust transactions root computation (#3)
---
builder/backend.go | 26 +++++++-------------------
builder/backend_test.go | 2 +-
go.mod | 2 +-
go.sum | 2 ++
4 files changed, 11 insertions(+), 21 deletions(-)
diff --git a/builder/backend.go b/builder/backend.go
index 5473e280dc..acbc0640dd 100644
--- a/builder/backend.go
+++ b/builder/backend.go
@@ -3,7 +3,6 @@ package builder
import (
"bytes"
"encoding/json"
- "fmt"
"html/template"
"math/big"
"net/http"
@@ -17,7 +16,6 @@ import (
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/trie"
"github.com/gorilla/mux"
"github.com/flashbots/go-boost-utils/bls"
@@ -358,7 +356,7 @@ func (b *Backend) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Blo
log.Info("newSealedBlock", "data", string(dataJson))
}
payload := executableDataToExecutionPayload(data)
- payloadHeader, err := payloadToPayloadHeader(payload)
+ payloadHeader, err := payloadToPayloadHeader(payload, data)
if err != nil {
log.Error("could not convert payload to header", "err", err)
return
@@ -371,23 +369,13 @@ func (b *Backend) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Blo
b.bestDataLock.Unlock()
}
-func decodeTransactions(enc []hexutil.Bytes) ([]*types.Transaction, error) {
- var txs = make([]*types.Transaction, len(enc))
- for i, encTx := range enc {
- var tx types.Transaction
- if err := tx.UnmarshalBinary(encTx); err != nil {
- return nil, fmt.Errorf("invalid transaction %d: %v", i, err)
- }
- txs[i] = &tx
- }
- return txs, nil
-}
-
-func payloadToPayloadHeader(p *boostTypes.ExecutionPayload) (*boostTypes.ExecutionPayloadHeader, error) {
- txs, err := decodeTransactions(p.Transactions)
+func payloadToPayloadHeader(p *boostTypes.ExecutionPayload, data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayloadHeader, error) {
+ txs := boostTypes.Transactions{data.Transactions}
+ txroot, err := txs.HashTreeRoot()
if err != nil {
return nil, err
}
+
return &boostTypes.ExecutionPayloadHeader{
ParentHash: p.ParentHash,
FeeRecipient: p.FeeRecipient,
@@ -399,10 +387,10 @@ func payloadToPayloadHeader(p *boostTypes.ExecutionPayload) (*boostTypes.Executi
GasLimit: p.GasLimit,
GasUsed: p.GasUsed,
Timestamp: p.Timestamp,
- ExtraData: []byte(p.ExtraData),
+ ExtraData: data.ExtraData,
BaseFeePerGas: p.BaseFeePerGas,
BlockHash: p.BlockHash,
- TransactionsRoot: [32]byte(types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil))),
+ TransactionsRoot: [32]byte(txroot),
}, nil
}
diff --git a/builder/backend_test.go b/builder/backend_test.go
index dbd56d7c35..4b1e39a7e7 100644
--- a/builder/backend_test.go
+++ b/builder/backend_test.go
@@ -146,7 +146,7 @@ func TestGetHeader(t *testing.T) {
err := json.Unmarshal(rr.Body.Bytes(), bid)
require.NoError(t, err)
- expectedHeader, err := payloadToPayloadHeader(executableDataToExecutionPayload(forkchoiceData))
+ expectedHeader, err := payloadToPayloadHeader(executableDataToExecutionPayload(forkchoiceData), forkchoiceData)
require.NoError(t, err)
require.EqualValues(t, &boostTypes.BuilderBid{
Header: expectedHeader,
diff --git a/go.mod b/go.mod
index e14925890f..c9facd7947 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,7 @@ require (
github.com/fatih/color v1.9.0
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
- github.com/flashbots/go-boost-utils v0.1.2
+ github.com/flashbots/go-boost-utils v0.1.3-0.20220601173756-01db408b8c89
github.com/flashbots/go-utils v0.4.4
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
github.com/go-stack/stack v1.8.0
diff --git a/go.sum b/go.sum
index 0cdb466eed..4b979baa2d 100644
--- a/go.sum
+++ b/go.sum
@@ -143,6 +143,8 @@ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlK
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/flashbots/go-boost-utils v0.1.2 h1:xcwO6rhLmdbZ+ttN8PjHQynqY1pm+RCA56eGP9wPp10=
github.com/flashbots/go-boost-utils v0.1.2/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
+github.com/flashbots/go-boost-utils v0.1.3-0.20220601173756-01db408b8c89 h1:3wVzFGJbXm8BjuFav3ZPKFAz01IY6JDjV38b3Ccuk2I=
+github.com/flashbots/go-boost-utils v0.1.3-0.20220601173756-01db408b8c89/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
github.com/flashbots/go-utils v0.4.4 h1:J0LUifVEpVYE+ZbK/DeGay4E3B6+Yh8pKLgfv5A0Oq0=
github.com/flashbots/go-utils v0.4.4/go.mod h1:weSbiNnH+xsmK8t3TDDJxluv4+qnwRWmKay2QQa8Yfc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
From 0211eab24ea59408e2edd4ce044e25744a027ccc Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 2 Jun 2022 11:36:53 +0200
Subject: [PATCH 03/83] Adjust logsBloom encoding (#4)
---
builder/backend.go | 5 ++---
builder/backend_test.go | 1 +
go.mod | 2 +-
go.sum | 6 ++----
4 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/builder/backend.go b/builder/backend.go
index acbc0640dd..7597666d57 100644
--- a/builder/backend.go
+++ b/builder/backend.go
@@ -399,14 +399,13 @@ func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) *boostTypes
for i, tx := range data.Transactions {
transactionData[i] = hexutil.Bytes(tx)
}
- logsBloom := boostTypes.Bloom{}
- logsBloom.FromSlice(data.LogsBloom)
+
return &boostTypes.ExecutionPayload{
ParentHash: [32]byte(data.ParentHash),
FeeRecipient: [20]byte(data.FeeRecipient),
StateRoot: [32]byte(data.StateRoot),
ReceiptsRoot: [32]byte(data.ReceiptsRoot),
- LogsBloom: logsBloom,
+ LogsBloom: boostTypes.Bloom(types.BytesToBloom(data.LogsBloom)),
Random: [32]byte(data.Random),
BlockNumber: data.Number,
GasLimit: data.GasLimit,
diff --git a/builder/backend_test.go b/builder/backend_test.go
index 4b1e39a7e7..1158d06f25 100644
--- a/builder/backend_test.go
+++ b/builder/backend_test.go
@@ -118,6 +118,7 @@ func TestGetHeader(t *testing.T) {
BlockHash: common.HexToHash("0xbfbfbfb"),
BaseFeePerGas: big.NewInt(12),
ExtraData: []byte{},
+ LogsBloom: []byte{0x00, 0x05, 0x10},
}
forkchoiceBlock := &types.Block{
Profit: big.NewInt(10),
diff --git a/go.mod b/go.mod
index c9facd7947..99146f88a4 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,7 @@ require (
github.com/fatih/color v1.9.0
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
- github.com/flashbots/go-boost-utils v0.1.3-0.20220601173756-01db408b8c89
+ github.com/flashbots/go-boost-utils v0.1.3-0.20220601182529-417a64679a5d
github.com/flashbots/go-utils v0.4.4
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
github.com/go-stack/stack v1.8.0
diff --git a/go.sum b/go.sum
index 4b979baa2d..1d4edf193d 100644
--- a/go.sum
+++ b/go.sum
@@ -141,10 +141,8 @@ github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
-github.com/flashbots/go-boost-utils v0.1.2 h1:xcwO6rhLmdbZ+ttN8PjHQynqY1pm+RCA56eGP9wPp10=
-github.com/flashbots/go-boost-utils v0.1.2/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
-github.com/flashbots/go-boost-utils v0.1.3-0.20220601173756-01db408b8c89 h1:3wVzFGJbXm8BjuFav3ZPKFAz01IY6JDjV38b3Ccuk2I=
-github.com/flashbots/go-boost-utils v0.1.3-0.20220601173756-01db408b8c89/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
+github.com/flashbots/go-boost-utils v0.1.3-0.20220601182529-417a64679a5d h1:hoqkcRKMupXCWW38fDfvNcuIxl9eCAZrguw3dNCw+Qg=
+github.com/flashbots/go-boost-utils v0.1.3-0.20220601182529-417a64679a5d/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
github.com/flashbots/go-utils v0.4.4 h1:J0LUifVEpVYE+ZbK/DeGay4E3B6+Yh8pKLgfv5A0Oq0=
github.com/flashbots/go-utils v0.4.4/go.mod h1:weSbiNnH+xsmK8t3TDDJxluv4+qnwRWmKay2QQa8Yfc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
From 6fbef37f31e6eb797b0675e0a57fbe6796aa2ce6 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Fri, 3 Jun 2022 10:34:22 +0200
Subject: [PATCH 04/83] Adjust base fee encoding (#5)
---
builder/backend.go | 2 +-
go.mod | 2 +-
go.sum | 2 ++
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/builder/backend.go b/builder/backend.go
index 7597666d57..d014a7efb2 100644
--- a/builder/backend.go
+++ b/builder/backend.go
@@ -412,7 +412,7 @@ func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) *boostTypes
GasUsed: data.GasUsed,
Timestamp: data.Timestamp,
ExtraData: data.ExtraData,
- BaseFeePerGas: [32]byte(common.BytesToHash(data.BaseFeePerGas.Bytes())),
+ BaseFeePerGas: *new(boostTypes.U256Str).FromBig(data.BaseFeePerGas),
BlockHash: [32]byte(data.BlockHash),
Transactions: transactionData,
}
diff --git a/go.mod b/go.mod
index 99146f88a4..a63f7763fa 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,7 @@ require (
github.com/fatih/color v1.9.0
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
- github.com/flashbots/go-boost-utils v0.1.3-0.20220601182529-417a64679a5d
+ github.com/flashbots/go-boost-utils v0.1.3-0.20220602181853-0b5ffd891e10
github.com/flashbots/go-utils v0.4.4
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
github.com/go-stack/stack v1.8.0
diff --git a/go.sum b/go.sum
index 1d4edf193d..f8ee19d494 100644
--- a/go.sum
+++ b/go.sum
@@ -143,6 +143,8 @@ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlK
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/flashbots/go-boost-utils v0.1.3-0.20220601182529-417a64679a5d h1:hoqkcRKMupXCWW38fDfvNcuIxl9eCAZrguw3dNCw+Qg=
github.com/flashbots/go-boost-utils v0.1.3-0.20220601182529-417a64679a5d/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
+github.com/flashbots/go-boost-utils v0.1.3-0.20220602181853-0b5ffd891e10 h1:3fbCrMmSOayxCHNkIVUWLFR+q/dR1KeXUD08OXhShBM=
+github.com/flashbots/go-boost-utils v0.1.3-0.20220602181853-0b5ffd891e10/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
github.com/flashbots/go-utils v0.4.4 h1:J0LUifVEpVYE+ZbK/DeGay4E3B6+Yh8pKLgfv5A0Oq0=
github.com/flashbots/go-utils v0.4.4/go.mod h1:weSbiNnH+xsmK8t3TDDJxluv4+qnwRWmKay2QQa8Yfc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
From b67d952a92737e20f50b4fb729b4f76e379c6ebb Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Tue, 7 Jun 2022 12:09:57 +0200
Subject: [PATCH 05/83] Allow resending of the same validator registration (#7)
---
builder/backend.go | 7 ++++++-
builder/backend_test.go | 3 +--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/builder/backend.go b/builder/backend.go
index d014a7efb2..77fdbc2b31 100644
--- a/builder/backend.go
+++ b/builder/backend.go
@@ -182,7 +182,12 @@ func (b *Backend) handleRegisterValidator(w http.ResponseWriter, req *http.Reque
for _, registerRequest := range payload {
pubkeyHex := PubkeyHex(registerRequest.Message.Pubkey.String())
if previousValidatorData, ok := b.validators[pubkeyHex]; ok {
- if registerRequest.Message.Timestamp <= previousValidatorData.Timestamp {
+ if registerRequest.Message.Timestamp < previousValidatorData.Timestamp {
+ respondError(w, http.StatusBadRequest, "invalid timestamp")
+ return
+ }
+
+ if registerRequest.Message.Timestamp == previousValidatorData.Timestamp && (registerRequest.Message.FeeRecipient != previousValidatorData.FeeRecipient || registerRequest.Message.GasLimit != previousValidatorData.GasLimit) {
respondError(w, http.StatusBadRequest, "invalid timestamp")
return
}
diff --git a/builder/backend_test.go b/builder/backend_test.go
index 1158d06f25..762903f923 100644
--- a/builder/backend_test.go
+++ b/builder/backend_test.go
@@ -64,8 +64,7 @@ func TestValidatorRegistration(t *testing.T) {
require.Equal(t, ValidatorData{FeeRecipient: payload[0].Message.FeeRecipient, GasLimit: payload[0].Message.GasLimit, Timestamp: payload[0].Message.Timestamp}, backend.validators[PubkeyHex(v.Pk.String())])
rr = testRequest(t, backend, "POST", "/eth/v1/builder/validators", payload)
- require.Equal(t, http.StatusBadRequest, rr.Code)
- require.Equal(t, `{"code":400,"message":"invalid timestamp"}`+"\n", rr.Body.String())
+ require.Equal(t, http.StatusOK, rr.Code)
payload[0].Message.Timestamp += 1
// Invalid signature
From 28fd947efda723c658360d7829782f7c694b2b0b Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Fri, 10 Jun 2022 14:26:32 +0200
Subject: [PATCH 06/83] Adjust value encoding (#9)
---
builder/backend.go | 3 +--
builder/backend_test.go | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/builder/backend.go b/builder/backend.go
index 77fdbc2b31..917222d197 100644
--- a/builder/backend.go
+++ b/builder/backend.go
@@ -11,7 +11,6 @@ import (
"sync"
"time"
- "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
@@ -248,7 +247,7 @@ func (b *Backend) handleGetHeader(w http.ResponseWriter, req *http.Request) {
bid := boostTypes.BuilderBid{
Header: bestHeader,
- Value: [32]byte(common.BytesToHash(profit.Bytes())),
+ Value: *new(boostTypes.U256Str).FromBig(profit),
Pubkey: b.builderPublicKey,
}
signature, err := boostTypes.SignMessage(&bid, b.builderSigningDomain, b.builderSecretKey)
diff --git a/builder/backend_test.go b/builder/backend_test.go
index 762903f923..cd6a508000 100644
--- a/builder/backend_test.go
+++ b/builder/backend_test.go
@@ -150,7 +150,7 @@ func TestGetHeader(t *testing.T) {
require.NoError(t, err)
require.EqualValues(t, &boostTypes.BuilderBid{
Header: expectedHeader,
- Value: [32]byte(common.BytesToHash(forkchoiceBlock.Profit.Bytes())),
+ Value: *new(boostTypes.U256Str).FromBig(forkchoiceBlock.Profit),
Pubkey: backend.builderPublicKey,
}, bid.Data.Message)
From 3f1283142a984f90893e791827d1cc7a71856413 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Fri, 10 Jun 2022 14:26:43 +0200
Subject: [PATCH 07/83] Adjust gas limit calculation (#8)
---
miner/worker.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/miner/worker.go b/miner/worker.go
index be9e87fcb8..5e14383406 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1012,7 +1012,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
header.BaseFee = misc.CalcBaseFee(w.chainConfig, parent.Header())
if !w.chainConfig.IsLondon(parent.Number()) {
parentGasLimit := parent.GasLimit() * params.ElasticityMultiplier
- header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil)
+ header.GasLimit = core.CalcGasLimit(parentGasLimit, gasTarget)
}
}
// Run the consensus preparation with the default or customized consensus engine.
From 0b356f240cba80decbec912a462001cee913f1a2 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Fri, 10 Jun 2022 18:11:39 +0200
Subject: [PATCH 08/83] Show builder pubkey on index page (#10)
---
builder/backend.go | 24 +++++++++++++++++++-----
builder/backend_test.go | 7 ++++++-
builder/index.go | 16 ++++++++++++++++
builder/service.go | 2 +-
4 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/builder/backend.go b/builder/backend.go
index 917222d197..09c145c08d 100644
--- a/builder/backend.go
+++ b/builder/backend.go
@@ -41,6 +41,7 @@ type Backend struct {
builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
serializedBuilderPoolPubkey hexutil.Bytes
+ fd ForkData
builderSigningDomain boostTypes.Domain
proposerSigningDomain boostTypes.Domain
enableBeaconChecks bool
@@ -56,7 +57,13 @@ type Backend struct {
indexTemplate *template.Template
}
-func NewBackend(sk *bls.SecretKey, bc IBeaconClient, builderSigningDomain boostTypes.Domain, proposerSigningDomain boostTypes.Domain, enableBeaconChecks bool) *Backend {
+type ForkData struct {
+ GenesisForkVersion string
+ BellatrixForkVersion string
+ GenesisValidatorsRoot string
+}
+
+func NewBackend(sk *bls.SecretKey, bc IBeaconClient, fd ForkData, builderSigningDomain boostTypes.Domain, proposerSigningDomain boostTypes.Domain, enableBeaconChecks bool) *Backend {
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)
@@ -77,6 +84,7 @@ func NewBackend(sk *bls.SecretKey, bc IBeaconClient, builderSigningDomain boostT
builderPublicKey: pk,
serializedBuilderPoolPubkey: pkBytes,
+ fd: fd,
builderSigningDomain: builderSigningDomain,
proposerSigningDomain: proposerSigningDomain,
enableBeaconChecks: enableBeaconChecks,
@@ -107,10 +115,16 @@ func (b *Backend) handleIndex(w http.ResponseWriter, req *http.Request) {
}
statusData := struct {
- NoValidators int
- Header string
- Blocks string
- }{noValidators, string(headerData), string(payloadData)}
+ Pubkey string
+ NoValidators int
+ GenesisForkVersion string
+ BellatrixForkVersion string
+ GenesisValidatorsRoot string
+ BuilderSigningDomain string
+ ProposerSigningDomain string
+ Header string
+ Blocks string
+ }{hexutil.Encode(b.serializedBuilderPoolPubkey), noValidators, b.fd.GenesisForkVersion, b.fd.BellatrixForkVersion, b.fd.GenesisValidatorsRoot, hexutil.Encode(b.builderSigningDomain[:]), hexutil.Encode(b.proposerSigningDomain[:]), string(headerData), string(payloadData)}
if err := b.indexTemplate.Execute(w, statusData); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/builder/backend_test.go b/builder/backend_test.go
index cd6a508000..9880f5cb8e 100644
--- a/builder/backend_test.go
+++ b/builder/backend_test.go
@@ -26,7 +26,7 @@ func newTestBackend(t *testing.T) (*Backend, *ValidatorPrivateData) {
bDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeAppBuilder, [4]byte{0x02, 0x0, 0x0, 0x0}, boostTypes.Hash{})
genesisValidatorsRoot := boostTypes.Hash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"))
cDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeBeaconProposer, [4]byte{0x02, 0x0, 0x0, 0x0}, genesisValidatorsRoot)
- backend := NewBackend(sk, &testBeaconClient{validator}, bDomain, cDomain, true)
+ backend := NewBackend(sk, &testBeaconClient{validator}, ForkData{}, bDomain, cDomain, true)
// service := NewService("127.0.0.1:31545", backend)
return backend, validator
@@ -231,3 +231,8 @@ func TestGetPayload(t *testing.T) {
require.NoError(t, err)
require.Equal(t, bid.Data.Message.Header.BlockHash, getPayloadResponse.Data.BlockHash)
}
+
+func TestXxx(t *testing.T) {
+ sk, _ := bls.GenerateRandomSecretKey()
+ fmt.Println(hexutil.Encode(sk.Serialize()))
+}
diff --git a/builder/index.go b/builder/index.go
index 6e1e02e4db..c65d248353 100644
--- a/builder/index.go
+++ b/builder/index.go
@@ -46,6 +46,22 @@ func parseIndexTemplate() (*template.Template, error) {
Boost Block Builder
+
+ Pubkey {{ .Pubkey }}
+
+
+
+ Genesis fork version {{ .GenesisForkVersion }}
+ Bellatrix fork version {{ .BellatrixForkVersion }}
+ Genesis validators root {{ .GenesisValidatorsRoot }}
+
+
+
+
+ Builder signing domain {{ .BuilderSigningDomain }}
+ Proposer signing domain {{ .ProposerSigningDomain }}
+
+
diff --git a/builder/service.go b/builder/service.go
index 2b47787174..e2647cbb44 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -98,7 +98,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
var beaconClient IBeaconClient
beaconClient = NewBeaconClient(cfg.BeaconEndpoint)
- builderBackend := NewBackend(sk, beaconClient, builderSigningDomain, proposerSigningDomain, cfg.EnableValidatorChecks)
+ builderBackend := NewBackend(sk, beaconClient, ForkData{cfg.GenesisForkVersion, cfg.BellatrixForkVersion, cfg.GenesisValidatorsRoot}, builderSigningDomain, proposerSigningDomain, cfg.EnableValidatorChecks)
builderService := NewService(cfg.ListenAddr, builderBackend)
builderService.Start()
From ab55c8c8d94507c7bc09ba14b4edb6c86c8179c3 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 21 Jul 2022 15:21:01 +0200
Subject: [PATCH 09/83] Add remote relay connection for getting validator data
(#11)
* Add remote relay connection for getting validator data
* Add block submission to remote relay
* Adjust readme
---
README.md | 20 +-
builder/beacon_client.go | 15 +-
builder/beacon_client_test.go | 20 +-
builder/builder.go | 178 +++++++++
builder/builder_test.go | 115 ++++++
builder/index.go | 2 +-
builder/{backend.go => local_relay.go} | 361 ++++++++----------
.../{backend_test.go => local_relay_test.go} | 78 ++--
builder/relay.go | 191 +++++++++
builder/relay_test.go | 128 +++++++
builder/service.go | 50 ++-
cmd/geth/config.go | 4 +-
cmd/geth/main.go | 2 +
cmd/utils/flags.go | 14 +-
core/beacon/types.go | 1 +
eth/backend.go | 8 +-
eth/catalyst/api.go | 3 +-
go.mod | 10 +-
go.sum | 24 +-
miner/miner.go | 4 +-
miner/worker.go | 4 +-
miner/worker_test.go | 12 +-
22 files changed, 925 insertions(+), 319 deletions(-)
create mode 100644 builder/builder.go
create mode 100644 builder/builder_test.go
rename builder/{backend.go => local_relay.go} (54%)
rename builder/{backend_test.go => local_relay_test.go} (67%)
create mode 100644 builder/relay.go
create mode 100644 builder/relay_test.go
diff --git a/README.md b/README.md
index f831771269..ebc21e8d0b 100644
--- a/README.md
+++ b/README.md
@@ -14,14 +14,17 @@ Provides summary page at the listening address' root (http://localhost:28545 by
## How it works
-Builder API has two hooks into geth:
+* Builder polls relay for the proposer registrations for the next epoch
+
+Builder has two hooks into geth:
* On forkchoice update, changing the payload attributes feeRecipient to the one registered for next slot's validator
-* On new sealed block, consuming the block as the next slot's proposed payload
+* On new sealed block, consuming the block as the next slot's proposed payload and submits it to the relay
+
+Local relay is enabled by default and overwrites remote relay data. This is only meant for the testnets!
## Limitations
* Blocks are only built on forkchoice update call from beacon node
-* Only works post-Bellatrix, fork version is static
* Does not accept external blocks
* Does not have payload cache, only the latest block is available
@@ -33,11 +36,12 @@ Builder API options:
```
$ geth --help
BUILDER API OPTIONS:
- --builder.validator_checks Enable the validator checks
- --builder.secret_key value Builder API key used for signing headers (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_SECRET_KEY]
+ --builder.secret_key value Builder key used for signing blocks (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_SECRET_KEY]
+ --builder.relay_secret_key value Builder local relay API key used for signing headers (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_RELAY_SECRET_KEY]
--builder.listen_addr value Listening address for builder endpoint (default: ":28545") [$BUILDER_LISTEN_ADDR]
- --builder.genesis_fork_version value Gensis fork version (default: "0x02000000") [$BUILDER_GENESIS_FORK_VERSION]
- --builder.bellatrix_fork_version value Bellatrix fork version (default: "0x02000000") [$BUILDER_BELLATRIX_FORK_VERSION]
- --builder.genesis_validators_root value Genesis validators root of the network (static). For kiln use 0x99b09fcd43e5905236c370f184056bec6e6638cfc31a323b304fc4aa789cb4ad (default: "0x0000000000000000000000000000000000000000000000000000000000000000") [$BUILDER_GENESIS_VALIDATORS_ROOT]
+ --builder.genesis_fork_version value Gensis fork version. For kiln use 0x70000069 (default: "0x00000000") [$BUILDER_GENESIS_FORK_VERSION]
+ --builder.bellatrix_fork_version value Bellatrix fork version. For kiln use 0x70000071 (default: "0x02000000") [$BUILDER_BELLATRIX_FORK_VERSION]
+ --builder.genesis_validators_root value Genesis validators root of the network. For kiln use 0x99b09fcd43e5905236c370f184056bec6e6638cfc31a323b304fc4aa789cb4ad (default: "0x0000000000000000000000000000000000000000000000000000000000000000") [$BUILDER_GENESIS_VALIDATORS_ROOT]
--builder.beacon_endpoint value Beacon endpoint to connect to for beacon chain data (default: "http://127.0.0.1:5052") [$BUILDER_BEACON_ENDPOINT]
+ --builder.remote_relay_endpoint value Relay endpoint to connect to for validator registration data, if not provided will expose validator registration locally [$BUILDER_REMOTE_RELAY_ENDPOINT]
```
diff --git a/builder/beacon_client.go b/builder/beacon_client.go
index 62138c9b05..769a5674c4 100644
--- a/builder/beacon_client.go
+++ b/builder/beacon_client.go
@@ -16,6 +16,7 @@ import (
type testBeaconClient struct {
validator *ValidatorPrivateData
+ slot uint64
}
func (b *testBeaconClient) isValidator(pubkey PubkeyHex) bool {
@@ -24,8 +25,8 @@ func (b *testBeaconClient) isValidator(pubkey PubkeyHex) bool {
func (b *testBeaconClient) getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error) {
return PubkeyHex(hexutil.Encode(b.validator.Pk)), nil
}
-func (b *testBeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
- return PubkeyHex(hexutil.Encode(b.validator.Pk)), nil
+func (b *testBeaconClient) onForkchoiceUpdate() (uint64, error) {
+ return b.slot, nil
}
type BeaconClient struct {
@@ -62,13 +63,13 @@ func (b *BeaconClient) getProposerForNextSlot(requestedSlot uint64) (PubkeyHex,
/* Returns next slot's proposer pubkey */
// TODO: what happens if no block for previous slot - should still get next slot
-func (b *BeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
+func (b *BeaconClient) onForkchoiceUpdate() (uint64, error) {
b.mu.Lock()
defer b.mu.Unlock()
currentSlot, err := fetchCurrentSlot(b.endpoint)
if err != nil {
- return PubkeyHex(""), err
+ return 0, err
}
nextSlot := currentSlot + 1
@@ -80,7 +81,7 @@ func (b *BeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
// TODO: this should be prepared in advance, possibly just fetch for next epoch in advance
slotProposerMap, err := fetchEpochProposersMap(b.endpoint, nextSlotEpoch)
if err != nil {
- return PubkeyHex(""), err
+ return 0, err
}
b.currentEpoch = nextSlotEpoch
@@ -90,10 +91,10 @@ func (b *BeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
nextSlotProposer, found := b.slotProposerMap[nextSlot]
if !found {
log.Error("inconsistent proposer mapping", "currentSlot", currentSlot, "slotProposerMap", b.slotProposerMap)
- return PubkeyHex(""), errors.New("inconsistent proposer mapping")
+ return 0, errors.New("inconsistent proposer mapping")
}
b.nextSlotProposer = nextSlotProposer
- return nextSlotProposer, nil
+ return nextSlot, nil
}
func fetchCurrentSlot(endpoint string) (uint64, error) {
diff --git a/builder/beacon_client_test.go b/builder/beacon_client_test.go
index c4cb9d6814..152e891c89 100644
--- a/builder/beacon_client_test.go
+++ b/builder/beacon_client_test.go
@@ -204,11 +204,11 @@ func TestOnForkchoiceUpdate(t *testing.T) {
}`)
bc := NewBeaconClient(mbn.srv.URL)
- pubkeyHex, err := bc.onForkchoiceUpdate()
+ slot, err := bc.onForkchoiceUpdate()
require.NoError(t, err)
- require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
+ require.Equal(t, slot, uint64(32))
- pubkeyHex, err = bc.getProposerForNextSlot(32)
+ pubkeyHex, err := bc.getProposerForNextSlot(32)
require.NoError(t, err)
require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
@@ -221,9 +221,9 @@ func TestOnForkchoiceUpdate(t *testing.T) {
mbn.headersCode = 404
mbn.headersResp = []byte(`{ "code": 404, "message": "State not found" }`)
- pubkeyHex, err = NewBeaconClient(mbn.srv.URL).onForkchoiceUpdate()
+ slot, err = NewBeaconClient(mbn.srv.URL).onForkchoiceUpdate()
require.EqualError(t, err, "State not found")
- require.Equal(t, PubkeyHex(""), pubkeyHex)
+ require.Equal(t, slot, uint64(0))
// Check that client does not fetch new proposers if epoch did not change
mbn.headersCode = 200
@@ -238,9 +238,9 @@ func TestOnForkchoiceUpdate(t *testing.T) {
]
}`)
- pubkeyHex, err = bc.onForkchoiceUpdate()
+ slot, err = bc.onForkchoiceUpdate()
require.NoError(t, err, "")
- require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
+ require.Equal(t, slot, uint64(32))
mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "63", "proposer_index": "1" } } } ] }`)
mbn.proposerDuties[2] = []byte(`{
@@ -253,9 +253,9 @@ func TestOnForkchoiceUpdate(t *testing.T) {
]
}`)
- pubkeyHex, err = bc.onForkchoiceUpdate()
+ slot, err = bc.onForkchoiceUpdate()
require.NoError(t, err, "")
- require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74d"), pubkeyHex)
+ require.Equal(t, slot, uint64(64))
pubkeyHex, err = bc.getProposerForNextSlot(64)
require.NoError(t, err)
@@ -263,6 +263,6 @@ func TestOnForkchoiceUpdate(t *testing.T) {
// Check proposers map error is routed out
mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "65", "proposer_index": "1" } } } ] }`)
- pubkeyHex, err = bc.onForkchoiceUpdate()
+ slot, err = bc.onForkchoiceUpdate()
require.EqualError(t, err, "inconsistent proposer mapping")
}
diff --git a/builder/builder.go b/builder/builder.go
new file mode 100644
index 0000000000..b74710387e
--- /dev/null
+++ b/builder/builder.go
@@ -0,0 +1,178 @@
+package builder
+
+import (
+ "encoding/json"
+ _ "os"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/beacon"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+
+ "github.com/flashbots/go-boost-utils/bls"
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+)
+
+type PubkeyHex string
+
+type ValidatorData struct {
+ Pubkey PubkeyHex
+ FeeRecipient boostTypes.Address `json:"feeRecipient"`
+ GasLimit uint64 `json:"gasLimit"`
+ Timestamp uint64 `json:"timestamp"`
+}
+
+type IBeaconClient interface {
+ isValidator(pubkey PubkeyHex) bool
+ getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error)
+ onForkchoiceUpdate() (uint64, error)
+}
+
+type IRelay interface {
+ SubmitBlock(msg *boostTypes.BuilderSubmitBlockRequest) error
+ GetValidatorForSlot(nextSlot uint64) (ValidatorData, error)
+}
+
+type Builder struct {
+ beaconClient IBeaconClient
+ relay IRelay
+
+ builderSecretKey *bls.SecretKey
+ builderPublicKey boostTypes.PublicKey
+ builderSigningDomain boostTypes.Domain
+}
+
+func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain) *Builder {
+ pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
+ pk := boostTypes.PublicKey{}
+ pk.FromSlice(pkBytes)
+
+ _, err := bc.onForkchoiceUpdate()
+ if err != nil {
+ log.Error("could not initialize beacon client", "err", err)
+ }
+
+ return &Builder{
+ beaconClient: bc,
+ relay: relay,
+ builderSecretKey: sk,
+ builderPublicKey: pk,
+
+ builderSigningDomain: builderSigningDomain,
+ }
+}
+
+func (b *Builder) onForkchoice(payloadAttributes *beacon.PayloadAttributesV1) {
+ dataJson, err := json.Marshal(payloadAttributes)
+ if err == nil {
+ log.Info("FCU", "data", string(dataJson))
+ } else {
+ log.Info("FCU", "data", payloadAttributes, "parsingError", err)
+
+ }
+
+ nextSlot, err := b.beaconClient.onForkchoiceUpdate()
+ if err != nil {
+ log.Error("FCU hook failed", "err", err)
+ return
+ }
+
+ if payloadAttributes != nil {
+ payloadAttributes.Slot = nextSlot
+ if vd, err := b.relay.GetValidatorForSlot(nextSlot); err == nil {
+ payloadAttributes.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
+ payloadAttributes.GasLimit = vd.GasLimit
+ }
+ }
+}
+
+func (b *Builder) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Block, payloadAttributes *beacon.PayloadAttributesV1) {
+ dataJson, err := json.Marshal(data)
+ if err == nil {
+ log.Info("newSealedBlock", "data", string(dataJson))
+ } else {
+ log.Info("newSealedBlock", "data", data, "parsingError", err)
+ }
+ payload, err := executableDataToExecutionPayload(data)
+ if err != nil {
+ log.Error("could not format execution payload", "err", err)
+ return
+ }
+
+ vd, err := b.relay.GetValidatorForSlot(payloadAttributes.Slot)
+ if err != nil {
+ log.Error("could not get validator while submitting block", "err", err, "slot", payloadAttributes.Slot)
+ return
+ }
+
+ pubkey, err := boostTypes.HexToPubkey(string(vd.Pubkey))
+ if err != nil {
+ log.Error("could not parse pubkey", "err", err, "pubkey", vd.Pubkey)
+ return
+ }
+
+ value := new(boostTypes.U256Str)
+ err = value.FromBig(block.Profit)
+ if err != nil {
+ log.Error("could not set block value", "err", err)
+ return
+ }
+
+ blockBidMsg := boostTypes.BidTraceMessage{
+ Slot: payloadAttributes.Slot,
+ ParentHash: payload.ParentHash,
+ BlockHash: payload.BlockHash,
+ BuilderPubkey: b.builderPublicKey,
+ ProposerPubkey: pubkey,
+ ProposerFeeRecipient: boostTypes.Address(payloadAttributes.SuggestedFeeRecipient),
+ Value: *value,
+ }
+
+ signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
+ if err != nil {
+ log.Error("could not sign builder bid", "err", err)
+ return
+ }
+
+ blockSubmitReq := boostTypes.BuilderSubmitBlockRequest{
+ Signature: signature,
+ Message: &blockBidMsg,
+ ExecutionPayload: payload,
+ }
+
+ err = b.relay.SubmitBlock(&blockSubmitReq)
+ if err != nil {
+ log.Error("could not submit block", "err", err)
+ return
+ }
+}
+
+func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayload, error) {
+ transactionData := make([]hexutil.Bytes, len(data.Transactions))
+ for i, tx := range data.Transactions {
+ transactionData[i] = hexutil.Bytes(tx)
+ }
+
+ baseFeePerGas := new(boostTypes.U256Str)
+ err := baseFeePerGas.FromBig(data.BaseFeePerGas)
+ if err != nil {
+ return nil, err
+ }
+
+ return &boostTypes.ExecutionPayload{
+ ParentHash: [32]byte(data.ParentHash),
+ FeeRecipient: [20]byte(data.FeeRecipient),
+ StateRoot: [32]byte(data.StateRoot),
+ ReceiptsRoot: [32]byte(data.ReceiptsRoot),
+ LogsBloom: boostTypes.Bloom(types.BytesToBloom(data.LogsBloom)),
+ Random: [32]byte(data.Random),
+ BlockNumber: data.Number,
+ GasLimit: data.GasLimit,
+ GasUsed: data.GasUsed,
+ Timestamp: data.Timestamp,
+ ExtraData: data.ExtraData,
+ BaseFeePerGas: *baseFeePerGas,
+ BlockHash: [32]byte(data.BlockHash),
+ Transactions: transactionData,
+ }, nil
+}
diff --git a/builder/builder_test.go b/builder/builder_test.go
new file mode 100644
index 0000000000..72add5814c
--- /dev/null
+++ b/builder/builder_test.go
@@ -0,0 +1,115 @@
+package builder
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/beacon"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/flashbots/go-boost-utils/bls"
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+ "github.com/stretchr/testify/require"
+)
+
+/*
+func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain) *Builder {
+func (b *Builder) onForkchoice(payloadAttributes *beacon.PayloadAttributesV1) {
+func (b *Builder) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Block, payloadAttributes *beacon.PayloadAttributesV1) {
+*/
+
+func TestOnNewSealedBlock(t *testing.T) {
+ testBeacon := testBeaconClient{
+ validator: NewRandomValidator(),
+ slot: 56,
+ }
+
+ feeRecipient, _ := boostTypes.HexToAddress("0xabcf8e0d4e9587369b2301d0790347320302cc00")
+ testRelay := testRelay{
+ validator: ValidatorData{
+ Pubkey: PubkeyHex(testBeacon.validator.Pk),
+ FeeRecipient: feeRecipient,
+ GasLimit: 10,
+ Timestamp: 15,
+ },
+ }
+
+ sk, err := bls.SecretKeyFromBytes(hexutil.MustDecode("0x31ee185dad1220a8c88ca5275e64cf5a5cb09cb621cb30df52c9bee8fbaaf8d7"))
+ require.NoError(t, err)
+
+ bDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeAppBuilder, [4]byte{0x02, 0x0, 0x0, 0x0}, boostTypes.Hash{})
+
+ builder := NewBuilder(sk, &testBeacon, &testRelay, bDomain)
+
+ testExecutableData := &beacon.ExecutableDataV1{
+ ParentHash: common.Hash{0x02, 0x03},
+ FeeRecipient: common.Address{0x06, 0x15},
+ StateRoot: common.Hash{0x07, 0x16},
+ ReceiptsRoot: common.Hash{0x08, 0x20},
+ LogsBloom: hexutil.MustDecode("0x000000000000000000000000000000"),
+ Number: uint64(10),
+ GasLimit: uint64(50),
+ GasUsed: uint64(100),
+ Timestamp: uint64(105),
+ ExtraData: hexutil.MustDecode("0x0042fafc"),
+
+ BaseFeePerGas: big.NewInt(16),
+
+ BlockHash: common.Hash{0x09, 0xff},
+ Transactions: [][]byte{},
+ }
+
+ testBlock := &types.Block{
+ Profit: big.NewInt(10),
+ }
+
+ testPayloadAttributes := &beacon.PayloadAttributesV1{
+ Timestamp: uint64(104),
+ Random: common.Hash{0x05, 0x10},
+ SuggestedFeeRecipient: common.Address{0x04, 0x10},
+ GasLimit: uint64(21),
+ Slot: uint64(25),
+ }
+
+ builder.newSealedBlock(testExecutableData, testBlock, testPayloadAttributes)
+
+ require.NotNil(t, testRelay.submittedMsg)
+
+ expectedMessage := boostTypes.BidTraceMessage{
+ Slot: uint64(25),
+ ParentHash: boostTypes.Hash{0x02, 0x03},
+ BlockHash: boostTypes.Hash{0x09, 0xff},
+ BuilderPubkey: builder.builderPublicKey,
+ ProposerPubkey: boostTypes.PublicKey{},
+ ProposerFeeRecipient: boostTypes.Address{0x04, 0x10},
+ Value: boostTypes.U256Str{0x0a},
+ }
+
+ require.Equal(t, expectedMessage, *testRelay.submittedMsg.Message)
+
+ expectedExecutionPayload := boostTypes.ExecutionPayload{
+ ParentHash: [32]byte(testExecutableData.ParentHash),
+ FeeRecipient: boostTypes.Address{0x6, 0x15},
+ StateRoot: [32]byte(testExecutableData.StateRoot),
+ ReceiptsRoot: [32]byte(testExecutableData.ReceiptsRoot),
+ LogsBloom: [256]byte{},
+ Random: [32]byte(testExecutableData.Random),
+ BlockNumber: testExecutableData.Number,
+ GasLimit: testExecutableData.GasLimit,
+ GasUsed: testExecutableData.GasUsed,
+ Timestamp: testExecutableData.Timestamp,
+ ExtraData: hexutil.MustDecode("0x0042fafc"),
+ BaseFeePerGas: boostTypes.U256Str{0x10},
+ BlockHash: boostTypes.Hash{0x09, 0xff},
+ Transactions: []hexutil.Bytes{},
+ }
+ require.Equal(t, expectedExecutionPayload, *testRelay.submittedMsg.ExecutionPayload)
+
+ expectedSignature, err := boostTypes.HexToSignature("0xb79f75f81c834d104afbf1fb45f2cc19d5b0b4367184a43b88e696c88e6ab1a150be1fde9de5d1ca28bd955063164ae001c99d516c6ccd278c6bfb2af9c08805e39698a4a4e0713681a012921c1e9d8d14be95b49f654aba1fb493892a00795d")
+
+ require.NoError(t, err)
+ require.Equal(t, expectedSignature, testRelay.submittedMsg.Signature)
+
+ require.Equal(t, uint64(25), testRelay.requestedSlot)
+}
diff --git a/builder/index.go b/builder/index.go
index c65d248353..8a096dab3f 100644
--- a/builder/index.go
+++ b/builder/index.go
@@ -75,7 +75,7 @@ func parseIndexTemplate() (*template.Template, error) {
- Registered Validators: {{ .NoValidators }}
+ {{ .ValidatorsStats }}
diff --git a/builder/backend.go b/builder/local_relay.go
similarity index 54%
rename from builder/backend.go
rename to builder/local_relay.go
index 09c145c08d..68ec8f91f1 100644
--- a/builder/backend.go
+++ b/builder/local_relay.go
@@ -3,152 +3,99 @@ package builder
import (
"bytes"
"encoding/json"
+ "errors"
+ "fmt"
"html/template"
- "math/big"
"net/http"
- _ "os"
"strconv"
+ "strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/core/beacon"
- "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
- "github.com/gorilla/mux"
-
"github.com/flashbots/go-boost-utils/bls"
boostTypes "github.com/flashbots/go-boost-utils/types"
+ "github.com/gorilla/mux"
)
-type PubkeyHex string
-
-type ValidatorData struct {
- FeeRecipient boostTypes.Address `json:"feeRecipient"`
- GasLimit uint64 `json:"gasLimit"`
- Timestamp uint64 `json:"timestamp"`
-}
-
-type IBeaconClient interface {
- isValidator(pubkey PubkeyHex) bool
- getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error)
- onForkchoiceUpdate() (PubkeyHex, error)
+type ForkData struct {
+ GenesisForkVersion string
+ BellatrixForkVersion string
+ GenesisValidatorsRoot string
}
-type Backend struct {
+type LocalRelay struct {
beaconClient IBeaconClient
- builderSecretKey *bls.SecretKey
- builderPublicKey boostTypes.PublicKey
- serializedBuilderPoolPubkey hexutil.Bytes
- fd ForkData
- builderSigningDomain boostTypes.Domain
- proposerSigningDomain boostTypes.Domain
- enableBeaconChecks bool
+ relaySecretKey *bls.SecretKey
+ relayPublicKey boostTypes.PublicKey
+ serializedRelayPubkey hexutil.Bytes
+
+ builderSigningDomain boostTypes.Domain
+ proposerSigningDomain boostTypes.Domain
validatorsLock sync.RWMutex
validators map[PubkeyHex]ValidatorData
+ enableBeaconChecks bool
+
bestDataLock sync.Mutex
bestHeader *boostTypes.ExecutionPayloadHeader
bestPayload *boostTypes.ExecutionPayload
- profit *big.Int
+ profit boostTypes.U256Str
indexTemplate *template.Template
+ fd ForkData
}
-type ForkData struct {
- GenesisForkVersion string
- BellatrixForkVersion string
- GenesisValidatorsRoot string
-}
-
-func NewBackend(sk *bls.SecretKey, bc IBeaconClient, fd ForkData, builderSigningDomain boostTypes.Domain, proposerSigningDomain boostTypes.Domain, enableBeaconChecks bool) *Backend {
+func NewLocalRelay(sk *bls.SecretKey, beaconClient IBeaconClient, builderSigningDomain boostTypes.Domain, proposerSigningDomain boostTypes.Domain, fd ForkData, enableBeaconChecks bool) *LocalRelay {
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)
- _, err := bc.onForkchoiceUpdate()
- if err != nil {
- log.Error("could not initialize beacon client", "err", err)
- }
-
indexTemplate, err := parseIndexTemplate()
if err != nil {
log.Error("could not parse index template", "err", err)
indexTemplate = nil
}
- return &Backend{
- beaconClient: bc,
- builderSecretKey: sk,
- builderPublicKey: pk,
- serializedBuilderPoolPubkey: pkBytes,
- fd: fd,
+ return &LocalRelay{
+ beaconClient: beaconClient,
+
+ relaySecretKey: sk,
+ relayPublicKey: pk,
+
builderSigningDomain: builderSigningDomain,
proposerSigningDomain: proposerSigningDomain,
- enableBeaconChecks: enableBeaconChecks,
- validators: make(map[PubkeyHex]ValidatorData),
- indexTemplate: indexTemplate,
- }
-}
+ serializedRelayPubkey: pkBytes,
-func (b *Backend) handleIndex(w http.ResponseWriter, req *http.Request) {
- if b.indexTemplate == nil {
- http.Error(w, "not available", http.StatusInternalServerError)
- }
+ validators: make(map[PubkeyHex]ValidatorData),
- b.validatorsLock.RLock()
- noValidators := len(b.validators)
- b.validatorsLock.RUnlock()
+ enableBeaconChecks: enableBeaconChecks,
- header := b.bestHeader
- headerData, err := json.MarshalIndent(header, "", " ")
- if err != nil {
- headerData = []byte{}
+ indexTemplate: indexTemplate,
+ fd: fd,
}
+}
- payload := b.bestPayload
- payloadData, err := json.MarshalIndent(payload, "", " ")
+func (r *LocalRelay) SubmitBlock(msg *boostTypes.BuilderSubmitBlockRequest) error {
+ payloadHeader, err := boostTypes.PayloadToPayloadHeader(msg.ExecutionPayload)
if err != nil {
- payloadData = []byte{}
+ log.Error("could not convert payload to header", "err", err)
+ return err
}
- statusData := struct {
- Pubkey string
- NoValidators int
- GenesisForkVersion string
- BellatrixForkVersion string
- GenesisValidatorsRoot string
- BuilderSigningDomain string
- ProposerSigningDomain string
- Header string
- Blocks string
- }{hexutil.Encode(b.serializedBuilderPoolPubkey), noValidators, b.fd.GenesisForkVersion, b.fd.BellatrixForkVersion, b.fd.GenesisValidatorsRoot, hexutil.Encode(b.builderSigningDomain[:]), hexutil.Encode(b.proposerSigningDomain[:]), string(headerData), string(payloadData)}
-
- if err := b.indexTemplate.Execute(w, statusData); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- }
-}
+ r.bestDataLock.Lock()
+ r.bestHeader = payloadHeader
+ r.bestPayload = msg.ExecutionPayload
+ r.profit = msg.Message.Value
+ r.bestDataLock.Unlock()
-func (b *Backend) handleStatus(w http.ResponseWriter, req *http.Request) {
- w.WriteHeader(http.StatusOK)
+ return nil
}
-type httpErrorResp struct {
- Code int `json:"code"`
- Message string `json:"message"`
-}
-
-func respondError(w http.ResponseWriter, code int, message string) {
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(code)
- if err := json.NewEncoder(w).Encode(httpErrorResp{code, message}); err != nil {
- http.Error(w, message, code)
- }
-}
-
-func (b *Backend) handleRegisterValidator(w http.ResponseWriter, req *http.Request) {
+func (r *LocalRelay) handleRegisterValidator(w http.ResponseWriter, req *http.Request) {
payload := []boostTypes.SignedValidatorRegistration{}
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
log.Error("could not decode payload", "err", err)
@@ -167,7 +114,7 @@ func (b *Backend) handleRegisterValidator(w http.ResponseWriter, req *http.Reque
return
}
- ok, err := boostTypes.VerifySignature(registerRequest.Message, b.builderSigningDomain, registerRequest.Message.Pubkey[:], registerRequest.Signature[:])
+ ok, err := boostTypes.VerifySignature(registerRequest.Message, r.builderSigningDomain, registerRequest.Message.Pubkey[:], registerRequest.Signature[:])
if !ok || err != nil {
log.Error("error verifying signature", "err", err)
respondError(w, http.StatusBadRequest, "invalid signature")
@@ -183,18 +130,18 @@ func (b *Backend) handleRegisterValidator(w http.ResponseWriter, req *http.Reque
for _, registerRequest := range payload {
pubkeyHex := PubkeyHex(registerRequest.Message.Pubkey.String())
- if !b.beaconClient.isValidator(pubkeyHex) {
+ if !r.beaconClient.isValidator(pubkeyHex) {
respondError(w, http.StatusBadRequest, "not a validator")
return
}
}
- b.validatorsLock.Lock()
- defer b.validatorsLock.Unlock()
+ r.validatorsLock.Lock()
+ defer r.validatorsLock.Unlock()
for _, registerRequest := range payload {
pubkeyHex := PubkeyHex(registerRequest.Message.Pubkey.String())
- if previousValidatorData, ok := b.validators[pubkeyHex]; ok {
+ if previousValidatorData, ok := r.validators[pubkeyHex]; ok {
if registerRequest.Message.Timestamp < previousValidatorData.Timestamp {
respondError(w, http.StatusBadRequest, "invalid timestamp")
return
@@ -208,20 +155,37 @@ func (b *Backend) handleRegisterValidator(w http.ResponseWriter, req *http.Reque
}
for _, registerRequest := range payload {
- pubkeyHex := PubkeyHex(registerRequest.Message.Pubkey.String())
- b.validators[pubkeyHex] = ValidatorData{
+ pubkeyHex := PubkeyHex(strings.ToLower(registerRequest.Message.Pubkey.String()))
+ r.validators[pubkeyHex] = ValidatorData{
+ Pubkey: pubkeyHex,
FeeRecipient: registerRequest.Message.FeeRecipient,
GasLimit: registerRequest.Message.GasLimit,
Timestamp: registerRequest.Message.Timestamp,
}
- log.Info("registered validator", "pubkey", pubkeyHex, "data", b.validators[pubkeyHex])
+ log.Info("registered validator", "pubkey", pubkeyHex, "data", r.validators[pubkeyHex])
}
w.WriteHeader(http.StatusOK)
}
-func (b *Backend) handleGetHeader(w http.ResponseWriter, req *http.Request) {
+func (r *LocalRelay) GetValidatorForSlot(nextSlot uint64) (ValidatorData, error) {
+ pubkeyHex, err := r.beaconClient.getProposerForNextSlot(nextSlot)
+ if err != nil {
+ return ValidatorData{}, err
+ }
+
+ r.validatorsLock.RLock()
+ if vd, ok := r.validators[pubkeyHex]; ok {
+ r.validatorsLock.RUnlock()
+ return vd, nil
+ }
+ r.validatorsLock.RUnlock()
+ log.Info("no local entry for validator", "validator", pubkeyHex)
+ return ValidatorData{}, errors.New("missing validator")
+}
+
+func (r *LocalRelay) handleGetHeader(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
slot, err := strconv.Atoi(vars["slot"])
if err != nil {
@@ -229,30 +193,30 @@ func (b *Backend) handleGetHeader(w http.ResponseWriter, req *http.Request) {
return
}
parentHashHex := vars["parent_hash"]
- pubkeyHex := PubkeyHex(vars["pubkey"])
+ pubkeyHex := PubkeyHex(strings.ToLower(vars["pubkey"]))
- b.validatorsLock.RLock()
- if _, ok := b.validators[pubkeyHex]; !ok {
- log.Error("missing validator", "validators", b.validators, "provided", pubkeyHex)
- b.validatorsLock.RUnlock()
- respondError(w, http.StatusBadRequest, "unknown validator")
+ // Do not validate slot separately, it will create a race between slot update and proposer key
+ if nextSlotProposer, err := r.beaconClient.getProposerForNextSlot(uint64(slot)); err != nil || nextSlotProposer != pubkeyHex {
+ log.Error("getHeader requested for public key other than next slots proposer", "requested", pubkeyHex, "expected", nextSlotProposer)
+ w.WriteHeader(http.StatusNoContent)
return
}
- b.validatorsLock.RUnlock()
- // Do not validate slot separately, it will create a race between slot update and proposer key
- if nextSlotProposer, err := b.beaconClient.getProposerForNextSlot(uint64(slot)); err != nil || nextSlotProposer != pubkeyHex {
- log.Error("getHeader requested for public key other than next slots proposer", "requested", pubkeyHex, "expected", nextSlotProposer)
- if b.enableBeaconChecks {
- respondError(w, http.StatusBadRequest, "unknown validator")
- return
- }
+ // Only check if slot is within a couple of the expected one, otherwise will force validators resync
+ vd, err := r.GetValidatorForSlot(uint64(slot))
+ if err != nil {
+ respondError(w, http.StatusBadRequest, "unknown validator")
+ return
+ }
+ if vd.Pubkey != pubkeyHex {
+ respondError(w, http.StatusBadRequest, "unknown validator")
+ return
}
- b.bestDataLock.Lock()
- bestHeader := b.bestHeader
- profit := b.profit
- b.bestDataLock.Unlock()
+ r.bestDataLock.Lock()
+ bestHeader := r.bestHeader
+ profit := r.profit
+ r.bestDataLock.Unlock()
if bestHeader == nil || bestHeader.ParentHash.String() != parentHashHex {
respondError(w, http.StatusBadRequest, "unknown payload")
@@ -261,10 +225,10 @@ func (b *Backend) handleGetHeader(w http.ResponseWriter, req *http.Request) {
bid := boostTypes.BuilderBid{
Header: bestHeader,
- Value: *new(boostTypes.U256Str).FromBig(profit),
- Pubkey: b.builderPublicKey,
+ Value: profit,
+ Pubkey: r.relayPublicKey,
}
- signature, err := boostTypes.SignMessage(&bid, b.builderSigningDomain, b.builderSecretKey)
+ signature, err := boostTypes.SignMessage(&bid, r.builderSigningDomain, r.relaySecretKey)
if err != nil {
respondError(w, http.StatusInternalServerError, "internal server error")
return
@@ -276,13 +240,14 @@ func (b *Backend) handleGetHeader(w http.ResponseWriter, req *http.Request) {
}
w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
}
-func (b *Backend) handleGetPayload(w http.ResponseWriter, req *http.Request) {
+func (r *LocalRelay) handleGetPayload(w http.ResponseWriter, req *http.Request) {
payload := new(boostTypes.SignedBlindedBeaconBlock)
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
respondError(w, http.StatusBadRequest, "invalid payload")
@@ -294,9 +259,9 @@ func (b *Backend) handleGetPayload(w http.ResponseWriter, req *http.Request) {
return
}
- nextSlotProposerPubkeyHex, err := b.beaconClient.getProposerForNextSlot(payload.Message.Slot)
+ nextSlotProposerPubkeyHex, err := r.beaconClient.getProposerForNextSlot(payload.Message.Slot)
if err != nil {
- if b.enableBeaconChecks {
+ if r.enableBeaconChecks {
respondError(w, http.StatusBadRequest, "unknown validator")
return
}
@@ -304,24 +269,26 @@ func (b *Backend) handleGetPayload(w http.ResponseWriter, req *http.Request) {
nextSlotProposerPubkeyBytes, err := hexutil.Decode(string(nextSlotProposerPubkeyHex))
if err != nil {
- if b.enableBeaconChecks {
+ if r.enableBeaconChecks {
respondError(w, http.StatusBadRequest, "unknown validator")
return
}
}
- ok, err := boostTypes.VerifySignature(payload.Message, b.proposerSigningDomain, nextSlotProposerPubkeyBytes[:], payload.Signature[:])
+ ok, err := boostTypes.VerifySignature(payload.Message, r.proposerSigningDomain, nextSlotProposerPubkeyBytes[:], payload.Signature[:])
if !ok || err != nil {
- if b.enableBeaconChecks {
+ if r.enableBeaconChecks {
respondError(w, http.StatusBadRequest, "invalid signature")
return
}
}
- b.bestDataLock.Lock()
- bestHeader := b.bestHeader
- bestPayload := b.bestPayload
- b.bestDataLock.Unlock()
+ r.bestDataLock.Lock()
+ bestHeader := r.bestHeader
+ bestPayload := r.bestPayload
+ r.bestDataLock.Unlock()
+
+ log.Info("Received blinded block", "payload", payload, "bestHeader", bestHeader)
if bestHeader == nil || bestPayload == nil {
respondError(w, http.StatusInternalServerError, "no payloads")
@@ -339,6 +306,7 @@ func (b *Backend) handleGetPayload(w http.ResponseWriter, req *http.Request) {
}
w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
respondError(w, http.StatusInternalServerError, "internal server error")
@@ -346,96 +314,65 @@ func (b *Backend) handleGetPayload(w http.ResponseWriter, req *http.Request) {
}
}
-func (b *Backend) onForkchoice(payloadAttributes *beacon.PayloadAttributesV1) {
- dataJson, err := json.Marshal(payloadAttributes)
- if err == nil {
- log.Info("FCU", "data", string(dataJson))
- }
- // if payloadAttributes.SuggestedFeeRecipient == common.Address{}
- pubkeyHex, err := b.beaconClient.onForkchoiceUpdate()
- if err != nil {
- return
+func (r *LocalRelay) handleIndex(w http.ResponseWriter, req *http.Request) {
+ if r.indexTemplate == nil {
+ http.Error(w, "not available", http.StatusInternalServerError)
}
- if payloadAttributes != nil {
- b.validatorsLock.RLock()
- vd, found := b.validators[pubkeyHex]
- if found {
- payloadAttributes.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
- payloadAttributes.GasLimit = vd.GasLimit
- }
- b.validatorsLock.RUnlock()
- }
-}
+ r.validatorsLock.RLock()
+ noValidators := len(r.validators)
+ r.validatorsLock.RUnlock()
+ validatorsStats := fmt.Sprint(noValidators) + " validators registered"
+
+ r.bestDataLock.Lock()
+ header := r.bestHeader
+ payload := r.bestPayload
+ r.bestDataLock.Lock()
-func (b *Backend) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Block) {
- dataJson, err := json.Marshal(data)
- if err == nil {
- log.Info("newSealedBlock", "data", string(dataJson))
+ headerData, err := json.MarshalIndent(header, "", " ")
+ if err != nil {
+ headerData = []byte{}
}
- payload := executableDataToExecutionPayload(data)
- payloadHeader, err := payloadToPayloadHeader(payload, data)
+
+ payloadData, err := json.MarshalIndent(payload, "", " ")
if err != nil {
- log.Error("could not convert payload to header", "err", err)
- return
+ payloadData = []byte{}
}
- b.bestDataLock.Lock()
- b.bestHeader = payloadHeader
- b.bestPayload = payload
- b.profit = new(big.Int).Set(block.Profit)
- b.bestDataLock.Unlock()
+ statusData := struct {
+ Pubkey string
+ ValidatorsStats string
+ GenesisForkVersion string
+ BellatrixForkVersion string
+ GenesisValidatorsRoot string
+ BuilderSigningDomain string
+ ProposerSigningDomain string
+ Header string
+ Blocks string
+ }{hexutil.Encode(r.serializedRelayPubkey), validatorsStats, r.fd.GenesisForkVersion, r.fd.BellatrixForkVersion, r.fd.GenesisValidatorsRoot, hexutil.Encode(r.builderSigningDomain[:]), hexutil.Encode(r.proposerSigningDomain[:]), string(headerData), string(payloadData)}
+
+ if err := r.indexTemplate.Execute(w, statusData); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
}
-func payloadToPayloadHeader(p *boostTypes.ExecutionPayload, data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayloadHeader, error) {
- txs := boostTypes.Transactions{data.Transactions}
- txroot, err := txs.HashTreeRoot()
- if err != nil {
- return nil, err
- }
-
- return &boostTypes.ExecutionPayloadHeader{
- ParentHash: p.ParentHash,
- FeeRecipient: p.FeeRecipient,
- StateRoot: p.StateRoot,
- ReceiptsRoot: p.ReceiptsRoot,
- LogsBloom: p.LogsBloom,
- Random: p.Random,
- BlockNumber: p.BlockNumber,
- GasLimit: p.GasLimit,
- GasUsed: p.GasUsed,
- Timestamp: p.Timestamp,
- ExtraData: data.ExtraData,
- BaseFeePerGas: p.BaseFeePerGas,
- BlockHash: p.BlockHash,
- TransactionsRoot: [32]byte(txroot),
- }, nil
+type httpErrorResp struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
}
-func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) *boostTypes.ExecutionPayload {
- transactionData := make([]hexutil.Bytes, len(data.Transactions))
- for i, tx := range data.Transactions {
- transactionData[i] = hexutil.Bytes(tx)
- }
-
- return &boostTypes.ExecutionPayload{
- ParentHash: [32]byte(data.ParentHash),
- FeeRecipient: [20]byte(data.FeeRecipient),
- StateRoot: [32]byte(data.StateRoot),
- ReceiptsRoot: [32]byte(data.ReceiptsRoot),
- LogsBloom: boostTypes.Bloom(types.BytesToBloom(data.LogsBloom)),
- Random: [32]byte(data.Random),
- BlockNumber: data.Number,
- GasLimit: data.GasLimit,
- GasUsed: data.GasUsed,
- Timestamp: data.Timestamp,
- ExtraData: data.ExtraData,
- BaseFeePerGas: *new(boostTypes.U256Str).FromBig(data.BaseFeePerGas),
- BlockHash: [32]byte(data.BlockHash),
- Transactions: transactionData,
+func respondError(w http.ResponseWriter, code int, message string) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ if err := json.NewEncoder(w).Encode(httpErrorResp{code, message}); err != nil {
+ http.Error(w, message, code)
}
}
+func (r *LocalRelay) handleStatus(w http.ResponseWriter, req *http.Request) {
+ w.WriteHeader(http.StatusOK)
+}
+
func ExecutionPayloadHeaderEqual(l *boostTypes.ExecutionPayloadHeader, r *boostTypes.ExecutionPayloadHeader) bool {
return l.ParentHash == r.ParentHash && l.FeeRecipient == r.FeeRecipient && l.StateRoot == r.StateRoot && l.ReceiptsRoot == r.ReceiptsRoot && l.LogsBloom == r.LogsBloom && l.Random == r.Random && l.BlockNumber == r.BlockNumber && l.GasLimit == r.GasLimit && l.GasUsed == r.GasUsed && l.Timestamp == r.Timestamp && l.BaseFeePerGas == r.BaseFeePerGas && bytes.Equal(l.ExtraData, r.ExtraData) && l.BlockHash == r.BlockHash && l.TransactionsRoot == r.TransactionsRoot
}
diff --git a/builder/backend_test.go b/builder/local_relay_test.go
similarity index 67%
rename from builder/backend_test.go
rename to builder/local_relay_test.go
index 9880f5cb8e..a1a8dad5cc 100644
--- a/builder/backend_test.go
+++ b/builder/local_relay_test.go
@@ -20,19 +20,21 @@ import (
"github.com/stretchr/testify/require"
)
-func newTestBackend(t *testing.T) (*Backend, *ValidatorPrivateData) {
+func newTestBackend(t *testing.T) (*Builder, *LocalRelay, *ValidatorPrivateData) {
validator := NewRandomValidator()
sk, _ := bls.GenerateRandomSecretKey()
bDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeAppBuilder, [4]byte{0x02, 0x0, 0x0, 0x0}, boostTypes.Hash{})
genesisValidatorsRoot := boostTypes.Hash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"))
cDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeBeaconProposer, [4]byte{0x02, 0x0, 0x0, 0x0}, genesisValidatorsRoot)
- backend := NewBackend(sk, &testBeaconClient{validator}, ForkData{}, bDomain, cDomain, true)
+ beaconClient := &testBeaconClient{validator: validator}
+ localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true)
+ backend := NewBuilder(sk, beaconClient, localRelay, bDomain)
// service := NewService("127.0.0.1:31545", backend)
- return backend, validator
+ return backend, localRelay, validator
}
-func testRequest(t *testing.T, backend *Backend, method string, path string, payload any) *httptest.ResponseRecorder {
+func testRequest(t *testing.T, localRelay *LocalRelay, method string, path string, payload any) *httptest.ResponseRecorder {
var req *http.Request
var err error
@@ -46,30 +48,30 @@ func testRequest(t *testing.T, backend *Backend, method string, path string, pay
require.NoError(t, err)
rr := httptest.NewRecorder()
- getRouter(backend).ServeHTTP(rr, req)
+ getRouter(localRelay).ServeHTTP(rr, req)
return rr
}
func TestValidatorRegistration(t *testing.T) {
- backend, _ := newTestBackend(t)
- log.Error("rsk", "sk", hexutil.Encode(backend.builderSecretKey.Serialize()))
+ _, relay, _ := newTestBackend(t)
+ log.Error("rsk", "sk", hexutil.Encode(relay.relaySecretKey.Serialize()))
v := NewRandomValidator()
- payload, err := prepareRegistrationMessage(t, backend.builderSigningDomain, v)
+ payload, err := prepareRegistrationMessage(t, relay.builderSigningDomain, v)
require.NoError(t, err)
- rr := testRequest(t, backend, "POST", "/eth/v1/builder/validators", payload)
+ rr := testRequest(t, relay, "POST", "/eth/v1/builder/validators", payload)
require.Equal(t, http.StatusOK, rr.Code)
- require.Contains(t, backend.validators, PubkeyHex(v.Pk.String()))
- require.Equal(t, ValidatorData{FeeRecipient: payload[0].Message.FeeRecipient, GasLimit: payload[0].Message.GasLimit, Timestamp: payload[0].Message.Timestamp}, backend.validators[PubkeyHex(v.Pk.String())])
+ require.Contains(t, relay.validators, PubkeyHex(v.Pk.String()))
+ require.Equal(t, ValidatorData{Pubkey: PubkeyHex(v.Pk.String()), FeeRecipient: payload[0].Message.FeeRecipient, GasLimit: payload[0].Message.GasLimit, Timestamp: payload[0].Message.Timestamp}, relay.validators[PubkeyHex(v.Pk.String())])
- rr = testRequest(t, backend, "POST", "/eth/v1/builder/validators", payload)
+ rr = testRequest(t, relay, "POST", "/eth/v1/builder/validators", payload)
require.Equal(t, http.StatusOK, rr.Code)
payload[0].Message.Timestamp += 1
// Invalid signature
payload[0].Signature[len(payload[0].Signature)-1] = 0x00
- rr = testRequest(t, backend, "POST", "/eth/v1/builder/validators", payload)
+ rr = testRequest(t, relay, "POST", "/eth/v1/builder/validators", payload)
require.Equal(t, http.StatusBadRequest, rr.Code)
require.Equal(t, `{"code":400,"message":"invalid signature"}`+"\n", rr.Body.String())
@@ -97,19 +99,19 @@ func prepareRegistrationMessage(t *testing.T, domain boostTypes.Domain, v *Valid
}}, nil
}
-func registerValidator(t *testing.T, v *ValidatorPrivateData, backend *Backend) {
- payload, err := prepareRegistrationMessage(t, backend.builderSigningDomain, v)
+func registerValidator(t *testing.T, v *ValidatorPrivateData, relay *LocalRelay) {
+ payload, err := prepareRegistrationMessage(t, relay.builderSigningDomain, v)
require.NoError(t, err)
log.Info("Registering", "payload", payload[0].Message)
- rr := testRequest(t, backend, "POST", "/eth/v1/builder/validators", payload)
+ rr := testRequest(t, relay, "POST", "/eth/v1/builder/validators", payload)
require.Equal(t, http.StatusOK, rr.Code)
- require.Contains(t, backend.validators, PubkeyHex(v.Pk.String()))
- require.Equal(t, ValidatorData{FeeRecipient: payload[0].Message.FeeRecipient, GasLimit: payload[0].Message.GasLimit, Timestamp: payload[0].Message.Timestamp}, backend.validators[PubkeyHex(v.Pk.String())])
+ require.Contains(t, relay.validators, PubkeyHex(v.Pk.String()))
+ require.Equal(t, ValidatorData{Pubkey: PubkeyHex(v.Pk.String()), FeeRecipient: payload[0].Message.FeeRecipient, GasLimit: payload[0].Message.GasLimit, Timestamp: payload[0].Message.Timestamp}, relay.validators[PubkeyHex(v.Pk.String())])
}
func TestGetHeader(t *testing.T) {
- backend, validator := newTestBackend(t)
+ backend, relay, validator := newTestBackend(t)
forkchoiceData := &beacon.ExecutableDataV1{
ParentHash: common.HexToHash("0xafafafa"),
@@ -124,33 +126,39 @@ func TestGetHeader(t *testing.T) {
}
path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
- rr := testRequest(t, backend, "GET", path, nil)
+ rr := testRequest(t, relay, "GET", path, nil)
require.Equal(t, `{"code":400,"message":"unknown validator"}`+"\n", rr.Body.String())
- registerValidator(t, validator, backend)
+ registerValidator(t, validator, relay)
- rr = testRequest(t, backend, "GET", path, nil)
+ rr = testRequest(t, relay, "GET", path, nil)
require.Equal(t, `{"code":400,"message":"unknown payload"}`+"\n", rr.Body.String())
path = fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), NewRandomValidator().Pk.String())
- rr = testRequest(t, backend, "GET", path, nil)
- require.Equal(t, `{"code":400,"message":"unknown validator"}`+"\n", rr.Body.String())
+ rr = testRequest(t, relay, "GET", path, nil)
+ require.Equal(t, ``, rr.Body.String())
+ require.Equal(t, 204, rr.Code)
- backend.newSealedBlock(forkchoiceData, forkchoiceBlock)
+ backend.newSealedBlock(forkchoiceData, forkchoiceBlock, &beacon.PayloadAttributesV1{})
path = fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
- rr = testRequest(t, backend, "GET", path, nil)
+ rr = testRequest(t, relay, "GET", path, nil)
require.Equal(t, http.StatusOK, rr.Code)
bid := new(boostTypes.GetHeaderResponse)
err := json.Unmarshal(rr.Body.Bytes(), bid)
require.NoError(t, err)
- expectedHeader, err := payloadToPayloadHeader(executableDataToExecutionPayload(forkchoiceData), forkchoiceData)
+ executionPayload, err := executableDataToExecutionPayload(forkchoiceData)
+ require.NoError(t, err)
+ expectedHeader, err := boostTypes.PayloadToPayloadHeader(executionPayload)
+ require.NoError(t, err)
+ expectedValue := new(boostTypes.U256Str)
+ err = expectedValue.FromBig(forkchoiceBlock.Profit)
require.NoError(t, err)
require.EqualValues(t, &boostTypes.BuilderBid{
Header: expectedHeader,
- Value: *new(boostTypes.U256Str).FromBig(forkchoiceBlock.Profit),
+ Value: *expectedValue,
Pubkey: backend.builderPublicKey,
}, bid.Data.Message)
@@ -162,7 +170,7 @@ func TestGetHeader(t *testing.T) {
}
func TestGetPayload(t *testing.T) {
- backend, validator := newTestBackend(t)
+ backend, relay, validator := newTestBackend(t)
forkchoiceData := &beacon.ExecutableDataV1{
ParentHash: common.HexToHash("0xafafafa"),
@@ -175,11 +183,11 @@ func TestGetPayload(t *testing.T) {
Profit: big.NewInt(10),
}
- registerValidator(t, validator, backend)
- backend.newSealedBlock(forkchoiceData, forkchoiceBlock)
+ registerValidator(t, validator, relay)
+ backend.newSealedBlock(forkchoiceData, forkchoiceBlock, &beacon.PayloadAttributesV1{})
path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
- rr := testRequest(t, backend, "GET", path, nil)
+ rr := testRequest(t, relay, "GET", path, nil)
require.Equal(t, http.StatusOK, rr.Code)
bid := new(boostTypes.GetHeaderResponse)
@@ -207,11 +215,11 @@ func TestGetPayload(t *testing.T) {
}
// TODO: test wrong signing domain
- signature, err := validator.Sign(msg, backend.proposerSigningDomain)
+ signature, err := validator.Sign(msg, relay.proposerSigningDomain)
require.NoError(t, err)
// Call getPayload with invalid signature
- rr = testRequest(t, backend, "POST", "/eth/v1/builder/blinded_blocks", boostTypes.SignedBlindedBeaconBlock{
+ rr = testRequest(t, relay, "POST", "/eth/v1/builder/blinded_blocks", boostTypes.SignedBlindedBeaconBlock{
Message: msg,
Signature: boostTypes.Signature{0x09},
})
@@ -219,7 +227,7 @@ func TestGetPayload(t *testing.T) {
require.Equal(t, `{"code":400,"message":"invalid signature"}`+"\n", rr.Body.String())
// Call getPayload with correct signature
- rr = testRequest(t, backend, "POST", "/eth/v1/builder/blinded_blocks", boostTypes.SignedBlindedBeaconBlock{
+ rr = testRequest(t, relay, "POST", "/eth/v1/builder/blinded_blocks", boostTypes.SignedBlindedBeaconBlock{
Message: msg,
Signature: signature,
})
diff --git a/builder/relay.go b/builder/relay.go
new file mode 100644
index 0000000000..b476b68f6c
--- /dev/null
+++ b/builder/relay.go
@@ -0,0 +1,191 @@
+package builder
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/log"
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+ "github.com/flashbots/mev-boost/server"
+)
+
+type testRelay struct {
+ validator ValidatorData
+ requestedSlot uint64
+ submittedMsg *boostTypes.BuilderSubmitBlockRequest
+}
+
+func (r *testRelay) SubmitBlock(msg *boostTypes.BuilderSubmitBlockRequest) error {
+ r.submittedMsg = msg
+ return nil
+}
+func (r *testRelay) GetValidatorForSlot(nextSlot uint64) (ValidatorData, error) {
+ r.requestedSlot = nextSlot
+ return r.validator, nil
+}
+func (r *testRelay) handleRegisterValidator(w http.ResponseWriter, req *http.Request) {
+}
+
+type RemoteRelay struct {
+ endpoint string
+ client http.Client
+
+ localRelay *LocalRelay
+
+ validatorsLock sync.RWMutex
+ validatorSyncOngoing bool
+ lastRequestedSlot uint64
+ validatorSlotMap map[uint64]ValidatorData
+}
+
+func NewRemoteRelay(endpoint string, localRelay *LocalRelay) (*RemoteRelay, error) {
+ r := &RemoteRelay{
+ endpoint: endpoint,
+ client: http.Client{Timeout: time.Second},
+ localRelay: localRelay,
+ validatorSyncOngoing: false,
+ lastRequestedSlot: 0,
+ validatorSlotMap: make(map[uint64]ValidatorData),
+ }
+
+ err := r.updateValidatorsMap(0, 3)
+ return r, err
+}
+
+type GetValidatorRelayResponse []struct {
+ Slot uint64 `json:"slot,string"`
+ Entry struct {
+ Message struct {
+ FeeRecipient string `json:"fee_recipient"`
+ GasLimit uint64 `json:"gas_limit,string"`
+ Timestamp uint64 `json:"timestamp,string"`
+ Pubkey string `json:"pubkey"`
+ } `json:"message"`
+ Signature string `json:"signature"`
+ } `json:"entry"`
+}
+
+func (r *RemoteRelay) updateValidatorsMap(currentSlot uint64, retries int) error {
+ r.validatorsLock.Lock()
+ if r.validatorSyncOngoing {
+ r.validatorsLock.Unlock()
+ return errors.New("sync is ongoing")
+ }
+ r.validatorSyncOngoing = true
+ r.validatorsLock.Unlock()
+
+ log.Info("requesting ", "currentSlot", currentSlot)
+ newMap, err := r.getSlotValidatorMapFromRelay()
+ for err != nil && retries > 0 {
+ log.Error("could not get validators map from relay, retrying", "err", err)
+ time.Sleep(time.Second)
+ newMap, err = r.getSlotValidatorMapFromRelay()
+ retries -= 1
+ }
+ r.validatorsLock.Lock()
+ r.validatorSyncOngoing = false
+ if err != nil {
+ r.validatorsLock.Unlock()
+ log.Error("could not get validators map from relay", "err", err)
+ return err
+ }
+
+ r.validatorSlotMap = newMap
+ r.lastRequestedSlot = currentSlot
+ r.validatorsLock.Unlock()
+
+ log.Info("Updated validators", "new", newMap, "for slot", currentSlot)
+
+ return nil
+}
+
+func (r *RemoteRelay) GetValidatorForSlot(nextSlot uint64) (ValidatorData, error) {
+ // next slot is expected to be the actual chain's next slot, not something requested by the user!
+ // if not sanitized it will force resync of validator data and possibly is a DoS vector
+
+ r.validatorsLock.RLock()
+ if r.lastRequestedSlot == 0 || nextSlot/32 > r.lastRequestedSlot/32 {
+ // Every epoch request validators map
+ go func() {
+ err := r.updateValidatorsMap(nextSlot, 1)
+ if err != nil {
+ log.Error("could not update validators map", "err", err)
+ }
+ }()
+ }
+
+ vd, found := r.validatorSlotMap[nextSlot]
+ r.validatorsLock.RUnlock()
+
+ if r.localRelay != nil {
+ localValidator, err := r.localRelay.GetValidatorForSlot(nextSlot)
+ if err == nil {
+ log.Info("Validator registration overwritten by local data", "slot", nextSlot, "validator", localValidator)
+ return localValidator, nil
+ }
+ }
+
+ if found {
+ return vd, nil
+ }
+
+ return ValidatorData{}, errors.New("validator not found")
+}
+
+func (r *RemoteRelay) SubmitBlock(msg *boostTypes.BuilderSubmitBlockRequest) error {
+ code, err := server.SendHTTPRequest(context.TODO(), *http.DefaultClient, http.MethodPost, r.endpoint+"/relay/v1/builder/blocks", msg, nil)
+ if err != nil {
+ return err
+ }
+ if code > 299 {
+ return fmt.Errorf("non-ok response code %d from relay ", code)
+ }
+
+ log.Info("submitted block", "msg", msg)
+
+ if r.localRelay != nil {
+ r.localRelay.SubmitBlock(msg)
+ }
+
+ return nil
+}
+
+func (r *RemoteRelay) getSlotValidatorMapFromRelay() (map[uint64]ValidatorData, error) {
+ var dst GetValidatorRelayResponse
+ code, err := server.SendHTTPRequest(context.TODO(), *http.DefaultClient, http.MethodGet, r.endpoint+"/relay/v1/builder/validators", nil, &dst)
+ if err != nil {
+ return nil, err
+ }
+
+ if code > 299 {
+ return nil, fmt.Errorf("non-ok response code %d from relay", code)
+ }
+
+ res := make(map[uint64]ValidatorData)
+ for _, data := range dst {
+ feeRecipientBytes, err := hexutil.Decode(data.Entry.Message.FeeRecipient)
+ if err != nil {
+ log.Error("Ill-formatted fee_recipient from relay", "data", data)
+ continue
+ }
+ var feeRecipient boostTypes.Address
+ feeRecipient.FromSlice(feeRecipientBytes[:])
+
+ pubkeyHex := PubkeyHex(strings.ToLower(data.Entry.Message.Pubkey))
+
+ res[data.Slot] = ValidatorData{
+ Pubkey: pubkeyHex,
+ FeeRecipient: feeRecipient,
+ GasLimit: data.Entry.Message.GasLimit,
+ Timestamp: data.Entry.Message.Timestamp,
+ }
+ }
+
+ return res, nil
+}
diff --git a/builder/relay_test.go b/builder/relay_test.go
new file mode 100644
index 0000000000..93def4cd51
--- /dev/null
+++ b/builder/relay_test.go
@@ -0,0 +1,128 @@
+package builder
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+ "github.com/gorilla/mux"
+ "github.com/stretchr/testify/require"
+)
+
+func TestRemoteRelay(t *testing.T) {
+ r := mux.NewRouter()
+ var validatorsHandler func(w http.ResponseWriter, r *http.Request)
+ r.HandleFunc("/relay/v1/builder/validators", func(w http.ResponseWriter, r *http.Request) { validatorsHandler(w, r) })
+
+ validatorsHandler = func(w http.ResponseWriter, r *http.Request) {
+ resp := `[{
+ "slot": "123",
+ "entry": {
+ "message": {
+ "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
+ "gas_limit": "1",
+ "timestamp": "1",
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
+ },
+ "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
+ }}, {
+ "slot": "155",
+ "entry": {
+ "message": {
+ "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc10",
+ "gas_limit": "1",
+ "timestamp": "1",
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
+ },
+ "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
+ }
+}]`
+
+ w.Header().Set("Content-Type", "application/json")
+ w.Write([]byte(resp))
+ }
+
+ srv := httptest.NewServer(r)
+ relay, err := NewRemoteRelay(srv.URL, nil)
+ require.NoError(t, err)
+ vd, found := relay.validatorSlotMap[123]
+ require.True(t, found)
+ expectedValidator_123 := ValidatorData{
+ Pubkey: "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
+ FeeRecipient: boostTypes.Address{0xab, 0xcf, 0x8e, 0xd, 0x4e, 0x95, 0x87, 0x36, 0x9b, 0x23, 0x1, 0xd0, 0x79, 0x3, 0x47, 0x32, 0x3, 0x2, 0xcc, 0x9},
+ GasLimit: uint64(1),
+ Timestamp: uint64(1),
+ }
+ require.Equal(t, expectedValidator_123, vd)
+
+ vd, err = relay.GetValidatorForSlot(123)
+ require.NoError(t, err)
+ require.Equal(t, expectedValidator_123, vd)
+
+ vd, err = relay.GetValidatorForSlot(124)
+ require.Error(t, err)
+ require.Equal(t, vd, ValidatorData{})
+
+ validatorsRequested := make(chan struct{})
+ validatorsHandler = func(w http.ResponseWriter, r *http.Request) {
+ resp := `[{
+ "slot": "155",
+ "entry": {
+ "message": {
+ "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc10",
+ "gas_limit": "1",
+ "timestamp": "1",
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
+ },
+ "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
+ }}, {
+ "slot": "156",
+ "entry": {
+ "message": {
+ "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc11",
+ "gas_limit": "1",
+ "timestamp": "1",
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
+ },
+ "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
+ }
+}]`
+
+ w.Header().Set("Content-Type", "application/json")
+ w.Write([]byte(resp))
+ validatorsRequested <- struct{}{}
+ }
+
+ expectedValidator_155 := ValidatorData{
+ Pubkey: "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
+ FeeRecipient: boostTypes.Address{0xab, 0xcf, 0x8e, 0xd, 0x4e, 0x95, 0x87, 0x36, 0x9b, 0x23, 0x1, 0xd0, 0x79, 0x3, 0x47, 0x32, 0x3, 0x2, 0xcc, 0x10},
+ GasLimit: uint64(1),
+ Timestamp: uint64(1),
+ }
+
+ expectedValidator_156 := ValidatorData{
+ Pubkey: "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
+ FeeRecipient: boostTypes.Address{0xab, 0xcf, 0x8e, 0xd, 0x4e, 0x95, 0x87, 0x36, 0x9b, 0x23, 0x1, 0xd0, 0x79, 0x3, 0x47, 0x32, 0x3, 0x2, 0xcc, 0x11},
+ GasLimit: uint64(1),
+ Timestamp: uint64(1),
+ }
+
+ vd, err = relay.GetValidatorForSlot(155)
+ require.NoError(t, err)
+ require.Equal(t, expectedValidator_155, vd)
+
+ select {
+ case <-validatorsRequested:
+ for i := 0; i < 10 && relay.lastRequestedSlot != 155; i++ {
+ time.Sleep(time.Millisecond)
+ }
+ case <-time.After(time.Second):
+ t.Error("timeout waiting for validator registration request")
+ }
+
+ vd, err = relay.GetValidatorForSlot(156)
+ require.NoError(t, err)
+ require.Equal(t, expectedValidator_156, vd)
+}
diff --git a/builder/service.go b/builder/service.go
index e2647cbb44..340cb46c6a 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -33,26 +33,26 @@ func (s *Service) Start() {
go s.srv.ListenAndServe()
}
-func getRouter(backend *Backend) http.Handler {
+func getRouter(localRelay *LocalRelay) http.Handler {
router := mux.NewRouter()
// Add routes
- router.HandleFunc("/", backend.handleIndex).Methods(http.MethodGet)
- router.HandleFunc(_PathStatus, backend.handleStatus).Methods(http.MethodGet)
- router.HandleFunc(_PathRegisterValidator, backend.handleRegisterValidator).Methods(http.MethodPost)
- router.HandleFunc(_PathGetHeader, backend.handleGetHeader).Methods(http.MethodGet)
- router.HandleFunc(_PathGetPayload, backend.handleGetPayload).Methods(http.MethodPost)
+ router.HandleFunc("/", localRelay.handleIndex).Methods(http.MethodGet)
+ router.HandleFunc(_PathStatus, localRelay.handleStatus).Methods(http.MethodGet)
+ router.HandleFunc(_PathRegisterValidator, localRelay.handleRegisterValidator).Methods(http.MethodPost)
+ router.HandleFunc(_PathGetHeader, localRelay.handleGetHeader).Methods(http.MethodGet)
+ router.HandleFunc(_PathGetPayload, localRelay.handleGetPayload).Methods(http.MethodPost)
// Add logging and return router
loggedRouter := httplogger.LoggingMiddleware(router)
return loggedRouter
}
-func NewService(listenAddr string, backend *Backend) *Service {
+func NewService(listenAddr string, localRelay *LocalRelay) *Service {
return &Service{
srv: &http.Server{
Addr: listenAddr,
- Handler: getRouter(backend),
+ Handler: getRouter(localRelay),
/*
ReadTimeout:
ReadHeaderTimeout:
@@ -65,21 +65,33 @@ func NewService(listenAddr string, backend *Backend) *Service {
type BuilderConfig struct {
EnableValidatorChecks bool
- SecretKey string
+ BuilderSecretKey string
+ RelaySecretKey string
ListenAddr string
GenesisForkVersion string
BellatrixForkVersion string
GenesisValidatorsRoot string
BeaconEndpoint string
+ RemoteRelayEndpoint string
}
func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error {
- envSkBytes, err := hexutil.Decode(cfg.SecretKey)
+ envRelaySkBytes, err := hexutil.Decode(cfg.RelaySecretKey)
if err != nil {
return errors.New("incorrect builder API secret key provided")
}
- sk, err := bls.SecretKeyFromBytes(envSkBytes[:])
+ relaySk, err := bls.SecretKeyFromBytes(envRelaySkBytes[:])
+ if err != nil {
+ return errors.New("incorrect builder API secret key provided")
+ }
+
+ envBuilderSkBytes, err := hexutil.Decode(cfg.BuilderSecretKey)
+ if err != nil {
+ return errors.New("incorrect builder API secret key provided")
+ }
+
+ builderSk, err := bls.SecretKeyFromBytes(envBuilderSkBytes[:])
if err != nil {
return errors.New("incorrect builder API secret key provided")
}
@@ -98,8 +110,20 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
var beaconClient IBeaconClient
beaconClient = NewBeaconClient(cfg.BeaconEndpoint)
- builderBackend := NewBackend(sk, beaconClient, ForkData{cfg.GenesisForkVersion, cfg.BellatrixForkVersion, cfg.GenesisValidatorsRoot}, builderSigningDomain, proposerSigningDomain, cfg.EnableValidatorChecks)
- builderService := NewService(cfg.ListenAddr, builderBackend)
+ localRelay := NewLocalRelay(relaySk, beaconClient, builderSigningDomain, proposerSigningDomain, ForkData{cfg.GenesisForkVersion, cfg.BellatrixForkVersion, cfg.GenesisValidatorsRoot}, cfg.EnableValidatorChecks)
+
+ var relay IRelay
+ if cfg.RemoteRelayEndpoint != "" {
+ relay, err = NewRemoteRelay(cfg.RemoteRelayEndpoint, localRelay)
+ if err != nil {
+ return err
+ }
+ } else {
+ relay = localRelay
+ }
+
+ builderBackend := NewBuilder(builderSk, beaconClient, relay, builderSigningDomain)
+ builderService := NewService(cfg.ListenAddr, localRelay)
builderService.Start()
backend.SetSealedBlockHook(builderBackend.newSealedBlock)
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 1fe5e8ed27..cc11359785 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -168,12 +168,14 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
bpConfig := &builder.BuilderConfig{
EnableValidatorChecks: ctx.IsSet(utils.BuilderEnableValidatorChecks.Name),
- SecretKey: ctx.String(utils.BuilderSecretKey.Name),
+ BuilderSecretKey: ctx.String(utils.BuilderSecretKey.Name),
+ RelaySecretKey: ctx.String(utils.BuilderRelaySecretKey.Name),
ListenAddr: ctx.String(utils.BuilderListenAddr.Name),
GenesisForkVersion: ctx.String(utils.BuilderGenesisForkVersion.Name),
BellatrixForkVersion: ctx.String(utils.BuilderBellatrixForkVersion.Name),
GenesisValidatorsRoot: ctx.String(utils.BuilderGenesisValidatorsRoot.Name),
BeaconEndpoint: ctx.String(utils.BuilderBeaconEndpoint.Name),
+ RemoteRelayEndpoint: ctx.String(utils.BuilderRemoteRelayEndpoint.Name),
}
backend, eth := utils.RegisterEthService(stack, &cfg.Eth, bpConfig)
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index ab159b91de..c8ca3bd02b 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -160,11 +160,13 @@ var (
builderApiFlags = []cli.Flag{
utils.BuilderEnableValidatorChecks,
utils.BuilderSecretKey,
+ utils.BuilderRelaySecretKey,
utils.BuilderListenAddr,
utils.BuilderGenesisForkVersion,
utils.BuilderBellatrixForkVersion,
utils.BuilderGenesisValidatorsRoot,
utils.BuilderBeaconEndpoint,
+ utils.BuilderRemoteRelayEndpoint,
}
rpcFlags = []cli.Flag{
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index c76e2f37ee..feff51a7c8 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -668,10 +668,16 @@ var (
}
BuilderSecretKey = &cli.StringFlag{
Name: "builder.secret_key",
- Usage: "Builder API key used for signing headers",
+ Usage: "Builder key used for signing blocks",
EnvVars: []string{"BUILDER_SECRET_KEY"},
Value: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11",
}
+ BuilderRelaySecretKey = &cli.StringFlag{
+ Name: "builder.relay_secret_key",
+ Usage: "Builder local relay API key used for signing headers",
+ EnvVars: []string{"BUILDER_RELAY_SECRET_KEY"},
+ Value: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11",
+ }
BuilderListenAddr = &cli.StringFlag{
Name: "builder.listen_addr",
Usage: "Listening address for builder endpoint",
@@ -702,6 +708,12 @@ var (
EnvVars: []string{"BUILDER_BEACON_ENDPOINT"},
Value: "http://127.0.0.1:5052",
}
+ BuilderRemoteRelayEndpoint = &cli.StringFlag{
+ Name: "builder.remote_relay_endpoint",
+ Usage: "Relay endpoint to connect to for validator registration data, if not provided will expose validator registration locally",
+ EnvVars: []string{"BUILDER_REMOTE_RELAY_ENDPOINT"},
+ Value: "",
+ }
// RPC settings
IPCDisabledFlag = &cli.BoolFlag{
Name: "ipcdisable",
diff --git a/core/beacon/types.go b/core/beacon/types.go
index d0e878bde1..6ebc6954a5 100644
--- a/core/beacon/types.go
+++ b/core/beacon/types.go
@@ -34,6 +34,7 @@ type PayloadAttributesV1 struct {
Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
GasLimit uint64
+ Slot uint64
}
// JSON type overrides for PayloadAttributesV1.
diff --git a/eth/backend.go b/eth/backend.go
index 7da37eb5a1..43a4994389 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -86,7 +86,7 @@ type Ethereum struct {
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
closeBloomHandler chan struct{}
- newSealedBlockHook func(*beaconTypes.ExecutableDataV1, *types.Block)
+ newSealedBlockHook func(*beaconTypes.ExecutableDataV1, *types.Block, *beaconTypes.PayloadAttributesV1)
forkchoiceHook func(*beaconTypes.PayloadAttributesV1)
APIBackend *EthAPIBackend
@@ -357,13 +357,13 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) {
return common.Address{}, fmt.Errorf("etherbase must be explicitly specified")
}
-func (s *Ethereum) SetSealedBlockHook(newSealedBlockHook func(*beaconTypes.ExecutableDataV1, *types.Block)) {
+func (s *Ethereum) SetSealedBlockHook(newSealedBlockHook func(*beaconTypes.ExecutableDataV1, *types.Block, *beaconTypes.PayloadAttributesV1)) {
s.newSealedBlockHook = newSealedBlockHook
}
-func (s *Ethereum) NewSealedBlock(data *beaconTypes.ExecutableDataV1, block *types.Block) {
+func (s *Ethereum) NewSealedBlock(data *beaconTypes.ExecutableDataV1, block *types.Block, payloadAttributes *beaconTypes.PayloadAttributesV1) {
if s.newSealedBlockHook != nil {
- s.newSealedBlockHook(data, block)
+ s.newSealedBlockHook(data, block, payloadAttributes)
}
}
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index cf748c5545..95171459c8 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -297,7 +297,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
resultPayload := &payload{empty: empty, result: resCh}
api.localBlocks.put(id, resultPayload)
go func() {
- api.eth.NewSealedBlock(resultPayload.resolve())
+ executableData, block := resultPayload.resolve()
+ api.eth.NewSealedBlock(executableData, block, payloadAttributes)
}()
return valid(&id), nil
}
diff --git a/go.mod b/go.mod
index a63f7763fa..dd0cdaa46c 100644
--- a/go.mod
+++ b/go.mod
@@ -21,8 +21,9 @@ require (
github.com/fatih/color v1.9.0
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
- github.com/flashbots/go-boost-utils v0.1.3-0.20220602181853-0b5ffd891e10
- github.com/flashbots/go-utils v0.4.4
+ github.com/flashbots/go-boost-utils v0.3.1
+ github.com/flashbots/go-utils v0.4.5
+ github.com/flashbots/mev-boost v0.7.3
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
github.com/go-stack/stack v1.8.0
github.com/golang-jwt/jwt/v4 v4.3.0
@@ -59,7 +60,7 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef
github.com/urfave/cli/v2 v2.10.2
- golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
+ golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
@@ -87,7 +88,7 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
- github.com/ferranbt/fastssz v0.1.1-0.20220303160658-88bb965b6747 // indirect
+ github.com/ferranbt/fastssz v0.1.1 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
@@ -103,6 +104,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
diff --git a/go.sum b/go.sum
index f8ee19d494..45094e42a5 100644
--- a/go.sum
+++ b/go.sum
@@ -135,18 +135,18 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
-github.com/ferranbt/fastssz v0.1.1-0.20220303160658-88bb965b6747 h1:0w9ZW2gY6Ws640PkDsyB1CK1ndAPJJ0mJbsDojQFl5c=
-github.com/ferranbt/fastssz v0.1.1-0.20220303160658-88bb965b6747/go.mod h1:S8yiDeAXy8f88W4Ul+0dBMPx49S05byYbmZD6Uv94K4=
+github.com/ferranbt/fastssz v0.1.1 h1:hBYNxKu51wjPC9sQYCjicmy5wtJqubENp3IiRVcdJBM=
+github.com/ferranbt/fastssz v0.1.1/go.mod h1:U2ZsxlYyvGeQGmadhz8PlEqwkBzDIhHwd3xuKrg2JIs=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq6Q+cGWcGHz0FjGR3QqcInWcW0=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
-github.com/flashbots/go-boost-utils v0.1.3-0.20220601182529-417a64679a5d h1:hoqkcRKMupXCWW38fDfvNcuIxl9eCAZrguw3dNCw+Qg=
-github.com/flashbots/go-boost-utils v0.1.3-0.20220601182529-417a64679a5d/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
-github.com/flashbots/go-boost-utils v0.1.3-0.20220602181853-0b5ffd891e10 h1:3fbCrMmSOayxCHNkIVUWLFR+q/dR1KeXUD08OXhShBM=
-github.com/flashbots/go-boost-utils v0.1.3-0.20220602181853-0b5ffd891e10/go.mod h1:v4f01OjPm5jFjzVhcJEKHLFZzX/yTeme9284Tbuah8s=
-github.com/flashbots/go-utils v0.4.4 h1:J0LUifVEpVYE+ZbK/DeGay4E3B6+Yh8pKLgfv5A0Oq0=
-github.com/flashbots/go-utils v0.4.4/go.mod h1:weSbiNnH+xsmK8t3TDDJxluv4+qnwRWmKay2QQa8Yfc=
+github.com/flashbots/go-boost-utils v0.3.1 h1:M1K4f97VnzW8v8hHdcVf14btZw343y9oSsfYHOgFl5A=
+github.com/flashbots/go-boost-utils v0.3.1/go.mod h1:7pAZp9AHt06uHUY+Avob0H88IN2La22uPZUpS3seXR0=
+github.com/flashbots/go-utils v0.4.5 h1:xTrVcfxQ+qpVVPyRBWUllwZAxbAijE06d9N7e7mmmlM=
+github.com/flashbots/go-utils v0.4.5/go.mod h1:3YKyfbtetVIXuWKbZ9WmK8bSF20hSFXk0wCWDNHYFvE=
+github.com/flashbots/mev-boost v0.7.3 h1:AwJQACv6/CJuMQe4oGNvIgW00oKFCzzDBrb4DlJsBKE=
+github.com/flashbots/mev-boost v0.7.3/go.mod h1:DOqKZloyaZnfr6eGbnQb8XGH3gtjts19ATxI1LnfcNM=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
@@ -338,10 +338,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
-github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
@@ -416,6 +414,8 @@ github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -475,8 +475,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
-golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
+golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
diff --git a/miner/miner.go b/miner/miner.go
index a51f49e82a..cc0af1253e 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -246,7 +246,7 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
// The difference is that if the execution fails, the returned result is nil
// and the concrete error is dropped silently.
func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (chan *types.Block, error) {
- resCh, _, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs)
+ resCh, _, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
if err != nil {
return nil, err
}
@@ -257,7 +257,7 @@ func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, c
// If the generation is failed or the underlying work is already closed, an error
// will be returned.
func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (*types.Block, error) {
- resCh, errCh, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs)
+ resCh, errCh, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
if err != nil {
return nil, err
}
diff --git a/miner/worker.go b/miner/worker.go
index 5e14383406..ec2e3ad61d 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1184,7 +1184,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
// getSealingBlock generates the sealing block based on the given parameters.
// The generation result will be passed back via the given channel no matter
// the generation itself succeeds or not.
-func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (chan *types.Block, chan error, error) {
+func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (chan *types.Block, chan error, error) {
var (
resCh = make(chan *types.Block, 1)
errCh = make(chan error, 1)
@@ -1198,7 +1198,7 @@ func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase
gasLimit: gasLimit,
random: random,
noUncle: true,
- noExtra: true,
+ noExtra: noExtra,
noTxs: noTxs,
},
result: resCh,
diff --git a/miner/worker_test.go b/miner/worker_test.go
index 2b5af2ffc2..1cade0aea4 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -557,7 +557,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
time.Sleep(100 * time.Millisecond)
}
timestamp := uint64(time.Now().Unix())
- assertBlock := func(block *types.Block, number uint64, coinbase common.Address, random common.Hash) {
+ assertBlock := func(block *types.Block, number uint64, coinbase common.Address, random common.Hash, noExtra bool) {
if block.Time() != timestamp {
// Sometime the timestamp will be mutated if the timestamp
// is even smaller than parent block's. It's OK.
@@ -568,7 +568,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
}
_, isClique := engine.(*clique.Clique)
if !isClique {
- if len(block.Extra()) != 0 {
+ if noExtra && len(block.Extra()) != 0 {
t.Error("Unexpected extra field")
}
if block.Coinbase() != coinbase {
@@ -637,7 +637,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
// This API should work even when the automatic sealing is not enabled
for _, c := range cases {
- resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false)
+ resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false, true)
block := <-resChan
err := <-errChan
if c.expectErr {
@@ -648,14 +648,14 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
if err != nil {
t.Errorf("Unexpected error %v", err)
}
- assertBlock(block, c.expectNumber, c.coinbase, c.random)
+ assertBlock(block, c.expectNumber, c.coinbase, c.random, true)
}
}
// This API should work even when the automatic sealing is enabled
w.start()
for _, c := range cases {
- resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false)
+ resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false, false)
block := <-resChan
err := <-errChan
if c.expectErr {
@@ -666,7 +666,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
if err != nil {
t.Errorf("Unexpected error %v", err)
}
- assertBlock(block, c.expectNumber, c.coinbase, c.random)
+ assertBlock(block, c.expectNumber, c.coinbase, c.random, false)
}
}
}
From 69168ab1c67e977d357ea2234bbc8357dc591ff2 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Mon, 25 Jul 2022 12:53:10 +0200
Subject: [PATCH 10/83] Run builder even if cannot connect to remote relay
(#12)
---
builder/relay.go | 7 +++++--
builder/relay_test.go | 5 ++---
builder/service.go | 5 +----
3 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/builder/relay.go b/builder/relay.go
index b476b68f6c..432c03bfd0 100644
--- a/builder/relay.go
+++ b/builder/relay.go
@@ -44,7 +44,7 @@ type RemoteRelay struct {
validatorSlotMap map[uint64]ValidatorData
}
-func NewRemoteRelay(endpoint string, localRelay *LocalRelay) (*RemoteRelay, error) {
+func NewRemoteRelay(endpoint string, localRelay *LocalRelay) *RemoteRelay {
r := &RemoteRelay{
endpoint: endpoint,
client: http.Client{Timeout: time.Second},
@@ -55,7 +55,10 @@ func NewRemoteRelay(endpoint string, localRelay *LocalRelay) (*RemoteRelay, erro
}
err := r.updateValidatorsMap(0, 3)
- return r, err
+ if err != nil {
+ log.Error("could not connect to remote relay, continuing anyway", "err", err)
+ }
+ return r
}
type GetValidatorRelayResponse []struct {
diff --git a/builder/relay_test.go b/builder/relay_test.go
index 93def4cd51..adc0f9aaba 100644
--- a/builder/relay_test.go
+++ b/builder/relay_test.go
@@ -45,8 +45,7 @@ func TestRemoteRelay(t *testing.T) {
}
srv := httptest.NewServer(r)
- relay, err := NewRemoteRelay(srv.URL, nil)
- require.NoError(t, err)
+ relay := NewRemoteRelay(srv.URL, nil)
vd, found := relay.validatorSlotMap[123]
require.True(t, found)
expectedValidator_123 := ValidatorData{
@@ -57,7 +56,7 @@ func TestRemoteRelay(t *testing.T) {
}
require.Equal(t, expectedValidator_123, vd)
- vd, err = relay.GetValidatorForSlot(123)
+ vd, err := relay.GetValidatorForSlot(123)
require.NoError(t, err)
require.Equal(t, expectedValidator_123, vd)
diff --git a/builder/service.go b/builder/service.go
index 340cb46c6a..d7da282ad2 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -114,10 +114,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
var relay IRelay
if cfg.RemoteRelayEndpoint != "" {
- relay, err = NewRemoteRelay(cfg.RemoteRelayEndpoint, localRelay)
- if err != nil {
- return err
- }
+ relay = NewRemoteRelay(cfg.RemoteRelayEndpoint, localRelay)
} else {
relay = localRelay
}
From 2cbfc714ca7353b588c1a4bb833ded2387a25494 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Fri, 29 Jul 2022 15:19:07 +0200
Subject: [PATCH 11/83] Add GasUsed and GasLimit to bid trace (#13)
---
builder/builder.go | 4 +++-
builder/builder_test.go | 28 +++++++++++++++++-----------
go.mod | 4 ++--
go.sum | 6 ++++++
4 files changed, 28 insertions(+), 14 deletions(-)
diff --git a/builder/builder.go b/builder/builder.go
index b74710387e..dc284b49d4 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -118,13 +118,15 @@ func (b *Builder) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Blo
return
}
- blockBidMsg := boostTypes.BidTraceMessage{
+ blockBidMsg := boostTypes.BidTrace{
Slot: payloadAttributes.Slot,
ParentHash: payload.ParentHash,
BlockHash: payload.BlockHash,
BuilderPubkey: b.builderPublicKey,
ProposerPubkey: pubkey,
ProposerFeeRecipient: boostTypes.Address(payloadAttributes.SuggestedFeeRecipient),
+ GasLimit: data.GasLimit,
+ GasUsed: data.GasUsed,
Value: *value,
}
diff --git a/builder/builder_test.go b/builder/builder_test.go
index 72add5814c..869340d13b 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -13,22 +13,23 @@ import (
"github.com/stretchr/testify/require"
)
-/*
-func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain) *Builder {
-func (b *Builder) onForkchoice(payloadAttributes *beacon.PayloadAttributesV1) {
-func (b *Builder) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Block, payloadAttributes *beacon.PayloadAttributesV1) {
-*/
-
func TestOnNewSealedBlock(t *testing.T) {
+ vsk, err := bls.SecretKeyFromBytes(hexutil.MustDecode("0x370bb8c1a6e62b2882f6ec76762a67b39609002076b95aae5b023997cf9b2dc9"))
+ require.NoError(t, err)
+ validator := &ValidatorPrivateData{
+ sk: vsk,
+ Pk: hexutil.MustDecode("0xb67d2c11bcab8c4394fc2faa9601d0b99c7f4b37e14911101da7d97077917862eed4563203d34b91b5cf0aa44d6cfa05"),
+ }
+
testBeacon := testBeaconClient{
- validator: NewRandomValidator(),
+ validator: validator,
slot: 56,
}
feeRecipient, _ := boostTypes.HexToAddress("0xabcf8e0d4e9587369b2301d0790347320302cc00")
testRelay := testRelay{
validator: ValidatorData{
- Pubkey: PubkeyHex(testBeacon.validator.Pk),
+ Pubkey: PubkeyHex(testBeacon.validator.Pk.String()),
FeeRecipient: feeRecipient,
GasLimit: 10,
Timestamp: 15,
@@ -76,13 +77,18 @@ func TestOnNewSealedBlock(t *testing.T) {
require.NotNil(t, testRelay.submittedMsg)
- expectedMessage := boostTypes.BidTraceMessage{
+ expectedProposerPubkey, err := boostTypes.HexToPubkey(testBeacon.validator.Pk.String())
+ require.NoError(t, err)
+
+ expectedMessage := boostTypes.BidTrace{
Slot: uint64(25),
ParentHash: boostTypes.Hash{0x02, 0x03},
BlockHash: boostTypes.Hash{0x09, 0xff},
BuilderPubkey: builder.builderPublicKey,
- ProposerPubkey: boostTypes.PublicKey{},
+ ProposerPubkey: expectedProposerPubkey,
ProposerFeeRecipient: boostTypes.Address{0x04, 0x10},
+ GasLimit: uint64(50),
+ GasUsed: uint64(100),
Value: boostTypes.U256Str{0x0a},
}
@@ -106,7 +112,7 @@ func TestOnNewSealedBlock(t *testing.T) {
}
require.Equal(t, expectedExecutionPayload, *testRelay.submittedMsg.ExecutionPayload)
- expectedSignature, err := boostTypes.HexToSignature("0xb79f75f81c834d104afbf1fb45f2cc19d5b0b4367184a43b88e696c88e6ab1a150be1fde9de5d1ca28bd955063164ae001c99d516c6ccd278c6bfb2af9c08805e39698a4a4e0713681a012921c1e9d8d14be95b49f654aba1fb493892a00795d")
+ expectedSignature, err := boostTypes.HexToSignature("0xadebce714127deea6b04c8f63e650ad6b4c0d3df14ecd9759bef741cd6d72509090f5e172033ce40475c322c0c0e3fae0e78a880a66cb324913ea490472d93e187a9a91284b05137f1554688c5e9b1ee73539a2b005b103e8bd50e973e8e0f49")
require.NoError(t, err)
require.Equal(t, expectedSignature, testRelay.submittedMsg.Signature)
diff --git a/go.mod b/go.mod
index dd0cdaa46c..97cca89c1c 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,7 @@ require (
github.com/fatih/color v1.9.0
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
- github.com/flashbots/go-boost-utils v0.3.1
+ github.com/flashbots/go-boost-utils v0.3.3-0.20220725102131-104d720dc46e
github.com/flashbots/go-utils v0.4.5
github.com/flashbots/mev-boost v0.7.3
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
@@ -88,7 +88,7 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
- github.com/ferranbt/fastssz v0.1.1 // indirect
+ github.com/ferranbt/fastssz v0.1.2-0.20220723134332-b3d3034a4575 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
diff --git a/go.sum b/go.sum
index 45094e42a5..1548c5cc92 100644
--- a/go.sum
+++ b/go.sum
@@ -137,12 +137,18 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/ferranbt/fastssz v0.1.1 h1:hBYNxKu51wjPC9sQYCjicmy5wtJqubENp3IiRVcdJBM=
github.com/ferranbt/fastssz v0.1.1/go.mod h1:U2ZsxlYyvGeQGmadhz8PlEqwkBzDIhHwd3xuKrg2JIs=
+github.com/ferranbt/fastssz v0.1.2-0.20220723134332-b3d3034a4575 h1:56lKKtcqQZ5sGjeuyBAeFwzcYuk32d8oqDvxQ9FUERA=
+github.com/ferranbt/fastssz v0.1.2-0.20220723134332-b3d3034a4575/go.mod h1:U2ZsxlYyvGeQGmadhz8PlEqwkBzDIhHwd3xuKrg2JIs=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq6Q+cGWcGHz0FjGR3QqcInWcW0=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/flashbots/go-boost-utils v0.3.1 h1:M1K4f97VnzW8v8hHdcVf14btZw343y9oSsfYHOgFl5A=
github.com/flashbots/go-boost-utils v0.3.1/go.mod h1:7pAZp9AHt06uHUY+Avob0H88IN2La22uPZUpS3seXR0=
+github.com/flashbots/go-boost-utils v0.3.2 h1:d7kudiDYngZy/7oORzSkXCZsNh8mEyr48Vm/IYIo1Wc=
+github.com/flashbots/go-boost-utils v0.3.2/go.mod h1:7pAZp9AHt06uHUY+Avob0H88IN2La22uPZUpS3seXR0=
+github.com/flashbots/go-boost-utils v0.3.3-0.20220725102131-104d720dc46e h1:6h6iyk4pT95hhTEbdMcpavN+FLHyrH9F80E9d/SVjO8=
+github.com/flashbots/go-boost-utils v0.3.3-0.20220725102131-104d720dc46e/go.mod h1:sqlzXlw4eKlY/URPHw1qEobsOoqOwsDrN272g2hUYgw=
github.com/flashbots/go-utils v0.4.5 h1:xTrVcfxQ+qpVVPyRBWUllwZAxbAijE06d9N7e7mmmlM=
github.com/flashbots/go-utils v0.4.5/go.mod h1:3YKyfbtetVIXuWKbZ9WmK8bSF20hSFXk0wCWDNHYFvE=
github.com/flashbots/mev-boost v0.7.3 h1:AwJQACv6/CJuMQe4oGNvIgW00oKFCzzDBrb4DlJsBKE=
From 1357e20b014bca977168cf86a77029ac7a269d5d Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Mon, 1 Aug 2022 10:37:39 +0200
Subject: [PATCH 12/83] Upgrade github.com/btcsuite/btcd (#14)
---
go.mod | 3 +--
go.sum | 59 ++--------------------------------------------------------
2 files changed, 3 insertions(+), 59 deletions(-)
diff --git a/go.mod b/go.mod
index 97cca89c1c..51895722c5 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.1.1
github.com/aws/aws-sdk-go-v2/credentials v1.1.1
github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1
- github.com/btcsuite/btcd/btcec/v2 v2.1.3
+ github.com/btcsuite/btcd/btcec/v2 v2.2.0
github.com/cespare/cp v1.1.1
github.com/cloudflare/cloudflare-go v0.14.0
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f
@@ -82,7 +82,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.1.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.1.1 // indirect
github.com/aws/smithy-go v1.1.0 // indirect
- github.com/btcsuite/btcd v0.22.0-beta // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
diff --git a/go.sum b/go.sum
index 1548c5cc92..0b421010ec 100644
--- a/go.sum
+++ b/go.sum
@@ -32,12 +32,10 @@ github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjl
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
-github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
-github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
@@ -63,21 +61,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
-github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
-github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo=
-github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
-github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE=
-github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
-github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
-github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
-github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
-github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
-github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
-github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
-github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
-github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
-github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
-github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
+github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
+github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU=
@@ -101,17 +86,14 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
-github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
-github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
-github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
@@ -121,7 +103,6 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
-github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/docker/docker v1.6.2 h1:HlFGsy+9/xrgMmhmN+NGhCc5SHGJ7I+kHosRR1xc/aI=
github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
@@ -135,18 +116,11 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
-github.com/ferranbt/fastssz v0.1.1 h1:hBYNxKu51wjPC9sQYCjicmy5wtJqubENp3IiRVcdJBM=
-github.com/ferranbt/fastssz v0.1.1/go.mod h1:U2ZsxlYyvGeQGmadhz8PlEqwkBzDIhHwd3xuKrg2JIs=
github.com/ferranbt/fastssz v0.1.2-0.20220723134332-b3d3034a4575 h1:56lKKtcqQZ5sGjeuyBAeFwzcYuk32d8oqDvxQ9FUERA=
github.com/ferranbt/fastssz v0.1.2-0.20220723134332-b3d3034a4575/go.mod h1:U2ZsxlYyvGeQGmadhz8PlEqwkBzDIhHwd3xuKrg2JIs=
-github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq6Q+cGWcGHz0FjGR3QqcInWcW0=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
-github.com/flashbots/go-boost-utils v0.3.1 h1:M1K4f97VnzW8v8hHdcVf14btZw343y9oSsfYHOgFl5A=
-github.com/flashbots/go-boost-utils v0.3.1/go.mod h1:7pAZp9AHt06uHUY+Avob0H88IN2La22uPZUpS3seXR0=
-github.com/flashbots/go-boost-utils v0.3.2 h1:d7kudiDYngZy/7oORzSkXCZsNh8mEyr48Vm/IYIo1Wc=
-github.com/flashbots/go-boost-utils v0.3.2/go.mod h1:7pAZp9AHt06uHUY+Avob0H88IN2La22uPZUpS3seXR0=
github.com/flashbots/go-boost-utils v0.3.3-0.20220725102131-104d720dc46e h1:6h6iyk4pT95hhTEbdMcpavN+FLHyrH9F80E9d/SVjO8=
github.com/flashbots/go-boost-utils v0.3.3-0.20220725102131-104d720dc46e/go.mod h1:sqlzXlw4eKlY/URPHw1qEobsOoqOwsDrN272g2hUYgw=
github.com/flashbots/go-utils v0.4.5 h1:xTrVcfxQ+qpVVPyRBWUllwZAxbAijE06d9N7e7mmmlM=
@@ -155,9 +129,7 @@ github.com/flashbots/mev-boost v0.7.3 h1:AwJQACv6/CJuMQe4oGNvIgW00oKFCzzDBrb4DlJ
github.com/flashbots/mev-boost v0.7.3/go.mod h1:DOqKZloyaZnfr6eGbnQb8XGH3gtjts19ATxI1LnfcNM=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc=
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
@@ -169,10 +141,8 @@ github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1T
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
@@ -223,7 +193,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -277,11 +246,8 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U=
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
-github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -296,7 +262,6 @@ github.com/karalabe/usb v0.0.2 h1:M6QQBNxF+CQ8OFvxrT90BA0qBOXymndZnk5q235mFc4=
github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -305,20 +270,15 @@ github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
-github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -359,20 +319,14 @@ github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hz
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
-github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
-github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -407,7 +361,6 @@ github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1
github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -468,15 +421,12 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
@@ -505,7 +455,6 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
@@ -514,7 +463,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
-golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -696,15 +644,12 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
From b06509e9ff72a850c3fdfccaad0e72ed1ca2891a Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Mon, 1 Aug 2022 10:39:52 +0200
Subject: [PATCH 13/83] Fix lint issues (#15)
---
builder/beacon_client_test.go | 6 +++---
builder/service.go | 9 +++++++++
builder/validator.go | 2 +-
miner/stress/beacon/main.go | 2 +-
4 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/builder/beacon_client_test.go b/builder/beacon_client_test.go
index 152e891c89..564275e5ad 100644
--- a/builder/beacon_client_test.go
+++ b/builder/beacon_client_test.go
@@ -212,10 +212,10 @@ func TestOnForkchoiceUpdate(t *testing.T) {
require.NoError(t, err)
require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
- pubkeyHex, err = bc.getProposerForNextSlot(31)
+ _, err = bc.getProposerForNextSlot(31)
require.EqualError(t, err, "slot out of sync")
- pubkeyHex, err = bc.getProposerForNextSlot(33)
+ _, err = bc.getProposerForNextSlot(33)
require.EqualError(t, err, "slot out of sync")
mbn.headersCode = 404
@@ -263,6 +263,6 @@ func TestOnForkchoiceUpdate(t *testing.T) {
// Check proposers map error is routed out
mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "65", "proposer_index": "1" } } } ] }`)
- slot, err = bc.onForkchoiceUpdate()
+ _, err = bc.onForkchoiceUpdate()
require.EqualError(t, err, "inconsistent proposer mapping")
}
diff --git a/builder/service.go b/builder/service.go
index d7da282ad2..f934d5e31c 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -2,6 +2,7 @@ package builder
import (
"errors"
+ "fmt"
"net/http"
"github.com/ethereum/go-ethereum/common"
@@ -97,12 +98,20 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
}
genesisForkVersionBytes, err := hexutil.Decode(cfg.GenesisForkVersion)
+ if err != nil {
+ return fmt.Errorf("invalid genesisForkVersion: %w", err)
+ }
+
var genesisForkVersion [4]byte
copy(genesisForkVersion[:], genesisForkVersionBytes[:4])
builderSigningDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeAppBuilder, genesisForkVersion, boostTypes.Root{})
genesisValidatorsRoot := boostTypes.Root(common.HexToHash(cfg.GenesisValidatorsRoot))
bellatrixForkVersionBytes, err := hexutil.Decode(cfg.BellatrixForkVersion)
+ if err != nil {
+ return fmt.Errorf("invalid bellatrixForkVersion: %w", err)
+ }
+
var bellatrixForkVersion [4]byte
copy(bellatrixForkVersion[:], bellatrixForkVersionBytes[:4])
proposerSigningDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeBeaconProposer, bellatrixForkVersion, genesisValidatorsRoot)
diff --git a/builder/validator.go b/builder/validator.go
index b1554a4f95..2a27d44b92 100644
--- a/builder/validator.go
+++ b/builder/validator.go
@@ -45,5 +45,5 @@ func (v *ValidatorPrivateData) PrepareRegistrationMessage(feeRecipientHex string
if err != nil {
return boostTypes.SignedValidatorRegistration{}, err
}
- return boostTypes.SignedValidatorRegistration{msg, signature}, nil
+ return boostTypes.SignedValidatorRegistration{Message: msg, Signature: signature}, nil
}
diff --git a/miner/stress/beacon/main.go b/miner/stress/beacon/main.go
index b30365052d..29e84b0c6f 100644
--- a/miner/stress/beacon/main.go
+++ b/miner/stress/beacon/main.go
@@ -164,7 +164,7 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64)
return nil, nil, err
}
data, err := n.api.GetPayloadV1(*payload.PayloadID)
- return data, nil, nil
+ return data, nil, err
}
func (n *ethNode) insertBlock(eb beacon.ExecutableDataV1) error {
From 74375b147446008902cac4b8582f763499ee0257 Mon Sep 17 00:00:00 2001
From: nick-staked <70966133+nick-staked@users.noreply.github.com>
Date: Tue, 2 Aug 2022 04:12:36 -0400
Subject: [PATCH 14/83] Update README.md (#16)
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ebc21e8d0b..99d8fbcf64 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ BUILDER API OPTIONS:
--builder.secret_key value Builder key used for signing blocks (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_SECRET_KEY]
--builder.relay_secret_key value Builder local relay API key used for signing headers (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_RELAY_SECRET_KEY]
--builder.listen_addr value Listening address for builder endpoint (default: ":28545") [$BUILDER_LISTEN_ADDR]
- --builder.genesis_fork_version value Gensis fork version. For kiln use 0x70000069 (default: "0x00000000") [$BUILDER_GENESIS_FORK_VERSION]
+ --builder.genesis_fork_version value Genesis fork version. For kiln use 0x70000069 (default: "0x00000000") [$BUILDER_GENESIS_FORK_VERSION]
--builder.bellatrix_fork_version value Bellatrix fork version. For kiln use 0x70000071 (default: "0x02000000") [$BUILDER_BELLATRIX_FORK_VERSION]
--builder.genesis_validators_root value Genesis validators root of the network. For kiln use 0x99b09fcd43e5905236c370f184056bec6e6638cfc31a323b304fc4aa789cb4ad (default: "0x0000000000000000000000000000000000000000000000000000000000000000") [$BUILDER_GENESIS_VALIDATORS_ROOT]
--builder.beacon_endpoint value Beacon endpoint to connect to for beacon chain data (default: "http://127.0.0.1:5052") [$BUILDER_BEACON_ENDPOINT]
From 32a72062510819eca8c2db4fa6200c2e8dc98739 Mon Sep 17 00:00:00 2001
From: shana
Date: Thu, 11 Aug 2022 19:13:01 +1000
Subject: [PATCH 15/83] Add block build trigger from beacon node api (#17)
* Add block build trigger from beacon node
* remove empty block building
Co-authored-by: avalonche
---
builder/builder.go | 163 ++++++++++++++++--------------------
builder/builder_test.go | 24 +++---
builder/eth_service.go | 60 +++++++++++++
builder/eth_service_test.go | 99 ++++++++++++++++++++++
builder/local_relay_test.go | 19 +++--
builder/service.go | 48 ++++++++---
eth/backend.go | 25 ------
eth/catalyst/api.go | 13 +--
eth/catalyst/queue.go | 12 +++
node/defaults.go | 2 +-
node/endpoints.go | 2 +-
rpc/server.go | 1 +
12 files changed, 309 insertions(+), 159 deletions(-)
create mode 100644 builder/eth_service.go
create mode 100644 builder/eth_service_test.go
diff --git a/builder/builder.go b/builder/builder.go
index dc284b49d4..7051da418c 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -1,7 +1,6 @@
package builder
import (
- "encoding/json"
_ "os"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -33,28 +32,29 @@ type IRelay interface {
GetValidatorForSlot(nextSlot uint64) (ValidatorData, error)
}
+type IBuilder interface {
+ OnPayloadAttribute(attrs *BuilderPayloadAttributes) error
+}
+
type Builder struct {
beaconClient IBeaconClient
relay IRelay
+ eth IEthereumService
builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
builderSigningDomain boostTypes.Domain
}
-func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain) *Builder {
+func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)
- _, err := bc.onForkchoiceUpdate()
- if err != nil {
- log.Error("could not initialize beacon client", "err", err)
- }
-
return &Builder{
beaconClient: bc,
relay: relay,
+ eth: eth,
builderSecretKey: sk,
builderPublicKey: pk,
@@ -62,91 +62,76 @@ func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSignin
}
}
-func (b *Builder) onForkchoice(payloadAttributes *beacon.PayloadAttributesV1) {
- dataJson, err := json.Marshal(payloadAttributes)
- if err == nil {
- log.Info("FCU", "data", string(dataJson))
- } else {
- log.Info("FCU", "data", payloadAttributes, "parsingError", err)
-
- }
-
- nextSlot, err := b.beaconClient.onForkchoiceUpdate()
- if err != nil {
- log.Error("FCU hook failed", "err", err)
- return
- }
-
- if payloadAttributes != nil {
- payloadAttributes.Slot = nextSlot
- if vd, err := b.relay.GetValidatorForSlot(nextSlot); err == nil {
- payloadAttributes.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
- payloadAttributes.GasLimit = vd.GasLimit
+func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
+ if attrs != nil {
+ vd, err := b.relay.GetValidatorForSlot(attrs.Slot)
+ if err != nil {
+ log.Info("could not get validator while submitting block", "err", err, "slot", attrs.Slot)
+ return err
}
- }
-}
-func (b *Builder) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Block, payloadAttributes *beacon.PayloadAttributesV1) {
- dataJson, err := json.Marshal(data)
- if err == nil {
- log.Info("newSealedBlock", "data", string(dataJson))
- } else {
- log.Info("newSealedBlock", "data", data, "parsingError", err)
- }
- payload, err := executableDataToExecutionPayload(data)
- if err != nil {
- log.Error("could not format execution payload", "err", err)
- return
- }
-
- vd, err := b.relay.GetValidatorForSlot(payloadAttributes.Slot)
- if err != nil {
- log.Error("could not get validator while submitting block", "err", err, "slot", payloadAttributes.Slot)
- return
- }
-
- pubkey, err := boostTypes.HexToPubkey(string(vd.Pubkey))
- if err != nil {
- log.Error("could not parse pubkey", "err", err, "pubkey", vd.Pubkey)
- return
- }
-
- value := new(boostTypes.U256Str)
- err = value.FromBig(block.Profit)
- if err != nil {
- log.Error("could not set block value", "err", err)
- return
- }
-
- blockBidMsg := boostTypes.BidTrace{
- Slot: payloadAttributes.Slot,
- ParentHash: payload.ParentHash,
- BlockHash: payload.BlockHash,
- BuilderPubkey: b.builderPublicKey,
- ProposerPubkey: pubkey,
- ProposerFeeRecipient: boostTypes.Address(payloadAttributes.SuggestedFeeRecipient),
- GasLimit: data.GasLimit,
- GasUsed: data.GasUsed,
- Value: *value,
- }
-
- signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
- if err != nil {
- log.Error("could not sign builder bid", "err", err)
- return
- }
-
- blockSubmitReq := boostTypes.BuilderSubmitBlockRequest{
- Signature: signature,
- Message: &blockBidMsg,
- ExecutionPayload: payload,
- }
-
- err = b.relay.SubmitBlock(&blockSubmitReq)
- if err != nil {
- log.Error("could not submit block", "err", err)
- return
+ attrs.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
+ attrs.GasLimit = vd.GasLimit
+
+ if b.eth.Synced() {
+ block := b.eth.GetBlockByHash(attrs.HeadHash)
+ if block == nil {
+ log.Info("Block hash not found in blocktree", "head block hash", attrs.HeadHash)
+ return err
+ }
+
+ executableData := b.eth.BuildBlock(attrs)
+ payload, err := executableDataToExecutionPayload(executableData)
+ if err != nil {
+ log.Error("could not format execution payload", "err", err)
+ return err
+ }
+
+ pubkey, err := boostTypes.HexToPubkey(string(vd.Pubkey))
+ if err != nil {
+ log.Error("could not parse pubkey", "err", err, "pubkey", vd.Pubkey)
+ return err
+ }
+
+ value := new(boostTypes.U256Str)
+ err = value.FromBig(block.Profit)
+ if err != nil {
+ log.Error("could not set block value", "err", err)
+ return err
+ }
+
+ blockBidMsg := boostTypes.BidTrace{
+ Slot: attrs.Slot,
+ ParentHash: payload.ParentHash,
+ BlockHash: payload.BlockHash,
+ BuilderPubkey: b.builderPublicKey,
+ ProposerPubkey: pubkey,
+ ProposerFeeRecipient: boostTypes.Address(attrs.SuggestedFeeRecipient),
+ GasLimit: executableData.GasLimit,
+ GasUsed: executableData.GasUsed,
+ Value: *value,
+ }
+
+ signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
+ if err != nil {
+ log.Error("could not sign builder bid", "err", err)
+ return err
+ }
+
+ blockSubmitReq := boostTypes.BuilderSubmitBlockRequest{
+ Signature: signature,
+ Message: &blockBidMsg,
+ ExecutionPayload: payload,
+ }
+
+ err = b.relay.SubmitBlock(&blockSubmitReq)
+ if err != nil {
+ log.Error("could not submit block", "err", err)
+ return err
+ }
+ }
}
+ return nil
}
func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayload, error) {
diff --git a/builder/builder_test.go b/builder/builder_test.go
index 869340d13b..f3df52a1e4 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/require"
)
-func TestOnNewSealedBlock(t *testing.T) {
+func TestOnPayloadAttributes(t *testing.T) {
vsk, err := bls.SecretKeyFromBytes(hexutil.MustDecode("0x370bb8c1a6e62b2882f6ec76762a67b39609002076b95aae5b023997cf9b2dc9"))
require.NoError(t, err)
validator := &ValidatorPrivateData{
@@ -41,11 +41,9 @@ func TestOnNewSealedBlock(t *testing.T) {
bDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeAppBuilder, [4]byte{0x02, 0x0, 0x0, 0x0}, boostTypes.Hash{})
- builder := NewBuilder(sk, &testBeacon, &testRelay, bDomain)
-
testExecutableData := &beacon.ExecutableDataV1{
ParentHash: common.Hash{0x02, 0x03},
- FeeRecipient: common.Address{0x06, 0x15},
+ FeeRecipient: common.Address(feeRecipient),
StateRoot: common.Hash{0x07, 0x16},
ReceiptsRoot: common.Hash{0x08, 0x20},
LogsBloom: hexutil.MustDecode("0x000000000000000000000000000000"),
@@ -65,18 +63,21 @@ func TestOnNewSealedBlock(t *testing.T) {
Profit: big.NewInt(10),
}
- testPayloadAttributes := &beacon.PayloadAttributesV1{
- Timestamp: uint64(104),
+ testPayloadAttributes := &BuilderPayloadAttributes{
+ Timestamp: hexutil.Uint64(104),
Random: common.Hash{0x05, 0x10},
SuggestedFeeRecipient: common.Address{0x04, 0x10},
GasLimit: uint64(21),
Slot: uint64(25),
}
- builder.newSealedBlock(testExecutableData, testBlock, testPayloadAttributes)
+ testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}
- require.NotNil(t, testRelay.submittedMsg)
+ builder := NewBuilder(sk, &testBeacon, &testRelay, bDomain, testEthService)
+
+ builder.OnPayloadAttribute(testPayloadAttributes)
+ require.NotNil(t, testRelay.submittedMsg)
expectedProposerPubkey, err := boostTypes.HexToPubkey(testBeacon.validator.Pk.String())
require.NoError(t, err)
@@ -86,7 +87,7 @@ func TestOnNewSealedBlock(t *testing.T) {
BlockHash: boostTypes.Hash{0x09, 0xff},
BuilderPubkey: builder.builderPublicKey,
ProposerPubkey: expectedProposerPubkey,
- ProposerFeeRecipient: boostTypes.Address{0x04, 0x10},
+ ProposerFeeRecipient: feeRecipient,
GasLimit: uint64(50),
GasUsed: uint64(100),
Value: boostTypes.U256Str{0x0a},
@@ -96,7 +97,7 @@ func TestOnNewSealedBlock(t *testing.T) {
expectedExecutionPayload := boostTypes.ExecutionPayload{
ParentHash: [32]byte(testExecutableData.ParentHash),
- FeeRecipient: boostTypes.Address{0x6, 0x15},
+ FeeRecipient: feeRecipient,
StateRoot: [32]byte(testExecutableData.StateRoot),
ReceiptsRoot: [32]byte(testExecutableData.ReceiptsRoot),
LogsBloom: [256]byte{},
@@ -110,9 +111,10 @@ func TestOnNewSealedBlock(t *testing.T) {
BlockHash: boostTypes.Hash{0x09, 0xff},
Transactions: []hexutil.Bytes{},
}
+
require.Equal(t, expectedExecutionPayload, *testRelay.submittedMsg.ExecutionPayload)
- expectedSignature, err := boostTypes.HexToSignature("0xadebce714127deea6b04c8f63e650ad6b4c0d3df14ecd9759bef741cd6d72509090f5e172033ce40475c322c0c0e3fae0e78a880a66cb324913ea490472d93e187a9a91284b05137f1554688c5e9b1ee73539a2b005b103e8bd50e973e8e0f49")
+ expectedSignature, err := boostTypes.HexToSignature("0xb086abc231a515559128122a6618ad316a76195ad39aa28195c9e8921b98561ca4fd12e2e1ea8d50d8e22f7e36d42ee1084fef26672beceda7650a87061e412d7742705077ac3af3ca1a1c3494eccb22fe7c234fd547a285ba699ff87f0e7759")
require.NoError(t, err)
require.Equal(t, expectedSignature, testRelay.submittedMsg.Signature)
diff --git a/builder/eth_service.go b/builder/eth_service.go
new file mode 100644
index 0000000000..4986e0d442
--- /dev/null
+++ b/builder/eth_service.go
@@ -0,0 +1,60 @@
+package builder
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/beacon"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/eth/catalyst"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+type IEthereumService interface {
+ BuildBlock(attrs *BuilderPayloadAttributes) *beacon.ExecutableDataV1
+ GetBlockByHash(hash common.Hash) *types.Block
+ Synced() bool
+}
+
+type testEthereumService struct {
+ synced bool
+ testExecutableData *beacon.ExecutableDataV1
+ testBlock *types.Block
+}
+
+func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes) *beacon.ExecutableDataV1 {
+ return t.testExecutableData
+}
+
+func (t *testEthereumService) GetBlockByHash(hash common.Hash) *types.Block { return t.testBlock }
+
+func (t *testEthereumService) Synced() bool { return t.synced }
+
+type EthereumService struct {
+ eth *eth.Ethereum
+}
+
+func NewEthereumService(eth *eth.Ethereum) *EthereumService {
+ return &EthereumService{eth: eth}
+}
+
+func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) *beacon.ExecutableDataV1 {
+ // Send a request to generate a full block in the background.
+ // The result can be obtained via the returned channel.
+ resCh, err := s.eth.Miner().GetSealingBlockAsync(attrs.HeadHash, uint64(attrs.Timestamp), attrs.SuggestedFeeRecipient, attrs.GasLimit, attrs.Random, false)
+ if err != nil {
+ log.Error("Failed to create async sealing payload", "err", err)
+ return nil
+ }
+
+ resultPayload := catalyst.NewPayload(resCh)
+ executableData, _ := resultPayload.Resolve()
+ return executableData
+}
+
+func (s *EthereumService) GetBlockByHash(hash common.Hash) *types.Block {
+ return s.eth.BlockChain().GetBlockByHash(hash)
+}
+
+func (s *EthereumService) Synced() bool {
+ return s.eth.Synced()
+}
diff --git a/builder/eth_service_test.go b/builder/eth_service_test.go
new file mode 100644
index 0000000000..a6b6bd32f0
--- /dev/null
+++ b/builder/eth_service_test.go
@@ -0,0 +1,99 @@
+package builder
+
+import (
+ "math/big"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/stretchr/testify/require"
+)
+
+func generatePreMergeChain(n int) (*core.Genesis, []*types.Block) {
+ db := rawdb.NewMemoryDatabase()
+ config := params.AllEthashProtocolChanges
+ genesis := &core.Genesis{
+ Config: config,
+ Alloc: core.GenesisAlloc{},
+ ExtraData: []byte("test genesis"),
+ Timestamp: 9000,
+ BaseFee: big.NewInt(params.InitialBaseFee),
+ Difficulty: big.NewInt(0),
+ }
+ gblock := genesis.ToBlock()
+ engine := ethash.NewFaker()
+ blocks, _ := core.GenerateChain(config, gblock, engine, db, n, nil)
+ totalDifficulty := big.NewInt(0)
+ for _, b := range blocks {
+ totalDifficulty.Add(totalDifficulty, b.Difficulty())
+ }
+ config.TerminalTotalDifficulty = totalDifficulty
+ return genesis, blocks
+}
+
+// startEthService creates a full node instance for testing.
+func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
+ t.Helper()
+
+ n, err := node.New(&node.Config{
+ P2P: p2p.Config{
+ ListenAddr: "0.0.0.0:0",
+ NoDiscovery: true,
+ MaxPeers: 25,
+ }})
+ if err != nil {
+ t.Fatal("can't create node:", err)
+ }
+
+ ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}, SyncMode: downloader.SnapSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
+ ethservice, err := eth.New(n, ethcfg)
+ if err != nil {
+ t.Fatal("can't create eth service:", err)
+ }
+ if err := n.Start(); err != nil {
+ t.Fatal("can't start node:", err)
+ }
+ if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil {
+ n.Close()
+ t.Fatal("can't import test blocks:", err)
+ }
+ time.Sleep(500 * time.Millisecond) // give txpool enough time to consume head event
+
+ ethservice.SetSynced()
+ return n, ethservice
+}
+
+func TestBuildBlock(t *testing.T) {
+ genesis, blocks := generatePreMergeChain(10)
+ n, ethservice := startEthService(t, genesis, blocks)
+ defer n.Close()
+
+ parent := ethservice.BlockChain().CurrentBlock()
+
+ testPayloadAttributes := &BuilderPayloadAttributes{
+ Timestamp: hexutil.Uint64(parent.Time() + 1),
+ Random: common.Hash{0x05, 0x10},
+ SuggestedFeeRecipient: common.Address{0x04, 0x10},
+ GasLimit: uint64(4800000),
+ Slot: uint64(25),
+ }
+
+ service := NewEthereumService(ethservice)
+ executableData := service.BuildBlock(testPayloadAttributes)
+
+ require.Equal(t, common.Address{0x04, 0x10}, executableData.FeeRecipient)
+ require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random)
+ require.Equal(t, parent.Hash(), executableData.ParentHash)
+ require.Equal(t, parent.Time()+1, executableData.Timestamp)
+}
diff --git a/builder/local_relay_test.go b/builder/local_relay_test.go
index a1a8dad5cc..3e706f5bfe 100644
--- a/builder/local_relay_test.go
+++ b/builder/local_relay_test.go
@@ -20,7 +20,7 @@ import (
"github.com/stretchr/testify/require"
)
-func newTestBackend(t *testing.T) (*Builder, *LocalRelay, *ValidatorPrivateData) {
+func newTestBackend(t *testing.T, forkchoiceData *beacon.ExecutableDataV1, block *types.Block) (*Builder, *LocalRelay, *ValidatorPrivateData) {
validator := NewRandomValidator()
sk, _ := bls.GenerateRandomSecretKey()
bDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeAppBuilder, [4]byte{0x02, 0x0, 0x0, 0x0}, boostTypes.Hash{})
@@ -28,7 +28,8 @@ func newTestBackend(t *testing.T) (*Builder, *LocalRelay, *ValidatorPrivateData)
cDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeBeaconProposer, [4]byte{0x02, 0x0, 0x0, 0x0}, genesisValidatorsRoot)
beaconClient := &testBeaconClient{validator: validator}
localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true)
- backend := NewBuilder(sk, beaconClient, localRelay, bDomain)
+ ethService := &testEthereumService{synced: true, testExecutableData: forkchoiceData, testBlock: block}
+ backend := NewBuilder(sk, beaconClient, localRelay, bDomain, ethService)
// service := NewService("127.0.0.1:31545", backend)
return backend, localRelay, validator
@@ -53,7 +54,7 @@ func testRequest(t *testing.T, localRelay *LocalRelay, method string, path strin
}
func TestValidatorRegistration(t *testing.T) {
- _, relay, _ := newTestBackend(t)
+ _, relay, _ := newTestBackend(t, nil, nil)
log.Error("rsk", "sk", hexutil.Encode(relay.relaySecretKey.Serialize()))
v := NewRandomValidator()
@@ -111,8 +112,6 @@ func registerValidator(t *testing.T, v *ValidatorPrivateData, relay *LocalRelay)
}
func TestGetHeader(t *testing.T) {
- backend, relay, validator := newTestBackend(t)
-
forkchoiceData := &beacon.ExecutableDataV1{
ParentHash: common.HexToHash("0xafafafa"),
FeeRecipient: common.Address{0x01},
@@ -125,6 +124,8 @@ func TestGetHeader(t *testing.T) {
Profit: big.NewInt(10),
}
+ backend, relay, validator := newTestBackend(t, forkchoiceData, forkchoiceBlock)
+
path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
rr := testRequest(t, relay, "GET", path, nil)
require.Equal(t, `{"code":400,"message":"unknown validator"}`+"\n", rr.Body.String())
@@ -139,7 +140,7 @@ func TestGetHeader(t *testing.T) {
require.Equal(t, ``, rr.Body.String())
require.Equal(t, 204, rr.Code)
- backend.newSealedBlock(forkchoiceData, forkchoiceBlock, &beacon.PayloadAttributesV1{})
+ backend.OnPayloadAttribute(&BuilderPayloadAttributes{})
path = fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
rr = testRequest(t, relay, "GET", path, nil)
@@ -170,8 +171,6 @@ func TestGetHeader(t *testing.T) {
}
func TestGetPayload(t *testing.T) {
- backend, relay, validator := newTestBackend(t)
-
forkchoiceData := &beacon.ExecutableDataV1{
ParentHash: common.HexToHash("0xafafafa"),
FeeRecipient: common.Address{0x01},
@@ -183,8 +182,10 @@ func TestGetPayload(t *testing.T) {
Profit: big.NewInt(10),
}
+ backend, relay, validator := newTestBackend(t, forkchoiceData, forkchoiceBlock)
+
registerValidator(t, validator, relay)
- backend.newSealedBlock(forkchoiceData, forkchoiceBlock, &beacon.PayloadAttributesV1{})
+ backend.OnPayloadAttribute(&BuilderPayloadAttributes{})
path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
rr := testRequest(t, relay, "GET", path, nil)
diff --git a/builder/service.go b/builder/service.go
index f934d5e31c..f33b2d4e45 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/mux"
"github.com/flashbots/go-boost-utils/bls"
@@ -25,8 +26,18 @@ const (
_PathGetPayload = "/eth/v1/builder/blinded_blocks"
)
+type BuilderPayloadAttributes struct {
+ Timestamp hexutil.Uint64 `json:"timestamp"`
+ Random common.Hash `json:"prevRandao"`
+ SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient,omitempty"`
+ Slot uint64 `json:"slot"`
+ HeadHash common.Hash `json:"blockHash"`
+ GasLimit uint64
+}
+
type Service struct {
- srv *http.Server
+ srv *http.Server
+ builder IBuilder
}
func (s *Service) Start() {
@@ -34,6 +45,10 @@ func (s *Service) Start() {
go s.srv.ListenAndServe()
}
+func (s *Service) PayloadAttributes(payloadAttributes *BuilderPayloadAttributes) error {
+ return s.builder.OnPayloadAttribute(payloadAttributes)
+}
+
func getRouter(localRelay *LocalRelay) http.Handler {
router := mux.NewRouter()
@@ -49,18 +64,19 @@ func getRouter(localRelay *LocalRelay) http.Handler {
return loggedRouter
}
-func NewService(listenAddr string, localRelay *LocalRelay) *Service {
+func NewService(listenAddr string, localRelay *LocalRelay, builder *Builder) *Service {
return &Service{
srv: &http.Server{
Addr: listenAddr,
Handler: getRouter(localRelay),
/*
- ReadTimeout:
- ReadHeaderTimeout:
- WriteTimeout:
- IdleTimeout:
+ ReadTimeout:
+ ReadHeaderTimeout:
+ WriteTimeout:
+ IdleTimeout:
*/
},
+ builder: builder,
}
}
@@ -116,8 +132,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
copy(bellatrixForkVersion[:], bellatrixForkVersionBytes[:4])
proposerSigningDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeBeaconProposer, bellatrixForkVersion, genesisValidatorsRoot)
- var beaconClient IBeaconClient
- beaconClient = NewBeaconClient(cfg.BeaconEndpoint)
+ beaconClient := NewBeaconClient(cfg.BeaconEndpoint)
localRelay := NewLocalRelay(relaySk, beaconClient, builderSigningDomain, proposerSigningDomain, ForkData{cfg.GenesisForkVersion, cfg.BellatrixForkVersion, cfg.GenesisValidatorsRoot}, cfg.EnableValidatorChecks)
@@ -128,11 +143,20 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
relay = localRelay
}
- builderBackend := NewBuilder(builderSk, beaconClient, relay, builderSigningDomain)
- builderService := NewService(cfg.ListenAddr, localRelay)
+ ethereumService := NewEthereumService(backend)
+
+ builderBackend := NewBuilder(builderSk, beaconClient, relay, builderSigningDomain, ethereumService)
+ builderService := NewService(cfg.ListenAddr, localRelay, builderBackend)
builderService.Start()
- backend.SetSealedBlockHook(builderBackend.newSealedBlock)
- backend.SetForkchoiceHook(builderBackend.onForkchoice)
+ stack.RegisterAPIs([]rpc.API{
+ {
+ Namespace: "builder",
+ Version: "1.0",
+ Service: builderService,
+ Public: true,
+ Authenticated: true,
+ },
+ })
return nil
}
diff --git a/eth/backend.go b/eth/backend.go
index 43a4994389..7782076363 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -33,7 +33,6 @@ import (
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/core"
- beaconTypes "github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/pruner"
@@ -86,9 +85,6 @@ type Ethereum struct {
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
closeBloomHandler chan struct{}
- newSealedBlockHook func(*beaconTypes.ExecutableDataV1, *types.Block, *beaconTypes.PayloadAttributesV1)
- forkchoiceHook func(*beaconTypes.PayloadAttributesV1)
-
APIBackend *EthAPIBackend
miner *miner.Miner
@@ -357,27 +353,6 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) {
return common.Address{}, fmt.Errorf("etherbase must be explicitly specified")
}
-func (s *Ethereum) SetSealedBlockHook(newSealedBlockHook func(*beaconTypes.ExecutableDataV1, *types.Block, *beaconTypes.PayloadAttributesV1)) {
- s.newSealedBlockHook = newSealedBlockHook
-}
-
-func (s *Ethereum) NewSealedBlock(data *beaconTypes.ExecutableDataV1, block *types.Block, payloadAttributes *beaconTypes.PayloadAttributesV1) {
- if s.newSealedBlockHook != nil {
- s.newSealedBlockHook(data, block, payloadAttributes)
- }
-}
-
-func (s *Ethereum) SetForkchoiceHook(forkchoiceHook func(*beaconTypes.PayloadAttributesV1)) {
- s.forkchoiceHook = forkchoiceHook
-}
-
-func (s *Ethereum) ForkchoiceHook(payloadAttributes *beaconTypes.PayloadAttributesV1) {
- // Possibly modifies payloadAttributes's fee recipient
- if s.forkchoiceHook != nil {
- s.forkchoiceHook(payloadAttributes)
- }
-}
-
// isLocalBlock checks whether the specified block is mined
// by local miner accounts.
//
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index 95171459c8..6305b77cbc 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -155,11 +155,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock()
- log.Info("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
-
- // Adjusts payload attributes to next slot's validator preferences
- api.eth.ForkchoiceHook(payloadAttributes)
-
+ log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
if update.HeadBlockHash == (common.Hash{}) {
log.Warn("Forkchoice requested update to zero hash")
return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
@@ -294,12 +290,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
return valid(nil), beacon.InvalidPayloadAttributes.With(err)
}
id := computePayloadId(update.HeadBlockHash, payloadAttributes)
- resultPayload := &payload{empty: empty, result: resCh}
- api.localBlocks.put(id, resultPayload)
- go func() {
- executableData, block := resultPayload.resolve()
- api.eth.NewSealedBlock(executableData, block, payloadAttributes)
- }()
+ api.localBlocks.put(id, &payload{empty: empty, result: resCh})
return valid(&id), nil
}
return valid(nil), nil
diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go
index 2995ff4860..9a1bf41662 100644
--- a/eth/catalyst/queue.go
+++ b/eth/catalyst/queue.go
@@ -35,6 +35,18 @@ const maxTrackedPayloads = 10
// latest one; but have a slight wiggle room for non-ideal conditions.
const maxTrackedHeaders = 10
+type Payload struct {
+ payload
+}
+
+func NewPayload(result chan *types.Block) *Payload {
+ return &Payload{payload{result: result}}
+}
+
+func (p *Payload) Resolve() (*beacon.ExecutableDataV1, *types.Block) {
+ return p.payload.resolve()
+}
+
// payload wraps the miner's block production channel, allowing the mined block
// to be retrieved later upon the GetPayload engine API call.
type payload struct {
diff --git a/node/defaults.go b/node/defaults.go
index fd0277e29d..0b254fcb3c 100644
--- a/node/defaults.go
+++ b/node/defaults.go
@@ -43,7 +43,7 @@ var (
DefaultAuthVhosts = []string{"localhost"} // Default virtual hosts for the authenticated apis
DefaultAuthOrigins = []string{"localhost"} // Default origins for the authenticated apis
DefaultAuthPrefix = "" // Default prefix for the authenticated apis
- DefaultAuthModules = []string{"eth", "engine"}
+ DefaultAuthModules = []string{"eth", "engine", "builder"}
)
// DefaultConfig contains reasonable default settings.
diff --git a/node/endpoints.go b/node/endpoints.go
index 14c12fd1f1..0f33d4be06 100644
--- a/node/endpoints.go
+++ b/node/endpoints.go
@@ -62,7 +62,7 @@ func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available [
}
for _, name := range modules {
if _, ok := availableSet[name]; !ok {
- if name != rpc.MetadataApi && name != rpc.EngineApi {
+ if name != rpc.MetadataApi && name != rpc.EngineApi && name != rpc.BuilderApi {
bad = append(bad, name)
}
}
diff --git a/rpc/server.go b/rpc/server.go
index bf1f71a28e..4f37dca63d 100644
--- a/rpc/server.go
+++ b/rpc/server.go
@@ -27,6 +27,7 @@ import (
const MetadataApi = "rpc"
const EngineApi = "engine"
+const BuilderApi = "builder"
// CodecOption specifies which type of messages a codec supports.
//
From 7a6d33d2e5ba60462d2481741893bff76ead0896 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 11 Aug 2022 11:18:31 +0200
Subject: [PATCH 16/83] Addjust profit calculation and use the blocks channel
directly
---
builder/builder.go | 11 ++++++++---
builder/eth_service.go | 30 +++++++++++++++++++++---------
builder/eth_service_test.go | 5 ++++-
cmd/geth/consolecmd_test.go | 2 +-
eth/catalyst/queue.go | 12 ------------
5 files changed, 34 insertions(+), 26 deletions(-)
diff --git a/builder/builder.go b/builder/builder.go
index 7051da418c..c99d7045c1 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -1,6 +1,7 @@
package builder
import (
+ "errors"
_ "os"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -74,13 +75,17 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
attrs.GasLimit = vd.GasLimit
if b.eth.Synced() {
- block := b.eth.GetBlockByHash(attrs.HeadHash)
- if block == nil {
+ parentBlock := b.eth.GetBlockByHash(attrs.HeadHash)
+ if parentBlock == nil {
log.Info("Block hash not found in blocktree", "head block hash", attrs.HeadHash)
return err
}
- executableData := b.eth.BuildBlock(attrs)
+ executableData, block := b.eth.BuildBlock(attrs)
+ if executableData == nil || block == nil {
+ log.Error("did not receive the payload")
+ return errors.New("could not build block")
+ }
payload, err := executableDataToExecutionPayload(executableData)
if err != nil {
log.Error("could not format execution payload", "err", err)
diff --git a/builder/eth_service.go b/builder/eth_service.go
index 4986e0d442..6ea8b7d3ca 100644
--- a/builder/eth_service.go
+++ b/builder/eth_service.go
@@ -1,16 +1,17 @@
package builder
import (
+ "time"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/log"
)
type IEthereumService interface {
- BuildBlock(attrs *BuilderPayloadAttributes) *beacon.ExecutableDataV1
+ BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block)
GetBlockByHash(hash common.Hash) *types.Block
Synced() bool
}
@@ -21,8 +22,8 @@ type testEthereumService struct {
testBlock *types.Block
}
-func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes) *beacon.ExecutableDataV1 {
- return t.testExecutableData
+func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block) {
+ return t.testExecutableData, t.testBlock
}
func (t *testEthereumService) GetBlockByHash(hash common.Hash) *types.Block { return t.testBlock }
@@ -37,18 +38,29 @@ func NewEthereumService(eth *eth.Ethereum) *EthereumService {
return &EthereumService{eth: eth}
}
-func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) *beacon.ExecutableDataV1 {
+func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block) {
// Send a request to generate a full block in the background.
// The result can be obtained via the returned channel.
resCh, err := s.eth.Miner().GetSealingBlockAsync(attrs.HeadHash, uint64(attrs.Timestamp), attrs.SuggestedFeeRecipient, attrs.GasLimit, attrs.Random, false)
if err != nil {
log.Error("Failed to create async sealing payload", "err", err)
- return nil
+ return nil, nil
}
- resultPayload := catalyst.NewPayload(resCh)
- executableData, _ := resultPayload.Resolve()
- return executableData
+ timer := time.NewTimer(4 * time.Second)
+ defer timer.Stop()
+
+ select {
+ case block := <-resCh:
+ if block == nil {
+ log.Error("received nil block from sealing work")
+ return nil, nil
+ }
+ return beacon.BlockToExecutableData(block), block
+ case <-timer.C:
+ log.Error("timeout waiting for block", "parent hash", attrs.HeadHash, "slot", attrs.Slot)
+ return nil, nil
+ }
}
func (s *EthereumService) GetBlockByHash(hash common.Hash) *types.Block {
diff --git a/builder/eth_service_test.go b/builder/eth_service_test.go
index a6b6bd32f0..14a11f4594 100644
--- a/builder/eth_service_test.go
+++ b/builder/eth_service_test.go
@@ -90,10 +90,13 @@ func TestBuildBlock(t *testing.T) {
}
service := NewEthereumService(ethservice)
- executableData := service.BuildBlock(testPayloadAttributes)
+ executableData, block := service.BuildBlock(testPayloadAttributes)
require.Equal(t, common.Address{0x04, 0x10}, executableData.FeeRecipient)
require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random)
require.Equal(t, parent.Hash(), executableData.ParentHash)
require.Equal(t, parent.Time()+1, executableData.Timestamp)
+ require.Equal(t, block.ParentHash(), parent.Hash())
+ require.Equal(t, block.Hash(), executableData.BlockHash)
+ require.Equal(t, block.Profit.Uint64(), uint64(0))
}
diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go
index 442b82df0b..5169a0b611 100644
--- a/cmd/geth/consolecmd_test.go
+++ b/cmd/geth/consolecmd_test.go
@@ -30,7 +30,7 @@ import (
)
const (
- ipcAPIs = "admin:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0"
+ ipcAPIs = "admin:1.0 builder:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0"
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
)
diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go
index 9a1bf41662..2995ff4860 100644
--- a/eth/catalyst/queue.go
+++ b/eth/catalyst/queue.go
@@ -35,18 +35,6 @@ const maxTrackedPayloads = 10
// latest one; but have a slight wiggle room for non-ideal conditions.
const maxTrackedHeaders = 10
-type Payload struct {
- payload
-}
-
-func NewPayload(result chan *types.Block) *Payload {
- return &Payload{payload{result: result}}
-}
-
-func (p *Payload) Resolve() (*beacon.ExecutableDataV1, *types.Block) {
- return p.payload.resolve()
-}
-
// payload wraps the miner's block production channel, allowing the mined block
// to be retrieved later upon the GetPayload engine API call.
type payload struct {
From 262f18ca83248b7bdb6a89c3683a1fde15b99887 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Tue, 16 Aug 2022 11:37:18 +0200
Subject: [PATCH 17/83] Add more explicit builder flags and other QoL
improvements (#20)
---
builder/builder.go | 154 +++++++++++++++++++-----------------
builder/service.go | 28 +++++--
cmd/geth/config.go | 2 +
cmd/geth/consolecmd_test.go | 2 +-
cmd/geth/main.go | 2 +
cmd/utils/flags.go | 14 +++-
6 files changed, 120 insertions(+), 82 deletions(-)
diff --git a/builder/builder.go b/builder/builder.go
index c99d7045c1..36ac0d2261 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -63,82 +63,92 @@ func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSignin
}
}
-func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
- if attrs != nil {
- vd, err := b.relay.GetValidatorForSlot(attrs.Slot)
- if err != nil {
- log.Info("could not get validator while submitting block", "err", err, "slot", attrs.Slot)
- return err
- }
-
- attrs.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
- attrs.GasLimit = vd.GasLimit
-
- if b.eth.Synced() {
- parentBlock := b.eth.GetBlockByHash(attrs.HeadHash)
- if parentBlock == nil {
- log.Info("Block hash not found in blocktree", "head block hash", attrs.HeadHash)
- return err
- }
-
- executableData, block := b.eth.BuildBlock(attrs)
- if executableData == nil || block == nil {
- log.Error("did not receive the payload")
- return errors.New("could not build block")
- }
- payload, err := executableDataToExecutionPayload(executableData)
- if err != nil {
- log.Error("could not format execution payload", "err", err)
- return err
- }
-
- pubkey, err := boostTypes.HexToPubkey(string(vd.Pubkey))
- if err != nil {
- log.Error("could not parse pubkey", "err", err, "pubkey", vd.Pubkey)
- return err
- }
-
- value := new(boostTypes.U256Str)
- err = value.FromBig(block.Profit)
- if err != nil {
- log.Error("could not set block value", "err", err)
- return err
- }
-
- blockBidMsg := boostTypes.BidTrace{
- Slot: attrs.Slot,
- ParentHash: payload.ParentHash,
- BlockHash: payload.BlockHash,
- BuilderPubkey: b.builderPublicKey,
- ProposerPubkey: pubkey,
- ProposerFeeRecipient: boostTypes.Address(attrs.SuggestedFeeRecipient),
- GasLimit: executableData.GasLimit,
- GasUsed: executableData.GasUsed,
- Value: *value,
- }
-
- signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
- if err != nil {
- log.Error("could not sign builder bid", "err", err)
- return err
- }
-
- blockSubmitReq := boostTypes.BuilderSubmitBlockRequest{
- Signature: signature,
- Message: &blockBidMsg,
- ExecutionPayload: payload,
- }
-
- err = b.relay.SubmitBlock(&blockSubmitReq)
- if err != nil {
- log.Error("could not submit block", "err", err)
- return err
- }
- }
+func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *types.Block, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, slot uint64) error {
+ payload, err := executableDataToExecutionPayload(executableData)
+ if err != nil {
+ log.Error("could not format execution payload", "err", err)
+ return err
+ }
+
+ value := new(boostTypes.U256Str)
+ err = value.FromBig(block.Profit)
+ if err != nil {
+ log.Error("could not set block value", "err", err)
+ return err
}
+
+ blockBidMsg := boostTypes.BidTrace{
+ Slot: slot,
+ ParentHash: payload.ParentHash,
+ BlockHash: payload.BlockHash,
+ BuilderPubkey: b.builderPublicKey,
+ ProposerPubkey: proposerPubkey,
+ ProposerFeeRecipient: proposerFeeRecipient,
+ GasLimit: executableData.GasLimit,
+ GasUsed: executableData.GasUsed,
+ Value: *value,
+ }
+
+ signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
+ if err != nil {
+ log.Error("could not sign builder bid", "err", err)
+ return err
+ }
+
+ blockSubmitReq := boostTypes.BuilderSubmitBlockRequest{
+ Signature: signature,
+ Message: &blockBidMsg,
+ ExecutionPayload: payload,
+ }
+
+ err = b.relay.SubmitBlock(&blockSubmitReq)
+ if err != nil {
+ log.Error("could not submit block", "err", err)
+ return err
+ }
+
return nil
}
+func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
+ if attrs == nil {
+ return nil
+ }
+
+ vd, err := b.relay.GetValidatorForSlot(attrs.Slot)
+ if err != nil {
+ log.Info("could not get validator while submitting block", "err", err, "slot", attrs.Slot)
+ return err
+ }
+
+ attrs.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
+ attrs.GasLimit = vd.GasLimit
+
+ proposerPubkey, err := boostTypes.HexToPubkey(string(vd.Pubkey))
+ if err != nil {
+ log.Error("could not parse pubkey", "err", err, "pubkey", vd.Pubkey)
+ return err
+ }
+
+ if !b.eth.Synced() {
+ return errors.New("backend not Synced")
+ }
+
+ parentBlock := b.eth.GetBlockByHash(attrs.HeadHash)
+ if parentBlock == nil {
+ log.Info("Block hash not found in blocktree", "head block hash", attrs.HeadHash)
+ return errors.New("parent block not found in blocktree")
+ }
+
+ executableData, block := b.eth.BuildBlock(attrs)
+ if executableData == nil || block == nil {
+ log.Error("did not receive the payload")
+ return errors.New("could not build block")
+ }
+
+ return b.onSealedBlock(executableData, block, proposerPubkey, vd.FeeRecipient, attrs.Slot)
+}
+
func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayload, error) {
transactionData := make([]hexutil.Bytes, len(data.Transactions))
for i, tx := range data.Transactions {
diff --git a/builder/service.go b/builder/service.go
index f33b2d4e45..0e459decd9 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -41,8 +41,10 @@ type Service struct {
}
func (s *Service) Start() {
- log.Info("Service started")
- go s.srv.ListenAndServe()
+ if s.srv != nil {
+ log.Info("Service started")
+ go s.srv.ListenAndServe()
+ }
}
func (s *Service) PayloadAttributes(payloadAttributes *BuilderPayloadAttributes) error {
@@ -65,8 +67,9 @@ func getRouter(localRelay *LocalRelay) http.Handler {
}
func NewService(listenAddr string, localRelay *LocalRelay, builder *Builder) *Service {
- return &Service{
- srv: &http.Server{
+ var srv *http.Server
+ if localRelay != nil {
+ srv = &http.Server{
Addr: listenAddr,
Handler: getRouter(localRelay),
/*
@@ -75,13 +78,19 @@ func NewService(listenAddr string, localRelay *LocalRelay, builder *Builder) *Se
WriteTimeout:
IdleTimeout:
*/
- },
+ }
+ }
+
+ return &Service{
+ srv: srv,
builder: builder,
}
}
type BuilderConfig struct {
+ Enabled bool
EnableValidatorChecks bool
+ EnableLocalRelay bool
BuilderSecretKey string
RelaySecretKey string
ListenAddr string
@@ -134,13 +143,18 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
beaconClient := NewBeaconClient(cfg.BeaconEndpoint)
- localRelay := NewLocalRelay(relaySk, beaconClient, builderSigningDomain, proposerSigningDomain, ForkData{cfg.GenesisForkVersion, cfg.BellatrixForkVersion, cfg.GenesisValidatorsRoot}, cfg.EnableValidatorChecks)
+ var localRelay *LocalRelay
+ if cfg.EnableLocalRelay {
+ localRelay = NewLocalRelay(relaySk, beaconClient, builderSigningDomain, proposerSigningDomain, ForkData{cfg.GenesisForkVersion, cfg.BellatrixForkVersion, cfg.GenesisValidatorsRoot}, cfg.EnableValidatorChecks)
+ }
var relay IRelay
if cfg.RemoteRelayEndpoint != "" {
relay = NewRemoteRelay(cfg.RemoteRelayEndpoint, localRelay)
- } else {
+ } else if localRelay != nil {
relay = localRelay
+ } else {
+ return errors.New("neither local nor remote relay specified")
}
ethereumService := NewEthereumService(backend)
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index cc11359785..9bff124bb7 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -167,7 +167,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
}
bpConfig := &builder.BuilderConfig{
+ Enabled: ctx.IsSet(utils.BuilderEnabled.Name),
EnableValidatorChecks: ctx.IsSet(utils.BuilderEnableValidatorChecks.Name),
+ EnableLocalRelay: ctx.IsSet(utils.BuilderEnableLocalRelay.Name),
BuilderSecretKey: ctx.String(utils.BuilderSecretKey.Name),
RelaySecretKey: ctx.String(utils.BuilderRelaySecretKey.Name),
ListenAddr: ctx.String(utils.BuilderListenAddr.Name),
diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go
index 5169a0b611..442b82df0b 100644
--- a/cmd/geth/consolecmd_test.go
+++ b/cmd/geth/consolecmd_test.go
@@ -30,7 +30,7 @@ import (
)
const (
- ipcAPIs = "admin:1.0 builder:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0"
+ ipcAPIs = "admin:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0"
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
)
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index c8ca3bd02b..2cbfa2359e 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -158,7 +158,9 @@ var (
}, utils.NetworkFlags, utils.DatabasePathFlags)
builderApiFlags = []cli.Flag{
+ utils.BuilderEnabled,
utils.BuilderEnableValidatorChecks,
+ utils.BuilderEnableLocalRelay,
utils.BuilderSecretKey,
utils.BuilderRelaySecretKey,
utils.BuilderListenAddr,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index feff51a7c8..22182b5d85 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -662,10 +662,18 @@ var (
Category: flags.MiscCategory,
}
// Builder API settings
+ BuilderEnabled = &cli.BoolFlag{
+ Name: "builder",
+ Usage: "Enable the builder",
+ }
BuilderEnableValidatorChecks = &cli.BoolFlag{
Name: "builder.validator_checks",
Usage: "Enable the validator checks",
}
+ BuilderEnableLocalRelay = &cli.BoolFlag{
+ Name: "builder.local_relay",
+ Usage: "Enable the local relay",
+ }
BuilderSecretKey = &cli.StringFlag{
Name: "builder.secret_key",
Usage: "Builder key used for signing blocks",
@@ -2066,8 +2074,10 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config, bpCfg *builder.
Fatalf("Failed to register the Engine API service: %v", err)
}
- if err := builder.Register(stack, backend, bpCfg); err != nil {
- Fatalf("Failed to register the builder service: %v", err)
+ if bpCfg.Enabled {
+ if err := builder.Register(stack, backend, bpCfg); err != nil {
+ Fatalf("Failed to register the builder service: %v", err)
+ }
}
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
From 9d24d684dfedc96acbc012dcdb5a7836e14ef7f4 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 25 Aug 2022 19:55:18 +0200
Subject: [PATCH 18/83] Resubmit block build job periodically (#22)
---
builder/builder.go | 25 +++++++++++++----
builder/builder_test.go | 7 +++++
builder/resubmitter.go | 42 ++++++++++++++++++++++++++++
builder/resubmitter_test.go | 56 +++++++++++++++++++++++++++++++++++++
4 files changed, 124 insertions(+), 6 deletions(-)
create mode 100644 builder/resubmitter.go
create mode 100644 builder/resubmitter_test.go
diff --git a/builder/builder.go b/builder/builder.go
index 36ac0d2261..8a8c82ca5a 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -3,6 +3,7 @@ package builder
import (
"errors"
_ "os"
+ "time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon"
@@ -41,6 +42,7 @@ type Builder struct {
beaconClient IBeaconClient
relay IRelay
eth IEthereumService
+ resubmitter Resubmitter
builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
@@ -56,6 +58,7 @@ func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSignin
beaconClient: bc,
relay: relay,
eth: eth,
+ resubmitter: Resubmitter{},
builderSecretKey: sk,
builderPublicKey: pk,
@@ -140,13 +143,23 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
return errors.New("parent block not found in blocktree")
}
- executableData, block := b.eth.BuildBlock(attrs)
- if executableData == nil || block == nil {
- log.Error("did not receive the payload")
- return errors.New("could not build block")
- }
+ firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error {
+ executableData, block := b.eth.BuildBlock(attrs)
+ if executableData == nil || block == nil {
+ log.Error("did not receive the payload")
+ return errors.New("did not receive the payload")
+ }
+
+ err := b.onSealedBlock(executableData, block, proposerPubkey, vd.FeeRecipient, attrs.Slot)
+ if err != nil {
+ log.Error("could not run block hook", "err", err)
+ return err
+ }
+
+ return nil
+ })
- return b.onSealedBlock(executableData, block, proposerPubkey, vd.FeeRecipient, attrs.Slot)
+ return firstBlockResult
}
func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayload, error) {
diff --git a/builder/builder_test.go b/builder/builder_test.go
index f3df52a1e4..b371a2d52b 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -3,6 +3,7 @@ package builder
import (
"math/big"
"testing"
+ "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -120,4 +121,10 @@ func TestOnPayloadAttributes(t *testing.T) {
require.Equal(t, expectedSignature, testRelay.submittedMsg.Signature)
require.Equal(t, uint64(25), testRelay.requestedSlot)
+
+ // Clear the submitted message and check that the job will be ran again and a new message will be submitted
+ testRelay.submittedMsg = nil
+ time.Sleep(2 * time.Second)
+ require.NotNil(t, testRelay.submittedMsg)
+ require.Equal(t, expectedMessage, *testRelay.submittedMsg.Message)
}
diff --git a/builder/resubmitter.go b/builder/resubmitter.go
new file mode 100644
index 0000000000..e167badbab
--- /dev/null
+++ b/builder/resubmitter.go
@@ -0,0 +1,42 @@
+package builder
+
+import (
+ "context"
+ "sync"
+ "time"
+)
+
+type Resubmitter struct {
+ mu sync.Mutex
+ cancel context.CancelFunc
+}
+
+func (r *Resubmitter) newTask(repeatFor time.Duration, interval time.Duration, fn func() error) error {
+ repeatUntilCh := time.After(repeatFor)
+
+ r.mu.Lock()
+ if r.cancel != nil {
+ r.cancel()
+ }
+ ctx, cancel := context.WithCancel(context.Background())
+ r.cancel = cancel
+ r.mu.Unlock()
+
+ firstRunErr := fn()
+
+ go func() {
+ for ctx.Err() == nil {
+ select {
+ case <-ctx.Done():
+ return
+ case <-repeatUntilCh:
+ cancel()
+ return
+ case <-time.After(interval):
+ fn()
+ }
+ }
+ }()
+
+ return firstRunErr
+}
diff --git a/builder/resubmitter_test.go b/builder/resubmitter_test.go
new file mode 100644
index 0000000000..516e5c7317
--- /dev/null
+++ b/builder/resubmitter_test.go
@@ -0,0 +1,56 @@
+package builder
+
+import (
+ "errors"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestResubmitter(t *testing.T) {
+
+ resubmitter := Resubmitter{}
+
+ pingCh := make(chan error)
+ go func() {
+ res := resubmitter.newTask(time.Second, 100*time.Millisecond, func() error {
+ return <-pingCh
+ })
+ require.ErrorContains(t, res, "xx")
+ }()
+
+ select {
+ case pingCh <- errors.New("xx"):
+ case <-time.After(time.Second):
+ t.Error("timeout waiting for the function")
+ }
+
+ select {
+ case pingCh <- nil:
+ t.Error("function restarted too soon")
+ default:
+ }
+
+ time.Sleep(200 * time.Millisecond)
+
+ select {
+ case pingCh <- nil:
+ default:
+ t.Error("function restarted too late")
+ }
+
+ time.Sleep(800 * time.Millisecond)
+
+ select {
+ case pingCh <- nil:
+ default:
+ t.Error("function restarted too late")
+ }
+
+ select {
+ case pingCh <- nil:
+ t.Error("function restarted after deadline")
+ case <-time.After(200 * time.Millisecond):
+ }
+}
From f248ccb546c104eff040673846e5c41e7ebca51a Mon Sep 17 00:00:00 2001
From: Bhakiyaraj Kalimuthu
Date: Thu, 11 Aug 2022 11:39:48 +0200
Subject: [PATCH 19/83] Implement proposer payment via transaction (#23)
---
builder/eth_service_test.go | 2 +-
miner/miner.go | 20 ++++----
miner/worker.go | 99 +++++++++++++++++++++++++++++++++++--
miner/worker_test.go | 8 +--
4 files changed, 110 insertions(+), 19 deletions(-)
diff --git a/builder/eth_service_test.go b/builder/eth_service_test.go
index 14a11f4594..d3e0daad8e 100644
--- a/builder/eth_service_test.go
+++ b/builder/eth_service_test.go
@@ -92,7 +92,7 @@ func TestBuildBlock(t *testing.T) {
service := NewEthereumService(ethservice)
executableData, block := service.BuildBlock(testPayloadAttributes)
- require.Equal(t, common.Address{0x04, 0x10}, executableData.FeeRecipient)
+ //require.Equal(t, common.Address{0x04, 0x10}, executableData.FeeRecipient)
require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random)
require.Equal(t, parent.Hash(), executableData.ParentHash)
require.Equal(t, parent.Time()+1, executableData.Timestamp)
diff --git a/miner/miner.go b/miner/miner.go
index cc0af1253e..155dd40000 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -18,6 +18,7 @@
package miner
import (
+ "crypto/ecdsa"
"fmt"
"math/big"
"sync"
@@ -44,15 +45,16 @@ type Backend interface {
// Config is the configuration parameters of mining.
type Config struct {
- Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards (default = first account)
- Notify []string `toml:",omitempty"` // HTTP URL list to be notified of new work packages (only useful in ethash).
- NotifyFull bool `toml:",omitempty"` // Notify with pending block headers instead of work packages
- ExtraData hexutil.Bytes `toml:",omitempty"` // Block extra data set by the miner
- GasFloor uint64 // Target gas floor for mined blocks.
- GasCeil uint64 // Target gas ceiling for mined blocks.
- GasPrice *big.Int // Minimum gas price for mining a transaction
- Recommit time.Duration // The time interval for miner to re-create mining work.
- Noverify bool // Disable remote mining solution verification(only useful in ethash).
+ Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards (default = first account)
+ Notify []string `toml:",omitempty"` // HTTP URL list to be notified of new work packages (only useful in ethash).
+ NotifyFull bool `toml:",omitempty"` // Notify with pending block headers instead of work packages
+ ExtraData hexutil.Bytes `toml:",omitempty"` // Block extra data set by the miner
+ GasFloor uint64 // Target gas floor for mined blocks.
+ GasCeil uint64 // Target gas ceiling for mined blocks.
+ GasPrice *big.Int // Minimum gas price for mining a transaction
+ Recommit time.Duration // The time interval for miner to re-create mining work.
+ Noverify bool // Disable remote mining solution verification(only useful in ethash).
+ BuilderTxSigningKey *ecdsa.PrivateKey // Signing key of builder coinbase to make transaction to validator
}
// Miner creates blocks and searches for proof-of-work values.
diff --git a/miner/worker.go b/miner/worker.go
index ec2e3ad61d..c727a0f365 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -17,9 +17,13 @@
package miner
import (
+ "crypto/ecdsa"
"errors"
"fmt"
"math/big"
+
+ "os"
+ "strings"
"sync"
"sync/atomic"
"time"
@@ -31,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
@@ -252,6 +257,26 @@ type worker struct {
}
func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *worker {
+ var err error
+ var builderCoinbase common.Address
+ key := os.Getenv("BUILDER_TX_SIGNING_KEY") // get builder private signing key
+ if key == "" {
+ log.Error("Builder signing key is empty, validator payout can not be done")
+ } else {
+ config.BuilderTxSigningKey, err = crypto.HexToECDSA(strings.TrimPrefix(key, "0x"))
+ if err != nil {
+ log.Error("Error creating builder tx signing key", "error", err)
+ } else {
+ publicKey := config.BuilderTxSigningKey.Public()
+ publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
+ if ok {
+ builderCoinbase = crypto.PubkeyToAddress(*publicKeyECDSA)
+ } else {
+ log.Error("Cannot assert type, builder tx signing key")
+ }
+ }
+ }
+ log.Info("builderCoinbase", builderCoinbase.String())
worker := &worker{
config: config,
chainConfig: chainConfig,
@@ -275,6 +300,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
startCh: make(chan struct{}, 1),
resubmitIntervalCh: make(chan time.Duration),
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
+ coinbase: builderCoinbase,
}
// Subscribe NewTxsEvent for tx pool
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
@@ -1052,7 +1078,8 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
// fillTransactions retrieves the pending transactions from the txpool and fills them
// into the given sealing block. The transaction selection and ordering strategy can
// be customized with the plugin in the future.
-func (w *worker) fillTransactions(interrupt *int32, env *environment) error {
+
+func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorCoinbase *common.Address) error {
// Split the pending transactions into locals and remotes
// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
@@ -1063,6 +1090,16 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment) error {
localTxs[account] = txs
}
}
+ if env.gasPool == nil {
+ env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
+ }
+ var builderCoinbaseBalanceBefore *big.Int
+ if validatorCoinbase != nil {
+ builderCoinbaseBalanceBefore = env.state.GetBalance(w.coinbase)
+ if err := env.gasPool.SubGas(params.TxGas); err != nil {
+ return err
+ }
+ }
if len(localTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
@@ -1075,29 +1112,68 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment) error {
return err
}
}
+ if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil {
+ builderCoinbaseBalanceAfter := env.state.GetBalance(w.coinbase)
+ log.Info("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
+
+ profit := new(big.Int).Sub(builderCoinbaseBalanceAfter, builderCoinbaseBalanceBefore)
+ env.gasPool.AddGas(params.TxGas)
+ if profit.Sign() == 1 {
+ tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit)
+ if err != nil {
+ log.Error("Proposer payout create tx failed", "err", err)
+ return fmt.Errorf("proposer payout create tx failed - %v", err)
+ }
+ if tx != nil {
+ log.Info("Proposer payout create tx succeeded, proceeding to commit tx")
+ env.state.Prepare(tx.Hash(), env.tcount)
+ _, err = w.commitTransaction(env, tx)
+ if err != nil {
+ log.Error("Proposer payout commit tx failed", "hash", tx.Hash().String(), "err", err)
+ return fmt.Errorf("proposer payout commit tx failed - %v", err)
+ }
+ log.Info("Proposer payout commit tx succeeded", "hash", tx.Hash().String())
+ env.tcount++
+ } else {
+ return errors.New("proposer payout create tx failed due to tx is nil")
+ }
+ } else {
+ log.Warn("Proposer payout create tx failed due to not enough balance", "profit", profit.String())
+ return errors.New("proposer payout create tx failed due to not enough balance")
+ }
+
+ }
return nil
}
// generateWork generates a sealing block based on the given parameters.
func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
+ validatorCoinbase := params.coinbase
+ // Set builder coinbase to be passed to beacon header
+ params.coinbase = w.coinbase
+
work, err := w.prepareWork(params)
if err != nil {
return nil, err
}
defer work.discard()
- coinbaseBalanceBefore := work.state.GetBalance(params.coinbase)
+ coinbaseBalanceBefore := work.state.GetBalance(validatorCoinbase)
if !params.noTxs {
- w.fillTransactions(nil, work)
+ if err := w.fillTransactions(nil, work, &validatorCoinbase); err != nil {
+ return nil, err
+ }
}
block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
if err != nil {
return nil, err
}
- coinbaseBalanceAfter := work.state.GetBalance(params.coinbase)
+ coinbaseBalanceAfter := work.state.GetBalance(validatorCoinbase)
block.Profit = big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
+ validatorBalance := work.state.GetBalance(validatorCoinbase)
+ log.Info("Block finalized and assembled", "blockProfit", block.Profit.String(), "validatorCoinbaseBalanceBefore", coinbaseBalanceBefore.String(), "validatorCoinbaseBalanceAfter", coinbaseBalanceAfter.String(), "builderCoinbase", params.coinbase.String(), "validatorCoinbase", validatorCoinbase.String(), "validatorBalance", validatorBalance.String())
return block, nil
}
@@ -1129,7 +1205,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) {
}
// Fill pending transactions from the txpool
- err = w.fillTransactions(interrupt, work)
+ err = w.fillTransactions(interrupt, work, nil)
if errors.Is(err, errBlockInterruptedByNewHead) {
work.discard()
return
@@ -1246,3 +1322,16 @@ func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float {
}
return new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether)))
}
+
+func (w *worker) createProposerPayoutTx(env *environment, recipient *common.Address, profit *big.Int) (*types.Transaction, error) {
+ sender := w.coinbase.String()
+ log.Info(sender)
+ nonce := env.state.GetNonce(w.coinbase)
+ fee := new(big.Int).Mul(big.NewInt(21000), env.header.BaseFee)
+ amount := new(big.Int).Sub(profit, fee)
+ gasPrice := new(big.Int).Set(env.header.BaseFee)
+ chainId := w.chainConfig.ChainID
+ log.Debug("createProposerPayoutTx", "sender", sender, "chainId", chainId.String(), "nonce", nonce, "amount", amount.String(), "gas", params.TxGas, "baseFee", env.header.BaseFee.String(), "fee", fee)
+ tx := types.NewTransaction(nonce, *recipient, amount, params.TxGas, gasPrice, nil)
+ return types.SignTx(tx, types.LatestSignerForChainID(chainId), w.config.BuilderTxSigningKey)
+}
diff --git a/miner/worker_test.go b/miner/worker_test.go
index 1cade0aea4..6bd3e13842 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -543,7 +543,7 @@ func TestGetSealingWorkPostMerge(t *testing.T) {
func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, postMerge bool) {
defer engine.Close()
-
+ //os.Setenv("BUILDER_TX_SIGNING_KEY", "0xb9ee6b07275bb71da8823290b68b667c075482696576c05e7989dee7d29a5855")
w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
defer w.close()
@@ -571,9 +571,9 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
if noExtra && len(block.Extra()) != 0 {
t.Error("Unexpected extra field")
}
- if block.Coinbase() != coinbase {
- t.Errorf("Unexpected coinbase got %x want %x", block.Coinbase(), coinbase)
- }
+ //if block.Coinbase() != coinbase {
+ // t.Errorf("Unexpected coinbase got %x want %x", block.Coinbase(), coinbase)
+ //}
} else {
if block.Coinbase() != (common.Address{}) {
t.Error("Unexpected coinbase")
From b87888ef4866d7e6b8d2f0cb0a3b6f4ffa02f47e Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Wed, 31 Aug 2022 11:35:30 +0200
Subject: [PATCH 20/83] Base block profit on the proposer payment instead of
balance difference (#24)
---
miner/worker.go | 32 ++++++++++++++++++++++++++------
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/miner/worker.go b/miner/worker.go
index c727a0f365..7b87860ffb 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1158,8 +1158,6 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
}
defer work.discard()
- coinbaseBalanceBefore := work.state.GetBalance(validatorCoinbase)
-
if !params.noTxs {
if err := w.fillTransactions(nil, work, &validatorCoinbase); err != nil {
return nil, err
@@ -1170,10 +1168,32 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
return nil, err
}
- coinbaseBalanceAfter := work.state.GetBalance(validatorCoinbase)
- block.Profit = big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
- validatorBalance := work.state.GetBalance(validatorCoinbase)
- log.Info("Block finalized and assembled", "blockProfit", block.Profit.String(), "validatorCoinbaseBalanceBefore", coinbaseBalanceBefore.String(), "validatorCoinbaseBalanceAfter", coinbaseBalanceAfter.String(), "builderCoinbase", params.coinbase.String(), "validatorCoinbase", validatorCoinbase.String(), "validatorBalance", validatorBalance.String())
+ block.Profit = big.NewInt(0)
+
+ if w.config.BuilderTxSigningKey == nil {
+ return block, nil
+ }
+
+ if len(work.txs) == 0 {
+ return nil, errors.New("no proposer payment tx")
+ } else if len(work.receipts) == 0 {
+ return nil, errors.New("no proposer payment receipt")
+ }
+
+ lastTx := work.txs[len(work.txs)-1]
+ receipt := work.receipts[len(work.receipts)-1]
+ if receipt.TxHash != lastTx.Hash() || receipt.Status != types.ReceiptStatusSuccessful {
+ log.Error("proposer payment not successful!", "lastTx", lastTx, "receipt", receipt)
+ return nil, errors.New("last transaction is not proposer payment")
+ }
+ lastTxTo := lastTx.To()
+ if lastTxTo == nil || *lastTxTo != validatorCoinbase {
+ log.Error("last transaction is not to the proposer!", "err", err, "lastTx", lastTx)
+ return nil, errors.New("last transaction is not proposer payment")
+ }
+
+ block.Profit.Set(lastTx.Value())
+ log.Info("Block finalized and assembled", "blockProfit", block.Profit.String(), "proposer payment tx", lastTx, "receipt", receipt)
return block, nil
}
From 1633a5d443e867f1afed8a7c0c6739c5a028188d Mon Sep 17 00:00:00 2001
From: Jason Paryani
Date: Wed, 27 Oct 2021 16:10:05 -0400
Subject: [PATCH 21/83] Flashbots change up to v0.3
---
.github/workflows/go.yml | 64 ++++
cmd/geth/main.go | 1 +
cmd/utils/flags.go | 8 +
core/tx_pool.go | 64 +++-
core/types/transaction.go | 8 +
eth/api_backend.go | 4 +
infra/Dockerfile.node | 23 ++
infra/Dockerfile.updater | 23 ++
infra/start-mev-geth-node.sh | 96 ++++++
infra/start-mev-geth-updater.sh | 181 ++++++++++
internal/ethapi/api.go | 51 +++
internal/ethapi/backend.go | 4 +
internal/ethapi/transaction_args_test.go | 4 +
internal/web3ext/web3ext.go | 5 +
les/api_backend.go | 3 +
light/txpool.go | 11 +
miner/miner.go | 15 +-
miner/multi_worker.go | 118 +++++++
miner/worker.go | 417 ++++++++++++++++++++++-
miner/worker_test.go | 5 +-
20 files changed, 1077 insertions(+), 28 deletions(-)
create mode 100644 .github/workflows/go.yml
create mode 100644 infra/Dockerfile.node
create mode 100644 infra/Dockerfile.updater
create mode 100755 infra/start-mev-geth-node.sh
create mode 100755 infra/start-mev-geth-updater.sh
create mode 100644 miner/multi_worker.go
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
new file mode 100644
index 0000000000..3fc1f2ff8c
--- /dev/null
+++ b/.github/workflows/go.yml
@@ -0,0 +1,64 @@
+name: Go
+
+on:
+ push:
+ pull_request:
+ branches: [ master ]
+
+jobs:
+
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Set up Go 1.x
+ uses: actions/setup-go@v2
+ with:
+ go-version: ^1.13
+ id: go
+
+ - name: Check out code into the Go module directory
+ uses: actions/checkout@v2
+
+ - name: Test
+ run: go test ./core ./miner/... ./internal/ethapi/... ./les/...
+
+ - name: Build
+ run: make geth
+
+ e2e:
+ name: End to End
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Set up Go 1.x
+ uses: actions/setup-go@v2
+ with:
+ go-version: ^1.13
+ id: go
+
+ - name: Use Node.js 12.x
+ uses: actions/setup-node@v1
+ with:
+ node-version: 12.x
+
+ - name: Check out code into the Go module directory
+ uses: actions/checkout@v2
+
+ - name: Build
+ run: make geth
+
+ - name: Check out the e2e code repo
+ uses: actions/checkout@v2
+ with:
+ repository: flashbots/mev-geth-demo
+ path: e2e
+
+ - run: cd e2e && yarn install
+ - run: |
+ cd e2e
+ GETH=`pwd`/../build/bin/geth ./run.sh &
+ sleep 15
+ yarn run demo-simple
+ yarn run demo-contract
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 2cbfa2359e..2f8f196774 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -133,6 +133,7 @@ var (
utils.MinerExtraDataFlag,
utils.MinerRecommitIntervalFlag,
utils.MinerNoVerifyFlag,
+ utils.MinerMaxMergedBundles,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 22182b5d85..047387a558 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -562,6 +562,12 @@ var (
Usage: "Disable remote sealing verification",
Category: flags.MinerCategory,
}
+ MinerMaxMergedBundles = &cli.IntFlag{
+ Name: "miner.maxmergedbundles",
+ Usage: "flashbots - The maximum amount of bundles to merge. The miner will run this many workers in parallel to calculate if the full block is more profitable with these additional bundles.",
+ Value: 3,
+ Category: flags.MinerCategory,
+ }
// Account settings
UnlockedAccountFlag = &cli.StringFlag{
@@ -1714,6 +1720,8 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
if ctx.IsSet(LegacyMinerGasTargetFlag.Name) {
log.Warn("The generic --miner.gastarget flag is deprecated and will be removed in the future!")
}
+
+ cfg.MaxMergedBundles = ctx.Int(MinerMaxMergedBundles.Name)
}
func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 1c25442dd9..ad1dd77605 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -251,11 +251,12 @@ type TxPool struct {
locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk
- pending map[common.Address]*txList // All currently processable transactions
- queue map[common.Address]*txList // Queued but non-processable transactions
- beats map[common.Address]time.Time // Last heartbeat from each known account
- all *txLookup // All transactions to allow lookups
- priced *txPricedList // All transactions sorted by price
+ pending map[common.Address]*txList // All currently processable transactions
+ queue map[common.Address]*txList // Queued but non-processable transactions
+ beats map[common.Address]time.Time // Last heartbeat from each known account
+ mevBundles []types.MevBundle
+ all *txLookup // All transactions to allow lookups
+ priced *txPricedList // All transactions sorted by price
chainHeadCh chan ChainHeadEvent
chainHeadSub event.Subscription
@@ -557,6 +558,59 @@ func (pool *TxPool) Pending(enforceTips bool) map[common.Address]types.Transacti
return pending
}
+/// AllMevBundles returns all the MEV Bundles currently in the pool
+func (pool *TxPool) AllMevBundles() []types.MevBundle {
+ return pool.mevBundles
+}
+
+// MevBundles returns a list of bundles valid for the given blockNumber/blockTimestamp
+// also prunes bundles that are outdated
+func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]types.MevBundle, error) {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ // returned values
+ var ret []types.MevBundle
+ // rolled over values
+ var bundles []types.MevBundle
+
+ for _, bundle := range pool.mevBundles {
+ // Prune outdated bundles
+ if (bundle.MaxTimestamp != 0 && blockTimestamp > bundle.MaxTimestamp) || blockNumber.Cmp(bundle.BlockNumber) > 0 {
+ continue
+ }
+
+ // Roll over future bundles
+ if (bundle.MinTimestamp != 0 && blockTimestamp < bundle.MinTimestamp) || blockNumber.Cmp(bundle.BlockNumber) < 0 {
+ bundles = append(bundles, bundle)
+ continue
+ }
+
+ // return the ones which are in time
+ ret = append(ret, bundle)
+ // keep the bundles around internally until they need to be pruned
+ bundles = append(bundles, bundle)
+ }
+
+ pool.mevBundles = bundles
+ return ret, nil
+}
+
+// AddMevBundle adds a mev bundle to the pool
+func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ pool.mevBundles = append(pool.mevBundles, types.MevBundle{
+ Txs: txs,
+ BlockNumber: blockNumber,
+ MinTimestamp: minTimestamp,
+ MaxTimestamp: maxTimestamp,
+ RevertingTxHashes: revertingTxHashes,
+ })
+ return nil
+}
+
// Locals retrieves the accounts currently considered local by the pool.
func (pool *TxPool) Locals() []common.Address {
pool.mu.Lock()
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 715ede15db..bdff810af0 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -651,3 +651,11 @@ func copyAddressPtr(a *common.Address) *common.Address {
cpy := *a
return &cpy
}
+
+type MevBundle struct {
+ Txs Transactions
+ BlockNumber *big.Int
+ MinTimestamp uint64
+ MaxTimestamp uint64
+ RevertingTxHashes []common.Hash
+}
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 00ecacc31d..ee7129a299 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -250,6 +250,10 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
return b.eth.txPool.AddLocal(signedTx)
}
+func (b *EthAPIBackend) SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
+ return b.eth.txPool.AddMevBundle(txs, big.NewInt(blockNumber.Int64()), minTimestamp, maxTimestamp, revertingTxHashes)
+}
+
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
pending := b.eth.txPool.Pending(false)
var txs types.Transactions
diff --git a/infra/Dockerfile.node b/infra/Dockerfile.node
new file mode 100644
index 0000000000..db8e99ac93
--- /dev/null
+++ b/infra/Dockerfile.node
@@ -0,0 +1,23 @@
+# Build Geth in a stock Go builder container
+FROM golang:1.15-alpine as builder
+
+RUN apk add --no-cache make gcc musl-dev linux-headers git
+
+ADD . /go-ethereum
+RUN cd /go-ethereum && make geth
+
+# Pull Geth into a second stage deploy alpine container
+FROM alpine:latest
+
+ENV PYTHONUNBUFFERED=1
+RUN apk add --update --no-cache groff less python3 curl jq ca-certificates && ln -sf python3 /usr/bin/python
+RUN python3 -m ensurepip
+RUN pip3 install --no-cache --upgrade pip setuptools awscli
+
+COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
+
+COPY ./infra/start-mev-geth-node.sh /root/start-mev-geth-node.sh
+RUN chmod 755 /root/start-mev-geth-node.sh
+
+EXPOSE 8545 8546 30303 30303/udp
+ENTRYPOINT ["/root/start-mev-geth-node.sh"]
diff --git a/infra/Dockerfile.updater b/infra/Dockerfile.updater
new file mode 100644
index 0000000000..d3099d19ce
--- /dev/null
+++ b/infra/Dockerfile.updater
@@ -0,0 +1,23 @@
+# Build Geth in a stock Go builder container
+FROM golang:1.15-alpine as builder
+
+RUN apk add --no-cache make gcc musl-dev linux-headers git
+
+ADD . /go-ethereum
+RUN cd /go-ethereum && make geth
+
+# Pull Geth into a second stage deploy alpine container
+FROM alpine:latest
+
+ENV PYTHONUNBUFFERED=1
+RUN apk add --update --no-cache groff less python3 curl jq ca-certificates && ln -sf python3 /usr/bin/python
+RUN python3 -m ensurepip
+RUN pip3 install --no-cache --upgrade pip setuptools awscli
+
+COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
+
+COPY ./infra/start-mev-geth-updater.sh /root/start-mev-geth-updater.sh
+RUN chmod 755 /root/start-mev-geth-updater.sh
+
+EXPOSE 8545 8546 30303 30303/udp
+ENTRYPOINT ["/root/start-mev-geth-updater.sh"]
diff --git a/infra/start-mev-geth-node.sh b/infra/start-mev-geth-node.sh
new file mode 100755
index 0000000000..05ad50c610
--- /dev/null
+++ b/infra/start-mev-geth-node.sh
@@ -0,0 +1,96 @@
+#!/bin/sh -x
+# Starts the Mev-Geth node client
+# Written by Luke Youngblood, luke@blockscale.net
+
+# network=mainnet # normally set by environment
+# syncmode=fast # normally set by environment
+# rpcport=8545 # normally set by environment
+# wsport=8546 # normally set by environment
+# netport=30303 # normally set by environment
+
+init_node() {
+ # You can put any commands you would like to run to initialize the node here.
+ echo Initializing node...
+}
+
+start_node() {
+ if [ $network = "goerli" ]
+ then
+ geth \
+ --port $netport \
+ --http \
+ --http.addr 0.0.0.0 \
+ --http.port $rpcport \
+ --http.api eth,net,web3 \
+ --http.vhosts '*' \
+ --http.corsdomain '*' \
+ --graphql \
+ --graphql.corsdomain '*' \
+ --graphql.vhosts '*' \
+ --ws \
+ --ws.addr 0.0.0.0 \
+ --ws.port $wsport \
+ --ws.api eth,net,web3 \
+ --ws.origins '*' \
+ --syncmode $syncmode \
+ --cache 4096 \
+ --maxpeers $connections \
+ --goerli
+ if [ $? -ne 0 ]
+ then
+ echo "Node failed to start; exiting."
+ exit 1
+ fi
+ else
+ geth \
+ --port $netport \
+ --http \
+ --http.addr 0.0.0.0 \
+ --http.port $rpcport \
+ --http.api eth,net,web3 \
+ --http.vhosts '*' \
+ --http.corsdomain '*' \
+ --graphql \
+ --graphql.corsdomain '*' \
+ --graphql.vhosts '*' \
+ --ws \
+ --ws.addr 0.0.0.0 \
+ --ws.port $wsport \
+ --ws.api eth,net,web3 \
+ --ws.origins '*' \
+ --syncmode $syncmode \
+ --cache 4096 \
+ --maxpeers $connections
+ if [ $? -ne 0 ]
+ then
+ echo "Node failed to start; exiting."
+ exit 1
+ fi
+ fi
+}
+
+s3_sync() {
+ # Determine data directory
+ if [ $network = "goerli" ]
+ then
+ datadir=/root/.ethereum/goerli/geth/chaindata
+ else
+ datadir=/root/.ethereum/geth/chaindata
+ fi
+ # If the current1 key exists, node1 is the most current set of blockchain data
+ echo "A 404 error below is expected and nothing to be concerned with."
+ aws s3api head-object --request-payer requester --bucket $chainbucket --key current1
+ if [ $? -eq 0 ]
+ then
+ s3key=node1
+ else
+ s3key=node2
+ fi
+ aws s3 sync --only-show-errors --request-payer requester --region $region s3://$chainbucket/$s3key $datadir
+}
+
+# main
+
+init_node
+s3_sync
+start_node
diff --git a/infra/start-mev-geth-updater.sh b/infra/start-mev-geth-updater.sh
new file mode 100755
index 0000000000..11a6a533aa
--- /dev/null
+++ b/infra/start-mev-geth-updater.sh
@@ -0,0 +1,181 @@
+#!/bin/sh -x
+# Starts the Mev-Geth updater client
+# Written by Luke Youngblood, luke@blockscale.net
+
+# netport=30303 # normally set by environment
+
+init_node() {
+ # Initialization steps can go here
+ echo Initializing node...
+ aws configure set default.s3.max_concurrent_requests 64
+ aws configure set default.s3.max_queue_size 20000
+}
+
+start_node() {
+ if [ $network = "goerli" ]
+ then
+ geth \
+ --port $netport \
+ --syncmode $syncmode \
+ --cache 4096 \
+ --maxpeers $connections \
+ --goerli &
+ if [ $? -ne 0 ]
+ then
+ echo "Node failed to start; exiting."
+ exit 1
+ fi
+ else
+ geth \
+ --port $netport \
+ --syncmode $syncmode \
+ --cache 4096 \
+ --maxpeers $connections &
+ if [ $? -ne 0 ]
+ then
+ echo "Node failed to start; exiting."
+ exit 1
+ fi
+ fi
+}
+
+s3_sync_down() {
+ # Determine data directory
+ if [ $network = "goerli" ]
+ then
+ datadir=/root/.ethereum/goerli/geth/chaindata
+ else
+ datadir=/root/.ethereum/geth/chaindata
+ fi
+
+ # If the current1 object exists, node1 is the key we should download
+ echo "A 404 error below is expected and nothing to be concerned with."
+ aws s3api head-object --bucket $chainbucket --key current1
+ if [ $? -eq 0 ]
+ then
+ echo "current1 key exists; downloading node1"
+ s3key=node1
+ else
+ echo "current1 key doesn't exist; downloading node2"
+ s3key=node2
+ fi
+
+ aws s3 sync --region $region --only-show-errors s3://$chainbucket/$s3key $datadir
+ if [ $? -ne 0 ]
+ then
+ echo "aws s3 sync command failed; exiting."
+ exit 2
+ fi
+}
+
+kill_node() {
+ tries=0
+ while [ ! -z `ps -ef |grep geth|grep -v geth-updater|grep -v grep|awk '{print $1}'` ]
+ do
+ ps -ef |grep geth|grep -v geth-updater|grep -v grep
+ pid=`ps -ef |grep geth|grep -v geth-updater|grep -v grep|awk '{print $1}'`
+ kill $pid
+ sleep 30
+ echo "Waiting for the node to shutdown cleanly... try number $tries"
+ let "tries+=1"
+ if [ $tries -gt 29 ]
+ then
+ echo "Node has not stopped cleanly after $tries, forcibly killing."
+ ps -ef |grep geth|grep -v geth-updater|grep -v grep
+ pid=`ps -ef |grep geth|grep -v geth-updater|grep -v grep|awk '{print $1}'`
+ kill -9 $pid
+ fi
+ if [ $tries -gt 30 ]
+ then
+ echo "Node has not stopped cleanly after $tries, exiting..."
+ exit 3
+ fi
+ done
+}
+
+s3_sync_up() {
+ # Determine data directory
+ if [ $network = "goerli" ]
+ then
+ datadir=/root/.ethereum/goerli/geth/chaindata
+ else
+ datadir=/root/.ethereum/geth/chaindata
+ fi
+
+ # If the current1 object exists, node1 is the folder that clients will download, so we should update node2
+ aws s3api head-object --bucket $chainbucket --key current1
+ if [ $? -eq 0 ]
+ then
+ echo "current1 key exists; updating node2"
+ s3key=node2
+ else
+ echo "current1 key doesn't exist; updating node1"
+ s3key=node1
+ fi
+
+ aws s3 sync --delete --region $region --only-show-errors --acl public-read $datadir s3://$chainbucket/$s3key
+ if [ $? -ne 0 ]
+ then
+ echo "aws s3 sync upload command failed; exiting."
+ exit 4
+ fi
+
+ if [ "$s3key" = "node2" ]
+ then
+ echo "Removing current1 key, as the node2 key was just updated."
+ aws s3 rm --region $region s3://$chainbucket/current1
+ if [ $? -ne 0 ]
+ then
+ echo "aws s3 rm command failed; retrying."
+ sleep 5
+ aws s3 rm --region $region s3://$chainbucket/current1
+ if [ $? -ne 0 ]
+ then
+ echo "aws s3 rm command failed; exiting."
+ exit 5
+ fi
+ fi
+ else
+ echo "Touching current1 key, as the node1 key was just updated."
+ touch ~/current1
+ aws s3 cp --region $region --acl public-read ~/current1 s3://$chainbucket/
+ if [ $? -ne 0 ]
+ then
+ echo "aws s3 cp command failed; retrying."
+ sleep 5
+ aws s3 cp --region $region --acl public-read ~/current1 s3://$chainbucket/
+ if [ $? -ne 0 ]
+ then
+ echo "aws s3 cp command failed; exiting."
+ exit 6
+ fi
+ fi
+ fi
+}
+
+continuous() {
+ # This function continuously stops the node every hour
+ # and syncs the chain data with S3, then restarts the node.
+ while true
+ do
+ echo "Sleeping for 60 minutes at `date`..."
+ sleep 3600
+ echo "Cleanly shutting down the node so we can update S3 with the latest chaindata at `date`..."
+ kill_node
+ echo "Syncing chain data to S3 at `date`..."
+ s3_sync_up
+ echo "Restarting the node after syncing to S3 at `date`..."
+ start_node
+ done
+}
+
+# main
+
+echo "Initializing the node at `date`..."
+init_node
+echo "Syncing initial chain data with stored chain data in S3 at `date`..."
+s3_sync_down
+echo "Starting the node at `date`..."
+start_node
+echo "Starting the continuous loop at `date`..."
+continuous
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index e6740942d8..1e8c603b61 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -2028,3 +2028,54 @@ func toHexSlice(b [][]byte) []string {
}
return r
}
+
+// ---------------------------------------------------------------- FlashBots ----------------------------------------------------------------
+
+// PrivateTxBundleAPI offers an API for accepting bundled transactions
+type PrivateTxBundleAPI struct {
+ b Backend
+}
+
+// NewPrivateTxBundleAPI creates a new Tx Bundle API instance.
+func NewPrivateTxBundleAPI(b Backend) *PrivateTxBundleAPI {
+ return &PrivateTxBundleAPI{b}
+}
+
+// SendBundleArgs represents the arguments for a call.
+type SendBundleArgs struct {
+ Txs []hexutil.Bytes `json:"txs"`
+ BlockNumber rpc.BlockNumber `json:"blockNumber"`
+ MinTimestamp *uint64 `json:"minTimestamp"`
+ MaxTimestamp *uint64 `json:"maxTimestamp"`
+ RevertingTxHashes []common.Hash `json:"revertingTxHashes"`
+}
+
+// SendBundle will add the signed transaction to the transaction pool.
+// The sender is responsible for signing the transaction and using the correct nonce and ensuring validity
+func (s *PrivateTxBundleAPI) SendBundle(ctx context.Context, args SendBundleArgs) error {
+ var txs types.Transactions
+ if len(args.Txs) == 0 {
+ return errors.New("bundle missing txs")
+ }
+ if args.BlockNumber == 0 {
+ return errors.New("bundle missing blockNumber")
+ }
+
+ for _, encodedTx := range args.Txs {
+ tx := new(types.Transaction)
+ if err := tx.UnmarshalBinary(encodedTx); err != nil {
+ return err
+ }
+ txs = append(txs, tx)
+ }
+
+ var minTimestamp, maxTimestamp uint64
+ if args.MinTimestamp != nil {
+ minTimestamp = *args.MinTimestamp
+ }
+ if args.MaxTimestamp != nil {
+ maxTimestamp = *args.MaxTimestamp
+ }
+
+ return s.b.SendBundle(ctx, txs, args.BlockNumber, minTimestamp, maxTimestamp, args.RevertingTxHashes)
+}
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index 5b4ceb6310..f3966b9d01 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -75,6 +75,7 @@ type Backend interface {
// Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction) error
+ SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction
@@ -116,6 +117,9 @@ func GetAPIs(apiBackend Backend) []rpc.API {
}, {
Namespace: "personal",
Service: NewPersonalAccountAPI(apiBackend, nonceLock),
+ }, {
+ Namespace: "eth",
+ Service: NewPrivateTxBundleAPI(apiBackend),
},
}
}
diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go
index 28dc561c36..0f97467437 100644
--- a/internal/ethapi/transaction_args_test.go
+++ b/internal/ethapi/transaction_args_test.go
@@ -212,6 +212,10 @@ type backendMock struct {
config *params.ChainConfig
}
+func (b *backendMock) SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
+ return nil
+}
+
func newBackendMock() *backendMock {
config := ¶ms.ChainConfig{
ChainID: big.NewInt(42),
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index 88c31c04da..305bfac05e 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -595,6 +595,11 @@ web3._extend({
call: 'eth_getLogs',
params: 1,
}),
+ new web3._extend.Method({
+ name: 'sendBundle',
+ call: 'eth_sendBundle',
+ params: 1,
+ }),
],
properties: [
new web3._extend.Property({
diff --git a/les/api_backend.go b/les/api_backend.go
index 5b4213134b..b635a66989 100644
--- a/les/api_backend.go
+++ b/les/api_backend.go
@@ -195,6 +195,9 @@ func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
func (b *LesApiBackend) RemoveTx(txHash common.Hash) {
b.eth.txPool.RemoveTx(txHash)
}
+func (b *LesApiBackend) SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
+ return b.eth.txPool.AddMevBundle(txs, big.NewInt(blockNumber.Int64()), minTimestamp, maxTimestamp, revertingTxHashes)
+}
func (b *LesApiBackend) GetPoolTransactions() (types.Transactions, error) {
return b.eth.txPool.GetTransactions()
diff --git a/light/txpool.go b/light/txpool.go
index b3e1a62e18..7f74b9091e 100644
--- a/light/txpool.go
+++ b/light/txpool.go
@@ -550,3 +550,14 @@ func (pool *TxPool) RemoveTx(hash common.Hash) {
pool.chainDb.Delete(hash[:])
pool.relay.Discard([]common.Hash{hash})
}
+
+// MevBundles returns a list of bundles valid for the given blockNumber/blockTimestamp
+// also prunes bundles that are outdated
+func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]types.Transactions, error) {
+ return nil, nil
+}
+
+// AddMevBundle adds a mev bundle to the pool
+func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
+ return nil
+}
diff --git a/miner/miner.go b/miner/miner.go
index 155dd40000..0ac433d2f5 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -55,12 +55,13 @@ type Config struct {
Recommit time.Duration // The time interval for miner to re-create mining work.
Noverify bool // Disable remote mining solution verification(only useful in ethash).
BuilderTxSigningKey *ecdsa.PrivateKey // Signing key of builder coinbase to make transaction to validator
+ MaxMergedBundles int
}
// Miner creates blocks and searches for proof-of-work values.
type Miner struct {
mux *event.TypeMux
- worker *worker
+ worker *multiWorker
coinbase common.Address
eth Backend
engine consensus.Engine
@@ -79,7 +80,7 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even
exitCh: make(chan struct{}),
startCh: make(chan common.Address),
stopCh: make(chan struct{}),
- worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, true),
+ worker: newMultiWorker(config, chainConfig, engine, eth, mux, isLocalBlock, true),
}
miner.wg.Add(1)
go miner.update()
@@ -191,7 +192,7 @@ func (miner *Miner) SetRecommitInterval(interval time.Duration) {
// Pending returns the currently pending block and associated state.
func (miner *Miner) Pending() (*types.Block, *state.StateDB) {
- return miner.worker.pending()
+ return miner.worker.regularWorker.pending()
}
// PendingBlock returns the currently pending block.
@@ -200,7 +201,7 @@ func (miner *Miner) Pending() (*types.Block, *state.StateDB) {
// simultaneously, please use Pending(), as the pending state can
// change between multiple method calls
func (miner *Miner) PendingBlock() *types.Block {
- return miner.worker.pendingBlock()
+ return miner.worker.regularWorker.pendingBlock()
}
// PendingBlockAndReceipts returns the currently pending block and corresponding receipts.
@@ -239,7 +240,7 @@ func (miner *Miner) DisablePreseal() {
// SubscribePendingLogs starts delivering logs from pending transactions
// to the given channel.
func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscription {
- return miner.worker.pendingLogsFeed.Subscribe(ch)
+ return miner.worker.regularWorker.pendingLogsFeed.Subscribe(ch)
}
// GetSealingBlockAsync requests to generate a sealing block according to the
@@ -248,7 +249,7 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
// The difference is that if the execution fails, the returned result is nil
// and the concrete error is dropped silently.
func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (chan *types.Block, error) {
- resCh, _, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
+ resCh, _, err := miner.worker.regularWorker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
if err != nil {
return nil, err
}
@@ -259,7 +260,7 @@ func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, c
// If the generation is failed or the underlying work is already closed, an error
// will be returned.
func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (*types.Block, error) {
- resCh, errCh, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
+ resCh, errCh, err := miner.worker.regularWorker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
if err != nil {
return nil, err
}
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
new file mode 100644
index 0000000000..9a39983c5a
--- /dev/null
+++ b/miner/multi_worker.go
@@ -0,0 +1,118 @@
+package miner
+
+import (
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+type multiWorker struct {
+ workers []*worker
+ regularWorker *worker
+}
+
+func (w *multiWorker) stop() {
+ for _, worker := range w.workers {
+ worker.stop()
+ }
+}
+
+func (w *multiWorker) start() {
+ for _, worker := range w.workers {
+ worker.start()
+ }
+}
+
+func (w *multiWorker) close() {
+ for _, worker := range w.workers {
+ worker.close()
+ }
+}
+
+func (w *multiWorker) isRunning() bool {
+ for _, worker := range w.workers {
+ if worker.isRunning() {
+ return true
+ }
+ }
+ return false
+}
+
+// pendingBlockAndReceipts returns pending block and corresponding receipts from the `regularWorker`
+func (w *multiWorker) pendingBlockAndReceipts() (*types.Block, types.Receipts) {
+ // return a snapshot to avoid contention on currentMu mutex
+ return w.regularWorker.pendingBlockAndReceipts()
+}
+
+func (w *multiWorker) setGasCeil(ceil uint64) {
+ for _, worker := range w.workers {
+ worker.setGasCeil(ceil)
+ }
+}
+
+func (w *multiWorker) setExtra(extra []byte) {
+ for _, worker := range w.workers {
+ worker.setExtra(extra)
+ }
+}
+
+func (w *multiWorker) setRecommitInterval(interval time.Duration) {
+ for _, worker := range w.workers {
+ worker.setRecommitInterval(interval)
+ }
+}
+
+func (w *multiWorker) setEtherbase(addr common.Address) {
+ for _, worker := range w.workers {
+ worker.setEtherbase(addr)
+ }
+}
+
+func (w *multiWorker) enablePreseal() {
+ for _, worker := range w.workers {
+ worker.enablePreseal()
+ }
+}
+
+func (w *multiWorker) disablePreseal() {
+ for _, worker := range w.workers {
+ worker.disablePreseal()
+ }
+}
+
+func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *multiWorker {
+ queue := make(chan *task)
+
+ regularWorker := newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
+ isFlashbots: false,
+ queue: queue,
+ })
+
+ workers := []*worker{regularWorker}
+
+ for i := 1; i <= config.MaxMergedBundles; i++ {
+ workers = append(workers,
+ newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
+ isFlashbots: true,
+ queue: queue,
+ maxMergedBundles: i,
+ }))
+ }
+
+ log.Info("creating multi worker", "config.MaxMergedBundles", config.MaxMergedBundles, "worker", len(workers))
+ return &multiWorker{
+ regularWorker: regularWorker,
+ workers: workers,
+ }
+}
+
+type flashbotsData struct {
+ isFlashbots bool
+ queue chan *task
+ maxMergedBundles int
+}
diff --git a/miner/worker.go b/miner/worker.go
index 7b87860ffb..52d074c6cb 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -23,6 +23,7 @@ import (
"math/big"
"os"
+ "sort"
"strings"
"sync"
"sync/atomic"
@@ -44,7 +45,7 @@ import (
const (
// resultQueueSize is the size of channel listening to sealing result.
- resultQueueSize = 10
+ resultQueueSize = 20
// txChanSize is the size of channel listening to NewTxsEvent.
// The number is referenced from the size of tx pool.
@@ -83,6 +84,8 @@ const (
)
var (
+ errCouldNotApplyTransaction = errors.New("could not apply transaction")
+ errBundleInterrupted = errors.New("interrupt while applying bundles")
errBlockInterruptedByNewHead = errors.New("new head arrived while building block")
errBlockInterruptedByRecommit = errors.New("recommit interrupt while building block")
)
@@ -98,6 +101,7 @@ type environment struct {
tcount int // tx count in cycle
gasPool *core.GasPool // available gas used to pack transactions
coinbase common.Address
+ profit *big.Int
header *types.Header
txs []*types.Transaction
@@ -114,6 +118,7 @@ func (env *environment) copy() *environment {
family: env.family.Clone(),
tcount: env.tcount,
coinbase: env.coinbase,
+ profit: new(big.Int).Set(env.profit),
header: types.CopyHeader(env.header),
receipts: copyReceipts(env.receipts),
}
@@ -157,6 +162,10 @@ type task struct {
state *state.StateDB
block *types.Block
createdAt time.Time
+
+ profit *big.Int
+ isFlashbots bool
+ worker int
}
const (
@@ -249,6 +258,8 @@ type worker struct {
// External functions
isLocalBlock func(header *types.Header) bool // Function used to determine whether the specified block is mined by local miner.
+ flashbots *flashbotsData
+
// Test hooks
newTaskHook func(*task) // Method to call upon receiving a new sealing task.
skipSealHook func(*task) bool // Method to decide whether skipping the sealing.
@@ -256,7 +267,7 @@ type worker struct {
resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval.
}
-func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *worker {
+func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool, flashbots *flashbotsData) *worker {
var err error
var builderCoinbase common.Address
key := os.Getenv("BUILDER_TX_SIGNING_KEY") // get builder private signing key
@@ -277,6 +288,29 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
}
}
log.Info("builderCoinbase", builderCoinbase.String())
+ exitCh := make(chan struct{})
+ taskCh := make(chan *task)
+ if flashbots.isFlashbots {
+ // publish to the flashbots queue
+ taskCh = flashbots.queue
+ } else {
+ // read from the flashbots queue
+ go func() {
+ for {
+ select {
+ case flashbotsTask := <-flashbots.queue:
+ select {
+ case taskCh <- flashbotsTask:
+ case <-exitCh:
+ return
+ }
+ case <-exitCh:
+ return
+ }
+ }
+ }()
+ }
+
worker := &worker{
config: config,
chainConfig: chainConfig,
@@ -294,13 +328,14 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
newWorkCh: make(chan *newWorkReq),
getWorkCh: make(chan *getWorkReq),
- taskCh: make(chan *task),
+ taskCh: taskCh,
resultCh: make(chan *types.Block, resultQueueSize),
- exitCh: make(chan struct{}),
+ exitCh: exitCh,
startCh: make(chan struct{}, 1),
resubmitIntervalCh: make(chan time.Duration),
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
coinbase: builderCoinbase,
+ flashbots: flashbots,
}
// Subscribe NewTxsEvent for tx pool
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
@@ -315,11 +350,15 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
recommit = minRecommitInterval
}
- worker.wg.Add(4)
+ worker.wg.Add(2)
go worker.mainLoop()
go worker.newWorkLoop(recommit)
- go worker.resultLoop()
- go worker.taskLoop()
+ if !flashbots.isFlashbots {
+ // only mine if not flashbots
+ worker.wg.Add(2)
+ go worker.resultLoop()
+ go worker.taskLoop()
+ }
// Submit first work to initialize pending state.
if init {
@@ -660,6 +699,9 @@ func (w *worker) taskLoop() {
var (
stopCh chan struct{}
prev common.Hash
+
+ prevParentHash common.Hash
+ prevProfit *big.Int
)
// interrupt aborts the in-flight sealing task.
@@ -680,10 +722,20 @@ func (w *worker) taskLoop() {
if sealHash == prev {
continue
}
+
+ taskParentHash := task.block.Header().ParentHash
+ // reject new tasks which don't profit
+ if taskParentHash == prevParentHash &&
+ prevProfit != nil && task.profit.Cmp(prevProfit) < 0 {
+ continue
+ }
+ prevParentHash = taskParentHash
+ prevProfit = task.profit
+
// Interrupt previous sealing operation
interrupt()
stopCh, prev = make(chan struct{}), sealHash
-
+ log.Info("Proposed miner block", "blockNumber", task.block.Number(), "profit", ethIntToFloat(prevProfit), "isFlashbots", task.isFlashbots, "sealhash", sealHash, "parentHash", prevParentHash, "worker", task.worker)
if w.skipSealHook != nil && w.skipSealHook(task) {
continue
}
@@ -796,6 +848,7 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header, coinbase com
family: mapset.NewSet(),
header: header,
uncles: make(map[common.Hash]*types.Header),
+ profit: new(big.Int),
}
// when 08 is processed ancestors contain 07 (quick block)
for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) {
@@ -807,6 +860,7 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header, coinbase com
}
// Keep track of transactions which return errors so they can be removed
env.tcount = 0
+ env.gasPool = new(core.GasPool).AddGas(header.GasLimit)
return env, nil
}
@@ -851,6 +905,11 @@ func (w *worker) updateSnapshot(env *environment) {
func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) {
snap := env.state.Snapshot()
+ gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee)
+ if err != nil {
+ return nil, err
+ }
+
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig())
if err != nil {
env.state.RevertToSnapshot(snap)
@@ -859,9 +918,120 @@ func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*
env.txs = append(env.txs, tx)
env.receipts = append(env.receipts, receipt)
+ gasUsed := new(big.Int).SetUint64(receipt.GasUsed)
+ env.profit.Add(env.profit, gasUsed.Mul(gasUsed, gasPrice))
+
return receipt.Logs, nil
}
+func (w *worker) commitBundle(env *environment, txs types.Transactions, interrupt *int32) error {
+ gasLimit := env.header.GasLimit
+ if env.gasPool == nil {
+ env.gasPool = new(core.GasPool).AddGas(gasLimit)
+ }
+
+ var coalescedLogs []*types.Log
+
+ for _, tx := range txs {
+ // In the following three cases, we will interrupt the execution of the transaction.
+ // (1) new head block event arrival, the interrupt signal is 1
+ // (2) worker start or restart, the interrupt signal is 1
+ // (3) worker recreate the sealing block with any newly arrived transactions, the interrupt signal is 2.
+ // For the first two cases, the semi-finished work will be discarded.
+ // For the third case, the semi-finished work will be submitted to the consensus engine.
+ if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone {
+ // Notify resubmit loop to increase resubmitting interval due to too frequent commits.
+ if atomic.LoadInt32(interrupt) == commitInterruptResubmit {
+ ratio := float64(gasLimit-env.gasPool.Gas()) / float64(gasLimit)
+ if ratio < 0.1 {
+ ratio = 0.1
+ }
+ w.resubmitAdjustCh <- &intervalAdjust{
+ ratio: ratio,
+ inc: true,
+ }
+ }
+ return errBundleInterrupted
+ }
+ // If we don't have enough gas for any further transactions then we're done
+ if env.gasPool.Gas() < params.TxGas {
+ log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
+ break
+ }
+
+ // Error may be ignored here. The error has already been checked
+ // during transaction acceptance is the transaction pool.
+ //
+ // We use the eip155 signer regardless of the current hf.
+ from, _ := types.Sender(env.signer, tx)
+ // Check whether the tx is replay protected. If we're not in the EIP155 hf
+ // phase, start ignoring the sender until we do.
+ if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
+ log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
+ return errCouldNotApplyTransaction
+ }
+ // Start executing the transaction
+ env.state.Prepare(tx.Hash(), env.tcount)
+
+ logs, err := w.commitTransaction(env, tx)
+ switch {
+ case errors.Is(err, core.ErrGasLimitReached):
+ // Pop the current out-of-gas transaction without shifting in the next from the account
+ log.Trace("Gas limit exceeded for current block", "sender", from)
+ return errCouldNotApplyTransaction
+
+ case errors.Is(err, core.ErrNonceTooLow):
+ // New head notification data race between the transaction pool and miner, shift
+ log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
+ return errCouldNotApplyTransaction
+
+ case errors.Is(err, core.ErrNonceTooHigh):
+ // Reorg notification data race between the transaction pool and miner, skip account =
+ log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
+ return errCouldNotApplyTransaction
+
+ case errors.Is(err, nil):
+ // Everything ok, collect the logs and shift in the next transaction from the same account
+ coalescedLogs = append(coalescedLogs, logs...)
+ env.tcount++
+ continue
+
+ case errors.Is(err, core.ErrTxTypeNotSupported):
+ // Pop the unsupported transaction without shifting in the next from the account
+ log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
+ return errCouldNotApplyTransaction
+
+ default:
+ // Strange error, discard the transaction and get the next in line (note, the
+ // nonce-too-high clause will prevent us from executing in vain).
+ log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
+ return errCouldNotApplyTransaction
+ }
+ }
+
+ if !w.isRunning() && len(coalescedLogs) > 0 {
+ // We don't push the pendingLogsEvent while we are sealing. The reason is that
+ // when we are sealing, the worker will regenerate a sealing block every 3 seconds.
+ // In order to avoid pushing the repeated pendingLog, we disable the pending log pushing.
+
+ // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined
+ // logs by filling in the block hash when the block was mined by the local miner. This can
+ // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed.
+ cpy := make([]*types.Log, len(coalescedLogs))
+ for i, l := range coalescedLogs {
+ cpy[i] = new(types.Log)
+ *cpy[i] = *l
+ }
+ w.pendingLogsFeed.Send(cpy)
+ }
+ // Notify resubmit loop to decrease resubmitting interval if current interval is larger
+ // than the user-specified one.
+ if interrupt != nil {
+ w.resubmitAdjustCh <- &intervalAdjust{inc: false}
+ }
+ return nil
+}
+
func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *int32) error {
gasLimit := env.header.GasLimit
if env.gasPool == nil {
@@ -1100,6 +1270,27 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
return err
}
}
+ if w.flashbots.isFlashbots {
+ bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
+ if err != nil {
+ log.Error("Failed to fetch pending transactions", "err", err)
+ return err
+ }
+
+ bundleTxs, bundle, numBundles, err := w.generateFlashbotsBundle(env, bundles, pending)
+ if err != nil {
+ log.Error("Failed to generate flashbots bundle", "err", err)
+ return err
+ }
+ log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(bundle.totalEth), "gasUsed", bundle.totalGasUsed, "bundleScore", bundle.mevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles)
+ if len(bundleTxs) == 0 {
+ return errors.New("no bundles to apply")
+ }
+ if err := w.commitBundle(env, bundleTxs, interrupt); err != nil {
+ return err
+ }
+ env.profit.Add(env.profit, bundle.totalEth)
+ }
if len(localTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
@@ -1226,7 +1417,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) {
// Fill pending transactions from the txpool
err = w.fillTransactions(interrupt, work, nil)
- if errors.Is(err, errBlockInterruptedByNewHead) {
+ if err != nil && !errors.Is(err, errBlockInterruptedByRecommit) {
work.discard()
return
}
@@ -1259,13 +1450,12 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
// If we're post merge, just ignore
if !w.isTTDReached(block.Header()) {
select {
- case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now()}:
+ case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now(), profit: env.profit, isFlashbots: w.flashbots.isFlashbots, worker: w.flashbots.maxMergedBundles}:
w.unconfirmed.Shift(block.NumberU64() - 1)
log.Info("Commit new sealing work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
- "uncles", len(env.uncles), "txs", env.tcount,
- "gas", block.GasUsed(), "fees", totalFees(block, env.receipts),
- "elapsed", common.PrettyDuration(time.Since(start)))
-
+ "uncles", len(env.uncles), "txs", env.tcount, "gas", block.GasUsed(), "fees", totalFees(block, env.receipts),
+ "profit", ethIntToFloat(env.profit), "elapsed", common.PrettyDuration(time.Since(start)),
+ "isFlashbots", w.flashbots.isFlashbots, "worker", w.flashbots.maxMergedBundles)
case <-w.exitCh:
log.Info("Worker has exited")
}
@@ -1315,6 +1505,195 @@ func (w *worker) isTTDReached(header *types.Header) bool {
return td != nil && ttd != nil && td.Cmp(ttd) >= 0
}
+type simulatedBundle struct {
+ mevGasPrice *big.Int
+ totalEth *big.Int
+ ethSentToCoinbase *big.Int
+ totalGasUsed uint64
+ originalBundle types.MevBundle
+}
+
+func (w *worker) generateFlashbotsBundle(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, int, error) {
+ simulatedBundles, err := w.simulateBundles(env, bundles, pendingTxs)
+ if err != nil {
+ return nil, simulatedBundle{}, 0, err
+ }
+
+ sort.SliceStable(simulatedBundles, func(i, j int) bool {
+ return simulatedBundles[j].mevGasPrice.Cmp(simulatedBundles[i].mevGasPrice) < 0
+ })
+
+ return w.mergeBundles(env, simulatedBundles, pendingTxs)
+}
+
+func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, int, error) {
+ finalBundle := types.Transactions{}
+
+ currentState := env.state.Copy()
+ gasPool := new(core.GasPool).AddGas(env.header.GasLimit)
+
+ var prevState *state.StateDB
+ var prevGasPool *core.GasPool
+
+ mergedBundle := simulatedBundle{
+ totalEth: new(big.Int),
+ ethSentToCoinbase: new(big.Int),
+ }
+
+ count := 0
+ for _, bundle := range bundles {
+ prevState = currentState.Copy()
+ prevGasPool = new(core.GasPool).AddGas(gasPool.Gas())
+
+ // the floor gas price is 99/100 what was simulated at the top of the block
+ floorGasPrice := new(big.Int).Mul(bundle.mevGasPrice, big.NewInt(99))
+ floorGasPrice = floorGasPrice.Div(floorGasPrice, big.NewInt(100))
+
+ simmed, err := w.computeBundleGas(env, bundle.originalBundle, currentState, gasPool, pendingTxs, len(finalBundle))
+ if err != nil || simmed.mevGasPrice.Cmp(floorGasPrice) <= 0 {
+ currentState = prevState
+ gasPool = prevGasPool
+ continue
+ }
+
+ log.Info("Included bundle", "ethToCoinbase", ethIntToFloat(simmed.totalEth), "gasUsed", simmed.totalGasUsed, "bundleScore", simmed.mevGasPrice, "bundleLength", len(simmed.originalBundle.Txs), "worker", w.flashbots.maxMergedBundles)
+
+ finalBundle = append(finalBundle, bundle.originalBundle.Txs...)
+ mergedBundle.totalEth.Add(mergedBundle.totalEth, simmed.totalEth)
+ mergedBundle.ethSentToCoinbase.Add(mergedBundle.ethSentToCoinbase, simmed.ethSentToCoinbase)
+ mergedBundle.totalGasUsed += simmed.totalGasUsed
+ count++
+
+ if count >= w.flashbots.maxMergedBundles {
+ break
+ }
+ }
+
+ if len(finalBundle) == 0 || count != w.flashbots.maxMergedBundles {
+ return nil, simulatedBundle{}, count, nil
+ }
+
+ return finalBundle, simulatedBundle{
+ mevGasPrice: new(big.Int).Div(mergedBundle.totalEth, new(big.Int).SetUint64(mergedBundle.totalGasUsed)),
+ totalEth: mergedBundle.totalEth,
+ ethSentToCoinbase: mergedBundle.ethSentToCoinbase,
+ totalGasUsed: mergedBundle.totalGasUsed,
+ }, count, nil
+}
+
+func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) ([]simulatedBundle, error) {
+ simulatedBundles := []simulatedBundle{}
+
+ for _, bundle := range bundles {
+ state := env.state.Copy()
+ gasPool := new(core.GasPool).AddGas(env.header.GasLimit)
+ if len(bundle.Txs) == 0 {
+ continue
+ }
+ simmed, err := w.computeBundleGas(env, bundle, state, gasPool, pendingTxs, 0)
+
+ if err != nil {
+ log.Debug("Error computing gas for a bundle", "error", err)
+ continue
+ }
+ simulatedBundles = append(simulatedBundles, simmed)
+ }
+
+ return simulatedBundles, nil
+}
+
+func containsHash(arr []common.Hash, match common.Hash) bool {
+ for _, elem := range arr {
+ if elem == match {
+ return true
+ }
+ }
+ return false
+}
+
+// Compute the adjusted gas price for a whole bundle
+// Done by calculating all gas spent, adding transfers to the coinbase, and then dividing by gas used
+func (w *worker) computeBundleGas(env *environment, bundle types.MevBundle, state *state.StateDB, gasPool *core.GasPool, pendingTxs map[common.Address]types.Transactions, currentTxCount int) (simulatedBundle, error) {
+ var totalGasUsed uint64 = 0
+ var tempGasUsed uint64
+ gasFees := new(big.Int)
+
+ ethSentToCoinbase := new(big.Int)
+
+ for i, tx := range bundle.Txs {
+ if env.header.BaseFee != nil && tx.Type() == 2 {
+ // Sanity check for extremely large numbers
+ if tx.GasFeeCap().BitLen() > 256 {
+ return simulatedBundle{}, core.ErrFeeCapVeryHigh
+ }
+ if tx.GasTipCap().BitLen() > 256 {
+ return simulatedBundle{}, core.ErrTipVeryHigh
+ }
+ // Ensure gasFeeCap is greater than or equal to gasTipCap.
+ if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
+ return simulatedBundle{}, core.ErrTipAboveFeeCap
+ }
+ }
+
+ state.Prepare(tx.Hash(), i+currentTxCount)
+ coinbaseBalanceBefore := state.GetBalance(env.coinbase)
+
+ receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, gasPool, state, env.header, tx, &tempGasUsed, *w.chain.GetVMConfig())
+ if err != nil {
+ return simulatedBundle{}, err
+ }
+ if receipt.Status == types.ReceiptStatusFailed && !containsHash(bundle.RevertingTxHashes, receipt.TxHash) {
+ return simulatedBundle{}, errors.New("failed tx")
+ }
+
+ totalGasUsed += receipt.GasUsed
+
+ from, err := types.Sender(env.signer, tx)
+ if err != nil {
+ return simulatedBundle{}, err
+ }
+
+ txInPendingPool := false
+ if accountTxs, ok := pendingTxs[from]; ok {
+ // check if tx is in pending pool
+ txNonce := tx.Nonce()
+
+ for _, accountTx := range accountTxs {
+ if accountTx.Nonce() == txNonce {
+ txInPendingPool = true
+ break
+ }
+ }
+ }
+
+ gasUsed := new(big.Int).SetUint64(receipt.GasUsed)
+ gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee)
+ if err != nil {
+ return simulatedBundle{}, err
+ }
+ gasFeesTx := gasUsed.Mul(gasUsed, gasPrice)
+ coinbaseBalanceAfter := state.GetBalance(env.coinbase)
+ coinbaseDelta := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
+ coinbaseDelta.Sub(coinbaseDelta, gasFeesTx)
+ ethSentToCoinbase.Add(ethSentToCoinbase, coinbaseDelta)
+
+ if !txInPendingPool {
+ // If tx is not in pending pool, count the gas fees
+ gasFees.Add(gasFees, gasFeesTx)
+ }
+ }
+
+ totalEth := new(big.Int).Add(ethSentToCoinbase, gasFees)
+
+ return simulatedBundle{
+ mevGasPrice: new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)),
+ totalEth: totalEth,
+ ethSentToCoinbase: ethSentToCoinbase,
+ totalGasUsed: totalGasUsed,
+ originalBundle: bundle,
+ }, nil
+}
+
// copyReceipts makes a deep copy of the given receipts.
func copyReceipts(receipts []*types.Receipt) []*types.Receipt {
result := make([]*types.Receipt, len(receipts))
@@ -1333,6 +1712,14 @@ func (w *worker) postSideBlock(event core.ChainSideEvent) {
}
}
+// ethIntToFloat is for formatting a big.Int in wei to eth
+func ethIntToFloat(eth *big.Int) *big.Float {
+ if eth == nil {
+ return big.NewFloat(0)
+ }
+ return new(big.Float).Quo(new(big.Float).SetInt(eth), new(big.Float).SetInt(big.NewInt(params.Ether)))
+}
+
// totalFees computes total consumed miner fees in ETH. Block transactions and receipts have to have the same order.
func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float {
feesWei := new(big.Int)
@@ -1340,7 +1727,7 @@ func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float {
minerFee, _ := tx.EffectiveGasTip(block.BaseFee())
feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), minerFee))
}
- return new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether)))
+ return ethIntToFloat(feesWei)
}
func (w *worker) createProposerPayoutTx(env *environment, recipient *common.Address, profit *big.Int) (*types.Transaction, error) {
diff --git a/miner/worker_test.go b/miner/worker_test.go
index 6bd3e13842..9d94ac4c88 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -201,7 +201,10 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) {
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
backend.txPool.AddLocals(pendingTxs)
- w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false)
+ w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, &flashbotsData{
+ isFlashbots: false,
+ queue: nil,
+ })
w.setEtherbase(testBankAddress)
return w, backend
}
From 7c424dfeecf2fcac0f7a1d03e4f998202f246814 Mon Sep 17 00:00:00 2001
From: Ivan Bogatyy
Date: Wed, 27 Oct 2021 16:08:07 -0400
Subject: [PATCH 22/83] Flashbots changes v0.3 to v0.4
---
cmd/geth/main.go | 3 +-
cmd/utils/flags.go | 29 ++++++++-
core/tx_pool.go | 62 +++++++++++++++++--
eth/api_backend.go | 4 ++
go.sum | 1 +
internal/ethapi/api.go | 76 +++++++++++++++++++++++-
internal/ethapi/backend.go | 1 +
internal/ethapi/transaction_args_test.go | 4 ++
internal/web3ext/web3ext.go | 5 ++
les/api_backend.go | 5 ++
miner/miner.go | 1 +
miner/multi_worker.go | 27 ++++++---
miner/worker.go | 51 +++++++++++++---
13 files changed, 244 insertions(+), 25 deletions(-)
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 2f8f196774..3abfc5954a 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -133,7 +133,8 @@ var (
utils.MinerExtraDataFlag,
utils.MinerRecommitIntervalFlag,
utils.MinerNoVerifyFlag,
- utils.MinerMaxMergedBundles,
+ utils.MinerMaxMergedBundlesFlag,
+ utils.MinerTrustedRelaysFlag,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 047387a558..1c8244dc14 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -562,12 +562,18 @@ var (
Usage: "Disable remote sealing verification",
Category: flags.MinerCategory,
}
- MinerMaxMergedBundles = &cli.IntFlag{
+ MinerMaxMergedBundlesFlag = &cli.IntFlag{
Name: "miner.maxmergedbundles",
Usage: "flashbots - The maximum amount of bundles to merge. The miner will run this many workers in parallel to calculate if the full block is more profitable with these additional bundles.",
Value: 3,
Category: flags.MinerCategory,
}
+ MinerTrustedRelaysFlag = &cli.StringFlag{
+ Name: "miner.trustedrelays",
+ Usage: "flashbots - The Ethereum addresses of trusted relays for signature verification. The miner will accept signed bundles and other tasks from the relay, being reasonably certain about DDoS safety.",
+ Value: "0x870e2734DdBe2Fba9864f33f3420d59Bc641f2be",
+ Category: flags.MinerCategory,
+ }
// Account settings
UnlockedAccountFlag = &cli.StringFlag{
@@ -1668,6 +1674,15 @@ func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
if ctx.IsSet(TxPoolLifetimeFlag.Name) {
cfg.Lifetime = ctx.Duration(TxPoolLifetimeFlag.Name)
}
+
+ addresses := strings.Split(ctx.String(MinerTrustedRelaysFlag.Name), ",")
+ for _, address := range addresses {
+ if trimmed := strings.TrimSpace(address); !common.IsHexAddress(trimmed) {
+ Fatalf("Invalid account in --miner.trustedrelays: %s", trimmed)
+ } else {
+ cfg.TrustedRelays = append(cfg.TrustedRelays, common.HexToAddress(trimmed))
+ }
+ }
}
func setEthash(ctx *cli.Context, cfg *ethconfig.Config) {
@@ -1721,7 +1736,17 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
log.Warn("The generic --miner.gastarget flag is deprecated and will be removed in the future!")
}
- cfg.MaxMergedBundles = ctx.Int(MinerMaxMergedBundles.Name)
+ cfg.MaxMergedBundles = ctx.Int(MinerMaxMergedBundlesFlag.Name)
+
+ addresses := strings.Split(ctx.String(MinerTrustedRelaysFlag.Name), ",")
+ for _, address := range addresses {
+ if trimmed := strings.TrimSpace(address); !common.IsHexAddress(trimmed) {
+ Fatalf("Invalid account in --miner.trustedrelays: %s", trimmed)
+ } else {
+ cfg.TrustedRelays = append(cfg.TrustedRelays, common.HexToAddress(trimmed))
+ }
+ }
+ log.Info("Trusted relays set as", "addresses", cfg.TrustedRelays)
}
func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
diff --git a/core/tx_pool.go b/core/tx_pool.go
index ad1dd77605..2c0d3d6b29 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -165,6 +165,8 @@ type TxPoolConfig struct {
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
+
+ TrustedRelays []common.Address // Trusted relay addresses. Duplicated from the miner config.
}
// DefaultTxPoolConfig contains the default configurations for the transaction
@@ -251,12 +253,13 @@ type TxPool struct {
locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk
- pending map[common.Address]*txList // All currently processable transactions
- queue map[common.Address]*txList // Queued but non-processable transactions
- beats map[common.Address]time.Time // Last heartbeat from each known account
- mevBundles []types.MevBundle
- all *txLookup // All transactions to allow lookups
- priced *txPricedList // All transactions sorted by price
+ pending map[common.Address]*txList // All currently processable transactions
+ queue map[common.Address]*txList // Queued but non-processable transactions
+ beats map[common.Address]time.Time // Last heartbeat from each known account
+ mevBundles []types.MevBundle
+ megabundles map[common.Address]types.MevBundle // One megabundle per each trusted relay
+ all *txLookup // All transactions to allow lookups
+ priced *txPricedList // All transactions sorted by price
chainHeadCh chan ChainHeadEvent
chainHeadSub event.Subscription
@@ -290,6 +293,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
pending: make(map[common.Address]*txList),
queue: make(map[common.Address]*txList),
beats: make(map[common.Address]time.Time),
+ megabundles: make(map[common.Address]types.MevBundle),
all: newTxLookup(),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
reqResetCh: make(chan *txpoolResetRequest),
@@ -611,6 +615,52 @@ func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, m
return nil
}
+// AddMegaBundle adds a megabundle to the pool. Assumes the relay signature has been verified already.
+func (pool *TxPool) AddMegabundle(relayAddr common.Address, txs types.Transactions, blockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ fromTrustedRelay := false
+ for _, trustedAddr := range pool.config.TrustedRelays {
+ if relayAddr == trustedAddr {
+ fromTrustedRelay = true
+ }
+ }
+ if !fromTrustedRelay {
+ return errors.New("megabundle from non-trusted address")
+ }
+
+ pool.megabundles[relayAddr] = types.MevBundle{
+ Txs: txs,
+ BlockNumber: blockNumber,
+ MinTimestamp: minTimestamp,
+ MaxTimestamp: maxTimestamp,
+ RevertingTxHashes: revertingTxHashes,
+ }
+ return nil
+}
+
+// GetMegabundle returns the latest megabundle submitted by a given relay.
+func (pool *TxPool) GetMegabundle(relayAddr common.Address, blockNumber *big.Int, blockTimestamp uint64) (types.MevBundle, error) {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ megabundle, ok := pool.megabundles[relayAddr]
+ if !ok {
+ return types.MevBundle{}, errors.New("No megabundle found")
+ }
+ if megabundle.BlockNumber.Cmp(blockNumber) != 0 {
+ return types.MevBundle{}, errors.New("Megabundle does not fit blockNumber constraints")
+ }
+ if megabundle.MinTimestamp != 0 && megabundle.MinTimestamp > blockTimestamp {
+ return types.MevBundle{}, errors.New("Megabundle does not fit minTimestamp constraints")
+ }
+ if megabundle.MaxTimestamp != 0 && megabundle.MaxTimestamp < blockTimestamp {
+ return types.MevBundle{}, errors.New("Megabundle does not fit maxTimestamp constraints")
+ }
+ return megabundle, nil
+}
+
// Locals retrieves the accounts currently considered local by the pool.
func (pool *TxPool) Locals() []common.Address {
pool.mu.Lock()
diff --git a/eth/api_backend.go b/eth/api_backend.go
index ee7129a299..f3bc2275d4 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -254,6 +254,10 @@ func (b *EthAPIBackend) SendBundle(ctx context.Context, txs types.Transactions,
return b.eth.txPool.AddMevBundle(txs, big.NewInt(blockNumber.Int64()), minTimestamp, maxTimestamp, revertingTxHashes)
}
+func (b *EthAPIBackend) SendMegabundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash, relayAddr common.Address) error {
+ return b.eth.txPool.AddMegabundle(relayAddr, txs, big.NewInt(blockNumber.Int64()), minTimestamp, maxTimestamp, revertingTxHashes)
+}
+
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
pending := b.eth.txPool.Pending(false)
var txs types.Transactions
diff --git a/go.sum b/go.sum
index 0b421010ec..593b682edc 100644
--- a/go.sum
+++ b/go.sum
@@ -650,6 +650,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 1e8c603b61..68d1948ebf 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -2041,7 +2041,7 @@ func NewPrivateTxBundleAPI(b Backend) *PrivateTxBundleAPI {
return &PrivateTxBundleAPI{b}
}
-// SendBundleArgs represents the arguments for a call.
+// SendBundleArgs represents the arguments for a SendBundle call.
type SendBundleArgs struct {
Txs []hexutil.Bytes `json:"txs"`
BlockNumber rpc.BlockNumber `json:"blockNumber"`
@@ -2050,6 +2050,25 @@ type SendBundleArgs struct {
RevertingTxHashes []common.Hash `json:"revertingTxHashes"`
}
+// SendMegabundleArgs represents the arguments for a SendMegabundle call.
+type SendMegabundleArgs struct {
+ Txs []hexutil.Bytes `json:"txs"`
+ BlockNumber uint64 `json:"blockNumber"`
+ MinTimestamp *uint64 `json:"minTimestamp"`
+ MaxTimestamp *uint64 `json:"maxTimestamp"`
+ RevertingTxHashes []common.Hash `json:"revertingTxHashes"`
+ RelaySignature hexutil.Bytes `json:"relaySignature"`
+}
+
+// UnsignedMegabundle is used for serialization and subsequent digital signing.
+type UnsignedMegabundle struct {
+ Txs []hexutil.Bytes
+ BlockNumber uint64
+ MinTimestamp uint64
+ MaxTimestamp uint64
+ RevertingTxHashes []common.Hash
+}
+
// SendBundle will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce and ensuring validity
func (s *PrivateTxBundleAPI) SendBundle(ctx context.Context, args SendBundleArgs) error {
@@ -2079,3 +2098,58 @@ func (s *PrivateTxBundleAPI) SendBundle(ctx context.Context, args SendBundleArgs
return s.b.SendBundle(ctx, txs, args.BlockNumber, minTimestamp, maxTimestamp, args.RevertingTxHashes)
}
+
+// Recovers the Ethereum address of the trusted relay that signed the megabundle.
+func RecoverRelayAddress(args SendMegabundleArgs) (common.Address, error) {
+ megabundle := UnsignedMegabundle{Txs: args.Txs, BlockNumber: args.BlockNumber, RevertingTxHashes: args.RevertingTxHashes}
+ if args.MinTimestamp != nil {
+ megabundle.MinTimestamp = *args.MinTimestamp
+ } else {
+ megabundle.MinTimestamp = 0
+ }
+ if args.MaxTimestamp != nil {
+ megabundle.MaxTimestamp = *args.MaxTimestamp
+ } else {
+ megabundle.MaxTimestamp = 0
+ }
+ rlpEncoding, _ := rlp.EncodeToBytes(megabundle)
+ signature := args.RelaySignature
+ signature[64] -= 27 // account for Ethereum V
+ recoveredPubkey, err := crypto.SigToPub(accounts.TextHash(rlpEncoding), args.RelaySignature)
+ if err != nil {
+ return common.Address{}, err
+ }
+ return crypto.PubkeyToAddress(*recoveredPubkey), nil
+}
+
+// SendMegabundle will add the signed megabundle to one of the workers for evaluation.
+func (s *PrivateTxBundleAPI) SendMegabundle(ctx context.Context, args SendMegabundleArgs) error {
+ log.Info("Received a Megabundle request", "signature", args.RelaySignature)
+ var txs types.Transactions
+ if len(args.Txs) == 0 {
+ return errors.New("megabundle missing txs")
+ }
+ if args.BlockNumber == 0 {
+ return errors.New("megabundle missing blockNumber")
+ }
+ for _, encodedTx := range args.Txs {
+ tx := new(types.Transaction)
+ if err := tx.UnmarshalBinary(encodedTx); err != nil {
+ return err
+ }
+ txs = append(txs, tx)
+ }
+ var minTimestamp, maxTimestamp uint64
+ if args.MinTimestamp != nil {
+ minTimestamp = *args.MinTimestamp
+ }
+ if args.MaxTimestamp != nil {
+ maxTimestamp = *args.MaxTimestamp
+ }
+ relayAddr, err := RecoverRelayAddress(args)
+ log.Info("Megabundle", "relayAddr", relayAddr, "err", err)
+ if err != nil {
+ return err
+ }
+ return s.b.SendMegabundle(ctx, txs, rpc.BlockNumber(args.BlockNumber), minTimestamp, maxTimestamp, args.RevertingTxHashes, relayAddr)
+}
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index f3966b9d01..dadf96509b 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -76,6 +76,7 @@ type Backend interface {
// Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction) error
SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error
+ SendMegabundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash, relayAddr common.Address) error
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction
diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go
index 0f97467437..b7c5b050af 100644
--- a/internal/ethapi/transaction_args_test.go
+++ b/internal/ethapi/transaction_args_test.go
@@ -212,6 +212,10 @@ type backendMock struct {
config *params.ChainConfig
}
+func (b *backendMock) SendMegabundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash, relayAddr common.Address) error {
+ return nil
+}
+
func (b *backendMock) SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
return nil
}
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index 305bfac05e..4ce1f84f22 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -600,6 +600,11 @@ web3._extend({
call: 'eth_sendBundle',
params: 1,
}),
+ new web3._extend.Method({
+ name: 'sendMegabundle',
+ call: 'eth_sendMegabundle',
+ params: 1
+ }),
],
properties: [
new web3._extend.Property({
diff --git a/les/api_backend.go b/les/api_backend.go
index b635a66989..5b327559f4 100644
--- a/les/api_backend.go
+++ b/les/api_backend.go
@@ -195,10 +195,15 @@ func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
func (b *LesApiBackend) RemoveTx(txHash common.Hash) {
b.eth.txPool.RemoveTx(txHash)
}
+
func (b *LesApiBackend) SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
return b.eth.txPool.AddMevBundle(txs, big.NewInt(blockNumber.Int64()), minTimestamp, maxTimestamp, revertingTxHashes)
}
+func (b *LesApiBackend) SendMegabundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash, relayAddr common.Address) error {
+ return nil
+}
+
func (b *LesApiBackend) GetPoolTransactions() (types.Transactions, error) {
return b.eth.txPool.GetTransactions()
}
diff --git a/miner/miner.go b/miner/miner.go
index 0ac433d2f5..55bd3da960 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -56,6 +56,7 @@ type Config struct {
Noverify bool // Disable remote mining solution verification(only useful in ethash).
BuilderTxSigningKey *ecdsa.PrivateKey // Signing key of builder coinbase to make transaction to validator
MaxMergedBundles int
+ TrustedRelays []common.Address `toml:",omitempty"` // Trusted relay addresses to receive tasks from.
}
// Miner creates blocks and searches for proof-of-work values.
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index 9a39983c5a..050ea38af4 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -98,13 +98,24 @@ func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine cons
for i := 1; i <= config.MaxMergedBundles; i++ {
workers = append(workers,
newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
- isFlashbots: true,
- queue: queue,
- maxMergedBundles: i,
+ isFlashbots: true,
+ isMegabundleWorker: false,
+ queue: queue,
+ maxMergedBundles: i,
}))
}
- log.Info("creating multi worker", "config.MaxMergedBundles", config.MaxMergedBundles, "worker", len(workers))
+ for i := 0; i < len(config.TrustedRelays); i++ {
+ workers = append(workers,
+ newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
+ isFlashbots: true,
+ isMegabundleWorker: true,
+ queue: queue,
+ relayAddr: config.TrustedRelays[i],
+ }))
+ }
+
+ log.Info("creating multi worker", "config.MaxMergedBundles", config.MaxMergedBundles, "config.TrustedRelays", config.TrustedRelays, "worker", len(workers))
return &multiWorker{
regularWorker: regularWorker,
workers: workers,
@@ -112,7 +123,9 @@ func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine cons
}
type flashbotsData struct {
- isFlashbots bool
- queue chan *task
- maxMergedBundles int
+ isFlashbots bool
+ isMegabundleWorker bool
+ queue chan *task
+ maxMergedBundles int
+ relayAddr common.Address
}
diff --git a/miner/worker.go b/miner/worker.go
index 52d074c6cb..91c492198b 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -163,9 +163,10 @@ type task struct {
block *types.Block
createdAt time.Time
- profit *big.Int
- isFlashbots bool
- worker int
+ profit *big.Int
+ isFlashbots bool
+ worker int
+ isMegabundle bool
}
const (
@@ -735,7 +736,7 @@ func (w *worker) taskLoop() {
// Interrupt previous sealing operation
interrupt()
stopCh, prev = make(chan struct{}), sealHash
- log.Info("Proposed miner block", "blockNumber", task.block.Number(), "profit", ethIntToFloat(prevProfit), "isFlashbots", task.isFlashbots, "sealhash", sealHash, "parentHash", prevParentHash, "worker", task.worker)
+ log.Info("Proposed miner block", "blockNumber", task.block.Number(), "profit", ethIntToFloat(prevProfit), "isFlashbots", task.isFlashbots, "sealhash", sealHash, "parentHash", prevParentHash, "worker", task.worker, "isMegabundle", task.isMegabundle)
if w.skipSealHook != nil && w.skipSealHook(task) {
continue
}
@@ -1270,7 +1271,7 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
return err
}
}
- if w.flashbots.isFlashbots {
+ if w.flashbots.isFlashbots && !w.flashbots.isMegabundleWorker {
bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
if err != nil {
log.Error("Failed to fetch pending transactions", "err", err)
@@ -1289,8 +1290,42 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
if err := w.commitBundle(env, bundleTxs, interrupt); err != nil {
return err
}
- env.profit.Add(env.profit, bundle.totalEth)
+ env.profit.Add(env.profit, bundle.ethSentToCoinbase)
}
+ if w.flashbots.isMegabundleWorker {
+ megabundle, err := w.eth.TxPool().GetMegabundle(w.flashbots.relayAddr, env.header.Number, env.header.Time)
+ log.Info("Starting to process a Megabundle", "relay", w.flashbots.relayAddr, "megabundle", megabundle, "error", err)
+ if err != nil {
+ return err // no valid megabundle for this relay, nothing to do
+ }
+ // Flashbots bundle merging duplicates work by simulating TXes and then committing them once more.
+ // Megabundles API focuses on speed and runs everything in one cycle.
+ coinbaseBalanceBefore := env.state.GetBalance(env.coinbase)
+ if err := w.commitBundle(env, megabundle.Txs, interrupt); err != nil {
+ log.Info("Could not commit a Megabundle", "relay", w.flashbots.relayAddr, "megabundle", megabundle, "err", err)
+ return err
+ }
+ var txStatuses = map[common.Hash]bool{}
+ for _, receipt := range env.receipts {
+ txStatuses[receipt.TxHash] = receipt.Status == types.ReceiptStatusSuccessful
+ }
+ for _, tx := range megabundle.Txs {
+ status, ok := txStatuses[tx.Hash()]
+ if !ok {
+ log.Error("No TX receipt after megabundle simulation", "TxHash", tx.Hash())
+ return errors.New("no tx receipt after megabundle simulation")
+ }
+ if !status && !containsHash(megabundle.RevertingTxHashes, tx.Hash()) {
+ log.Info("Ignoring megabundle because of failing TX", "relay", w.flashbots.relayAddr, "TxHash", tx.Hash())
+ return errors.New("megabundle contains failing tx")
+ }
+ }
+ coinbaseBalanceAfter := env.state.GetBalance(env.coinbase)
+ coinbaseDelta := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
+ env.profit = coinbaseDelta
+ log.Info("Megabundle processed", "relay", w.flashbots.relayAddr, "totalProfit", ethIntToFloat(env.profit))
+ }
+
if len(localTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
@@ -1450,12 +1485,12 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
// If we're post merge, just ignore
if !w.isTTDReached(block.Header()) {
select {
- case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now(), profit: env.profit, isFlashbots: w.flashbots.isFlashbots, worker: w.flashbots.maxMergedBundles}:
+ case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now(), profit: env.profit, isFlashbots: w.flashbots.isFlashbots, worker: w.flashbots.maxMergedBundles, isMegabundle: w.flashbots.isMegabundleWorker}:
w.unconfirmed.Shift(block.NumberU64() - 1)
log.Info("Commit new sealing work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
"uncles", len(env.uncles), "txs", env.tcount, "gas", block.GasUsed(), "fees", totalFees(block, env.receipts),
"profit", ethIntToFloat(env.profit), "elapsed", common.PrettyDuration(time.Since(start)),
- "isFlashbots", w.flashbots.isFlashbots, "worker", w.flashbots.maxMergedBundles)
+ "isFlashbots", w.flashbots.isFlashbots, "worker", w.flashbots.maxMergedBundles, "isMegabundle", w.flashbots.isMegabundleWorker)
case <-w.exitCh:
log.Info("Worker has exited")
}
From 4ea7e11df06ab7f5b45e5ce7cf1c54f6453a41a2 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Mon, 21 Feb 2022 11:06:38 +0100
Subject: [PATCH 23/83] Flashbots changes v0.4 to v0.5
* fix issue with geth not shutting down (#97)
* Add eth_callBundle rpc method (#14)
* flashbots: add eth_estimateGasBundle (#102)
* feat(ethash): flashbots_getWork RPC with profit (#106)
* Calculate megabundle as soon as it's received (#112)
* Add v0.5 specification link (#118)
---
cmd/evm/internal/t8ntool/block.go | 2 +-
cmd/geth/consolecmd_test.go | 2 +-
consensus/beacon/consensus.go | 4 +-
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 ++-
consensus/ethash/sealer_test.go | 10 +-
core/state_processor.go | 56 ++++++
core/tx_pool.go | 24 ++-
eth/backend.go | 2 +-
infra/Dockerfile.node | 2 +-
infra/Dockerfile.updater | 2 +-
infra/start-mev-geth-node.sh | 5 +-
infra/start-mev-geth-updater.sh | 2 +
internal/ethapi/api.go | 310 ++++++++++++++++++++++++++++++
internal/ethapi/backend.go | 5 +-
les/client.go | 2 +-
miner/multi_worker.go | 29 ++-
miner/worker.go | 48 +++--
23 files changed, 533 insertions(+), 59 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 4a070b6c71..d63d41e84a 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/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go
index 442b82df0b..c290b65ecc 100644
--- a/cmd/geth/consolecmd_test.go
+++ b/cmd/geth/consolecmd_test.go
@@ -30,7 +30,7 @@ import (
)
const (
- ipcAPIs = "admin:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0"
+ ipcAPIs = "admin:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 flashbots:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0"
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
)
diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go
index 7e4d657413..92e2de99f4 100644
--- a/consensus/beacon/consensus.go
+++ b/consensus/beacon/consensus.go
@@ -352,9 +352,9 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
//
// Note, the method returns immediately and will send the result async. More
// than one result may also be returned depending on the consensus algorithm.
-func (beacon *Beacon) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
+func (beacon *Beacon) Seal(chain consensus.ChainHeaderReader, block *types.Block, profit *big.Int, results chan<- *types.Block, stop <-chan struct{}) error {
if !beacon.IsPoSHeader(block.Header()) {
- return beacon.ethone.Seal(chain, block, results, stop)
+ return beacon.ethone.Seal(chain, block, profit, results, stop)
}
// The seal verification is done by the external consensus engine,
// return directly without pushing any block back. In another word
diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go
index dcdfb20c63..aae7ce0fcc 100644
--- a/consensus/clique/clique.go
+++ b/consensus/clique/clique.go
@@ -592,7 +592,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 af8ce98ff3..540c78209f 100644
--- a/consensus/consensus.go
+++ b/consensus/consensus.go
@@ -105,7 +105,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 f4d3802e0b..8aece9c7bb 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 dfe00d4b93..69686f5bc3 100644
--- a/consensus/ethash/ethash.go
+++ b/consensus/ethash/ethash.go
@@ -687,6 +687,12 @@ func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API {
Namespace: "ethash",
Service: &API{ethash},
},
+ {
+ 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 eb6bad9622..6e3f3bb59f 100644
--- a/consensus/ethash/ethash_test.go
+++ b/consensus/ethash/ethash_test.go
@@ -37,7 +37,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)
}
@@ -112,7 +112,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
@@ -129,7 +129,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 0000000000..527d2a4435
--- /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 6fa60ef6a8..d2b9253e5c 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/consensus/ethash/sealer_test.go b/consensus/ethash/sealer_test.go
index e338f75290..4957788f21 100644
--- a/consensus/ethash/sealer_test.go
+++ b/consensus/ethash/sealer_test.go
@@ -57,7 +57,7 @@ func TestRemoteNotify(t *testing.T) {
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
block := types.NewBlockWithHeader(header)
- ethash.Seal(nil, block, nil, nil)
+ ethash.Seal(nil, block, nil, nil, nil)
select {
case work := <-sink:
if want := ethash.SealHash(header).Hex(); work[0] != want {
@@ -105,7 +105,7 @@ func TestRemoteNotifyFull(t *testing.T) {
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
block := types.NewBlockWithHeader(header)
- ethash.Seal(nil, block, nil, nil)
+ ethash.Seal(nil, block, nil, nil, nil)
select {
case work := <-sink:
if want := "0x" + strconv.FormatUint(header.Number.Uint64(), 16); work["number"] != want {
@@ -151,7 +151,7 @@ func TestRemoteMultiNotify(t *testing.T) {
for i := 0; i < cap(sink); i++ {
header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
block := types.NewBlockWithHeader(header)
- ethash.Seal(nil, block, results, nil)
+ ethash.Seal(nil, block, nil, results, nil)
}
for i := 0; i < cap(sink); i++ {
@@ -200,7 +200,7 @@ func TestRemoteMultiNotifyFull(t *testing.T) {
for i := 0; i < cap(sink); i++ {
header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
block := types.NewBlockWithHeader(header)
- ethash.Seal(nil, block, results, nil)
+ ethash.Seal(nil, block, nil, results, nil)
}
for i := 0; i < cap(sink); i++ {
@@ -266,7 +266,7 @@ func TestStaleSubmission(t *testing.T) {
for id, c := range testcases {
for _, h := range c.headers {
- ethash.Seal(nil, types.NewBlockWithHeader(h), results, nil)
+ ethash.Seal(nil, types.NewBlockWithHeader(h), nil, results, nil)
}
if res := api.SubmitWork(fakeNonce, ethash.SealHash(c.headers[c.submitIndex]), fakeDigest); res != c.submitRes {
t.Errorf("case %d submit result mismatch, want %t, get %t", id+1, c.submitRes, res)
diff --git a/core/state_processor.go b/core/state_processor.go
index e511697c5f..22c4cf90bc 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -137,6 +137,51 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, author *com
return receipt, err
}
+func applyTransactionWithResult(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, *ExecutionResult, error) {
+ // Create a new context to be used in the EVM environment.
+ txContext := NewEVMTxContext(msg)
+ evm.Reset(txContext, statedb)
+
+ // Apply the transaction to the current state (included in the env).
+ result, err := ApplyMessage(evm, msg, gp)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Update the state with pending changes.
+ var root []byte
+ if config.IsByzantium(header.Number) {
+ statedb.Finalise(true)
+ } else {
+ root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
+ }
+ *usedGas += result.UsedGas
+
+ // Create a new receipt for the transaction, storing the intermediate root and gas used
+ // by the tx.
+ receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
+ if result.Failed() {
+ receipt.Status = types.ReceiptStatusFailed
+ } else {
+ receipt.Status = types.ReceiptStatusSuccessful
+ }
+ receipt.TxHash = tx.Hash()
+ receipt.GasUsed = result.UsedGas
+
+ // If the transaction created a contract, store the creation address in the receipt.
+ if msg.To() == nil {
+ receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
+ }
+
+ // Set the receipt logs and create the bloom filter.
+ receipt.Logs = statedb.GetLogs(tx.Hash(), header.Hash())
+ receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
+ receipt.BlockHash = header.Hash()
+ receipt.BlockNumber = header.Number
+ receipt.TransactionIndex = uint(statedb.TxIndex())
+ return receipt, result, err
+}
+
// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
@@ -151,3 +196,14 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
return applyTransaction(msg, config, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
}
+
+func ApplyTransactionWithResult(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, *ExecutionResult, error) {
+ msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee)
+ if err != nil {
+ return nil, nil, err
+ }
+ // Create a new context to be used in the EVM environment
+ blockContext := NewEVMBlockContext(header, bc, author)
+ vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
+ return applyTransactionWithResult(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv)
+}
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 2c0d3d6b29..1f069b2cac 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -253,13 +253,14 @@ type TxPool struct {
locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk
- pending map[common.Address]*txList // All currently processable transactions
- queue map[common.Address]*txList // Queued but non-processable transactions
- beats map[common.Address]time.Time // Last heartbeat from each known account
- mevBundles []types.MevBundle
- megabundles map[common.Address]types.MevBundle // One megabundle per each trusted relay
- all *txLookup // All transactions to allow lookups
- priced *txPricedList // All transactions sorted by price
+ pending map[common.Address]*txList // All currently processable transactions
+ queue map[common.Address]*txList // Queued but non-processable transactions
+ beats map[common.Address]time.Time // Last heartbeat from each known account
+ mevBundles []types.MevBundle
+ megabundles map[common.Address]types.MevBundle // One megabundle per each trusted relay
+ NewMegabundleHooks []func(common.Address, *types.MevBundle)
+ all *txLookup // All transactions to allow lookups
+ priced *txPricedList // All transactions sorted by price
chainHeadCh chan ChainHeadEvent
chainHeadSub event.Subscription
@@ -630,13 +631,20 @@ func (pool *TxPool) AddMegabundle(relayAddr common.Address, txs types.Transactio
return errors.New("megabundle from non-trusted address")
}
- pool.megabundles[relayAddr] = types.MevBundle{
+ megabundle := types.MevBundle{
Txs: txs,
BlockNumber: blockNumber,
MinTimestamp: minTimestamp,
MaxTimestamp: maxTimestamp,
RevertingTxHashes: revertingTxHashes,
}
+
+ pool.megabundles[relayAddr] = megabundle
+
+ for _, hook := range pool.NewMegabundleHooks {
+ go hook(relayAddr, &megabundle)
+ }
+
return nil
}
diff --git a/eth/backend.go b/eth/backend.go
index 7782076363..fbb75d89b6 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -297,7 +297,7 @@ func makeExtraData(extra []byte) []byte {
// APIs return the collection of RPC services the ethereum package offers.
// NOTE, some of these services probably need to be moved to somewhere else.
func (s *Ethereum) APIs() []rpc.API {
- apis := ethapi.GetAPIs(s.APIBackend)
+ apis := ethapi.GetAPIs(s.APIBackend, s.BlockChain())
// Append any APIs exposed explicitly by the consensus engine
apis = append(apis, s.engine.APIs(s.BlockChain())...)
diff --git a/infra/Dockerfile.node b/infra/Dockerfile.node
index db8e99ac93..7868453eba 100644
--- a/infra/Dockerfile.node
+++ b/infra/Dockerfile.node
@@ -4,7 +4,7 @@ FROM golang:1.15-alpine as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git
ADD . /go-ethereum
-RUN cd /go-ethereum && make geth
+RUN cd /go-ethereum && GO111MODULE=on go run build/ci.go install ./cmd/geth
# Pull Geth into a second stage deploy alpine container
FROM alpine:latest
diff --git a/infra/Dockerfile.updater b/infra/Dockerfile.updater
index d3099d19ce..808f55aa2b 100644
--- a/infra/Dockerfile.updater
+++ b/infra/Dockerfile.updater
@@ -4,7 +4,7 @@ FROM golang:1.15-alpine as builder
RUN apk add --no-cache make gcc musl-dev linux-headers git
ADD . /go-ethereum
-RUN cd /go-ethereum && make geth
+RUN cd /go-ethereum && GO111MODULE=on go run build/ci.go install ./cmd/geth
# Pull Geth into a second stage deploy alpine container
FROM alpine:latest
diff --git a/infra/start-mev-geth-node.sh b/infra/start-mev-geth-node.sh
index 05ad50c610..45ef0c5197 100755
--- a/infra/start-mev-geth-node.sh
+++ b/infra/start-mev-geth-node.sh
@@ -33,6 +33,7 @@ start_node() {
--ws.api eth,net,web3 \
--ws.origins '*' \
--syncmode $syncmode \
+ --gcmode archive \
--cache 4096 \
--maxpeers $connections \
--goerli
@@ -41,7 +42,7 @@ start_node() {
echo "Node failed to start; exiting."
exit 1
fi
- else
+ else
geth \
--port $netport \
--http \
@@ -59,7 +60,9 @@ start_node() {
--ws.api eth,net,web3 \
--ws.origins '*' \
--syncmode $syncmode \
+ --gcmode archive \
--cache 4096 \
+ --snapshot=false \
--maxpeers $connections
if [ $? -ne 0 ]
then
diff --git a/infra/start-mev-geth-updater.sh b/infra/start-mev-geth-updater.sh
index 11a6a533aa..abad72fab9 100755
--- a/infra/start-mev-geth-updater.sh
+++ b/infra/start-mev-geth-updater.sh
@@ -18,6 +18,7 @@ start_node() {
--port $netport \
--syncmode $syncmode \
--cache 4096 \
+ --gcmode archive \
--maxpeers $connections \
--goerli &
if [ $? -ne 0 ]
@@ -30,6 +31,7 @@ start_node() {
--port $netport \
--syncmode $syncmode \
--cache 4096 \
+ --gcmode archive \
--maxpeers $connections &
if [ $? -ne 0 ]
then
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 68d1948ebf..32200b23cf 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -18,6 +18,8 @@ package ethapi
import (
"context"
+ "crypto/rand"
+ "encoding/hex"
"errors"
"fmt"
"math/big"
@@ -46,6 +48,7 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/tyler-smith/go-bip39"
+ "golang.org/x/crypto/sha3"
)
// EthereumAPI provides an API to access Ethereum related information.
@@ -2153,3 +2156,310 @@ func (s *PrivateTxBundleAPI) SendMegabundle(ctx context.Context, args SendMegabu
}
return s.b.SendMegabundle(ctx, txs, rpc.BlockNumber(args.BlockNumber), minTimestamp, maxTimestamp, args.RevertingTxHashes, relayAddr)
}
+
+// BundleAPI offers an API for accepting bundled transactions
+type BundleAPI struct {
+ b Backend
+ chain *core.BlockChain
+}
+
+// NewBundleAPI creates a new Tx Bundle API instance.
+func NewBundleAPI(b Backend, chain *core.BlockChain) *BundleAPI {
+ return &BundleAPI{b, chain}
+}
+
+// CallBundleArgs represents the arguments for a call.
+type CallBundleArgs struct {
+ Txs []hexutil.Bytes `json:"txs"`
+ BlockNumber rpc.BlockNumber `json:"blockNumber"`
+ StateBlockNumberOrHash rpc.BlockNumberOrHash `json:"stateBlockNumber"`
+ Coinbase *string `json:"coinbase"`
+ Timestamp *uint64 `json:"timestamp"`
+ Timeout *int64 `json:"timeout"`
+ GasLimit *uint64 `json:"gasLimit"`
+ Difficulty *big.Int `json:"difficulty"`
+ BaseFee *big.Int `json:"baseFee"`
+}
+
+// CallBundle will simulate a bundle of transactions at the top of a given block
+// number with the state of another (or the same) block. This can be used to
+// simulate future blocks with the current state, or it can be used to simulate
+// a past block.
+// The sender is responsible for signing the transactions and using the correct
+// nonce and ensuring validity
+func (s *BundleAPI) CallBundle(ctx context.Context, args CallBundleArgs) (map[string]interface{}, error) {
+ if len(args.Txs) == 0 {
+ return nil, errors.New("bundle missing txs")
+ }
+ if args.BlockNumber == 0 {
+ return nil, errors.New("bundle missing blockNumber")
+ }
+
+ var txs types.Transactions
+
+ for _, encodedTx := range args.Txs {
+ tx := new(types.Transaction)
+ if err := tx.UnmarshalBinary(encodedTx); err != nil {
+ return nil, err
+ }
+ txs = append(txs, tx)
+ }
+ defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
+
+ timeoutMilliSeconds := int64(5000)
+ if args.Timeout != nil {
+ timeoutMilliSeconds = *args.Timeout
+ }
+ timeout := time.Millisecond * time.Duration(timeoutMilliSeconds)
+ state, parent, err := s.b.StateAndHeaderByNumberOrHash(ctx, args.StateBlockNumberOrHash)
+ if state == nil || err != nil {
+ return nil, err
+ }
+ blockNumber := big.NewInt(int64(args.BlockNumber))
+
+ timestamp := parent.Time + 1
+ if args.Timestamp != nil {
+ timestamp = *args.Timestamp
+ }
+ coinbase := parent.Coinbase
+ if args.Coinbase != nil {
+ coinbase = common.HexToAddress(*args.Coinbase)
+ }
+ difficulty := parent.Difficulty
+ if args.Difficulty != nil {
+ difficulty = args.Difficulty
+ }
+ gasLimit := parent.GasLimit
+ if args.GasLimit != nil {
+ gasLimit = *args.GasLimit
+ }
+ var baseFee *big.Int
+ if args.BaseFee != nil {
+ baseFee = args.BaseFee
+ } else if s.b.ChainConfig().IsLondon(big.NewInt(args.BlockNumber.Int64())) {
+ baseFee = misc.CalcBaseFee(s.b.ChainConfig(), parent)
+ }
+ header := &types.Header{
+ ParentHash: parent.Hash(),
+ Number: blockNumber,
+ GasLimit: gasLimit,
+ Time: timestamp,
+ Difficulty: difficulty,
+ Coinbase: coinbase,
+ BaseFee: baseFee,
+ }
+
+ // Setup context so it may be cancelled the call has completed
+ // or, in case of unmetered gas, setup a context with a timeout.
+ var cancel context.CancelFunc
+ if timeout > 0 {
+ ctx, cancel = context.WithTimeout(ctx, timeout)
+ } else {
+ ctx, cancel = context.WithCancel(ctx)
+ }
+ // Make sure the context is cancelled when the call has completed
+ // this makes sure resources are cleaned up.
+ defer cancel()
+
+ vmconfig := vm.Config{}
+
+ // Setup the gas pool (also for unmetered requests)
+ // and apply the message.
+ gp := new(core.GasPool).AddGas(math.MaxUint64)
+
+ results := []map[string]interface{}{}
+ coinbaseBalanceBefore := state.GetBalance(coinbase)
+
+ bundleHash := sha3.NewLegacyKeccak256()
+ signer := types.MakeSigner(s.b.ChainConfig(), blockNumber)
+ var totalGasUsed uint64
+ gasFees := new(big.Int)
+ for i, tx := range txs {
+ coinbaseBalanceBeforeTx := state.GetBalance(coinbase)
+ state.Prepare(tx.Hash(), i)
+
+ receipt, result, err := core.ApplyTransactionWithResult(s.b.ChainConfig(), s.chain, &coinbase, gp, state, header, tx, &header.GasUsed, vmconfig)
+ if err != nil {
+ return nil, fmt.Errorf("err: %w; txhash %s", err, tx.Hash())
+ }
+
+ txHash := tx.Hash().String()
+ from, err := types.Sender(signer, tx)
+ if err != nil {
+ return nil, fmt.Errorf("err: %w; txhash %s", err, tx.Hash())
+ }
+ to := "0x"
+ if tx.To() != nil {
+ to = tx.To().String()
+ }
+ jsonResult := map[string]interface{}{
+ "txHash": txHash,
+ "gasUsed": receipt.GasUsed,
+ "fromAddress": from.String(),
+ "toAddress": to,
+ }
+ totalGasUsed += receipt.GasUsed
+ gasPrice, err := tx.EffectiveGasTip(header.BaseFee)
+ if err != nil {
+ return nil, fmt.Errorf("err: %w; txhash %s", err, tx.Hash())
+ }
+ gasFeesTx := new(big.Int).Mul(big.NewInt(int64(receipt.GasUsed)), gasPrice)
+ gasFees.Add(gasFees, gasFeesTx)
+ bundleHash.Write(tx.Hash().Bytes())
+ if result.Err != nil {
+ jsonResult["error"] = result.Err.Error()
+ revert := result.Revert()
+ if len(revert) > 0 {
+ jsonResult["revert"] = string(revert)
+ }
+ } else {
+ dst := make([]byte, hex.EncodedLen(len(result.Return())))
+ hex.Encode(dst, result.Return())
+ jsonResult["value"] = "0x" + string(dst)
+ }
+ coinbaseDiffTx := new(big.Int).Sub(state.GetBalance(coinbase), coinbaseBalanceBeforeTx)
+ jsonResult["coinbaseDiff"] = coinbaseDiffTx.String()
+ jsonResult["gasFees"] = gasFeesTx.String()
+ jsonResult["ethSentToCoinbase"] = new(big.Int).Sub(coinbaseDiffTx, gasFeesTx).String()
+ jsonResult["gasPrice"] = new(big.Int).Div(coinbaseDiffTx, big.NewInt(int64(receipt.GasUsed))).String()
+ jsonResult["gasUsed"] = receipt.GasUsed
+ results = append(results, jsonResult)
+ }
+
+ ret := map[string]interface{}{}
+ ret["results"] = results
+ coinbaseDiff := new(big.Int).Sub(state.GetBalance(coinbase), coinbaseBalanceBefore)
+ ret["coinbaseDiff"] = coinbaseDiff.String()
+ ret["gasFees"] = gasFees.String()
+ ret["ethSentToCoinbase"] = new(big.Int).Sub(coinbaseDiff, gasFees).String()
+ ret["bundleGasPrice"] = new(big.Int).Div(coinbaseDiff, big.NewInt(int64(totalGasUsed))).String()
+ ret["totalGasUsed"] = totalGasUsed
+ ret["stateBlockNumber"] = parent.Number.Int64()
+
+ ret["bundleHash"] = "0x" + common.Bytes2Hex(bundleHash.Sum(nil))
+ return ret, nil
+}
+
+// EstimateGasBundleArgs represents the arguments for a call
+type EstimateGasBundleArgs struct {
+ Txs []TransactionArgs `json:"txs"`
+ BlockNumber rpc.BlockNumber `json:"blockNumber"`
+ StateBlockNumberOrHash rpc.BlockNumberOrHash `json:"stateBlockNumber"`
+ Coinbase *string `json:"coinbase"`
+ Timestamp *uint64 `json:"timestamp"`
+ Timeout *int64 `json:"timeout"`
+}
+
+func (s *BundleAPI) EstimateGasBundle(ctx context.Context, args EstimateGasBundleArgs) (map[string]interface{}, error) {
+ if len(args.Txs) == 0 {
+ return nil, errors.New("bundle missing txs")
+ }
+ if args.BlockNumber == 0 {
+ return nil, errors.New("bundle missing blockNumber")
+ }
+
+ timeoutMS := int64(5000)
+ if args.Timeout != nil {
+ timeoutMS = *args.Timeout
+ }
+ timeout := time.Millisecond * time.Duration(timeoutMS)
+
+ state, parent, err := s.b.StateAndHeaderByNumberOrHash(ctx, args.StateBlockNumberOrHash)
+ if state == nil || err != nil {
+ return nil, err
+ }
+ blockNumber := big.NewInt(int64(args.BlockNumber))
+ timestamp := parent.Time + 1
+ if args.Timestamp != nil {
+ timestamp = *args.Timestamp
+ }
+ coinbase := parent.Coinbase
+ if args.Coinbase != nil {
+ coinbase = common.HexToAddress(*args.Coinbase)
+ }
+
+ header := &types.Header{
+ ParentHash: parent.Hash(),
+ Number: blockNumber,
+ GasLimit: parent.GasLimit,
+ Time: timestamp,
+ Difficulty: parent.Difficulty,
+ Coinbase: coinbase,
+ BaseFee: parent.BaseFee,
+ }
+
+ // Setup context so it may be cancelled when the call
+ // has completed or, in case of unmetered gas, setup
+ // a context with a timeout
+ var cancel context.CancelFunc
+ if timeout > 0 {
+ ctx, cancel = context.WithTimeout(ctx, timeout)
+ } else {
+ ctx, cancel = context.WithCancel(ctx)
+ }
+
+ // Make sure the context is cancelled when the call has completed
+ // This makes sure resources are cleaned up
+ defer cancel()
+
+ // RPC Call gas cap
+ globalGasCap := s.b.RPCGasCap()
+
+ // Results
+ results := []map[string]interface{}{}
+
+ // Copy the original db so we don't modify it
+ statedb := state.Copy()
+
+ // Gas pool
+ gp := new(core.GasPool).AddGas(math.MaxUint64)
+
+ // Block context
+ blockContext := core.NewEVMBlockContext(header, s.chain, &coinbase)
+
+ // Feed each of the transactions into the VM ctx
+ // And try and estimate the gas used
+ for i, txArgs := range args.Txs {
+ // Since its a txCall we'll just prepare the
+ // state with a random hash
+ var randomHash common.Hash
+ rand.Read(randomHash[:])
+
+ // New random hash since its a call
+ statedb.Prepare(randomHash, i)
+
+ // Convert tx args to msg to apply state transition
+ msg, err := txArgs.ToMessage(globalGasCap, header.BaseFee)
+ if err != nil {
+ return nil, err
+ }
+
+ // Prepare the hashes
+ txContext := core.NewEVMTxContext(msg)
+
+ // Get EVM Environment
+ vmenv := vm.NewEVM(blockContext, txContext, statedb, s.b.ChainConfig(), vm.Config{NoBaseFee: true})
+
+ // Apply state transition
+ result, err := core.ApplyMessage(vmenv, msg, gp)
+ if err != nil {
+ return nil, err
+ }
+
+ // Modifications are committed to the state
+ // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+ statedb.Finalise(vmenv.ChainConfig().IsEIP158(blockNumber))
+
+ // Append result
+ jsonResult := map[string]interface{}{
+ "gasUsed": result.UsedGas,
+ }
+ results = append(results, jsonResult)
+ }
+
+ // Return results
+ ret := map[string]interface{}{}
+ ret["results"] = results
+
+ return ret, nil
+}
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index dadf96509b..d8b4b2b451 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -94,7 +94,7 @@ type Backend interface {
filters.Backend
}
-func GetAPIs(apiBackend Backend) []rpc.API {
+func GetAPIs(apiBackend Backend, chain *core.BlockChain) []rpc.API {
nonceLock := new(AddrLocker)
return []rpc.API{
{
@@ -121,6 +121,9 @@ func GetAPIs(apiBackend Backend) []rpc.API {
}, {
Namespace: "eth",
Service: NewPrivateTxBundleAPI(apiBackend),
+ }, {
+ Namespace: "eth",
+ Service: NewBundleAPI(apiBackend, chain),
},
}
}
diff --git a/les/client.go b/les/client.go
index 6504fe2af8..9b98df7235 100644
--- a/les/client.go
+++ b/les/client.go
@@ -288,7 +288,7 @@ func (s *LightDummyAPI) Mining() bool {
// APIs returns the collection of RPC services the ethereum package offers.
// NOTE, some of these services probably need to be moved to somewhere else.
func (s *LightEthereum) APIs() []rpc.API {
- apis := ethapi.GetAPIs(s.ApiBackend)
+ apis := ethapi.GetAPIs(s.ApiBackend, nil)
apis = append(apis, s.engine.APIs(s.BlockChain().HeaderChain())...)
return append(apis, []rpc.API{
{
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index 050ea38af4..da1471fa3e 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -105,16 +105,31 @@ func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine cons
}))
}
+ relayWorkerMap := make(map[common.Address]*worker)
+
for i := 0; i < len(config.TrustedRelays); i++ {
- workers = append(workers,
- newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
- isFlashbots: true,
- isMegabundleWorker: true,
- queue: queue,
- relayAddr: config.TrustedRelays[i],
- }))
+ relayWorker := newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
+ isFlashbots: true,
+ isMegabundleWorker: true,
+ queue: queue,
+ relayAddr: config.TrustedRelays[i],
+ })
+ workers = append(workers, relayWorker)
+ relayWorkerMap[config.TrustedRelays[i]] = relayWorker
}
+ eth.TxPool().NewMegabundleHooks = append(eth.TxPool().NewMegabundleHooks, func(relayAddr common.Address, megabundle *types.MevBundle) {
+ worker, found := relayWorkerMap[relayAddr]
+ if !found {
+ return
+ }
+
+ select {
+ case worker.newMegabundleCh <- megabundle:
+ default:
+ }
+ })
+
log.Info("creating multi worker", "config.MaxMergedBundles", config.MaxMergedBundles, "config.TrustedRelays", config.TrustedRelays, "worker", len(workers))
return &multiWorker{
regularWorker: regularWorker,
diff --git a/miner/worker.go b/miner/worker.go
index 91c492198b..c152a37df1 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -225,6 +225,7 @@ type worker struct {
exitCh chan struct{}
resubmitIntervalCh chan time.Duration
resubmitAdjustCh chan *intervalAdjust
+ newMegabundleCh chan *types.MevBundle
wg sync.WaitGroup
@@ -327,17 +328,19 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
txsCh: make(chan core.NewTxsEvent, txChanSize),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
- newWorkCh: make(chan *newWorkReq),
+ newWorkCh: make(chan *newWorkReq, 1),
getWorkCh: make(chan *getWorkReq),
taskCh: taskCh,
resultCh: make(chan *types.Block, resultQueueSize),
exitCh: exitCh,
startCh: make(chan struct{}, 1),
+ newMegabundleCh: make(chan *types.MevBundle),
resubmitIntervalCh: make(chan time.Duration),
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
coinbase: builderCoinbase,
flashbots: flashbots,
}
+
// Subscribe NewTxsEvent for tx pool
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
// Subscribe events for blockchain
@@ -483,26 +486,38 @@ func recalcRecommit(minRecommit, prev time.Duration, target float64, inc bool) t
func (w *worker) newWorkLoop(recommit time.Duration) {
defer w.wg.Done()
var (
- interrupt *int32
- minRecommit = recommit // minimal resubmit interval specified by user.
- timestamp int64 // timestamp for each round of sealing.
+ runningInterrupt *int32 // Running task interrupt
+ queuedInterrupt *int32 // Queued task interrupt
+ minRecommit = recommit // minimal resubmit interval specified by user.
+ timestamp int64 // timestamp for each round of sealing.
)
timer := time.NewTimer(0)
defer timer.Stop()
<-timer.C // discard the initial tick
- // commit aborts in-flight transaction execution with given signal and resubmits a new one.
+ // commit aborts in-flight transaction execution with highest seen signal and resubmits a new one
commit := func(noempty bool, s int32) {
- if interrupt != nil {
- atomic.StoreInt32(interrupt, s)
- }
- interrupt = new(int32)
select {
- case w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}:
case <-w.exitCh:
return
+ case queuedRequest := <-w.newWorkCh:
+ // Previously queued request wasn't started yet, update the request and resubmit
+ queuedRequest.noempty = queuedRequest.noempty || noempty
+ queuedRequest.timestamp = timestamp
+ w.newWorkCh <- queuedRequest // guaranteed to be nonblocking
+ default:
+ // Previously queued request has already started, cycle interrupt pointer and submit new work
+ runningInterrupt = queuedInterrupt
+ queuedInterrupt = new(int32)
+
+ w.newWorkCh <- &newWorkReq{interrupt: queuedInterrupt, noempty: noempty, timestamp: timestamp} // guaranteed to be nonblocking
+ }
+
+ if runningInterrupt != nil && s > atomic.LoadInt32(runningInterrupt) {
+ atomic.StoreInt32(runningInterrupt, s)
}
+
timer.Reset(recommit)
atomic.StoreInt32(&w.newTxs, 0)
}
@@ -529,6 +544,11 @@ func (w *worker) newWorkLoop(recommit time.Duration) {
timestamp = time.Now().Unix()
commit(false, commitInterruptNewHead)
+ case <-w.newMegabundleCh:
+ if w.isRunning() {
+ commit(true, commitInterruptNone)
+ }
+
case <-timer.C:
// If sealing is running resubmit a new work cycle periodically to pull in
// higher priced transactions. Disable this overhead for pending blocks.
@@ -597,7 +617,10 @@ func (w *worker) mainLoop() {
for {
select {
case req := <-w.newWorkCh:
- w.commitWork(req.interrupt, req.noempty, req.timestamp)
+ // Don't start if the work has already been interrupted
+ if req.interrupt == nil || atomic.LoadInt32(req.interrupt) == commitInterruptNone {
+ w.commitWork(req.interrupt, req.noempty, req.timestamp)
+ }
case req := <-w.getWorkCh:
block, err := w.generateWork(req.params)
@@ -744,7 +767,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)
@@ -1298,6 +1321,7 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
if err != nil {
return err // no valid megabundle for this relay, nothing to do
}
+
// Flashbots bundle merging duplicates work by simulating TXes and then committing them once more.
// Megabundles API focuses on speed and runs everything in one cycle.
coinbaseBalanceBefore := env.state.GetBalance(env.coinbase)
From 70a26aa595c9bc96f995ab981497a139f58c6b0a Mon Sep 17 00:00:00 2001
From: Carlo Xu
Date: Mon, 29 Nov 2021 16:57:18 -0600
Subject: [PATCH 24/83] Private Transaction API Sample (v1.10.13)
---
cmd/geth/main.go | 1 +
cmd/utils/flags.go | 9 +++
core/tx_pool.go | 113 +++++++++++++++++++++++++++++++----
eth/api_backend.go | 8 ++-
eth/handler.go | 4 ++
eth/handler_test.go | 5 ++
eth/protocols/eth/handler.go | 4 ++
eth/sync.go | 7 ++-
graphql/graphql.go | 2 +-
internal/ethapi/api.go | 25 ++++++--
internal/ethapi/backend.go | 2 +-
internal/web3ext/web3ext.go | 6 ++
les/api_backend.go | 2 +-
13 files changed, 164 insertions(+), 24 deletions(-)
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 3abfc5954a..ec70016cd1 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -90,6 +90,7 @@ var (
utils.TxPoolAccountQueueFlag,
utils.TxPoolGlobalQueueFlag,
utils.TxPoolLifetimeFlag,
+ utils.TxPoolPrivateLifetimeFlag,
utils.SyncModeFlag,
utils.ExitWhenSyncedFlag,
utils.GCModeFlag,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 1c8244dc14..d9a8882064 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -441,6 +441,12 @@ var (
Category: flags.TxPoolCategory,
}
+ TxPoolPrivateLifetimeFlag = &cli.DurationFlag{
+ Name: "txpool.privatelifetime",
+ Usage: "Maximum amount of time private transactions are withheld from public broadcasting",
+ Value: ethconfig.Defaults.TxPool.PrivateTxLifetime,
+ Category: flags.TxPoolCategory,
+ }
// Performance tuning settings
CacheFlag = &cli.IntFlag{
Name: "cache",
@@ -1674,6 +1680,9 @@ func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
if ctx.IsSet(TxPoolLifetimeFlag.Name) {
cfg.Lifetime = ctx.Duration(TxPoolLifetimeFlag.Name)
}
+ if ctx.IsSet(TxPoolPrivateLifetimeFlag.Name) {
+ cfg.PrivateTxLifetime = ctx.Duration(TxPoolPrivateLifetimeFlag.Name)
+ }
addresses := strings.Split(ctx.String(MinerTrustedRelaysFlag.Name), ",")
for _, address := range addresses {
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 1f069b2cac..639a951585 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -88,8 +88,9 @@ var (
)
var (
- evictionInterval = time.Minute // Time interval to check for evictable transactions
- statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats
+ evictionInterval = time.Minute // Time interval to check for evictable transactions
+ statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats
+ privateTxCleanupInterval = 1 * time.Hour
)
var (
@@ -164,7 +165,8 @@ type TxPoolConfig struct {
AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
- Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
+ Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
+ PrivateTxLifetime time.Duration // Maximum amount of time to keep private transactions private
TrustedRelays []common.Address // Trusted relay addresses. Duplicated from the miner config.
}
@@ -183,7 +185,8 @@ var DefaultTxPoolConfig = TxPoolConfig{
AccountQueue: 64,
GlobalQueue: 1024,
- Lifetime: 3 * time.Hour,
+ Lifetime: 3 * time.Hour,
+ PrivateTxLifetime: 3 * 24 * time.Hour,
}
// sanitize checks the provided user configurations and changes anything that's
@@ -222,6 +225,10 @@ func (config *TxPoolConfig) sanitize() TxPoolConfig {
log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultTxPoolConfig.Lifetime)
conf.Lifetime = DefaultTxPoolConfig.Lifetime
}
+ if conf.PrivateTxLifetime < 1 {
+ log.Warn("Sanitizing invalid txpool private tx lifetime", "provided", conf.PrivateTxLifetime, "updated", DefaultTxPoolConfig.PrivateTxLifetime)
+ conf.PrivateTxLifetime = DefaultTxPoolConfig.PrivateTxLifetime
+ }
return conf
}
@@ -261,6 +268,7 @@ type TxPool struct {
NewMegabundleHooks []func(common.Address, *types.MevBundle)
all *txLookup // All transactions to allow lookups
priced *txPricedList // All transactions sorted by price
+ privateTxs *timestampedTxHashSet
chainHeadCh chan ChainHeadEvent
chainHeadSub event.Subscription
@@ -296,6 +304,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
beats: make(map[common.Address]time.Time),
megabundles: make(map[common.Address]types.MevBundle),
all: newTxLookup(),
+ privateTxs: newExpiringTxHashSet(config.PrivateTxLifetime),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
reqResetCh: make(chan *txpoolResetRequest),
reqPromoteCh: make(chan *accountSet),
@@ -346,9 +355,10 @@ func (pool *TxPool) loop() {
var (
prevPending, prevQueued, prevStales int
// Start the stats reporting and transaction eviction tickers
- report = time.NewTicker(statsReportInterval)
- evict = time.NewTicker(evictionInterval)
- journal = time.NewTicker(pool.config.Rejournal)
+ report = time.NewTicker(statsReportInterval)
+ evict = time.NewTicker(evictionInterval)
+ journal = time.NewTicker(pool.config.Rejournal)
+ privateTx = time.NewTicker(privateTxCleanupInterval)
// Track the previous head headers for transaction reorgs
head = pool.chain.CurrentBlock()
)
@@ -412,6 +422,10 @@ func (pool *TxPool) loop() {
}
pool.mu.Unlock()
}
+
+ // Remove stale hashes that must be kept private
+ case <-privateTx.C:
+ pool.privateTxs.prune()
}
}
}
@@ -532,6 +546,11 @@ func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types.
return pending, queued
}
+// IsPrivateTxHash indicates whether the transaction should be shared with peers
+func (pool *TxPool) IsPrivateTxHash(hash common.Hash) bool {
+ return pool.privateTxs.Contains(hash)
+}
+
// Pending retrieves all currently processable transactions, grouped by origin
// account and sorted by nonce. The returned transaction set is a copy and can be
// freely modified by calling code.
@@ -958,7 +977,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
// This method is used to add transactions from the RPC API and performs synchronous pool
// reorganization and event propagation.
func (pool *TxPool) AddLocals(txs []*types.Transaction) []error {
- return pool.addTxs(txs, !pool.config.NoLocals, true)
+ return pool.addTxs(txs, !pool.config.NoLocals, true, false)
}
// AddLocal enqueues a single local transaction into the pool if it is valid. This is
@@ -974,12 +993,18 @@ func (pool *TxPool) AddLocal(tx *types.Transaction) error {
// This method is used to add transactions from the p2p network and does not wait for pool
// reorganization and internal event propagation.
func (pool *TxPool) AddRemotes(txs []*types.Transaction) []error {
- return pool.addTxs(txs, false, false)
+ return pool.addTxs(txs, false, false, false)
+}
+
+// AddPrivateRemote adds transactions to the pool, but does not broadcast these transactions to any peers.
+func (pool *TxPool) AddPrivateRemote(tx *types.Transaction) error {
+ errs := pool.addTxs([]*types.Transaction{tx}, false, false, true)
+ return errs[0]
}
// This is like AddRemotes, but waits for pool reorganization. Tests use this method.
func (pool *TxPool) AddRemotesSync(txs []*types.Transaction) []error {
- return pool.addTxs(txs, false, true)
+ return pool.addTxs(txs, false, true, false)
}
// This is like AddRemotes with a single transaction, but waits for pool reorganization. Tests use this method.
@@ -998,7 +1023,7 @@ func (pool *TxPool) AddRemote(tx *types.Transaction) error {
}
// addTxs attempts to queue a batch of transactions if they are valid.
-func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
+func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync, private bool) []error {
// Filter out known ones without obtaining the pool lock or recovering signatures
var (
errs = make([]error, len(txs))
@@ -1027,6 +1052,13 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
return errs
}
+ // Track private transactions, so they don't get leaked to the public mempool
+ if private {
+ for _, tx := range news {
+ pool.privateTxs.Add(tx.Hash())
+ }
+ }
+
// Process all the new transaction and merge any errors into the original slice
pool.mu.Lock()
newErrs, dirtyAddrs := pool.addTxsLocked(news, local)
@@ -1321,7 +1353,11 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
if len(events) > 0 {
var txs []*types.Transaction
for _, set := range events {
- txs = append(txs, set.Flatten()...)
+ for _, tx := range set.Flatten() {
+ if !pool.IsPrivateTxHash(tx.Hash()) {
+ txs = append(txs, tx)
+ }
+ }
}
pool.txFeed.Send(NewTxsEvent{txs})
}
@@ -1931,6 +1967,59 @@ func (t *txLookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
return found
}
+type timestampedTxHashSet struct {
+ lock sync.RWMutex
+ hashes []common.Hash
+ timestamps map[common.Hash]time.Time
+ ttl time.Duration
+}
+
+func newExpiringTxHashSet(ttl time.Duration) *timestampedTxHashSet {
+ s := ×tampedTxHashSet{
+ hashes: make([]common.Hash, 0),
+ timestamps: make(map[common.Hash]time.Time),
+ ttl: ttl,
+ }
+
+ return s
+}
+
+func (s *timestampedTxHashSet) Add(hash common.Hash) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.hashes = append(s.hashes, hash)
+ s.timestamps[hash] = time.Now().Add(s.ttl)
+}
+
+func (s *timestampedTxHashSet) Contains(hash common.Hash) bool {
+ s.lock.RLock()
+ defer s.lock.RUnlock()
+ _, ok := s.timestamps[hash]
+ return ok
+}
+
+func (s *timestampedTxHashSet) prune() {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ var (
+ count int
+ now = time.Now()
+ )
+ for _, hash := range s.hashes {
+ ts := s.timestamps[hash]
+ if ts.After(now) {
+ break
+ }
+
+ delete(s.timestamps, hash)
+ count += 1
+ }
+
+ s.hashes = s.hashes[count:]
+}
+
// numSlots calculates the number of slots needed for a single transaction.
func numSlots(tx *types.Transaction) int {
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
diff --git a/eth/api_backend.go b/eth/api_backend.go
index f3bc2275d4..b97d53f14a 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -246,8 +246,12 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
return b.eth.BlockChain().SubscribeLogsEvent(ch)
}
-func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
- return b.eth.txPool.AddLocal(signedTx)
+func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction, private bool) error {
+ if private {
+ return b.eth.txPool.AddPrivateRemote(signedTx)
+ } else {
+ return b.eth.txPool.AddLocal(signedTx)
+ }
}
func (b *EthAPIBackend) SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
diff --git a/eth/handler.go b/eth/handler.go
index 4224a9f33a..3539856c01 100644
--- a/eth/handler.go
+++ b/eth/handler.go
@@ -72,6 +72,10 @@ type txPool interface {
// SubscribeNewTxsEvent should return an event subscription of
// NewTxsEvent and send events to the given channel.
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
+
+ // IsPrivateTxHash indicates if the transaction hash should not
+ // be broadcast on public channels
+ IsPrivateTxHash(hash common.Hash) bool
}
// handlerConfig is the collection of initialization parameters to create a full
diff --git a/eth/handler_test.go b/eth/handler_test.go
index d967b6df93..382bed491b 100644
--- a/eth/handler_test.go
+++ b/eth/handler_test.go
@@ -113,6 +113,11 @@ func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subs
return p.txFeed.Subscribe(ch)
}
+// IsPrivateTxHash always returns false in tests
+func (p *testTxPool) IsPrivateTxHash(hash common.Hash) bool {
+ return false
+}
+
// testHandler is a live implementation of the Ethereum protocol handler, just
// preinitialized with some sane testing defaults and the transaction pool mocked
// out.
diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go
index 3a0b21c30b..b655e1e6d0 100644
--- a/eth/protocols/eth/handler.go
+++ b/eth/protocols/eth/handler.go
@@ -91,6 +91,10 @@ type Backend interface {
type TxPool interface {
// Get retrieves the transaction from the local txpool with the given hash.
Get(hash common.Hash) *types.Transaction
+
+ // IsPrivateTxHash indicates if the transaction hash should not
+ // be broadcast on public channels
+ IsPrivateTxHash(hash common.Hash) bool
}
// MakeProtocols constructs the P2P protocol definitions for `eth`.
diff --git a/eth/sync.go b/eth/sync.go
index 8fd86b578c..2edb7a731c 100644
--- a/eth/sync.go
+++ b/eth/sync.go
@@ -46,7 +46,12 @@ func (h *handler) syncTransactions(p *eth.Peer) {
var txs types.Transactions
pending := h.txpool.Pending(false)
for _, batch := range pending {
- txs = append(txs, batch...)
+ for _, tx := range batch {
+ // don't share any transactions marked as private
+ if !h.txpool.IsPrivateTxHash(tx.Hash()) {
+ txs = append(txs, tx)
+ }
+ }
}
if len(txs) == 0 {
return
diff --git a/graphql/graphql.go b/graphql/graphql.go
index 97b460c205..dc2f7a56ac 100644
--- a/graphql/graphql.go
+++ b/graphql/graphql.go
@@ -1266,7 +1266,7 @@ func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hex
if err := tx.UnmarshalBinary(args.Data); err != nil {
return common.Hash{}, err
}
- hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx)
+ hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx, false)
return hash, err
}
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 32200b23cf..d99109579f 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -468,7 +468,7 @@ func (s *PersonalAccountAPI) SendTransaction(ctx context.Context, args Transacti
log.Warn("Failed transaction send attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err)
return common.Hash{}, err
}
- return SubmitTransaction(ctx, s.b, signed)
+ return SubmitTransaction(ctx, s.b, signed, false)
}
// SignTransaction will create a transaction from the given arguments and
@@ -1653,7 +1653,7 @@ func (s *TransactionAPI) sign(addr common.Address, tx *types.Transaction) (*type
}
// SubmitTransaction is a helper function that submits tx to txPool and logs a message.
-func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
+func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction, private bool) (common.Hash, error) {
// If the transaction fee cap is already specified, ensure the
// fee of the given transaction is _reasonable_.
if err := checkTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil {
@@ -1663,7 +1663,7 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
}
- if err := b.SendTx(ctx, tx); err != nil {
+ if err := b.SendTx(ctx, tx, private); err != nil {
return common.Hash{}, err
}
// Print a log with full tx details for manual investigations and interventions
@@ -1711,7 +1711,7 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr
if err != nil {
return common.Hash{}, err
}
- return SubmitTransaction(ctx, s.b, signed)
+ return SubmitTransaction(ctx, s.b, signed, false)
}
// FillTransaction fills the defaults (nonce, gas, gasPrice or 1559 fields)
@@ -1738,7 +1738,20 @@ func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.B
if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err
}
- return SubmitTransaction(ctx, s.b, tx)
+ return SubmitTransaction(ctx, s.b, tx, false)
+}
+
+// SendPrivateRawTransaction will add the signed transaction to the transaction pool,
+// without broadcasting the transaction to its peers, and mark the transaction to avoid
+// future syncs.
+//
+// See SendRawTransaction.
+func (s *TransactionAPI) SendPrivateRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
+ tx := new(types.Transaction)
+ if err := tx.UnmarshalBinary(input); err != nil {
+ return common.Hash{}, err
+ }
+ return SubmitTransaction(ctx, s.b, tx, true)
}
// Sign calculates an ECDSA signature for:
@@ -1871,7 +1884,7 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g
if err != nil {
return common.Hash{}, err
}
- if err = s.b.SendTx(ctx, signedTx); err != nil {
+ if err = s.b.SendTx(ctx, signedTx, false); err != nil {
return common.Hash{}, err
}
return signedTx.Hash(), nil
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index d8b4b2b451..57335ea368 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -74,7 +74,7 @@ type Backend interface {
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
// Transaction pool API
- SendTx(ctx context.Context, signedTx *types.Transaction) error
+ SendTx(ctx context.Context, signedTx *types.Transaction, private bool) error
SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error
SendMegabundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash, relayAddr common.Address) error
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index 4ce1f84f22..de877f5159 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -530,6 +530,12 @@ web3._extend({
params: 1,
inputFormatter: [web3._extend.formatters.inputTransactionFormatter]
}),
+ new web3._extend.Method({
+ name: 'sendPrivateRawTransaction',
+ call: 'eth_sendPrivateRawTransaction',
+ params: 1,
+ inputFormatter: [null]
+ }),
new web3._extend.Method({
name: 'fillTransaction',
call: 'eth_fillTransaction',
diff --git a/les/api_backend.go b/les/api_backend.go
index 5b327559f4..a0c2234fc7 100644
--- a/les/api_backend.go
+++ b/les/api_backend.go
@@ -188,7 +188,7 @@ func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *sta
return vm.NewEVM(context, txContext, state, b.eth.chainConfig, *vmConfig), state.Error, nil
}
-func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
+func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction, private bool) error {
return b.eth.txPool.Add(ctx, signedTx)
}
From a3d99c22c00837fe3f7d3f43d15f6debd9fb4764 Mon Sep 17 00:00:00 2001
From: Kevin Chen
Date: Fri, 28 Jan 2022 14:46:57 -0600
Subject: [PATCH 25/83] Remove private transactions that are confirmed in
blocks
---
core/tx_pool.go | 36 +++++++++++++++++++-----------------
1 file changed, 19 insertions(+), 17 deletions(-)
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 639a951585..c7b9ece9d3 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -1669,6 +1669,7 @@ func (pool *TxPool) demoteUnexecutables() {
for _, tx := range olds {
hash := tx.Hash()
pool.all.Remove(hash)
+ pool.privateTxs.Remove(hash)
log.Trace("Removed old pending transaction", "hash", hash)
}
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
@@ -1969,14 +1970,12 @@ func (t *txLookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
type timestampedTxHashSet struct {
lock sync.RWMutex
- hashes []common.Hash
timestamps map[common.Hash]time.Time
ttl time.Duration
}
func newExpiringTxHashSet(ttl time.Duration) *timestampedTxHashSet {
s := ×tampedTxHashSet{
- hashes: make([]common.Hash, 0),
timestamps: make(map[common.Hash]time.Time),
ttl: ttl,
}
@@ -1988,8 +1987,10 @@ func (s *timestampedTxHashSet) Add(hash common.Hash) {
s.lock.Lock()
defer s.lock.Unlock()
- s.hashes = append(s.hashes, hash)
- s.timestamps[hash] = time.Now().Add(s.ttl)
+ _, ok := s.timestamps[hash]
+ if !ok {
+ s.timestamps[hash] = time.Now().Add(s.ttl)
+ }
}
func (s *timestampedTxHashSet) Contains(hash common.Hash) bool {
@@ -1999,25 +2000,26 @@ func (s *timestampedTxHashSet) Contains(hash common.Hash) bool {
return ok
}
-func (s *timestampedTxHashSet) prune() {
+func (s *timestampedTxHashSet) Remove(hash common.Hash) {
s.lock.Lock()
defer s.lock.Unlock()
- var (
- count int
- now = time.Now()
- )
- for _, hash := range s.hashes {
- ts := s.timestamps[hash]
- if ts.After(now) {
- break
- }
-
+ _, ok := s.timestamps[hash]
+ if ok {
delete(s.timestamps, hash)
- count += 1
}
+}
+
+func (s *timestampedTxHashSet) prune() {
+ s.lock.Lock()
+ defer s.lock.Unlock()
- s.hashes = s.hashes[count:]
+ now := time.Now()
+ for hash, ts := range s.timestamps {
+ if ts.Before(now) {
+ delete(s.timestamps, hash)
+ }
+ }
}
// numSlots calculates the number of slots needed for a single transaction.
From 6fe1708bc68c88ffcd0257bec87c25ae6759cdef Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 24 Feb 2022 19:40:14 +0100
Subject: [PATCH 26/83] Run private transactions e2e tests in ci
---
.github/workflows/go.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 3fc1f2ff8c..47c3013398 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -62,3 +62,4 @@ jobs:
sleep 15
yarn run demo-simple
yarn run demo-contract
+ yarn run demo-private-tx
From 231448f4df0db149f08c79cc61867b393e13b0e8 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Wed, 30 Mar 2022 23:39:29 +0200
Subject: [PATCH 27/83] Discard reverting megabundle blocks and head change
interrupted blocks (#123)
* Discard reverting megabundle blocks and head change interrupted blocks
* Discard all blocks with incomplete bundles
* Run reverting megabundles regression test separately from bundle tests
---
.github/workflows/go.yml | 23 ++++++++++++++++++++++-
miner/worker.go | 12 ++++++------
2 files changed, 28 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 47c3013398..d198d152cd 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -56,10 +56,31 @@ jobs:
path: e2e
- run: cd e2e && yarn install
- - run: |
+ - name: Run single node e2e
+ run: |
cd e2e
GETH=`pwd`/../build/bin/geth ./run.sh &
sleep 15
yarn run demo-simple
+ yarn run e2e-reverting-bundles
yarn run demo-contract
+ pkill -9 geth || true
+ - name: Run private tx with two nodes
+ run: |
+ cd e2e
+ GETH=`pwd`/../build/bin/geth ./run.sh &
+ # Second node, not mining
+ P2P_PORT=30302 DATADIR=datadir2 HTTP_PORT=8546 MINER_ARGS='--nodiscover' GETH=`pwd`/../build/bin/geth ./run.sh &
+ sleep 15
+ DATADIR1=datadir DATADIR2=datadir2 GETH=`pwd`/../build/bin/geth ./peer_nodes.sh
+ sleep 15
yarn run demo-private-tx
+ pkill -9 geth || true
+ - name: Run megabundle-only node checking for reverts
+ run: |
+ cd e2e
+ # Disable bundle workers
+ MINER_ARGS='--miner.etherbase=0xd912aecb07e9f4e1ea8e6b4779e7fb6aa1c3e4d8 --miner.trustedrelays=0xfb11e78C4DaFec86237c2862441817701fdf197F --mine --miner.threads=2 --miner.maxmergedbundles=0' GETH=`pwd`/../build/bin/geth ./run.sh &
+ sleep 15
+ yarn run e2e-reverting-megabundle
+ pkill -9 geth || true
diff --git a/miner/worker.go b/miner/worker.go
index c152a37df1..1de8db1996 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -961,8 +961,7 @@ func (w *worker) commitBundle(env *environment, txs types.Transactions, interrup
// (1) new head block event arrival, the interrupt signal is 1
// (2) worker start or restart, the interrupt signal is 1
// (3) worker recreate the sealing block with any newly arrived transactions, the interrupt signal is 2.
- // For the first two cases, the semi-finished work will be discarded.
- // For the third case, the semi-finished work will be submitted to the consensus engine.
+ // Discard the interrupted work, since it is incomplete and contains partial bundles
if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone {
// Notify resubmit loop to increase resubmitting interval due to too frequent commits.
if atomic.LoadInt32(interrupt) == commitInterruptResubmit {
@@ -977,10 +976,11 @@ func (w *worker) commitBundle(env *environment, txs types.Transactions, interrup
}
return errBundleInterrupted
}
- // If we don't have enough gas for any further transactions then we're done
+ // If we don't have enough gas for any further transactions discard the block
+ // since not all bundles of the were applied
if env.gasPool.Gas() < params.TxGas {
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
- break
+ return errCouldNotApplyTransaction
}
// Error may be ignored here. The error has already been checked
@@ -1644,11 +1644,11 @@ func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pe
simulatedBundles := []simulatedBundle{}
for _, bundle := range bundles {
- state := env.state.Copy()
- gasPool := new(core.GasPool).AddGas(env.header.GasLimit)
if len(bundle.Txs) == 0 {
continue
}
+ state := env.state.Copy()
+ gasPool := new(core.GasPool).AddGas(env.header.GasLimit)
simmed, err := w.computeBundleGas(env, bundle, state, gasPool, pendingTxs, 0)
if err != nil {
From 997e95b5d7aba39c0268e774a5af790b82d03b92 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Fri, 29 Jul 2022 17:33:18 +0200
Subject: [PATCH 28/83] Seal the best block available
---
miner/miner.go | 12 ++--------
miner/multi_worker.go | 55 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+), 10 deletions(-)
diff --git a/miner/miner.go b/miner/miner.go
index 55bd3da960..acf08d3e83 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -250,20 +250,12 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
// The difference is that if the execution fails, the returned result is nil
// and the concrete error is dropped silently.
func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (chan *types.Block, error) {
- resCh, _, err := miner.worker.regularWorker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
- if err != nil {
- return nil, err
- }
- return resCh, nil
+ return miner.worker.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
}
// GetSealingBlockSync creates a sealing block according to the given parameters.
// If the generation is failed or the underlying work is already closed, an error
// will be returned.
func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (*types.Block, error) {
- resCh, errCh, err := miner.worker.regularWorker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
- if err != nil {
- return nil, err
- }
- return <-resCh, <-errCh
+ return miner.worker.GetSealingBlockSync(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
}
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index da1471fa3e..4330e64769 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -1,6 +1,7 @@
package miner
import (
+ "errors"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -85,6 +86,60 @@ func (w *multiWorker) disablePreseal() {
}
}
+type resChPair struct {
+ resCh chan *types.Block
+ errCh chan error
+}
+
+func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (chan *types.Block, error) {
+ resChans := []resChPair{}
+
+ for _, worker := range append(w.workers, w.regularWorker) {
+ resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra)
+ if err != nil {
+ log.Error("could not start async block construction", "isFlashbotsWorker", worker.flashbots.isFlashbots, "isMegabundleWorker", worker.flashbots.isMegabundleWorker, "#bundles", worker.flashbots.maxMergedBundles)
+ continue
+ }
+ resChans = append(resChans, resChPair{resCh, errCh})
+ }
+
+ if len(resChans) == 0 {
+ return nil, errors.New("no worker could start async block construction")
+ }
+
+ resCh := make(chan *types.Block)
+
+ go func(resCh chan *types.Block) {
+ var res *types.Block = nil
+ for _, chPair := range resChans {
+ err := <-chPair.errCh
+ if err != nil {
+ log.Error("could not generate block", "err", err)
+ continue
+ }
+ newBlock := <-chPair.resCh
+ if res == nil || (newBlock != nil && newBlock.Profit.Cmp(res.Profit) > 0) {
+ res = newBlock
+ }
+ }
+ resCh <- res
+ }(resCh)
+
+ return resCh, nil
+}
+
+func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (*types.Block, error) {
+ resCh, err := w.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra)
+ if err != nil {
+ return nil, err
+ }
+ res := <-resCh
+ if res == nil {
+ return nil, errors.New("no viable blocks created")
+ }
+ return res, nil
+}
+
func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *multiWorker {
queue := make(chan *task)
From 913a7920141919d65cabb436c5074d915796f51a Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Wed, 17 Aug 2022 13:04:30 +0200
Subject: [PATCH 29/83] Implement block validation API
---
cmd/geth/config.go | 7 +-
cmd/geth/main.go | 1 +
cmd/utils/flags.go | 8 +
core/beacon/types.go | 34 ++++
core/blockchain.go | 71 ++++++++
eth/block-validation/api.go | 175 +++++++++++++++++++
eth/block-validation/api_test.go | 291 +++++++++++++++++++++++++++++++
go.mod | 4 +
ofac_blacklist.json | 71 ++++++++
9 files changed, 661 insertions(+), 1 deletion(-)
create mode 100644 eth/block-validation/api.go
create mode 100644 eth/block-validation/api_test.go
create mode 100644 ofac_blacklist.json
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 9bff124bb7..7c71fd47c9 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -33,6 +33,7 @@ import (
builder "github.com/ethereum/go-ethereum/builder"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core/rawdb"
+ blockvalidationapi "github.com/ethereum/go-ethereum/eth/block-validation"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags"
@@ -203,7 +204,11 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
// Configure log filter RPC API.
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
- // Configure GraphQL if requested.
+ if err := blockvalidationapi.Register(stack, eth, ctx); err != nil {
+ utils.Fatalf("Failed to register the Block Validation API: %v", err)
+ }
+
+ // Configure GraphQL if requested
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
}
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index ec70016cd1..b172ffe57c 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -157,6 +157,7 @@ var (
utils.GpoIgnoreGasPriceFlag,
utils.MinerNotifyFullFlag,
utils.IgnoreLegacyReceiptsFlag,
+ utils.BuilderBlockValidationBlacklistSourceFilePath,
configFileFlag,
}, utils.NetworkFlags, utils.DatabasePathFlags)
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index d9a8882064..c9f4450c11 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -1056,6 +1056,14 @@ var (
Value: metrics.DefaultConfig.InfluxDBOrganization,
Category: flags.MetricsCategory,
}
+
+ // Builder API flags
+ BuilderBlockValidationBlacklistSourceFilePath = &cli.StringFlag{
+ Name: "builder.validation_blacklist",
+ Usage: "Path to file containing blacklisted addresses, json-encoded list of strings. Default assumes CWD is repo's root",
+ Value: "ofac_blacklist.json",
+ Category: flags.EthCategory,
+ }
)
var (
diff --git a/core/beacon/types.go b/core/beacon/types.go
index 6ebc6954a5..cf02850324 100644
--- a/core/beacon/types.go
+++ b/core/beacon/types.go
@@ -24,6 +24,8 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie"
+
+ boostTypes "github.com/flashbots/go-boost-utils/types"
)
//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go
@@ -181,6 +183,38 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
return block, nil
}
+func ExecutionPayloadToBlock(payload *boostTypes.ExecutionPayload) (*types.Block, error) {
+ // TODO: separate decode function to avoid allocating twice
+ transactionBytes := make([][]byte, len(payload.Transactions))
+ for i, txHexBytes := range payload.Transactions {
+ transactionBytes[i] = txHexBytes[:]
+ }
+ txs, err := decodeTransactions(transactionBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ header := &types.Header{
+ ParentHash: common.Hash(payload.ParentHash),
+ UncleHash: types.EmptyUncleHash,
+ Coinbase: common.Address(payload.FeeRecipient),
+ Root: common.Hash(payload.StateRoot),
+ TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
+ ReceiptHash: common.Hash(payload.ReceiptsRoot),
+ Bloom: types.BytesToBloom(payload.LogsBloom[:]),
+ Difficulty: common.Big0,
+ Number: new(big.Int).SetUint64(payload.BlockNumber),
+ GasLimit: payload.GasLimit,
+ GasUsed: payload.GasUsed,
+ Time: payload.Timestamp,
+ BaseFee: payload.BaseFeePerGas.BigInt(),
+ Extra: payload.ExtraData,
+ MixDigest: common.Hash(payload.Random),
+ }
+ block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
+ return block, nil
+}
+
// BlockToExecutableData constructs the executableDataV1 structure by filling the
// fields from the given block. It assumes the given block is post-merge block.
func BlockToExecutableData(block *types.Block) *ExecutableDataV1 {
diff --git a/core/blockchain.go b/core/blockchain.go
index a98c3b4dbe..29ff5e42d4 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -2421,3 +2421,74 @@ func (bc *BlockChain) SetBlockValidatorAndProcessorForTesting(v Validator, p Pro
bc.validator = v
bc.processor = p
}
+
+func (bc *BlockChain) ValidatePayload(block *types.Block, feeRecipient common.Address, expectedProfit *big.Int, vmConfig vm.Config) error {
+ header := block.Header()
+ if err := bc.engine.VerifyHeader(bc, header, true); err != nil {
+ return err
+ }
+
+ current := bc.CurrentBlock()
+ reorg, err := bc.forker.ReorgNeeded(current.Header(), header)
+ if err == nil && reorg {
+ return errors.New("block requires a reorg")
+ }
+
+ parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
+ if parent == nil {
+ return errors.New("parent not found")
+ }
+
+ statedb, err := bc.StateAt(parent.Root)
+ if err != nil {
+ return err
+ }
+
+ // The chain importer is starting and stopping trie prefetchers. If a bad
+ // block or other error is hit however, an early return may not properly
+ // terminate the background threads. This defer ensures that we clean up
+ // and dangling prefetcher, without defering each and holding on live refs.
+ defer statedb.StopPrefetcher()
+
+ receipts, _, usedGas, err := bc.processor.Process(block, statedb, vmConfig)
+ if err != nil {
+ return err
+ }
+
+ if err := bc.validator.ValidateBody(block); err != nil {
+ return err
+ }
+
+ if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
+ return err
+ }
+
+ if len(receipts) == 0 {
+ return errors.New("no proposer payment receipt")
+ }
+
+ lastReceipt := receipts[len(receipts)-1]
+ if lastReceipt.Status != types.ReceiptStatusSuccessful {
+ return errors.New("proposer payment not successful")
+ }
+ txIndex := lastReceipt.TransactionIndex
+ if txIndex+1 != uint(len(block.Transactions())) {
+ return fmt.Errorf("proposer payment index not last transaction in the block (%d of %d)", txIndex, len(block.Transactions())-1)
+ }
+
+ paymentTx := block.Transaction(lastReceipt.TxHash)
+ if paymentTx == nil {
+ return errors.New("payment tx not in the block")
+ }
+
+ paymentTo := paymentTx.To()
+ if paymentTo == nil || *paymentTo != feeRecipient {
+ return fmt.Errorf("payment tx not to the proposers fee recipient (%v)", paymentTo)
+ }
+
+ if paymentTx.Value().Cmp(expectedProfit) != 0 {
+ return fmt.Errorf("inaccurate payment %s, expected %s", paymentTx.Value().String(), expectedProfit.String())
+ }
+
+ return nil
+}
diff --git a/eth/block-validation/api.go b/eth/block-validation/api.go
new file mode 100644
index 0000000000..ca1ea29626
--- /dev/null
+++ b/eth/block-validation/api.go
@@ -0,0 +1,175 @@
+package blockvalidation
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math/big"
+ "os"
+
+ "github.com/ethereum/go-ethereum/cmd/utils"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/beacon"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/eth/tracers/logger"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/urfave/cli/v2"
+
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+)
+
+type BlacklistedAddresses []common.Address
+
+type AccessVerifier struct {
+ blacklistedAddresses map[common.Address]struct{}
+}
+
+func (a *AccessVerifier) verifyTraces(tracer *logger.AccessListTracer) error {
+ log.Info("x", "tracer.AccessList()", tracer.AccessList())
+ for _, accessTuple := range tracer.AccessList() {
+ // TODO: should we ignore common.Address{}?
+ if _, found := a.blacklistedAddresses[accessTuple.Address]; found {
+ log.Info("bundle accesses blacklisted address", "address", accessTuple.Address)
+ return fmt.Errorf("blacklisted address %s in execution trace", accessTuple.Address.String())
+ }
+ }
+
+ return nil
+}
+
+func (a *AccessVerifier) verifyTransactions(signer types.Signer, txs types.Transactions) error {
+ for _, tx := range txs {
+ from, err := signer.Sender(tx)
+ if err == nil {
+ if _, present := a.blacklistedAddresses[from]; present {
+ return fmt.Errorf("transaction from blacklisted address %s", from.String())
+ }
+ }
+ to := tx.To()
+ if to != nil {
+ if _, present := a.blacklistedAddresses[*to]; present {
+ return fmt.Errorf("transaction to blacklisted address %s", to.String())
+ }
+ }
+ }
+ return nil
+}
+
+func NewAccessVerifierFromFile(path string) (*AccessVerifier, error) {
+ bytes, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ var ba BlacklistedAddresses
+ if err := json.Unmarshal(bytes, &ba); err != nil {
+ return nil, err
+ }
+
+ blacklistedAddresses := make(map[common.Address]struct{}, len(ba))
+ for _, address := range ba {
+ blacklistedAddresses[address] = struct{}{}
+ }
+
+ return &AccessVerifier{
+ blacklistedAddresses: blacklistedAddresses,
+ }, nil
+}
+
+// Register adds catalyst APIs to the full node.
+func Register(stack *node.Node, backend *eth.Ethereum, ctx *cli.Context) error {
+ var accessVerifier *AccessVerifier
+ if ctx.IsSet(utils.BuilderBlockValidationBlacklistSourceFilePath.Name) {
+ var err error
+ accessVerifier, err = NewAccessVerifierFromFile(ctx.String(utils.BuilderBlockValidationBlacklistSourceFilePath.Name))
+ if err != nil {
+ return err
+ }
+ }
+
+ stack.RegisterAPIs([]rpc.API{
+ {
+ Namespace: "flashbots",
+ Service: NewBlockValidationAPI(backend, accessVerifier),
+ },
+ })
+ return nil
+}
+
+type BlockValidationAPI struct {
+ eth *eth.Ethereum
+ accessVerifier *AccessVerifier
+}
+
+// NewConsensusAPI creates a new consensus api for the given backend.
+// The underlying blockchain needs to have a valid terminal total difficulty set.
+func NewBlockValidationAPI(eth *eth.Ethereum, accessVerifier *AccessVerifier) *BlockValidationAPI {
+ return &BlockValidationAPI{
+ eth: eth,
+ accessVerifier: accessVerifier,
+ }
+}
+
+func (api *BlockValidationAPI) ValidateBuilderSubmissionV1(params *boostTypes.BuilderSubmitBlockRequest) error {
+ // TODO: fuzztest, make sure the validation is sound
+ // TODO: handle context!
+
+ if params.ExecutionPayload == nil {
+ return errors.New("nil execution payload")
+ }
+ payload := params.ExecutionPayload
+ block, err := beacon.ExecutionPayloadToBlock(payload)
+ if err != nil {
+ return err
+ }
+
+ if params.Message.ParentHash != boostTypes.Hash(block.ParentHash()) {
+ return fmt.Errorf("incorrect ParentHash %s, expected %s", params.Message.ParentHash.String(), block.ParentHash().String())
+ }
+
+ if params.Message.BlockHash != boostTypes.Hash(block.Hash()) {
+ return fmt.Errorf("incorrect BlockHash %s, expected %s", params.Message.BlockHash.String(), block.Hash().String())
+ }
+
+ if params.Message.GasLimit != block.GasLimit() {
+ return fmt.Errorf("incorrect GasLimit %d, expected %d", params.Message.GasLimit, block.GasLimit())
+ }
+
+ if params.Message.GasUsed != block.GasUsed() {
+ return fmt.Errorf("incorrect GasUsed %d, expected %d", params.Message.GasUsed, block.GasUsed())
+ }
+
+ feeRecipient := common.BytesToAddress(params.Message.ProposerFeeRecipient[:])
+ expectedProfit := params.Message.Value.BigInt()
+
+ var vmconfig vm.Config
+ var tracer *logger.AccessListTracer = nil
+ if api.accessVerifier != nil {
+ if err := api.accessVerifier.verifyTransactions(types.LatestSigner(api.eth.BlockChain().Config()), block.Transactions()); err != nil {
+ return err
+ }
+ isPostMerge := true // the call is PoS-native
+ precompiles := vm.ActivePrecompiles(api.eth.APIBackend.ChainConfig().Rules(new(big.Int).SetUint64(params.ExecutionPayload.BlockNumber), isPostMerge))
+ tracer = logger.NewAccessListTracer(nil, common.Address{}, common.Address{}, precompiles)
+ vmconfig = vm.Config{Tracer: tracer, Debug: true}
+ }
+
+ err = api.eth.BlockChain().ValidatePayload(block, feeRecipient, expectedProfit, vmconfig)
+ if err != nil {
+ log.Error("invalid payload", "hash", payload.BlockHash.String(), "number", payload.BlockNumber, "parentHash", payload.ParentHash.String(), "err", err)
+ return err
+ }
+
+ if api.accessVerifier != nil && tracer != nil {
+ if err := api.accessVerifier.verifyTraces(tracer); err != nil {
+ return err
+ }
+ }
+
+ log.Info("validated block", "hash", block.Hash(), "number", block.NumberU64(), "parentHash", block.ParentHash())
+ return nil
+}
diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go
new file mode 100644
index 0000000000..961e091181
--- /dev/null
+++ b/eth/block-validation/api_test.go
@@ -0,0 +1,291 @@
+package blockvalidation
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "math/big"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/beacon"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
+ "github.com/ethereum/go-ethereum/eth/tracers/logger"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/stretchr/testify/require"
+
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+)
+
+/* Based on catalyst API tests */
+
+var (
+ // testKey is a private key to use for funding a tester account.
+ testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+
+ // testAddr is the Ethereum address of the tester account.
+ testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
+
+ testValidatorKey, _ = crypto.HexToECDSA("28c3cd61b687fdd03488e167a5d84f50269df2a4c29a2cfb1390903aa775c5d0")
+ testValidatorAddr = crypto.PubkeyToAddress(testValidatorKey.PublicKey)
+
+ testMinerKey, _ = crypto.HexToECDSA("28c3cd61b687fdd03488e167a5d84f50269df2a4c29a2cfb1390903aa775c5d0")
+ testMinerAddr = crypto.PubkeyToAddress(testValidatorKey.PublicKey)
+
+ testBalance = big.NewInt(2e18)
+)
+
+func TestValidateBuilderSubmissionV1(t *testing.T) {
+ genesis, preMergeBlocks := generatePreMergeChain(20)
+ os.Setenv("BUILDER_TX_SIGNING_KEY", "0x28c3cd61b687fdd03488e167a5d84f50269df2a4c29a2cfb1390903aa775c5d0")
+ n, ethservice := startEthService(t, genesis, preMergeBlocks)
+ ethservice.Merger().ReachTTD()
+ defer n.Close()
+
+ api := NewBlockValidationAPI(ethservice, nil)
+ parent := preMergeBlocks[len(preMergeBlocks)-1]
+
+ api.eth.APIBackend.Miner().SetEtherbase(testMinerAddr)
+
+ // This EVM code generates a log when the contract is created.
+ logCode := common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
+
+ statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
+ nonce := statedb.GetNonce(testAddr)
+
+ tx1, _ := types.SignTx(types.NewTransaction(nonce, common.Address{0x16}, big.NewInt(10), 21000, big.NewInt(2*params.InitialBaseFee), nil), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
+ ethservice.TxPool().AddLocal(tx1)
+
+ cc, _ := types.SignTx(types.NewContractCreation(nonce+1, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
+ ethservice.TxPool().AddLocal(cc)
+
+ tx2, _ := types.SignTx(types.NewTransaction(nonce+2, testAddr, big.NewInt(10), 21000, big.NewInt(2*params.InitialBaseFee), nil), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
+ ethservice.TxPool().AddLocal(tx2)
+
+ execData, err := assembleBlock(api, parent.Hash(), &beacon.PayloadAttributesV1{
+ Timestamp: parent.Time() + 5,
+ SuggestedFeeRecipient: testValidatorAddr,
+ })
+ require.NoError(t, err)
+ require.EqualValues(t, len(execData.Transactions), 4)
+ require.NoError(t, err)
+
+ payload, err := ExecutableDataToExecutionPayload(execData)
+ require.NoError(t, err)
+
+ proposerAddr := boostTypes.Address{}
+ proposerAddr.FromSlice(testValidatorAddr[:])
+
+ blockRequest := &boostTypes.BuilderSubmitBlockRequest{
+ Signature: boostTypes.Signature{},
+ Message: &boostTypes.BidTrace{
+ ParentHash: boostTypes.Hash(execData.ParentHash),
+ BlockHash: boostTypes.Hash(execData.BlockHash),
+ ProposerFeeRecipient: proposerAddr,
+ GasLimit: execData.GasLimit,
+ GasUsed: execData.GasUsed,
+ },
+ ExecutionPayload: payload,
+ }
+ blockRequest.Message.Value = boostTypes.IntToU256(190526394825529)
+ require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "inaccurate payment")
+ blockRequest.Message.Value = boostTypes.IntToU256(190526394825530)
+ require.NoError(t, api.ValidateBuilderSubmissionV1(blockRequest))
+
+ // TODO: test with contract calling blacklisted address
+ // Test tx from blacklisted address
+ api.accessVerifier = &AccessVerifier{
+ blacklistedAddresses: map[common.Address]struct{}{
+ testAddr: struct{}{},
+ },
+ }
+ require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "transaction from blacklisted address 0x71562b71999873DB5b286dF957af199Ec94617F7")
+
+ // Test tx to blacklisted address
+ api.accessVerifier = &AccessVerifier{
+ blacklistedAddresses: map[common.Address]struct{}{
+ common.Address{0x16}: struct{}{},
+ },
+ }
+ require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "transaction to blacklisted address 0x1600000000000000000000000000000000000000")
+
+ api.accessVerifier = nil
+
+ blockRequest.Message.GasUsed = 10
+ require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "incorrect GasUsed 10, expected 119990")
+ blockRequest.Message.GasUsed = execData.GasUsed
+
+ newTestKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f290")
+ invalidTx, err := types.SignTx(types.NewTransaction(0, common.Address{}, new(big.Int).Mul(big.NewInt(2e18), big.NewInt(10)), 19000, big.NewInt(2*params.InitialBaseFee), nil), types.LatestSigner(ethservice.BlockChain().Config()), newTestKey)
+ require.NoError(t, err)
+
+ txData, err := invalidTx.MarshalBinary()
+ require.NoError(t, err)
+ execData.Transactions = append(execData.Transactions, execData.Transactions[3])
+ execData.Transactions[3] = txData
+
+ invalidPayload, err := ExecutableDataToExecutionPayload(execData)
+ require.NoError(t, err)
+ invalidPayload.GasUsed = execData.GasUsed
+ invalidPayload.LogsBloom = boostTypes.Bloom{}
+ copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
+ blockRequest.ExecutionPayload = invalidPayload
+ copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x48c4ee43556e149344878f5d1fee26b0b8d569e1697a4d9cce74034c6c45fac1")[:32])
+ require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value")
+}
+
+func generatePreMergeChain(n int) (*core.Genesis, []*types.Block) {
+ db := rawdb.NewMemoryDatabase()
+ config := params.AllEthashProtocolChanges
+ genesis := &core.Genesis{
+ Config: config,
+ Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}},
+ ExtraData: []byte("test genesis"),
+ Timestamp: 9000,
+ BaseFee: big.NewInt(params.InitialBaseFee),
+ Difficulty: big.NewInt(0),
+ }
+ testNonce := uint64(0)
+ generate := func(i int, g *core.BlockGen) {
+ g.OffsetTime(5)
+ g.SetExtra([]byte("test"))
+ tx, _ := types.SignTx(types.NewTransaction(testNonce, common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"), big.NewInt(1), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), types.LatestSigner(config), testKey)
+ g.AddTx(tx)
+ testNonce++
+ }
+ gblock := genesis.MustCommit(db)
+ engine := ethash.NewFaker()
+ blocks, _ := core.GenerateChain(config, gblock, engine, db, n, generate)
+ totalDifficulty := big.NewInt(0)
+ for _, b := range blocks {
+ totalDifficulty.Add(totalDifficulty, b.Difficulty())
+ }
+ config.TerminalTotalDifficulty = totalDifficulty
+ return genesis, blocks
+}
+
+// startEthService creates a full node instance for testing.
+func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
+ t.Helper()
+
+ n, err := node.New(&node.Config{
+ P2P: p2p.Config{
+ ListenAddr: "0.0.0.0:0",
+ NoDiscovery: true,
+ MaxPeers: 25,
+ }})
+ if err != nil {
+ t.Fatal("can't create node:", err)
+ }
+
+ ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}, SyncMode: downloader.SnapSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
+ ethservice, err := eth.New(n, ethcfg)
+ if err != nil {
+ t.Fatal("can't create eth service:", err)
+ }
+ if err := n.Start(); err != nil {
+ t.Fatal("can't start node:", err)
+ }
+ if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil {
+ n.Close()
+ t.Fatal("can't import test blocks:", err)
+ }
+ time.Sleep(500 * time.Millisecond) // give txpool enough time to consume head event
+
+ ethservice.SetEtherbase(testAddr)
+ ethservice.SetSynced()
+ return n, ethservice
+}
+
+func assembleBlock(api *BlockValidationAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) {
+ block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false)
+ if err != nil {
+ return nil, err
+ }
+ log.Info("b", "block", block)
+ return beacon.BlockToExecutableData(block), nil
+}
+
+func TestBlacklistLoad(t *testing.T) {
+ file, err := os.CreateTemp(".", "blacklist")
+ require.NoError(t, err)
+ defer os.Remove(file.Name())
+
+ av, err := NewAccessVerifierFromFile(file.Name())
+ require.Error(t, err)
+ require.Nil(t, av)
+
+ ba := BlacklistedAddresses{common.Address{0x13}, common.Address{0x14}}
+ bytes, err := json.MarshalIndent(ba, "", " ")
+ require.NoError(t, err)
+ err = ioutil.WriteFile(file.Name(), bytes, 0644)
+ require.NoError(t, err)
+
+ av, err = NewAccessVerifierFromFile(file.Name())
+ require.NoError(t, err)
+ require.NotNil(t, av)
+ require.EqualValues(t, av.blacklistedAddresses, map[common.Address]struct{}{
+ common.Address{0x13}: struct{}{},
+ common.Address{0x14}: struct{}{},
+ })
+
+ require.NoError(t, av.verifyTraces(logger.NewAccessListTracer(nil, common.Address{}, common.Address{}, nil)))
+
+ acl := types.AccessList{
+ types.AccessTuple{
+ Address: common.Address{0x14},
+ },
+ }
+ tracer := logger.NewAccessListTracer(acl, common.Address{}, common.Address{}, nil)
+ require.ErrorContains(t, av.verifyTraces(tracer), "blacklisted address 0x1400000000000000000000000000000000000000 in execution trace")
+
+ acl = types.AccessList{
+ types.AccessTuple{
+ Address: common.Address{0x15},
+ },
+ }
+ tracer = logger.NewAccessListTracer(acl, common.Address{}, common.Address{}, nil)
+ require.NoError(t, av.verifyTraces(tracer))
+}
+
+func ExecutableDataToExecutionPayload(data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayload, error) {
+ transactionData := make([]hexutil.Bytes, len(data.Transactions))
+ for i, tx := range data.Transactions {
+ transactionData[i] = hexutil.Bytes(tx)
+ }
+
+ baseFeePerGas := new(boostTypes.U256Str)
+ err := baseFeePerGas.FromBig(data.BaseFeePerGas)
+ if err != nil {
+ return nil, err
+ }
+
+ return &boostTypes.ExecutionPayload{
+ ParentHash: [32]byte(data.ParentHash),
+ FeeRecipient: [20]byte(data.FeeRecipient),
+ StateRoot: [32]byte(data.StateRoot),
+ ReceiptsRoot: [32]byte(data.ReceiptsRoot),
+ LogsBloom: boostTypes.Bloom(types.BytesToBloom(data.LogsBloom)),
+ Random: [32]byte(data.Random),
+ BlockNumber: data.Number,
+ GasLimit: data.GasLimit,
+ GasUsed: data.GasUsed,
+ Timestamp: data.Timestamp,
+ ExtraData: data.ExtraData,
+ BaseFeePerGas: *baseFeePerGas,
+ BlockHash: [32]byte(data.BlockHash),
+ Transactions: transactionData,
+ }, nil
+}
diff --git a/go.mod b/go.mod
index 51895722c5..1153762061 100644
--- a/go.mod
+++ b/go.mod
@@ -88,11 +88,14 @@ require (
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
github.com/ferranbt/fastssz v0.1.2-0.20220723134332-b3d3034a4575 // indirect
+ github.com/flashbots/go-boost-utils v0.3.5 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
+ github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
+ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
@@ -111,6 +114,7 @@ require (
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
google.golang.org/protobuf v1.26.0 // indirect
+ gopkg.in/urfave/cli.v1 v1.20.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/ofac_blacklist.json b/ofac_blacklist.json
new file mode 100644
index 0000000000..5467935254
--- /dev/null
+++ b/ofac_blacklist.json
@@ -0,0 +1,71 @@
+[
+ "0x03893a7c7463ae47d46bc7f091665f1893656003",
+ "0x07687e702b410fa43f4cb4af7fa097918ffd2730",
+ "0x0836222f2b2b24a3f36f98668ed8f0b38d1a872f",
+ "0x08723392ed15743cc38513c4925f5e6be5c17243",
+ "0x098b716b8aaf21512996dc57eb0615e2383e2f96",
+ "0x12d66f87a04a9e220743712ce6d9bb1b5616b8fc",
+ "0x1356c899d8c9467c7f71c195612f8a395abf2f0a",
+ "0x169ad27a470d064dede56a2d3ff727986b15d52b",
+ "0x178169b423a011fff22b9e3f3abea13414ddd0f1",
+ "0x19aa5fe80d33a56d56c78e82ea5e50e5d80b4dff",
+ "0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a",
+ "0x22aaa7720ddd5388a3c0a3333430953c68f1849b",
+ "0x23773e65ed146a459791799d01336db287f25334",
+ "0x2717c5e28cf931547b621a5dddb772ab6a35b701",
+ "0x2f389ce8bd8ff92de3402ffce4691d17fc4f6535",
+ "0x308ed4b7b49797e1a98d3818bff6fe5385410370",
+ "0x35fb6f6db4fb05e6a4ce86f2c93691425626d4b1",
+ "0x3cbded43efdaf0fc77b9c55f6fc9988fcc9b757d",
+ "0x3cffd56b47b7b41c56258d9c7731abadc360e073",
+ "0x3e37627deaa754090fbfbb8bd226c1ce66d255e9",
+ "0x4736dcf1b7a3d580672cce6e7c65cd5cc9cfba9d",
+ "0x47ce0c6ed5b0ce3d3a51fdb1c52dc66a7c3c2936",
+ "0x48549a34ae37b12f6a30566245176994e17c6b4a",
+ "0x527653ea119f3e6a1f5bd18fbf4714081d7b31ce",
+ "0x53b6936513e738f44fb50d2b9476730c0ab3bfc1",
+ "0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0",
+ "0x58e8dcc13be9780fc42e8723d8ead4cf46943df2",
+ "0x610b717796ad172b316836ac95a2ffad065ceab4",
+ "0x67d40ee1a85bf4a4bb7ffae16de985e8427b6b45",
+ "0x6acdfba02d390b97ac2b2d42a63e85293bcc160e",
+ "0x6f1ca141a28907f78ebaa64fb83a9088b02a8352",
+ "0x722122df12d4e14e13ac3b6895a86e84145b6967",
+ "0x72a5843cc08275c8171e582972aa4fda8c397b2a",
+ "0x7db418b5d567a4e0e8c59ad71be1fce48f3e6107",
+ "0x7f19720a857f834887fc9a7bc0a0fbe7fc7f8102",
+ "0x7f367cc41522ce07553e823bf3be79a889debe1b",
+ "0x7ff9cfad3877f21d41da833e2f775db0569ee3d9",
+ "0x8576acc5c05d6ce88f4e49bf65bdf0c62f91353c",
+ "0x8589427373d6d84e98730d7795d8f6f8731fda16",
+ "0x901bb9583b24d97e995513c6778dc6888ab6870e",
+ "0x905b63fff465b9ffbf41dea908ceb12478ec7601",
+ "0x910cbd523d972eb0a6f4cae4618ad62622b39dbf",
+ "0x94a1b5cdb22c43faab4abeb5c74999895464ddaf",
+ "0x9ad122c22b14202b4490edaf288fdb3c7cb3ff5e",
+ "0x9f4cda013e354b8fc285bf4b9a60460cee7f7ea9",
+ "0xa0e1c89ef1a489c9c7de96311ed5ce5d32c20e4b",
+ "0xa160cdab225685da1d56aa342ad8841c3b53f291",
+ "0xa60c772958a3ed56c1f15dd055ba37ac8e523a0d",
+ "0xa7e5d5a720f06526557c513402f2e6b5fa20b008",
+ "0xaeaac358560e11f52454d997aaff2c5731b6f8a6",
+ "0xb1c8094b234dce6e03f10a5b673c1d8c69739a00",
+ "0xb541fc07bc7619fd4062a54d96268525cbc6ffef",
+ "0xba214c1c1928a32bffe790263e38b4af9bfcd659",
+ "0xbb93e510bbcd0b7beb5a853875f9ec60275cf498",
+ "0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a",
+ "0xca0840578f57fe71599d29375e16783424023357",
+ "0xd21be7248e0197ee08e0c20d4a96debdac3d20af",
+ "0xd4b88df4d29f5cedd6857912842cff3b20c8cfa3",
+ "0xd691f27f38b395864ea86cfc7253969b409c362d",
+ "0xd882cfc20f52f2599d84b8e8d58c7fb62cfe344b",
+ "0xd90e2f925da726b50c4ed8d0fb90ad053324f31b",
+ "0xd96f2b1c14db8458374d9aca76e26c3d18364307",
+ "0xdd4c48c0b24039969fc16d1cdf626eab821d3384",
+ "0xe7aa314c77f4233c18c6cc84384a9247c0cf367b",
+ "0xf60dd140cff0706bae9cd734ac3ae76ad9ebc32a",
+ "0xf67721a2d8f736e75a49fdd7fad2e31d8676542a",
+ "0xf7b31119c2682c88d88d455dbb9d5932c65cf1be",
+ "0xfd8610d20aa15b7b2e3be39b396a1bc3516c7144",
+ "0xfec8a60023265364d066a1212fde3930f6ae8da7"
+]
From e16c69e28e0e23868b8e64a13fbd7bbc7744dd16 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 11 Aug 2022 13:42:16 +0200
Subject: [PATCH 30/83] Add timeout to e2e
---
.github/workflows/go.yml | 1 +
internal/ethapi/transaction_args_test.go | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index d198d152cd..e4a1cf0f0e 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -30,6 +30,7 @@ jobs:
e2e:
name: End to End
runs-on: ubuntu-latest
+ timeout-minutes: 20
steps:
- name: Set up Go 1.x
diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go
index b7c5b050af..d10014a52c 100644
--- a/internal/ethapi/transaction_args_test.go
+++ b/internal/ethapi/transaction_args_test.go
@@ -320,7 +320,9 @@ func (b *backendMock) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) eve
func (b *backendMock) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
return nil
}
-func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { return nil }
+func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction, private bool) error {
+ return nil
+}
func (b *backendMock) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
return nil, [32]byte{}, 0, 0, nil
}
From d55f648da91f6849b1715a0c6c302b44147c2c11 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Tue, 30 Aug 2022 20:21:34 +0200
Subject: [PATCH 31/83] Upgrade dependencies
---
go.mod | 15 +++++----------
go.sum | 46 +++++++++++++++++++++++++++++++++-------------
2 files changed, 38 insertions(+), 23 deletions(-)
diff --git a/go.mod b/go.mod
index 1153762061..b34ba9079e 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.1.1
github.com/aws/aws-sdk-go-v2/credentials v1.1.1
github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1
- github.com/btcsuite/btcd/btcec/v2 v2.2.0
+ github.com/btcsuite/btcd/btcec/v2 v2.2.1
github.com/cespare/cp v1.1.1
github.com/cloudflare/cloudflare-go v0.14.0
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f
@@ -21,7 +21,7 @@ require (
github.com/fatih/color v1.9.0
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
- github.com/flashbots/go-boost-utils v0.3.3-0.20220725102131-104d720dc46e
+ github.com/flashbots/go-boost-utils v0.3.5
github.com/flashbots/go-utils v0.4.5
github.com/flashbots/mev-boost v0.7.3
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
@@ -60,16 +60,15 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef
github.com/urfave/cli/v2 v2.10.2
- golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
+ golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
- golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
+ golang.org/x/sys v0.0.0-20220829200755-d48e67d00261
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.7
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
golang.org/x/tools v0.1.10
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
- gopkg.in/urfave/cli.v1 v1.20.0
)
require (
@@ -84,18 +83,15 @@ require (
github.com/aws/smithy-go v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
- github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
github.com/ferranbt/fastssz v0.1.2-0.20220723134332-b3d3034a4575 // indirect
- github.com/flashbots/go-boost-utils v0.3.5 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
- github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
- github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
@@ -114,7 +110,6 @@ require (
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
google.golang.org/protobuf v1.26.0 // indirect
- gopkg.in/urfave/cli.v1 v1.20.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 593b682edc..d7ebaa3e99 100644
--- a/go.sum
+++ b/go.sum
@@ -36,6 +36,7 @@ github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
+github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
@@ -61,8 +62,9 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
-github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
-github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
+github.com/btcsuite/btcd/btcec/v2 v2.2.1 h1:xP60mv8fvp+0khmrN0zTdPC3cNm24rfeE6lh2R/Yv3E=
+github.com/btcsuite/btcd/btcec/v2 v2.2.1/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU=
@@ -91,9 +93,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
-github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
+github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
@@ -103,6 +105,7 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
+github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/docker/docker v1.6.2 h1:HlFGsy+9/xrgMmhmN+NGhCc5SHGJ7I+kHosRR1xc/aI=
github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
@@ -118,18 +121,21 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/ferranbt/fastssz v0.1.2-0.20220723134332-b3d3034a4575 h1:56lKKtcqQZ5sGjeuyBAeFwzcYuk32d8oqDvxQ9FUERA=
github.com/ferranbt/fastssz v0.1.2-0.20220723134332-b3d3034a4575/go.mod h1:U2ZsxlYyvGeQGmadhz8PlEqwkBzDIhHwd3xuKrg2JIs=
+github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq6Q+cGWcGHz0FjGR3QqcInWcW0=
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
-github.com/flashbots/go-boost-utils v0.3.3-0.20220725102131-104d720dc46e h1:6h6iyk4pT95hhTEbdMcpavN+FLHyrH9F80E9d/SVjO8=
-github.com/flashbots/go-boost-utils v0.3.3-0.20220725102131-104d720dc46e/go.mod h1:sqlzXlw4eKlY/URPHw1qEobsOoqOwsDrN272g2hUYgw=
+github.com/flashbots/go-boost-utils v0.3.5 h1:RApwo1+8SGKhEGWH/WFIhg21pkCCW+RpO7MkZwiMI6A=
+github.com/flashbots/go-boost-utils v0.3.5/go.mod h1:vEfLpq6MLgdRtfWUG0fGDBbHXu0SyRInkw6ekLJeRIU=
github.com/flashbots/go-utils v0.4.5 h1:xTrVcfxQ+qpVVPyRBWUllwZAxbAijE06d9N7e7mmmlM=
github.com/flashbots/go-utils v0.4.5/go.mod h1:3YKyfbtetVIXuWKbZ9WmK8bSF20hSFXk0wCWDNHYFvE=
github.com/flashbots/mev-boost v0.7.3 h1:AwJQACv6/CJuMQe4oGNvIgW00oKFCzzDBrb4DlJsBKE=
github.com/flashbots/mev-boost v0.7.3/go.mod h1:DOqKZloyaZnfr6eGbnQb8XGH3gtjts19ATxI1LnfcNM=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc=
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
@@ -141,8 +147,10 @@ github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1T
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
@@ -193,6 +201,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -270,15 +279,20 @@ github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
+github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -319,14 +333,17 @@ github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hz
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -361,6 +378,7 @@ github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1
github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -402,6 +420,7 @@ github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
+github.com/trailofbits/go-fuzz-utils v0.0.0-20210901195358-9657fcfd256c h1:4WU+p200eLYtBsx3M5CKXvkjVdf5SC3W9nMg37y0TFI=
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4=
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
@@ -431,8 +450,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
-golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -455,6 +474,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
@@ -540,8 +560,8 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
+golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
@@ -644,14 +664,14 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
-gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
From 1bc67bd4fbc51e7739184f5939effc0859415264 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Wed, 31 Aug 2022 14:51:40 +0200
Subject: [PATCH 32/83] Only submit blocks that improve profit (#19)
---
builder/builder.go | 29 ++++++++++++++++++++++++++---
builder/builder_test.go | 10 +++++++---
2 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/builder/builder.go b/builder/builder.go
index 8a8c82ca5a..c4c415c1fe 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -2,7 +2,9 @@ package builder
import (
"errors"
+ "math/big"
_ "os"
+ "sync"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -47,6 +49,10 @@ type Builder struct {
builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
builderSigningDomain boostTypes.Domain
+
+ bestMu sync.Mutex
+ bestAttrs BuilderPayloadAttributes
+ bestBlockProfit *big.Int
}
func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
@@ -63,10 +69,25 @@ func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSignin
builderPublicKey: pk,
builderSigningDomain: builderSigningDomain,
+ bestBlockProfit: big.NewInt(0),
}
}
-func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *types.Block, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, slot uint64) error {
+func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *types.Block, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
+ b.bestMu.Lock()
+ defer b.bestMu.Unlock()
+
+ // Do not submit blocks that don't improve the profit
+ if b.bestAttrs != *attrs {
+ b.bestAttrs = *attrs
+ b.bestBlockProfit.SetInt64(0)
+ } else {
+ if block.Profit.Cmp(b.bestBlockProfit) <= 0 {
+ log.Info("Ignoring block that is not improving the profit")
+ return nil
+ }
+ }
+
payload, err := executableDataToExecutionPayload(executableData)
if err != nil {
log.Error("could not format execution payload", "err", err)
@@ -81,7 +102,7 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *
}
blockBidMsg := boostTypes.BidTrace{
- Slot: slot,
+ Slot: attrs.Slot,
ParentHash: payload.ParentHash,
BlockHash: payload.BlockHash,
BuilderPubkey: b.builderPublicKey,
@@ -110,6 +131,8 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *
return err
}
+ b.bestBlockProfit.Set(block.Profit)
+
return nil
}
@@ -150,7 +173,7 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
return errors.New("did not receive the payload")
}
- err := b.onSealedBlock(executableData, block, proposerPubkey, vd.FeeRecipient, attrs.Slot)
+ err := b.onSealedBlock(executableData, block, proposerPubkey, vd.FeeRecipient, attrs)
if err != nil {
log.Error("could not run block hook", "err", err)
return err
diff --git a/builder/builder_test.go b/builder/builder_test.go
index b371a2d52b..599352ee52 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -122,9 +122,13 @@ func TestOnPayloadAttributes(t *testing.T) {
require.Equal(t, uint64(25), testRelay.requestedSlot)
- // Clear the submitted message and check that the job will be ran again and a new message will be submitted
+ // Clear the submitted message and check that the job will be ran again and but a new message will not be submitted since the profit is the same
testRelay.submittedMsg = nil
- time.Sleep(2 * time.Second)
+ time.Sleep(1200 * time.Millisecond)
+ require.Nil(t, testRelay.submittedMsg)
+
+ // Up the profit, expect to get the block
+ testEthService.testBlock.Profit.SetInt64(11)
+ time.Sleep(1200 * time.Millisecond)
require.NotNil(t, testRelay.submittedMsg)
- require.Equal(t, expectedMessage, *testRelay.submittedMsg.Message)
}
From 64498113807a4365c9521751f3f8564889a57a38 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Wed, 31 Aug 2022 23:22:30 +0200
Subject: [PATCH 33/83] Remove megabundles as they are no longer needed (#20)
---
.github/workflows/go.yml | 11 +-----
core/tx_pool.go | 70 ++++-------------------------------
eth/api_backend.go | 4 --
internal/ethapi/api.go | 74 -------------------------------------
internal/ethapi/backend.go | 1 -
internal/web3ext/web3ext.go | 5 ---
miner/multi_worker.go | 43 ++++-----------------
miner/worker.go | 56 ++++------------------------
8 files changed, 24 insertions(+), 240 deletions(-)
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index e4a1cf0f0e..f871a64839 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -54,6 +54,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: flashbots/mev-geth-demo
+ ref: no-megabundles
path: e2e
- run: cd e2e && yarn install
@@ -71,17 +72,9 @@ jobs:
cd e2e
GETH=`pwd`/../build/bin/geth ./run.sh &
# Second node, not mining
- P2P_PORT=30302 DATADIR=datadir2 HTTP_PORT=8546 MINER_ARGS='--nodiscover' GETH=`pwd`/../build/bin/geth ./run.sh &
+ P2P_PORT=30302 DATADIR=datadir2 HTTP_PORT=8546 AUTH_PORT=8552 MINER_ARGS='--nodiscover' GETH=`pwd`/../build/bin/geth ./run.sh &
sleep 15
DATADIR1=datadir DATADIR2=datadir2 GETH=`pwd`/../build/bin/geth ./peer_nodes.sh
sleep 15
yarn run demo-private-tx
pkill -9 geth || true
- - name: Run megabundle-only node checking for reverts
- run: |
- cd e2e
- # Disable bundle workers
- MINER_ARGS='--miner.etherbase=0xd912aecb07e9f4e1ea8e6b4779e7fb6aa1c3e4d8 --miner.trustedrelays=0xfb11e78C4DaFec86237c2862441817701fdf197F --mine --miner.threads=2 --miner.maxmergedbundles=0' GETH=`pwd`/../build/bin/geth ./run.sh &
- sleep 15
- yarn run e2e-reverting-megabundle
- pkill -9 geth || true
diff --git a/core/tx_pool.go b/core/tx_pool.go
index c7b9ece9d3..7cc825667c 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -260,15 +260,13 @@ type TxPool struct {
locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk
- pending map[common.Address]*txList // All currently processable transactions
- queue map[common.Address]*txList // Queued but non-processable transactions
- beats map[common.Address]time.Time // Last heartbeat from each known account
- mevBundles []types.MevBundle
- megabundles map[common.Address]types.MevBundle // One megabundle per each trusted relay
- NewMegabundleHooks []func(common.Address, *types.MevBundle)
- all *txLookup // All transactions to allow lookups
- priced *txPricedList // All transactions sorted by price
- privateTxs *timestampedTxHashSet
+ pending map[common.Address]*txList // All currently processable transactions
+ queue map[common.Address]*txList // Queued but non-processable transactions
+ beats map[common.Address]time.Time // Last heartbeat from each known account
+ mevBundles []types.MevBundle
+ all *txLookup // All transactions to allow lookups
+ priced *txPricedList // All transactions sorted by price
+ privateTxs *timestampedTxHashSet
chainHeadCh chan ChainHeadEvent
chainHeadSub event.Subscription
@@ -302,7 +300,6 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
pending: make(map[common.Address]*txList),
queue: make(map[common.Address]*txList),
beats: make(map[common.Address]time.Time),
- megabundles: make(map[common.Address]types.MevBundle),
all: newTxLookup(),
privateTxs: newExpiringTxHashSet(config.PrivateTxLifetime),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
@@ -635,59 +632,6 @@ func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, m
return nil
}
-// AddMegaBundle adds a megabundle to the pool. Assumes the relay signature has been verified already.
-func (pool *TxPool) AddMegabundle(relayAddr common.Address, txs types.Transactions, blockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- fromTrustedRelay := false
- for _, trustedAddr := range pool.config.TrustedRelays {
- if relayAddr == trustedAddr {
- fromTrustedRelay = true
- }
- }
- if !fromTrustedRelay {
- return errors.New("megabundle from non-trusted address")
- }
-
- megabundle := types.MevBundle{
- Txs: txs,
- BlockNumber: blockNumber,
- MinTimestamp: minTimestamp,
- MaxTimestamp: maxTimestamp,
- RevertingTxHashes: revertingTxHashes,
- }
-
- pool.megabundles[relayAddr] = megabundle
-
- for _, hook := range pool.NewMegabundleHooks {
- go hook(relayAddr, &megabundle)
- }
-
- return nil
-}
-
-// GetMegabundle returns the latest megabundle submitted by a given relay.
-func (pool *TxPool) GetMegabundle(relayAddr common.Address, blockNumber *big.Int, blockTimestamp uint64) (types.MevBundle, error) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- megabundle, ok := pool.megabundles[relayAddr]
- if !ok {
- return types.MevBundle{}, errors.New("No megabundle found")
- }
- if megabundle.BlockNumber.Cmp(blockNumber) != 0 {
- return types.MevBundle{}, errors.New("Megabundle does not fit blockNumber constraints")
- }
- if megabundle.MinTimestamp != 0 && megabundle.MinTimestamp > blockTimestamp {
- return types.MevBundle{}, errors.New("Megabundle does not fit minTimestamp constraints")
- }
- if megabundle.MaxTimestamp != 0 && megabundle.MaxTimestamp < blockTimestamp {
- return types.MevBundle{}, errors.New("Megabundle does not fit maxTimestamp constraints")
- }
- return megabundle, nil
-}
-
// Locals retrieves the accounts currently considered local by the pool.
func (pool *TxPool) Locals() []common.Address {
pool.mu.Lock()
diff --git a/eth/api_backend.go b/eth/api_backend.go
index b97d53f14a..709d3a808f 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -258,10 +258,6 @@ func (b *EthAPIBackend) SendBundle(ctx context.Context, txs types.Transactions,
return b.eth.txPool.AddMevBundle(txs, big.NewInt(blockNumber.Int64()), minTimestamp, maxTimestamp, revertingTxHashes)
}
-func (b *EthAPIBackend) SendMegabundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash, relayAddr common.Address) error {
- return b.eth.txPool.AddMegabundle(relayAddr, txs, big.NewInt(blockNumber.Int64()), minTimestamp, maxTimestamp, revertingTxHashes)
-}
-
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
pending := b.eth.txPool.Pending(false)
var txs types.Transactions
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index d99109579f..0e101c5dd3 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -2066,25 +2066,6 @@ type SendBundleArgs struct {
RevertingTxHashes []common.Hash `json:"revertingTxHashes"`
}
-// SendMegabundleArgs represents the arguments for a SendMegabundle call.
-type SendMegabundleArgs struct {
- Txs []hexutil.Bytes `json:"txs"`
- BlockNumber uint64 `json:"blockNumber"`
- MinTimestamp *uint64 `json:"minTimestamp"`
- MaxTimestamp *uint64 `json:"maxTimestamp"`
- RevertingTxHashes []common.Hash `json:"revertingTxHashes"`
- RelaySignature hexutil.Bytes `json:"relaySignature"`
-}
-
-// UnsignedMegabundle is used for serialization and subsequent digital signing.
-type UnsignedMegabundle struct {
- Txs []hexutil.Bytes
- BlockNumber uint64
- MinTimestamp uint64
- MaxTimestamp uint64
- RevertingTxHashes []common.Hash
-}
-
// SendBundle will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce and ensuring validity
func (s *PrivateTxBundleAPI) SendBundle(ctx context.Context, args SendBundleArgs) error {
@@ -2115,61 +2096,6 @@ func (s *PrivateTxBundleAPI) SendBundle(ctx context.Context, args SendBundleArgs
return s.b.SendBundle(ctx, txs, args.BlockNumber, minTimestamp, maxTimestamp, args.RevertingTxHashes)
}
-// Recovers the Ethereum address of the trusted relay that signed the megabundle.
-func RecoverRelayAddress(args SendMegabundleArgs) (common.Address, error) {
- megabundle := UnsignedMegabundle{Txs: args.Txs, BlockNumber: args.BlockNumber, RevertingTxHashes: args.RevertingTxHashes}
- if args.MinTimestamp != nil {
- megabundle.MinTimestamp = *args.MinTimestamp
- } else {
- megabundle.MinTimestamp = 0
- }
- if args.MaxTimestamp != nil {
- megabundle.MaxTimestamp = *args.MaxTimestamp
- } else {
- megabundle.MaxTimestamp = 0
- }
- rlpEncoding, _ := rlp.EncodeToBytes(megabundle)
- signature := args.RelaySignature
- signature[64] -= 27 // account for Ethereum V
- recoveredPubkey, err := crypto.SigToPub(accounts.TextHash(rlpEncoding), args.RelaySignature)
- if err != nil {
- return common.Address{}, err
- }
- return crypto.PubkeyToAddress(*recoveredPubkey), nil
-}
-
-// SendMegabundle will add the signed megabundle to one of the workers for evaluation.
-func (s *PrivateTxBundleAPI) SendMegabundle(ctx context.Context, args SendMegabundleArgs) error {
- log.Info("Received a Megabundle request", "signature", args.RelaySignature)
- var txs types.Transactions
- if len(args.Txs) == 0 {
- return errors.New("megabundle missing txs")
- }
- if args.BlockNumber == 0 {
- return errors.New("megabundle missing blockNumber")
- }
- for _, encodedTx := range args.Txs {
- tx := new(types.Transaction)
- if err := tx.UnmarshalBinary(encodedTx); err != nil {
- return err
- }
- txs = append(txs, tx)
- }
- var minTimestamp, maxTimestamp uint64
- if args.MinTimestamp != nil {
- minTimestamp = *args.MinTimestamp
- }
- if args.MaxTimestamp != nil {
- maxTimestamp = *args.MaxTimestamp
- }
- relayAddr, err := RecoverRelayAddress(args)
- log.Info("Megabundle", "relayAddr", relayAddr, "err", err)
- if err != nil {
- return err
- }
- return s.b.SendMegabundle(ctx, txs, rpc.BlockNumber(args.BlockNumber), minTimestamp, maxTimestamp, args.RevertingTxHashes, relayAddr)
-}
-
// BundleAPI offers an API for accepting bundled transactions
type BundleAPI struct {
b Backend
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index 57335ea368..f5ab88915b 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -76,7 +76,6 @@ type Backend interface {
// Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction, private bool) error
SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) error
- SendMegabundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash, relayAddr common.Address) error
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index de877f5159..d80b6b8ac2 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -606,11 +606,6 @@ web3._extend({
call: 'eth_sendBundle',
params: 1,
}),
- new web3._extend.Method({
- name: 'sendMegabundle',
- call: 'eth_sendMegabundle',
- params: 1
- }),
],
properties: [
new web3._extend.Property({
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index 4330e64769..cd0068c1e9 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -97,7 +97,7 @@ func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64,
for _, worker := range append(w.workers, w.regularWorker) {
resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra)
if err != nil {
- log.Error("could not start async block construction", "isFlashbotsWorker", worker.flashbots.isFlashbots, "isMegabundleWorker", worker.flashbots.isMegabundleWorker, "#bundles", worker.flashbots.maxMergedBundles)
+ log.Error("could not start async block construction", "isFlashbotsWorker", worker.flashbots.isFlashbots, "#bundles", worker.flashbots.maxMergedBundles)
continue
}
resChans = append(resChans, resChPair{resCh, errCh})
@@ -153,38 +153,12 @@ func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine cons
for i := 1; i <= config.MaxMergedBundles; i++ {
workers = append(workers,
newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
- isFlashbots: true,
- isMegabundleWorker: false,
- queue: queue,
- maxMergedBundles: i,
+ isFlashbots: true,
+ queue: queue,
+ maxMergedBundles: i,
}))
}
- relayWorkerMap := make(map[common.Address]*worker)
-
- for i := 0; i < len(config.TrustedRelays); i++ {
- relayWorker := newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
- isFlashbots: true,
- isMegabundleWorker: true,
- queue: queue,
- relayAddr: config.TrustedRelays[i],
- })
- workers = append(workers, relayWorker)
- relayWorkerMap[config.TrustedRelays[i]] = relayWorker
- }
-
- eth.TxPool().NewMegabundleHooks = append(eth.TxPool().NewMegabundleHooks, func(relayAddr common.Address, megabundle *types.MevBundle) {
- worker, found := relayWorkerMap[relayAddr]
- if !found {
- return
- }
-
- select {
- case worker.newMegabundleCh <- megabundle:
- default:
- }
- })
-
log.Info("creating multi worker", "config.MaxMergedBundles", config.MaxMergedBundles, "config.TrustedRelays", config.TrustedRelays, "worker", len(workers))
return &multiWorker{
regularWorker: regularWorker,
@@ -193,9 +167,8 @@ func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine cons
}
type flashbotsData struct {
- isFlashbots bool
- isMegabundleWorker bool
- queue chan *task
- maxMergedBundles int
- relayAddr common.Address
+ isFlashbots bool
+ queue chan *task
+ maxMergedBundles int
+ relayAddr common.Address
}
diff --git a/miner/worker.go b/miner/worker.go
index 1de8db1996..b25b1cb5bd 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -163,10 +163,9 @@ type task struct {
block *types.Block
createdAt time.Time
- profit *big.Int
- isFlashbots bool
- worker int
- isMegabundle bool
+ profit *big.Int
+ isFlashbots bool
+ worker int
}
const (
@@ -225,7 +224,6 @@ type worker struct {
exitCh chan struct{}
resubmitIntervalCh chan time.Duration
resubmitAdjustCh chan *intervalAdjust
- newMegabundleCh chan *types.MevBundle
wg sync.WaitGroup
@@ -334,7 +332,6 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
resultCh: make(chan *types.Block, resultQueueSize),
exitCh: exitCh,
startCh: make(chan struct{}, 1),
- newMegabundleCh: make(chan *types.MevBundle),
resubmitIntervalCh: make(chan time.Duration),
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
coinbase: builderCoinbase,
@@ -544,11 +541,6 @@ func (w *worker) newWorkLoop(recommit time.Duration) {
timestamp = time.Now().Unix()
commit(false, commitInterruptNewHead)
- case <-w.newMegabundleCh:
- if w.isRunning() {
- commit(true, commitInterruptNone)
- }
-
case <-timer.C:
// If sealing is running resubmit a new work cycle periodically to pull in
// higher priced transactions. Disable this overhead for pending blocks.
@@ -759,7 +751,7 @@ func (w *worker) taskLoop() {
// Interrupt previous sealing operation
interrupt()
stopCh, prev = make(chan struct{}), sealHash
- log.Info("Proposed miner block", "blockNumber", task.block.Number(), "profit", ethIntToFloat(prevProfit), "isFlashbots", task.isFlashbots, "sealhash", sealHash, "parentHash", prevParentHash, "worker", task.worker, "isMegabundle", task.isMegabundle)
+ log.Info("Proposed miner block", "blockNumber", task.block.Number(), "profit", ethIntToFloat(prevProfit), "isFlashbots", task.isFlashbots, "sealhash", sealHash, "parentHash", prevParentHash, "worker", task.worker)
if w.skipSealHook != nil && w.skipSealHook(task) {
continue
}
@@ -1294,7 +1286,7 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
return err
}
}
- if w.flashbots.isFlashbots && !w.flashbots.isMegabundleWorker {
+ if w.flashbots.isFlashbots {
bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
if err != nil {
log.Error("Failed to fetch pending transactions", "err", err)
@@ -1315,40 +1307,6 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
}
env.profit.Add(env.profit, bundle.ethSentToCoinbase)
}
- if w.flashbots.isMegabundleWorker {
- megabundle, err := w.eth.TxPool().GetMegabundle(w.flashbots.relayAddr, env.header.Number, env.header.Time)
- log.Info("Starting to process a Megabundle", "relay", w.flashbots.relayAddr, "megabundle", megabundle, "error", err)
- if err != nil {
- return err // no valid megabundle for this relay, nothing to do
- }
-
- // Flashbots bundle merging duplicates work by simulating TXes and then committing them once more.
- // Megabundles API focuses on speed and runs everything in one cycle.
- coinbaseBalanceBefore := env.state.GetBalance(env.coinbase)
- if err := w.commitBundle(env, megabundle.Txs, interrupt); err != nil {
- log.Info("Could not commit a Megabundle", "relay", w.flashbots.relayAddr, "megabundle", megabundle, "err", err)
- return err
- }
- var txStatuses = map[common.Hash]bool{}
- for _, receipt := range env.receipts {
- txStatuses[receipt.TxHash] = receipt.Status == types.ReceiptStatusSuccessful
- }
- for _, tx := range megabundle.Txs {
- status, ok := txStatuses[tx.Hash()]
- if !ok {
- log.Error("No TX receipt after megabundle simulation", "TxHash", tx.Hash())
- return errors.New("no tx receipt after megabundle simulation")
- }
- if !status && !containsHash(megabundle.RevertingTxHashes, tx.Hash()) {
- log.Info("Ignoring megabundle because of failing TX", "relay", w.flashbots.relayAddr, "TxHash", tx.Hash())
- return errors.New("megabundle contains failing tx")
- }
- }
- coinbaseBalanceAfter := env.state.GetBalance(env.coinbase)
- coinbaseDelta := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
- env.profit = coinbaseDelta
- log.Info("Megabundle processed", "relay", w.flashbots.relayAddr, "totalProfit", ethIntToFloat(env.profit))
- }
if len(localTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
@@ -1509,12 +1467,12 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
// If we're post merge, just ignore
if !w.isTTDReached(block.Header()) {
select {
- case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now(), profit: env.profit, isFlashbots: w.flashbots.isFlashbots, worker: w.flashbots.maxMergedBundles, isMegabundle: w.flashbots.isMegabundleWorker}:
+ case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now(), profit: env.profit, isFlashbots: w.flashbots.isFlashbots, worker: w.flashbots.maxMergedBundles}:
w.unconfirmed.Shift(block.NumberU64() - 1)
log.Info("Commit new sealing work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
"uncles", len(env.uncles), "txs", env.tcount, "gas", block.GasUsed(), "fees", totalFees(block, env.receipts),
"profit", ethIntToFloat(env.profit), "elapsed", common.PrettyDuration(time.Since(start)),
- "isFlashbots", w.flashbots.isFlashbots, "worker", w.flashbots.maxMergedBundles, "isMegabundle", w.flashbots.isMegabundleWorker)
+ "isFlashbots", w.flashbots.isFlashbots, "worker", w.flashbots.maxMergedBundles)
case <-w.exitCh:
log.Info("Worker has exited")
}
From 9b27c8ad534ee541c7d52ceecb231e7af0572ce5 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 1 Sep 2022 00:50:53 +0200
Subject: [PATCH 34/83] Implement pushing blocks data to the DB (#18)
Co-authored-by: Bhakiyaraj Kalimuthu
---
builder/builder.go | 25 +++---
builder/builder_test.go | 18 ++--
builder/database.go | 136 +++++++++++++++++++++++++++++++
builder/database_test.go | 71 ++++++++++++++++
builder/database_types.go | 77 +++++++++++++++++
builder/eth_service.go | 22 ++---
builder/eth_service_test.go | 22 +++--
builder/local_relay_test.go | 27 +++---
builder/service.go | 11 ++-
core/tx_pool.go | 8 ++
core/types/transaction.go | 9 ++
eth/block-validation/api_test.go | 2 +-
eth/catalyst/api.go | 4 +-
eth/catalyst/api_test.go | 4 +-
go.mod | 2 +
go.sum | 6 ++
miner/miner.go | 8 +-
miner/multi_worker.go | 8 +-
miner/worker.go | 135 ++++++++++++++++--------------
miner/worker_test.go | 4 +-
20 files changed, 469 insertions(+), 130 deletions(-)
create mode 100644 builder/database.go
create mode 100644 builder/database_test.go
create mode 100644 builder/database_types.go
diff --git a/builder/builder.go b/builder/builder.go
index c4c415c1fe..1aa197d900 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -41,6 +41,7 @@ type IBuilder interface {
}
type Builder struct {
+ ds IDatabaseService
beaconClient IBeaconClient
relay IRelay
eth IEthereumService
@@ -55,12 +56,13 @@ type Builder struct {
bestBlockProfit *big.Int
}
-func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
+func NewBuilder(sk *bls.SecretKey, ds IDatabaseService, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)
return &Builder{
+ ds: ds,
beaconClient: bc,
relay: relay,
eth: eth,
@@ -73,7 +75,7 @@ func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSignin
}
}
-func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *types.Block, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
+func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
b.bestMu.Lock()
defer b.bestMu.Unlock()
@@ -88,6 +90,7 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *
}
}
+ executableData := beacon.BlockToExecutableData(block)
payload, err := executableDataToExecutionPayload(executableData)
if err != nil {
log.Error("could not format execution payload", "err", err)
@@ -133,6 +136,7 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *
b.bestBlockProfit.Set(block.Profit)
+ go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)
return nil
}
@@ -166,20 +170,15 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
return errors.New("parent block not found in blocktree")
}
- firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error {
- executableData, block := b.eth.BuildBlock(attrs)
- if executableData == nil || block == nil {
- log.Error("did not receive the payload")
- return errors.New("did not receive the payload")
- }
-
- err := b.onSealedBlock(executableData, block, proposerPubkey, vd.FeeRecipient, attrs)
+ blockHook := func(block *types.Block, bundles []types.SimulatedBundle) {
+ err := b.onSealedBlock(block, bundles, proposerPubkey, vd.FeeRecipient, attrs)
if err != nil {
- log.Error("could not run block hook", "err", err)
- return err
+ log.Error("could not run sealed block hook", "err", err)
}
+ }
- return nil
+ firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error {
+ return b.eth.BuildBlock(attrs, blockHook)
})
return firstBlockResult
diff --git a/builder/builder_test.go b/builder/builder_test.go
index 599352ee52..8bd98aab7b 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -47,7 +47,7 @@ func TestOnPayloadAttributes(t *testing.T) {
FeeRecipient: common.Address(feeRecipient),
StateRoot: common.Hash{0x07, 0x16},
ReceiptsRoot: common.Hash{0x08, 0x20},
- LogsBloom: hexutil.MustDecode("0x000000000000000000000000000000"),
+ LogsBloom: types.Bloom{}.Bytes(),
Number: uint64(10),
GasLimit: uint64(50),
GasUsed: uint64(100),
@@ -56,13 +56,13 @@ func TestOnPayloadAttributes(t *testing.T) {
BaseFeePerGas: big.NewInt(16),
- BlockHash: common.Hash{0x09, 0xff},
+ BlockHash: common.HexToHash("0xca4147f0d4150183ece9155068f34ee3c375448814e4ca557d482b1d40ee5407"),
Transactions: [][]byte{},
}
- testBlock := &types.Block{
- Profit: big.NewInt(10),
- }
+ testBlock, err := beacon.ExecutableDataToBlock(*testExecutableData)
+ require.NoError(t, err)
+ testBlock.Profit = big.NewInt(10)
testPayloadAttributes := &BuilderPayloadAttributes{
Timestamp: hexutil.Uint64(104),
@@ -74,7 +74,7 @@ func TestOnPayloadAttributes(t *testing.T) {
testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}
- builder := NewBuilder(sk, &testBeacon, &testRelay, bDomain, testEthService)
+ builder := NewBuilder(sk, NilDbService{}, &testBeacon, &testRelay, bDomain, testEthService)
builder.OnPayloadAttribute(testPayloadAttributes)
@@ -85,7 +85,6 @@ func TestOnPayloadAttributes(t *testing.T) {
expectedMessage := boostTypes.BidTrace{
Slot: uint64(25),
ParentHash: boostTypes.Hash{0x02, 0x03},
- BlockHash: boostTypes.Hash{0x09, 0xff},
BuilderPubkey: builder.builderPublicKey,
ProposerPubkey: expectedProposerPubkey,
ProposerFeeRecipient: feeRecipient,
@@ -93,6 +92,7 @@ func TestOnPayloadAttributes(t *testing.T) {
GasUsed: uint64(100),
Value: boostTypes.U256Str{0x0a},
}
+ expectedMessage.BlockHash.FromSlice(hexutil.MustDecode("0xca4147f0d4150183ece9155068f34ee3c375448814e4ca557d482b1d40ee5407")[:])
require.Equal(t, expectedMessage, *testRelay.submittedMsg.Message)
@@ -109,13 +109,13 @@ func TestOnPayloadAttributes(t *testing.T) {
Timestamp: testExecutableData.Timestamp,
ExtraData: hexutil.MustDecode("0x0042fafc"),
BaseFeePerGas: boostTypes.U256Str{0x10},
- BlockHash: boostTypes.Hash{0x09, 0xff},
+ BlockHash: expectedMessage.BlockHash,
Transactions: []hexutil.Bytes{},
}
require.Equal(t, expectedExecutionPayload, *testRelay.submittedMsg.ExecutionPayload)
- expectedSignature, err := boostTypes.HexToSignature("0xb086abc231a515559128122a6618ad316a76195ad39aa28195c9e8921b98561ca4fd12e2e1ea8d50d8e22f7e36d42ee1084fef26672beceda7650a87061e412d7742705077ac3af3ca1a1c3494eccb22fe7c234fd547a285ba699ff87f0e7759")
+ expectedSignature, err := boostTypes.HexToSignature("0xad09f171b1da05636acfc86778c319af69e39c79515d44bdfed616ba2ef677ffd4d155d87b3363c6bae651ce1e92786216b75f1ac91dd65f3b1d1902bf8485e742170732dd82ffdf4decb0151eeb7926dd053efa9794b2ebed1a203e62bb13e9")
require.NoError(t, err)
require.Equal(t, expectedSignature, testRelay.submittedMsg.Signature)
diff --git a/builder/database.go b/builder/database.go
new file mode 100644
index 0000000000..f4799208e5
--- /dev/null
+++ b/builder/database.go
@@ -0,0 +1,136 @@
+package builder
+
+import (
+ "context"
+ "database/sql"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+ "github.com/jmoiron/sqlx"
+ _ "github.com/lib/pq"
+)
+
+type IDatabaseService interface {
+ ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace)
+}
+
+type NilDbService struct{}
+
+func (NilDbService) ConsumeBuiltBlock(*types.Block, []types.SimulatedBundle, *boostTypes.BidTrace) {}
+
+type DatabaseService struct {
+ db *sqlx.DB
+
+ insertBuiltBlockStmt *sqlx.NamedStmt
+ insertBlockBuiltBundleNoIdStmt *sqlx.NamedStmt
+ insertBlockBuiltBundleWithIdStmt *sqlx.NamedStmt
+ insertMissingBundleStmt *sqlx.NamedStmt
+}
+
+func NewDatabaseService(postgresDSN string) (*DatabaseService, error) {
+ db, err := sqlx.Connect("postgres", postgresDSN)
+ if err != nil {
+ return nil, err
+ }
+
+ insertBuiltBlockStmt, err := db.PrepareNamed("insert into built_blocks (block_number, profit, slot, hash, gas_limit, gas_used, base_fee, parent_hash, proposer_pubkey, proposer_fee_recipient, builder_pubkey, timestamp, timestamp_datetime) values (:block_number, :profit, :slot, :hash, :gas_limit, :gas_used, :base_fee, :parent_hash, :proposer_pubkey, :proposer_fee_recipient, :builder_pubkey, :timestamp, to_timestamp(:timestamp)) returning block_id")
+ if err != nil {
+ return nil, err
+ }
+
+ insertBlockBuiltBundleNoIdStmt, err := db.PrepareNamed("insert into built_blocks_bundles (block_id, bundle_id) select :block_id, id from bundles where bundle_hash = :bundle_hash and param_block_number = :block_number returning bundle_id")
+ if err != nil {
+ return nil, err
+ }
+
+ insertBlockBuiltBundleWithIdStmt, err := db.PrepareNamed("insert into built_blocks_bundles (block_id, bundle_id) select :block_id, :bundle_id returning bundle_id")
+ if err != nil {
+ return nil, err
+ }
+
+ insertMissingBundleStmt, err := db.PrepareNamed("insert into bundles (bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase) values (:bundle_hash, :param_signed_txs, :param_block_number, :param_timestamp, :received_timestamp, :param_reverting_tx_hashes, :coinbase_diff, :total_gas_used, :state_block_number, :gas_fees, :eth_sent_to_coinbase) on conflict (bundle_hash, param_block_number) do nothing returning id")
+ if err != nil {
+ return nil, err
+ }
+
+ return &DatabaseService{
+ db: db,
+ insertBuiltBlockStmt: insertBuiltBlockStmt,
+ insertBlockBuiltBundleNoIdStmt: insertBlockBuiltBundleNoIdStmt,
+ insertBlockBuiltBundleWithIdStmt: insertBlockBuiltBundleWithIdStmt,
+ insertMissingBundleStmt: insertMissingBundleStmt,
+ }, nil
+}
+
+func (ds *DatabaseService) ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace) {
+ tx, err := ds.db.Beginx()
+
+ blockData := BuiltBlock{
+ BlockNumber: block.NumberU64(),
+ Profit: new(big.Rat).SetFrac(block.Profit, big.NewInt(1e18)).FloatString(18),
+ Slot: bidTrace.Slot,
+ Hash: block.Hash().String(),
+ GasLimit: block.GasLimit(),
+ GasUsed: block.GasUsed(),
+ BaseFee: block.BaseFee().Uint64(),
+ ParentHash: block.ParentHash().String(),
+ ProposerPubkey: bidTrace.ProposerPubkey.String(),
+ ProposerFeeRecipient: bidTrace.ProposerFeeRecipient.String(),
+ BuilderPubkey: bidTrace.BuilderPubkey.String(),
+ Timestamp: block.Time(),
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
+ defer cancel()
+ var blockId uint64
+ if err = tx.NamedStmtContext(ctx, ds.insertBuiltBlockStmt).GetContext(ctx, &blockId, blockData); err != nil {
+ log.Error("could not insert built block", "err", err)
+ tx.Rollback()
+ return
+ }
+
+ for _, bundle := range bundles {
+ bundleData := BuiltBlockBundle{
+ BlockId: blockId,
+ BundleId: nil,
+ BlockNumber: blockData.BlockNumber,
+ BundleHash: bundle.OriginalBundle.Hash.String(),
+ }
+
+ var bundleId uint64
+ err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData)
+ if err == nil {
+ continue
+ }
+
+ if err != sql.ErrNoRows {
+ log.Error("could not insert bundle", "err", err)
+ // Try anyway
+ }
+
+ missingBundleData := SimulatedBundleToDbBundle(&bundle)
+ err = ds.insertMissingBundleStmt.GetContext(ctx, &bundleId, missingBundleData) // not using the tx as it relies on the unique constraint!
+ if err == nil {
+ bundleData.BundleId = &bundleId
+ _, err = tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleWithIdStmt).ExecContext(ctx, bundleData)
+ if err != nil {
+ log.Error("could not insert built block bundle after inserting missing bundle", "err", err)
+ }
+ } else if err == sql.ErrNoRows /* conflict, someone else inserted the bundle before we could */ {
+ if err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData); err != nil {
+ log.Error("could not insert bundle on retry", "err", err)
+ continue
+ }
+ } else {
+ log.Error("could not insert missing bundle", "err", err)
+ }
+ }
+
+ err = tx.Commit()
+ if err != nil {
+ log.Error("could not commit DB trasnaction", "err", err)
+ }
+}
diff --git a/builder/database_test.go b/builder/database_test.go
new file mode 100644
index 0000000000..2ff2c7bb8a
--- /dev/null
+++ b/builder/database_test.go
@@ -0,0 +1,71 @@
+package builder
+
+import (
+ "math/big"
+ "os"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ boostTypes "github.com/flashbots/go-boost-utils/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestDatabaseBlockInsertion(t *testing.T) {
+ dsn := os.Getenv("FLASHBOTS_TEST_POSTGRES_DSN")
+ if dsn == "" {
+ return
+ }
+
+ ds, err := NewDatabaseService(dsn)
+ require.NoError(t, err)
+
+ _, err = ds.db.Exec("insert into bundles (id, param_block_number, bundle_hash) values (10, 20, '0x1078')")
+ require.NoError(t, err)
+
+ block := types.NewBlock(
+ &types.Header{
+ ParentHash: common.HexToHash("0xafafafa"),
+ Number: big.NewInt(132),
+ GasLimit: uint64(10000),
+ GasUsed: uint64(1000),
+ Time: 16000000,
+ BaseFee: big.NewInt(7),
+ }, nil, nil, nil, nil)
+ block.Profit = big.NewInt(10)
+
+ simBundle1 := types.SimulatedBundle{
+ MevGasPrice: big.NewInt(9),
+ TotalEth: big.NewInt(11),
+ EthSentToCoinbase: big.NewInt(10),
+ TotalGasUsed: uint64(100),
+ OriginalBundle: types.MevBundle{
+ Txs: types.Transactions{types.NewTransaction(uint64(50), common.Address{0x60}, big.NewInt(19), uint64(67), big.NewInt(43), []byte{})},
+ BlockNumber: big.NewInt(12),
+ MinTimestamp: uint64(1000000),
+ RevertingTxHashes: []common.Hash{common.Hash{0x10, 0x17}},
+ Hash: common.Hash{0x09, 0x78},
+ },
+ }
+ simBundle2 := types.SimulatedBundle{
+ MevGasPrice: big.NewInt(90),
+ TotalEth: big.NewInt(110),
+ EthSentToCoinbase: big.NewInt(100),
+ TotalGasUsed: uint64(1000),
+ OriginalBundle: types.MevBundle{
+ Txs: types.Transactions{types.NewTransaction(uint64(51), common.Address{0x61}, big.NewInt(109), uint64(167), big.NewInt(433), []byte{})},
+ BlockNumber: big.NewInt(20),
+ MinTimestamp: uint64(1000020),
+ RevertingTxHashes: []common.Hash{common.Hash{0x11, 0x17}},
+ Hash: common.Hash{0x10, 0x78},
+ },
+ }
+
+ bidTrace := &boostTypes.BidTrace{}
+
+ ds.ConsumeBuiltBlock(block, []types.SimulatedBundle{simBundle1, simBundle2}, bidTrace)
+
+ var dbBlock BuiltBlock
+ ds.db.Get(&dbBlock, "select * from built_blocks where hash = '0x24e6998e4d2b4fd85f7f0d27ac4b87aaf0ec18e156e4b6e194ab5dadee0cd004'")
+ t.Logf("block %v", dbBlock)
+}
diff --git a/builder/database_types.go b/builder/database_types.go
new file mode 100644
index 0000000000..96965616b0
--- /dev/null
+++ b/builder/database_types.go
@@ -0,0 +1,77 @@
+package builder
+
+import (
+ "math/big"
+ "strings"
+ "time"
+
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+type BuiltBlock struct {
+ BlockId uint64 `db:"block_id"`
+ BlockNumber uint64 `db:"block_number"`
+ Profit string `db:"profit"`
+ Slot uint64 `db:"slot"`
+ Hash string `db:"hash"`
+ GasLimit uint64 `db:"gas_limit"`
+ GasUsed uint64 `db:"gas_used"`
+ BaseFee uint64 `db:"base_fee"`
+ ParentHash string `db:"parent_hash"`
+ ProposerPubkey string `db:"proposer_pubkey"`
+ ProposerFeeRecipient string `db:"proposer_fee_recipient"`
+ BuilderPubkey string `db:"builder_pubkey"`
+ Timestamp uint64 `db:"timestamp"`
+ TimestampDatetime time.Time `db:"timestamp_datetime"`
+}
+
+type BuiltBlockBundle struct {
+ BlockId uint64 `db:"block_id"`
+ BundleId *uint64 `db:"bundle_id"`
+ BlockNumber uint64 `db:"block_number"`
+ BundleHash string `db:"bundle_hash"`
+}
+
+type DbBundle struct {
+ DbId uint64 `db:"id"`
+ BundleHash string `db:"bundle_hash"`
+
+ ParamSignedTxs string `db:"param_signed_txs"`
+ ParamBlockNumber uint64 `db:"param_block_number"`
+ ParamTimestamp uint64 `db:"param_timestamp"`
+ ReceivedTimestamp time.Time `db:"received_timestamp"`
+ ParamRevertingTxHashes string `db:"param_reverting_tx_hashes"`
+
+ CoinbaseDiff string `db:"coinbase_diff"`
+ TotalGasUsed uint64 `db:"total_gas_used"`
+ StateBlockNumber uint64 `db:"state_block_number"`
+ GasFees string `db:"gas_fees"`
+ EthSentToCoinbase string `db:"eth_sent_to_coinbase"`
+}
+
+func SimulatedBundleToDbBundle(bundle *types.SimulatedBundle) DbBundle {
+ revertingTxHashes := make([]string, len(bundle.OriginalBundle.RevertingTxHashes))
+ for i, rTxHash := range bundle.OriginalBundle.RevertingTxHashes {
+ revertingTxHashes[i] = rTxHash.String()
+ }
+
+ signedTxsStrings := make([]string, len(bundle.OriginalBundle.Txs))
+ for i, tx := range bundle.OriginalBundle.Txs {
+ signedTxsStrings[i] = tx.Hash().String()
+ }
+
+ return DbBundle{
+ BundleHash: bundle.OriginalBundle.Hash.String(),
+
+ ParamSignedTxs: strings.Join(signedTxsStrings, ","),
+ ParamBlockNumber: bundle.OriginalBundle.BlockNumber.Uint64(),
+ ParamTimestamp: bundle.OriginalBundle.MinTimestamp,
+ ParamRevertingTxHashes: strings.Join(revertingTxHashes, ","),
+
+ CoinbaseDiff: new(big.Rat).SetFrac(bundle.TotalEth, big.NewInt(1e18)).FloatString(18),
+ TotalGasUsed: bundle.TotalGasUsed,
+ StateBlockNumber: bundle.OriginalBundle.BlockNumber.Uint64(),
+ GasFees: new(big.Int).Mul(big.NewInt(int64(bundle.TotalGasUsed)), bundle.MevGasPrice).String(),
+ EthSentToCoinbase: new(big.Rat).SetFrac(bundle.EthSentToCoinbase, big.NewInt(1e18)).FloatString(18),
+ }
+}
diff --git a/builder/eth_service.go b/builder/eth_service.go
index 6ea8b7d3ca..48490b0ce5 100644
--- a/builder/eth_service.go
+++ b/builder/eth_service.go
@@ -1,6 +1,7 @@
package builder
import (
+ "errors"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -11,7 +12,7 @@ import (
)
type IEthereumService interface {
- BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block)
+ BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error
GetBlockByHash(hash common.Hash) *types.Block
Synced() bool
}
@@ -20,10 +21,12 @@ type testEthereumService struct {
synced bool
testExecutableData *beacon.ExecutableDataV1
testBlock *types.Block
+ testBundlesMerged []types.SimulatedBundle
}
-func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block) {
- return t.testExecutableData, t.testBlock
+func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error {
+ sealedBlockCallback(t.testBlock, t.testBundlesMerged)
+ return nil
}
func (t *testEthereumService) GetBlockByHash(hash common.Hash) *types.Block { return t.testBlock }
@@ -38,13 +41,13 @@ func NewEthereumService(eth *eth.Ethereum) *EthereumService {
return &EthereumService{eth: eth}
}
-func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block) {
+func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error {
// Send a request to generate a full block in the background.
// The result can be obtained via the returned channel.
- resCh, err := s.eth.Miner().GetSealingBlockAsync(attrs.HeadHash, uint64(attrs.Timestamp), attrs.SuggestedFeeRecipient, attrs.GasLimit, attrs.Random, false)
+ resCh, err := s.eth.Miner().GetSealingBlockAsync(attrs.HeadHash, uint64(attrs.Timestamp), attrs.SuggestedFeeRecipient, attrs.GasLimit, attrs.Random, false, sealedBlockCallback)
if err != nil {
log.Error("Failed to create async sealing payload", "err", err)
- return nil, nil
+ return err
}
timer := time.NewTimer(4 * time.Second)
@@ -53,13 +56,12 @@ func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.E
select {
case block := <-resCh:
if block == nil {
- log.Error("received nil block from sealing work")
- return nil, nil
+ return errors.New("received nil block from sealing work")
}
- return beacon.BlockToExecutableData(block), block
+ return nil
case <-timer.C:
log.Error("timeout waiting for block", "parent hash", attrs.HeadHash, "slot", attrs.Slot)
- return nil, nil
+ return errors.New("timeout waiting for block result")
}
}
diff --git a/builder/eth_service_test.go b/builder/eth_service_test.go
index d3e0daad8e..9443a19f08 100644
--- a/builder/eth_service_test.go
+++ b/builder/eth_service_test.go
@@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
@@ -90,13 +91,18 @@ func TestBuildBlock(t *testing.T) {
}
service := NewEthereumService(ethservice)
- executableData, block := service.BuildBlock(testPayloadAttributes)
+ service.eth.APIBackend.Miner().SetEtherbase(common.Address{0x05, 0x11})
- //require.Equal(t, common.Address{0x04, 0x10}, executableData.FeeRecipient)
- require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random)
- require.Equal(t, parent.Hash(), executableData.ParentHash)
- require.Equal(t, parent.Time()+1, executableData.Timestamp)
- require.Equal(t, block.ParentHash(), parent.Hash())
- require.Equal(t, block.Hash(), executableData.BlockHash)
- require.Equal(t, block.Profit.Uint64(), uint64(0))
+ err := service.BuildBlock(testPayloadAttributes, func(block *types.Block, _ []types.SimulatedBundle) {
+ executableData := beacon.BlockToExecutableData(block)
+ require.Equal(t, common.Address{0x05, 0x11}, executableData.FeeRecipient)
+ require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random)
+ require.Equal(t, parent.Hash(), executableData.ParentHash)
+ require.Equal(t, parent.Time()+1, executableData.Timestamp)
+ require.Equal(t, block.ParentHash(), parent.Hash())
+ require.Equal(t, block.Hash(), executableData.BlockHash)
+ require.Equal(t, block.Profit.Uint64(), uint64(0))
+ })
+
+ require.NoError(t, err)
}
diff --git a/builder/local_relay_test.go b/builder/local_relay_test.go
index 3e706f5bfe..b101029b48 100644
--- a/builder/local_relay_test.go
+++ b/builder/local_relay_test.go
@@ -29,7 +29,7 @@ func newTestBackend(t *testing.T, forkchoiceData *beacon.ExecutableDataV1, block
beaconClient := &testBeaconClient{validator: validator}
localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true)
ethService := &testEthereumService{synced: true, testExecutableData: forkchoiceData, testBlock: block}
- backend := NewBuilder(sk, beaconClient, localRelay, bDomain, ethService)
+ backend := NewBuilder(sk, NilDbService{}, beaconClient, localRelay, bDomain, ethService)
// service := NewService("127.0.0.1:31545", backend)
return backend, localRelay, validator
@@ -115,15 +115,16 @@ func TestGetHeader(t *testing.T) {
forkchoiceData := &beacon.ExecutableDataV1{
ParentHash: common.HexToHash("0xafafafa"),
FeeRecipient: common.Address{0x01},
- BlockHash: common.HexToHash("0xbfbfbfb"),
+ LogsBloom: types.Bloom{0x00, 0x05, 0x10}.Bytes(),
+ BlockHash: common.HexToHash("0x24e6998e4d2b4fd85f7f0d27ac4b87aaf0ec18e156e4b6e194ab5dadee0cd004"),
BaseFeePerGas: big.NewInt(12),
ExtraData: []byte{},
- LogsBloom: []byte{0x00, 0x05, 0x10},
- }
- forkchoiceBlock := &types.Block{
- Profit: big.NewInt(10),
}
+ forkchoiceBlock, err := beacon.ExecutableDataToBlock(*forkchoiceData)
+ require.NoError(t, err)
+ forkchoiceBlock.Profit = big.NewInt(10)
+
backend, relay, validator := newTestBackend(t, forkchoiceData, forkchoiceBlock)
path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
@@ -147,7 +148,7 @@ func TestGetHeader(t *testing.T) {
require.Equal(t, http.StatusOK, rr.Code)
bid := new(boostTypes.GetHeaderResponse)
- err := json.Unmarshal(rr.Body.Bytes(), bid)
+ err = json.Unmarshal(rr.Body.Bytes(), bid)
require.NoError(t, err)
executionPayload, err := executableDataToExecutionPayload(forkchoiceData)
@@ -174,13 +175,15 @@ func TestGetPayload(t *testing.T) {
forkchoiceData := &beacon.ExecutableDataV1{
ParentHash: common.HexToHash("0xafafafa"),
FeeRecipient: common.Address{0x01},
- BlockHash: common.HexToHash("0xbfbfbfb"),
+ LogsBloom: types.Bloom{}.Bytes(),
+ BlockHash: common.HexToHash("0xc4a012b67027b3ab6c00acd31aeee24aa1515d6a5d7e81b0ee2e69517fdc387f"),
BaseFeePerGas: big.NewInt(12),
ExtraData: []byte{},
}
- forkchoiceBlock := &types.Block{
- Profit: big.NewInt(10),
- }
+
+ forkchoiceBlock, err := beacon.ExecutableDataToBlock(*forkchoiceData)
+ require.NoError(t, err)
+ forkchoiceBlock.Profit = big.NewInt(10)
backend, relay, validator := newTestBackend(t, forkchoiceData, forkchoiceBlock)
@@ -192,7 +195,7 @@ func TestGetPayload(t *testing.T) {
require.Equal(t, http.StatusOK, rr.Code)
bid := new(boostTypes.GetHeaderResponse)
- err := json.Unmarshal(rr.Body.Bytes(), bid)
+ err = json.Unmarshal(rr.Body.Bytes(), bid)
require.NoError(t, err)
// Create request payload
diff --git a/builder/service.go b/builder/service.go
index 0e459decd9..131b8d5640 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"net/http"
+ "os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -159,7 +160,15 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
ethereumService := NewEthereumService(backend)
- builderBackend := NewBuilder(builderSk, beaconClient, relay, builderSigningDomain, ethereumService)
+ // TODO: move to proper flags
+ var ds IDatabaseService
+ ds, err = NewDatabaseService(os.Getenv("FLASHBOTS_POSTGRES_DSN"))
+ if err != nil {
+ log.Error("could not connect to the DB", "err", err)
+ ds = NilDbService{}
+ }
+
+ builderBackend := NewBuilder(builderSk, ds, beaconClient, relay, builderSigningDomain, ethereumService)
builderService := NewService(cfg.ListenAddr, localRelay, builderBackend)
builderService.Start()
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 7cc825667c..4d0008e81d 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
+ "golang.org/x/crypto/sha3"
)
const (
@@ -619,6 +620,12 @@ func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]t
// AddMevBundle adds a mev bundle to the pool
func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
+ bundleHasher := sha3.NewLegacyKeccak256()
+ for _, tx := range txs {
+ bundleHasher.Write(tx.Hash().Bytes())
+ }
+ bundleHash := common.BytesToHash(bundleHasher.Sum(nil))
+
pool.mu.Lock()
defer pool.mu.Unlock()
@@ -628,6 +635,7 @@ func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, m
MinTimestamp: minTimestamp,
MaxTimestamp: maxTimestamp,
RevertingTxHashes: revertingTxHashes,
+ Hash: bundleHash,
})
return nil
}
diff --git a/core/types/transaction.go b/core/types/transaction.go
index bdff810af0..1a31046db8 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -658,4 +658,13 @@ type MevBundle struct {
MinTimestamp uint64
MaxTimestamp uint64
RevertingTxHashes []common.Hash
+ Hash common.Hash
+}
+
+type SimulatedBundle struct {
+ MevGasPrice *big.Int
+ TotalEth *big.Int
+ EthSentToCoinbase *big.Int
+ TotalGasUsed uint64
+ OriginalBundle MevBundle
}
diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go
index 961e091181..98f8646c02 100644
--- a/eth/block-validation/api_test.go
+++ b/eth/block-validation/api_test.go
@@ -210,7 +210,7 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
}
func assembleBlock(api *BlockValidationAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) {
- block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false)
+ block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false, nil)
if err != nil {
return nil, err
}
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index 6305b77cbc..c7fbbacb6f 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -277,14 +277,14 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
// might replace it arbitrarily many times in between.
if payloadAttributes != nil {
// Create an empty block first which can be used as a fallback
- empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.GasLimit, payloadAttributes.Random, true)
+ empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.GasLimit, payloadAttributes.Random, true, nil)
if err != nil {
log.Error("Failed to create empty sealing payload", "err", err)
return valid(nil), beacon.InvalidPayloadAttributes.With(err)
}
// Send a request to generate a full block in the background.
// The result can be obtained via the returned channel.
- resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.GasLimit, payloadAttributes.Random, false)
+ resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.GasLimit, payloadAttributes.Random, false, nil)
if err != nil {
log.Error("Failed to create async sealing payload", "err", err)
return valid(nil), beacon.InvalidPayloadAttributes.With(err)
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
index 68fc0a05ff..d4d475742e 100644
--- a/eth/catalyst/api_test.go
+++ b/eth/catalyst/api_test.go
@@ -603,7 +603,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
}
func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) {
- block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false)
+ block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false, nil)
if err != nil {
return nil, err
}
@@ -846,7 +846,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
Random: crypto.Keccak256Hash([]byte{byte(1)}),
SuggestedFeeRecipient: parent.Coinbase(),
}
- empty, err := api.eth.Miner().GetSealingBlockSync(parent.Hash(), params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, true)
+ empty, err := api.eth.Miner().GetSealingBlockSync(parent.Hash(), params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, true, nil)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err)
}
diff --git a/go.mod b/go.mod
index b34ba9079e..fc1aff681c 100644
--- a/go.mod
+++ b/go.mod
@@ -43,8 +43,10 @@ require (
github.com/influxdata/influxdb-client-go/v2 v2.4.0
github.com/jackpal/go-nat-pmp v1.0.2
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e
+ github.com/jmoiron/sqlx v1.3.5
github.com/julienschmidt/httprouter v1.3.0
github.com/karalabe/usb v0.0.2
+ github.com/lib/pq v1.2.0
github.com/mattn/go-colorable v0.1.9
github.com/mattn/go-isatty v0.0.14
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
diff --git a/go.sum b/go.sum
index d7ebaa3e99..2bd0339c78 100644
--- a/go.sum
+++ b/go.sum
@@ -159,6 +159,7 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@@ -257,6 +258,8 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+U
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
+github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -295,6 +298,8 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
@@ -316,6 +321,7 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
diff --git a/miner/miner.go b/miner/miner.go
index acf08d3e83..43a2bf0e74 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -249,13 +249,13 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
// there is always a result that will be returned through the result channel.
// The difference is that if the execution fails, the returned result is nil
// and the concrete error is dropped silently.
-func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (chan *types.Block, error) {
- return miner.worker.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
+func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, error) {
+ return miner.worker.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, false, blockHook)
}
// GetSealingBlockSync creates a sealing block according to the given parameters.
// If the generation is failed or the underlying work is already closed, an error
// will be returned.
-func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool) (*types.Block, error) {
- return miner.worker.GetSealingBlockSync(parent, timestamp, coinbase, gasLimit, random, noTxs, false)
+func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, blockHook func(*types.Block, []types.SimulatedBundle)) (*types.Block, error) {
+ return miner.worker.GetSealingBlockSync(parent, timestamp, coinbase, gasLimit, random, noTxs, false, blockHook)
}
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index cd0068c1e9..4ab9a3619c 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -91,11 +91,11 @@ type resChPair struct {
errCh chan error
}
-func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (chan *types.Block, error) {
+func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, error) {
resChans := []resChPair{}
for _, worker := range append(w.workers, w.regularWorker) {
- resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra)
+ resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook)
if err != nil {
log.Error("could not start async block construction", "isFlashbotsWorker", worker.flashbots.isFlashbots, "#bundles", worker.flashbots.maxMergedBundles)
continue
@@ -128,8 +128,8 @@ func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64,
return resCh, nil
}
-func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (*types.Block, error) {
- resCh, err := w.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra)
+func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (*types.Block, error) {
+ resCh, err := w.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook)
if err != nil {
return nil, err
}
diff --git a/miner/worker.go b/miner/worker.go
index b25b1cb5bd..c79d8bfa75 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1164,15 +1164,16 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
// generateParams wraps various of settings for generating sealing task.
type generateParams struct {
- timestamp uint64 // The timstamp for sealing task
- forceTime bool // Flag whether the given timestamp is immutable or not
- parentHash common.Hash // Parent block hash, empty means the latest chain head
- coinbase common.Address // The fee recipient address for including transaction
- gasLimit uint64 // The validator's requested gas limit target
- random common.Hash // The randomness generated by beacon chain, empty before the merge
- noUncle bool // Flag whether the uncle block inclusion is allowed
- noExtra bool // Flag whether the extra field assignment is allowed
- noTxs bool // Flag whether an empty block without any transaction is expected
+ timestamp uint64 // The timstamp for sealing task
+ forceTime bool // Flag whether the given timestamp is immutable or not
+ parentHash common.Hash // Parent block hash, empty means the latest chain head
+ coinbase common.Address // The fee recipient address for including transaction
+ gasLimit uint64 // The validator's requested gas limit target
+ random common.Hash // The randomness generated by beacon chain, empty before the merge
+ noUncle bool // Flag whether the uncle block inclusion is allowed
+ noExtra bool // Flag whether the extra field assignment is allowed
+ noTxs bool // Flag whether an empty block without any transaction is expected
+ onBlock func(*types.Block, []types.SimulatedBundle) // Callback to call for each produced block
}
// prepareWork constructs the sealing task according to the given parameters,
@@ -1264,8 +1265,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
// fillTransactions retrieves the pending transactions from the txpool and fills them
// into the given sealing block. The transaction selection and ordering strategy can
// be customized with the plugin in the future.
-
-func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorCoinbase *common.Address) error {
+func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorCoinbase *common.Address) (error, []types.SimulatedBundle) {
// Split the pending transactions into locals and remotes
// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
@@ -1283,43 +1283,47 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
if validatorCoinbase != nil {
builderCoinbaseBalanceBefore = env.state.GetBalance(w.coinbase)
if err := env.gasPool.SubGas(params.TxGas); err != nil {
- return err
+ return err, nil
}
}
+
+ var blockBundles []types.SimulatedBundle
if w.flashbots.isFlashbots {
bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
if err != nil {
log.Error("Failed to fetch pending transactions", "err", err)
- return err
+ return err, nil
}
- bundleTxs, bundle, numBundles, err := w.generateFlashbotsBundle(env, bundles, pending)
+ bundleTxs, bundle, mergedBundles, numBundles, err := w.generateFlashbotsBundle(env, bundles, pending)
if err != nil {
log.Error("Failed to generate flashbots bundle", "err", err)
- return err
+ return err, nil
}
- log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(bundle.totalEth), "gasUsed", bundle.totalGasUsed, "bundleScore", bundle.mevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles)
+ log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(bundle.TotalEth), "gasUsed", bundle.TotalGasUsed, "bundleScore", bundle.MevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles)
if len(bundleTxs) == 0 {
- return errors.New("no bundles to apply")
+ return errors.New("no bundles to apply"), nil
}
if err := w.commitBundle(env, bundleTxs, interrupt); err != nil {
- return err
+ return err, nil
}
- env.profit.Add(env.profit, bundle.ethSentToCoinbase)
+ blockBundles = mergedBundles
+ env.profit.Add(env.profit, bundle.EthSentToCoinbase)
}
if len(localTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
- return err
+ return err, nil
}
}
if len(remoteTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
- return err
+ return err, nil
}
}
+
if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil {
builderCoinbaseBalanceAfter := env.state.GetBalance(w.coinbase)
log.Info("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
@@ -1330,7 +1334,7 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit)
if err != nil {
log.Error("Proposer payout create tx failed", "err", err)
- return fmt.Errorf("proposer payout create tx failed - %v", err)
+ return fmt.Errorf("proposer payout create tx failed - %v", err), nil
}
if tx != nil {
log.Info("Proposer payout create tx succeeded, proceeding to commit tx")
@@ -1338,20 +1342,21 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
_, err = w.commitTransaction(env, tx)
if err != nil {
log.Error("Proposer payout commit tx failed", "hash", tx.Hash().String(), "err", err)
- return fmt.Errorf("proposer payout commit tx failed - %v", err)
+ return fmt.Errorf("proposer payout commit tx failed - %v", err), nil
}
log.Info("Proposer payout commit tx succeeded", "hash", tx.Hash().String())
env.tcount++
} else {
- return errors.New("proposer payout create tx failed due to tx is nil")
+ return errors.New("proposer payout create tx failed due to tx is nil"), nil
}
} else {
log.Warn("Proposer payout create tx failed due to not enough balance", "profit", profit.String())
- return errors.New("proposer payout create tx failed due to not enough balance")
+ return errors.New("proposer payout create tx failed due to not enough balance"), nil
}
}
- return nil
+
+ return nil, blockBundles
}
// generateWork generates a sealing block based on the given parameters.
@@ -1366,10 +1371,13 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
}
defer work.discard()
+ var blockBundles []types.SimulatedBundle
if !params.noTxs {
- if err := w.fillTransactions(nil, work, &validatorCoinbase); err != nil {
+ err, mergedBundles := w.fillTransactions(nil, work, &validatorCoinbase)
+ if err != nil {
return nil, err
}
+ blockBundles = mergedBundles
}
block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
if err != nil {
@@ -1402,6 +1410,13 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
block.Profit.Set(lastTx.Value())
log.Info("Block finalized and assembled", "blockProfit", block.Profit.String(), "proposer payment tx", lastTx, "receipt", receipt)
+
+ if params.onBlock != nil {
+ go params.onBlock(block, blockBundles)
+ }
+
+ log.Info("Block finalized and assembled", "blockProfit", block.Profit.String(), "hash", block.Hash())
+
return block, nil
}
@@ -1433,7 +1448,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) {
}
// Fill pending transactions from the txpool
- err = w.fillTransactions(interrupt, work, nil)
+ err, _ = w.fillTransactions(interrupt, work, nil)
if err != nil && !errors.Is(err, errBlockInterruptedByRecommit) {
work.discard()
return
@@ -1487,7 +1502,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
// getSealingBlock generates the sealing block based on the given parameters.
// The generation result will be passed back via the given channel no matter
// the generation itself succeeds or not.
-func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (chan *types.Block, chan error, error) {
+func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, chan error, error) {
var (
resCh = make(chan *types.Block, 1)
errCh = make(chan error, 1)
@@ -1503,6 +1518,7 @@ func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase
noUncle: true,
noExtra: noExtra,
noTxs: noTxs,
+ onBlock: blockHook,
},
result: resCh,
err: errCh,
@@ -1522,28 +1538,23 @@ func (w *worker) isTTDReached(header *types.Header) bool {
return td != nil && ttd != nil && td.Cmp(ttd) >= 0
}
-type simulatedBundle struct {
- mevGasPrice *big.Int
- totalEth *big.Int
- ethSentToCoinbase *big.Int
- totalGasUsed uint64
- originalBundle types.MevBundle
-}
+type simulatedBundle = types.SimulatedBundle
-func (w *worker) generateFlashbotsBundle(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, int, error) {
+func (w *worker) generateFlashbotsBundle(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, []types.SimulatedBundle, int, error) {
simulatedBundles, err := w.simulateBundles(env, bundles, pendingTxs)
if err != nil {
- return nil, simulatedBundle{}, 0, err
+ return nil, simulatedBundle{}, nil, 0, err
}
sort.SliceStable(simulatedBundles, func(i, j int) bool {
- return simulatedBundles[j].mevGasPrice.Cmp(simulatedBundles[i].mevGasPrice) < 0
+ return simulatedBundles[j].MevGasPrice.Cmp(simulatedBundles[i].MevGasPrice) < 0
})
return w.mergeBundles(env, simulatedBundles, pendingTxs)
}
-func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, int, error) {
+func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, []types.SimulatedBundle, int, error) {
+ mergedBundles := []types.SimulatedBundle{}
finalBundle := types.Transactions{}
currentState := env.state.Copy()
@@ -1553,8 +1564,8 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
var prevGasPool *core.GasPool
mergedBundle := simulatedBundle{
- totalEth: new(big.Int),
- ethSentToCoinbase: new(big.Int),
+ TotalEth: new(big.Int),
+ EthSentToCoinbase: new(big.Int),
}
count := 0
@@ -1563,22 +1574,22 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
prevGasPool = new(core.GasPool).AddGas(gasPool.Gas())
// the floor gas price is 99/100 what was simulated at the top of the block
- floorGasPrice := new(big.Int).Mul(bundle.mevGasPrice, big.NewInt(99))
+ floorGasPrice := new(big.Int).Mul(bundle.MevGasPrice, big.NewInt(99))
floorGasPrice = floorGasPrice.Div(floorGasPrice, big.NewInt(100))
- simmed, err := w.computeBundleGas(env, bundle.originalBundle, currentState, gasPool, pendingTxs, len(finalBundle))
- if err != nil || simmed.mevGasPrice.Cmp(floorGasPrice) <= 0 {
+ simmed, err := w.computeBundleGas(env, bundle.OriginalBundle, currentState, gasPool, pendingTxs, len(finalBundle))
+ if err != nil || simmed.MevGasPrice.Cmp(floorGasPrice) <= 0 {
currentState = prevState
gasPool = prevGasPool
continue
}
- log.Info("Included bundle", "ethToCoinbase", ethIntToFloat(simmed.totalEth), "gasUsed", simmed.totalGasUsed, "bundleScore", simmed.mevGasPrice, "bundleLength", len(simmed.originalBundle.Txs), "worker", w.flashbots.maxMergedBundles)
-
- finalBundle = append(finalBundle, bundle.originalBundle.Txs...)
- mergedBundle.totalEth.Add(mergedBundle.totalEth, simmed.totalEth)
- mergedBundle.ethSentToCoinbase.Add(mergedBundle.ethSentToCoinbase, simmed.ethSentToCoinbase)
- mergedBundle.totalGasUsed += simmed.totalGasUsed
+ log.Info("Included bundle", "ethToCoinbase", ethIntToFloat(simmed.TotalEth), "gasUsed", simmed.TotalGasUsed, "bundleScore", simmed.MevGasPrice, "bundleLength", len(simmed.OriginalBundle.Txs), "worker", w.flashbots.maxMergedBundles)
+ mergedBundles = append(mergedBundles, simmed)
+ finalBundle = append(finalBundle, bundle.OriginalBundle.Txs...)
+ mergedBundle.TotalEth.Add(mergedBundle.TotalEth, simmed.TotalEth)
+ mergedBundle.EthSentToCoinbase.Add(mergedBundle.EthSentToCoinbase, simmed.EthSentToCoinbase)
+ mergedBundle.TotalGasUsed += simmed.TotalGasUsed
count++
if count >= w.flashbots.maxMergedBundles {
@@ -1587,15 +1598,15 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
}
if len(finalBundle) == 0 || count != w.flashbots.maxMergedBundles {
- return nil, simulatedBundle{}, count, nil
+ return nil, simulatedBundle{}, nil, count, nil
}
return finalBundle, simulatedBundle{
- mevGasPrice: new(big.Int).Div(mergedBundle.totalEth, new(big.Int).SetUint64(mergedBundle.totalGasUsed)),
- totalEth: mergedBundle.totalEth,
- ethSentToCoinbase: mergedBundle.ethSentToCoinbase,
- totalGasUsed: mergedBundle.totalGasUsed,
- }, count, nil
+ MevGasPrice: new(big.Int).Div(mergedBundle.TotalEth, new(big.Int).SetUint64(mergedBundle.TotalGasUsed)),
+ TotalEth: mergedBundle.TotalEth,
+ EthSentToCoinbase: mergedBundle.EthSentToCoinbase,
+ TotalGasUsed: mergedBundle.TotalGasUsed,
+ }, mergedBundles, count, nil
}
func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) ([]simulatedBundle, error) {
@@ -1703,11 +1714,11 @@ func (w *worker) computeBundleGas(env *environment, bundle types.MevBundle, stat
totalEth := new(big.Int).Add(ethSentToCoinbase, gasFees)
return simulatedBundle{
- mevGasPrice: new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)),
- totalEth: totalEth,
- ethSentToCoinbase: ethSentToCoinbase,
- totalGasUsed: totalGasUsed,
- originalBundle: bundle,
+ MevGasPrice: new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)),
+ TotalEth: totalEth,
+ EthSentToCoinbase: ethSentToCoinbase,
+ TotalGasUsed: totalGasUsed,
+ OriginalBundle: bundle,
}, nil
}
diff --git a/miner/worker_test.go b/miner/worker_test.go
index 9d94ac4c88..f590df7610 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -640,7 +640,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
// This API should work even when the automatic sealing is not enabled
for _, c := range cases {
- resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false, true)
+ resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false, true, nil)
block := <-resChan
err := <-errChan
if c.expectErr {
@@ -658,7 +658,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
// This API should work even when the automatic sealing is enabled
w.start()
for _, c := range cases {
- resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false, false)
+ resChan, errChan, _ := w.getSealingBlock(c.parent, timestamp, c.coinbase, 0, c.random, false, false, nil)
block := <-resChan
err := <-errChan
if c.expectErr {
From 74b7659ed8eb7913ae4588e2574b32f8ac1023ad Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Thu, 1 Sep 2022 11:34:52 +0300
Subject: [PATCH 35/83] add miner blocklist (#21)
---
cmd/geth/main.go | 1 +
cmd/utils/flags.go | 18 ++++++
eth/tracers/logger/account_touch_tracer.go | 69 ++++++++++++++++++++++
miner/miner.go | 1 +
miner/worker.go | 62 +++++++++++++++++--
5 files changed, 147 insertions(+), 4 deletions(-)
create mode 100644 eth/tracers/logger/account_touch_tracer.go
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index b172ffe57c..9a13c436eb 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -136,6 +136,7 @@ var (
utils.MinerNoVerifyFlag,
utils.MinerMaxMergedBundlesFlag,
utils.MinerTrustedRelaysFlag,
+ utils.MinerBlocklistFileFlag,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index c9f4450c11..699e94e232 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -19,6 +19,7 @@ package utils
import (
"crypto/ecdsa"
+ "encoding/json"
"fmt"
"math"
"math/big"
@@ -580,6 +581,12 @@ var (
Value: "0x870e2734DdBe2Fba9864f33f3420d59Bc641f2be",
Category: flags.MinerCategory,
}
+ MinerBlocklistFileFlag = &cli.StringFlag{
+ Name: "miner.blocklist",
+ Usage: "flashbots - Path to JSON file with list of blocked addresses. Miner will ignore txs that touch mentioned addresses.",
+ Value: "",
+ Category: flags.MinerCategory,
+ }
// Account settings
UnlockedAccountFlag = &cli.StringFlag{
@@ -1764,6 +1771,17 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
}
}
log.Info("Trusted relays set as", "addresses", cfg.TrustedRelays)
+
+ if ctx.IsSet(MinerBlocklistFileFlag.Name) {
+ bytes, err := os.ReadFile(ctx.String(MinerBlocklistFileFlag.Name))
+ if err != nil {
+ Fatalf("Failed to read blocklist file: %s", err)
+ }
+
+ if err := json.Unmarshal(bytes, &cfg.Blocklist); err != nil {
+ Fatalf("Failed to parse blocklist: %s", err)
+ }
+ }
}
func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
diff --git a/eth/tracers/logger/account_touch_tracer.go b/eth/tracers/logger/account_touch_tracer.go
new file mode 100644
index 0000000000..120f449bb0
--- /dev/null
+++ b/eth/tracers/logger/account_touch_tracer.go
@@ -0,0 +1,69 @@
+// Copyright 2022 flashbots
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package logger
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "math/big"
+ "time"
+)
+
+type AccountTouchTracer struct {
+ touched map[common.Address]struct{}
+}
+
+// NewAccountTouchTracer creates new AccountTouchTracer
+// that collect all addresses touched in the given tx
+// including tx sender and tx.to from the top level call
+func NewAccountTouchTracer() *AccountTouchTracer {
+ return &AccountTouchTracer{
+ touched: map[common.Address]struct{}{},
+ }
+}
+
+func (t *AccountTouchTracer) TouchedAddresses() []common.Address {
+ result := make([]common.Address, 0, len(t.touched))
+
+ for address := range t.touched {
+ result = append(result, address)
+ }
+ return result
+}
+
+func (t *AccountTouchTracer) CaptureTxStart(uint64) {}
+
+func (t *AccountTouchTracer) CaptureTxEnd(uint64) {}
+
+func (t *AccountTouchTracer) CaptureStart(_ *vm.EVM, from common.Address, to common.Address, _ bool, _ []byte, _ uint64, _ *big.Int) {
+ t.touched[from] = struct{}{}
+ t.touched[to] = struct{}{}
+}
+
+func (t *AccountTouchTracer) CaptureEnd([]byte, uint64, time.Duration, error) {}
+
+func (t *AccountTouchTracer) CaptureEnter(_ vm.OpCode, _ common.Address, to common.Address, _ []byte, _ uint64, _ *big.Int) {
+ t.touched[to] = struct{}{}
+}
+
+func (t *AccountTouchTracer) CaptureExit([]byte, uint64, error) {}
+
+func (t *AccountTouchTracer) CaptureState(uint64, vm.OpCode, uint64, uint64, *vm.ScopeContext, []byte, int, error) {
+}
+
+func (t *AccountTouchTracer) CaptureFault(uint64, vm.OpCode, uint64, uint64, *vm.ScopeContext, int, error) {
+}
diff --git a/miner/miner.go b/miner/miner.go
index 43a2bf0e74..eadb5956b6 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -57,6 +57,7 @@ type Config struct {
BuilderTxSigningKey *ecdsa.PrivateKey // Signing key of builder coinbase to make transaction to validator
MaxMergedBundles int
TrustedRelays []common.Address `toml:",omitempty"` // Trusted relay addresses to receive tasks from.
+ Blocklist []common.Address `toml:",omitempty"`
}
// Miner creates blocks and searches for proof-of-work values.
diff --git a/miner/worker.go b/miner/worker.go
index c79d8bfa75..7e2e7aca25 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
@@ -88,6 +89,7 @@ var (
errBundleInterrupted = errors.New("interrupt while applying bundles")
errBlockInterruptedByNewHead = errors.New("new head arrived while building block")
errBlockInterruptedByRecommit = errors.New("recommit interrupt while building block")
+ errBlocklistViolation = errors.New("blocklist violation")
)
// environment is the worker's current environment and holds all
@@ -202,6 +204,7 @@ type worker struct {
engine consensus.Engine
eth Backend
chain *core.BlockChain
+ blockList map[common.Address]struct{}
// Feeds
pendingLogsFeed event.Feed
@@ -311,6 +314,11 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
}()
}
+ blockList := make(map[common.Address]struct{})
+ for _, address := range config.Blocklist {
+ blockList[address] = struct{}{}
+ }
+
worker := &worker{
config: config,
chainConfig: chainConfig,
@@ -318,6 +326,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
eth: eth,
mux: mux,
chain: eth.BlockChain(),
+ blockList: blockList,
isLocalBlock: isLocalBlock,
localUncles: make(map[common.Hash]*types.Block),
remoteUncles: make(map[common.Hash]*types.Block),
@@ -919,18 +928,47 @@ func (w *worker) updateSnapshot(env *environment) {
}
func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) {
- snap := env.state.Snapshot()
+ gasPool := *env.gasPool
+ envGasUsed := env.header.GasUsed
+ var stateDB *state.StateDB
+ if len(w.blockList) != 0 {
+ stateDB = env.state.Copy()
+ } else {
+ stateDB = env.state
+ }
+
+ snapshot := stateDB.Snapshot()
gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee)
if err != nil {
return nil, err
}
- receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig())
+ var tracer *logger.AccountTouchTracer
+ config := *w.chain.GetVMConfig()
+ if len(w.blockList) != 0 {
+ tracer = logger.NewAccountTouchTracer()
+ config.Tracer = tracer
+ config.Debug = true
+ }
+
+ receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, &gasPool, stateDB, env.header, tx, &envGasUsed, config)
if err != nil {
- env.state.RevertToSnapshot(snap)
+ stateDB.RevertToSnapshot(snapshot)
return nil, err
}
+ if len(w.blockList) != 0 {
+ for _, address := range tracer.TouchedAddresses() {
+ if _, in := w.blockList[address]; in {
+ return nil, errBlocklistViolation
+ }
+ }
+ }
+
+ *env.gasPool = gasPool
+ env.header.GasUsed = envGasUsed
+ env.state = stateDB
+
env.txs = append(env.txs, tx)
env.receipts = append(env.receipts, receipt)
@@ -1373,11 +1411,13 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
var blockBundles []types.SimulatedBundle
if !params.noTxs {
+ start := time.Now()
err, mergedBundles := w.fillTransactions(nil, work, &validatorCoinbase)
if err != nil {
return nil, err
}
blockBundles = mergedBundles
+ log.Info("Filled block with transactions", "time", time.Since(start), "gas used", work.header.GasUsed)
}
block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
if err != nil {
@@ -1666,13 +1706,27 @@ func (w *worker) computeBundleGas(env *environment, bundle types.MevBundle, stat
state.Prepare(tx.Hash(), i+currentTxCount)
coinbaseBalanceBefore := state.GetBalance(env.coinbase)
- receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, gasPool, state, env.header, tx, &tempGasUsed, *w.chain.GetVMConfig())
+ config := *w.chain.GetVMConfig()
+ var tracer *logger.AccountTouchTracer
+ if len(w.blockList) != 0 {
+ tracer = logger.NewAccountTouchTracer()
+ config.Tracer = tracer
+ config.Debug = true
+ }
+ receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, gasPool, state, env.header, tx, &tempGasUsed, config)
if err != nil {
return simulatedBundle{}, err
}
if receipt.Status == types.ReceiptStatusFailed && !containsHash(bundle.RevertingTxHashes, receipt.TxHash) {
return simulatedBundle{}, errors.New("failed tx")
}
+ if len(w.blockList) != 0 {
+ for _, address := range tracer.TouchedAddresses() {
+ if _, in := w.blockList[address]; in {
+ return simulatedBundle{}, errBlocklistViolation
+ }
+ }
+ }
totalGasUsed += receipt.GasUsed
From 624b2df7707126fa64661dca878c687340b18711 Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Fri, 2 Sep 2022 10:13:02 +0300
Subject: [PATCH 36/83] fix statedb copy (#22)
---
miner/worker.go | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/miner/worker.go b/miner/worker.go
index 7e2e7aca25..fee4e1e224 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -937,6 +937,9 @@ func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*
stateDB = env.state
}
+ // It's important to copy then .Prepare() - don't reorder.
+ stateDB.Prepare(tx.Hash(), env.tcount)
+
snapshot := stateDB.Snapshot()
gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee)
@@ -1024,8 +1027,6 @@ func (w *worker) commitBundle(env *environment, txs types.Transactions, interrup
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
return errCouldNotApplyTransaction
}
- // Start executing the transaction
- env.state.Prepare(tx.Hash(), env.tcount)
logs, err := w.commitTransaction(env, tx)
switch {
@@ -1138,8 +1139,6 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
txs.Pop()
continue
}
- // Start executing the transaction
- env.state.Prepare(tx.Hash(), env.tcount)
logs, err := w.commitTransaction(env, tx)
switch {
@@ -1376,7 +1375,6 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
}
if tx != nil {
log.Info("Proposer payout create tx succeeded, proceeding to commit tx")
- env.state.Prepare(tx.Hash(), env.tcount)
_, err = w.commitTransaction(env, tx)
if err != nil {
log.Error("Proposer payout commit tx failed", "hash", tx.Hash().String(), "err", err)
From a39100e064fbc192b681724282f4c2d031f65852 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Wed, 7 Sep 2022 09:23:35 +0200
Subject: [PATCH 37/83] Rate limit blocks submitted to the relay and DB (#25)
---
builder/block_submission_rate_limiter.go | 92 +++++++++++++++++++
builder/block_submission_rate_limiter_test.go | 72 +++++++++++++++
builder/builder.go | 55 ++++++++---
builder/builder_test.go | 5 +-
builder/relay.go | 2 -
builder/service.go | 18 +++-
6 files changed, 225 insertions(+), 19 deletions(-)
create mode 100644 builder/block_submission_rate_limiter.go
create mode 100644 builder/block_submission_rate_limiter_test.go
diff --git a/builder/block_submission_rate_limiter.go b/builder/block_submission_rate_limiter.go
new file mode 100644
index 0000000000..ec83cc90b3
--- /dev/null
+++ b/builder/block_submission_rate_limiter.go
@@ -0,0 +1,92 @@
+package builder
+
+import (
+ "context"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+type blockRateLimitSubmission struct {
+ resultCh chan bool
+ block *types.Block
+}
+
+type BlockSubmissionRateLimiter struct {
+ submissionsCh chan blockRateLimitSubmission
+ started uint32
+ ctx context.Context
+ cancel context.CancelFunc
+}
+
+func NewBlockSubmissionRateLimiter() *BlockSubmissionRateLimiter {
+ ctx, cancel := context.WithCancel(context.Background())
+ r := &BlockSubmissionRateLimiter{
+ submissionsCh: make(chan blockRateLimitSubmission),
+ started: uint32(0),
+ ctx: ctx,
+ cancel: cancel,
+ }
+
+ return r
+}
+func (r *BlockSubmissionRateLimiter) Limit(block *types.Block) chan bool {
+ resultCh := make(chan bool, 1)
+ if atomic.LoadUint32(&r.started) != 1 {
+ resultCh <- true
+ return resultCh
+ }
+
+ select {
+ case r.submissionsCh <- blockRateLimitSubmission{
+ resultCh: resultCh,
+ block: block,
+ }:
+ case <-r.ctx.Done():
+ resultCh <- true
+ }
+ return resultCh
+}
+
+func (r *BlockSubmissionRateLimiter) Start() {
+ if !atomic.CompareAndSwapUint32(&r.started, 0, 1) {
+ return
+ }
+
+ go r.rateLimit()
+}
+
+func (r *BlockSubmissionRateLimiter) rateLimit() {
+ for r.ctx.Err() == nil {
+ // Beginning of the rate limit bucket
+ bestSubmission := <-r.submissionsCh
+
+ bucketCutoffCh := time.After(100 * time.Millisecond)
+
+ bucketClosed := false
+ for !bucketClosed {
+ select {
+ case <-r.ctx.Done():
+ bucketClosed = true
+ break
+ case <-bucketCutoffCh:
+ bucketClosed = true
+ break
+ case newSubmission := <-r.submissionsCh:
+ if bestSubmission.block.Profit.Cmp(newSubmission.block.Profit) < 0 {
+ bestSubmission.resultCh <- false
+ bestSubmission = newSubmission
+ } else {
+ newSubmission.resultCh <- false
+ }
+ }
+ }
+
+ bestSubmission.resultCh <- true
+ }
+}
+
+func (r *BlockSubmissionRateLimiter) Stop() {
+ r.cancel()
+}
diff --git a/builder/block_submission_rate_limiter_test.go b/builder/block_submission_rate_limiter_test.go
new file mode 100644
index 0000000000..9a0ce5b55d
--- /dev/null
+++ b/builder/block_submission_rate_limiter_test.go
@@ -0,0 +1,72 @@
+package builder
+
+import (
+ "math/big"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestLimit(t *testing.T) {
+ rl := NewBlockSubmissionRateLimiter()
+
+ // Check that before starting requests are passed through
+ ch1 := rl.Limit(&types.Block{Profit: new(big.Int)})
+ ch2 := rl.Limit(&types.Block{Profit: new(big.Int)})
+ ch3 := rl.Limit(&types.Block{Profit: new(big.Int)})
+
+ time.Sleep(200 * time.Millisecond)
+
+ for _, ch := range []chan bool{ch1, ch2, ch3} {
+ select {
+ case shouldSubmit := <-ch:
+ require.True(t, shouldSubmit)
+ default:
+ t.Error("chan was not ready")
+ }
+ }
+
+ // Check that after starting requests are rate limited
+ rl.Start()
+
+ // Check that before starting requests are passed through
+ ch1 = rl.Limit(&types.Block{Profit: new(big.Int)})
+ ch2 = rl.Limit(&types.Block{Profit: new(big.Int)})
+ ch3 = rl.Limit(&types.Block{Profit: big.NewInt(1)})
+
+ time.Sleep(200 * time.Millisecond)
+
+ for _, ch := range []chan bool{ch1, ch2, ch3} {
+ select {
+ case shouldSubmit := <-ch:
+ if ch == ch3 {
+ require.True(t, shouldSubmit)
+ } else {
+ require.False(t, shouldSubmit)
+ }
+ default:
+ t.Error("chan was not ready")
+ }
+ }
+
+ // Check that after stopping requests are passed through
+ rl.Stop()
+
+ ch1 = rl.Limit(&types.Block{Profit: new(big.Int)})
+ ch2 = rl.Limit(&types.Block{Profit: new(big.Int)})
+ ch3 = rl.Limit(&types.Block{Profit: new(big.Int)})
+
+ time.Sleep(200 * time.Millisecond)
+
+ for _, ch := range []chan bool{ch1, ch2, ch3} {
+ select {
+ case shouldSubmit := <-ch:
+ require.True(t, shouldSubmit)
+ default:
+ t.Error("chan was not ready")
+ }
+ }
+
+}
diff --git a/builder/builder.go b/builder/builder.go
index 1aa197d900..b7287ecaac 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -38,14 +38,17 @@ type IRelay interface {
type IBuilder interface {
OnPayloadAttribute(attrs *BuilderPayloadAttributes) error
+ Start() error
+ Stop() error
}
type Builder struct {
- ds IDatabaseService
- beaconClient IBeaconClient
- relay IRelay
- eth IEthereumService
- resubmitter Resubmitter
+ ds IDatabaseService
+ beaconClient IBeaconClient
+ relay IRelay
+ eth IEthereumService
+ resubmitter Resubmitter
+ blockSubmissionRateLimiter *BlockSubmissionRateLimiter
builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
@@ -62,19 +65,30 @@ func NewBuilder(sk *bls.SecretKey, ds IDatabaseService, bc IBeaconClient, relay
pk.FromSlice(pkBytes)
return &Builder{
- ds: ds,
- beaconClient: bc,
- relay: relay,
- eth: eth,
- resubmitter: Resubmitter{},
- builderSecretKey: sk,
- builderPublicKey: pk,
+ ds: ds,
+ beaconClient: bc,
+ relay: relay,
+ eth: eth,
+ resubmitter: Resubmitter{},
+ blockSubmissionRateLimiter: NewBlockSubmissionRateLimiter(),
+ builderSecretKey: sk,
+ builderPublicKey: pk,
builderSigningDomain: builderSigningDomain,
bestBlockProfit: big.NewInt(0),
}
}
+func (b *Builder) Start() error {
+ b.blockSubmissionRateLimiter.Start()
+ return nil
+}
+
+func (b *Builder) Stop() error {
+ b.blockSubmissionRateLimiter.Stop()
+ return nil
+}
+
func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
b.bestMu.Lock()
defer b.bestMu.Unlock()
@@ -116,6 +130,8 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
Value: *value,
}
+ go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)
+
signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
if err != nil {
log.Error("could not sign builder bid", "err", err)
@@ -134,9 +150,9 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
return err
}
- b.bestBlockProfit.Set(block.Profit)
+ log.Info("submitted block", "header", block.Header(), "bid", blockBidMsg)
- go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)
+ b.bestBlockProfit.Set(block.Profit)
return nil
}
@@ -171,6 +187,16 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
}
blockHook := func(block *types.Block, bundles []types.SimulatedBundle) {
+ select {
+ case shouldSubmit := <-b.blockSubmissionRateLimiter.Limit(block):
+ if !shouldSubmit {
+ log.Info("Block rate limited", "blochHash", block.Hash())
+ return
+ }
+ case <-time.After(200 * time.Millisecond):
+ log.Info("Block rate limit timeout, submitting the block anyway")
+ }
+
err := b.onSealedBlock(block, bundles, proposerPubkey, vd.FeeRecipient, attrs)
if err != nil {
log.Error("could not run sealed block hook", "err", err)
@@ -178,6 +204,7 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
}
firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error {
+ log.Info("Resubmitting build job")
return b.eth.BuildBlock(attrs, blockHook)
})
diff --git a/builder/builder_test.go b/builder/builder_test.go
index 8bd98aab7b..2f143bf446 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -75,8 +75,11 @@ func TestOnPayloadAttributes(t *testing.T) {
testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}
builder := NewBuilder(sk, NilDbService{}, &testBeacon, &testRelay, bDomain, testEthService)
+ builder.Start()
+ defer builder.Stop()
- builder.OnPayloadAttribute(testPayloadAttributes)
+ err = builder.OnPayloadAttribute(testPayloadAttributes)
+ require.NoError(t, err)
require.NotNil(t, testRelay.submittedMsg)
expectedProposerPubkey, err := boostTypes.HexToPubkey(testBeacon.validator.Pk.String())
diff --git a/builder/relay.go b/builder/relay.go
index 432c03bfd0..4d9e192c4c 100644
--- a/builder/relay.go
+++ b/builder/relay.go
@@ -150,8 +150,6 @@ func (r *RemoteRelay) SubmitBlock(msg *boostTypes.BuilderSubmitBlockRequest) err
return fmt.Errorf("non-ok response code %d from relay ", code)
}
- log.Info("submitted block", "msg", msg)
-
if r.localRelay != nil {
r.localRelay.SubmitBlock(msg)
}
diff --git a/builder/service.go b/builder/service.go
index 131b8d5640..0d26cd6e48 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -41,11 +41,23 @@ type Service struct {
builder IBuilder
}
-func (s *Service) Start() {
+func (s *Service) Start() error {
if s.srv != nil {
log.Info("Service started")
go s.srv.ListenAndServe()
}
+
+ s.builder.Start()
+
+ return nil
+}
+
+func (s *Service) Stop() error {
+ if s.srv != nil {
+ s.srv.Close()
+ }
+ s.builder.Stop()
+ return nil
}
func (s *Service) PayloadAttributes(payloadAttributes *BuilderPayloadAttributes) error {
@@ -170,7 +182,6 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
builderBackend := NewBuilder(builderSk, ds, beaconClient, relay, builderSigningDomain, ethereumService)
builderService := NewService(cfg.ListenAddr, localRelay, builderBackend)
- builderService.Start()
stack.RegisterAPIs([]rpc.API{
{
@@ -181,5 +192,8 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
Authenticated: true,
},
})
+
+ stack.RegisterLifecycle(builderService)
+
return nil
}
From 39d4fab49f776ec9720f8fc0be1a025513a85447 Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Tue, 13 Sep 2022 18:45:07 +0300
Subject: [PATCH 38/83] fix proposer payout bug with payout tx (#27)
---
miner/worker.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/miner/worker.go b/miner/worker.go
index fee4e1e224..8f3ff96667 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1816,6 +1816,9 @@ func (w *worker) createProposerPayoutTx(env *environment, recipient *common.Addr
nonce := env.state.GetNonce(w.coinbase)
fee := new(big.Int).Mul(big.NewInt(21000), env.header.BaseFee)
amount := new(big.Int).Sub(profit, fee)
+ if amount.Sign() == -1 {
+ return nil, errors.New("negative amount of proposer payout")
+ }
gasPrice := new(big.Int).Set(env.header.BaseFee)
chainId := w.chainConfig.ChainID
log.Debug("createProposerPayoutTx", "sender", sender, "chainId", chainId.String(), "nonce", nonce, "amount", amount.String(), "gas", params.TxGas, "baseFee", env.header.BaseFee.String(), "fee", fee)
From 04d0defdbc6535cdac501050c647eca002bcd22c Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Tue, 13 Sep 2022 18:45:23 +0300
Subject: [PATCH 39/83] add other state access to touch tracer (#26)
---
eth/tracers/logger/account_touch_tracer.go | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/eth/tracers/logger/account_touch_tracer.go b/eth/tracers/logger/account_touch_tracer.go
index 120f449bb0..10d2fb32af 100644
--- a/eth/tracers/logger/account_touch_tracer.go
+++ b/eth/tracers/logger/account_touch_tracer.go
@@ -62,7 +62,14 @@ func (t *AccountTouchTracer) CaptureEnter(_ vm.OpCode, _ common.Address, to comm
func (t *AccountTouchTracer) CaptureExit([]byte, uint64, error) {}
-func (t *AccountTouchTracer) CaptureState(uint64, vm.OpCode, uint64, uint64, *vm.ScopeContext, []byte, int, error) {
+func (t *AccountTouchTracer) CaptureState(_ uint64, op vm.OpCode, _, _ uint64, scope *vm.ScopeContext, _ []byte, _ int, _ error) {
+ stack := scope.Stack
+ stackData := stack.Data()
+ stackLen := len(stackData)
+ if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 {
+ addr := common.Address(stackData[stackLen-1].Bytes20())
+ t.touched[addr] = struct{}{}
+ }
}
func (t *AccountTouchTracer) CaptureFault(uint64, vm.OpCode, uint64, uint64, *vm.ScopeContext, int, error) {
From ae3e497cfe898f3a25331204883b4b18dfefe779 Mon Sep 17 00:00:00 2001
From: Bhakiyaraj Kalimuthu
Date: Tue, 13 Sep 2022 18:25:02 +0200
Subject: [PATCH 40/83] init bundle fetcher (#24)
* high prio and low prio bundle fetcher
---
builder/block_submission_rate_limiter_test.go | 1 -
builder/builder.go | 12 +-
builder/builder_test.go | 3 +-
builder/local_relay_test.go | 3 +-
builder/resubmitter_test.go | 1 -
builder/service.go | 27 ++-
cmd/geth/config.go | 1 -
core/tx_pool.go | 9 +
flashbotsextra/cmd/bundle_fetcher.go | 37 ++++
{builder => flashbotsextra}/database.go | 42 ++++-
{builder => flashbotsextra}/database_test.go | 2 +-
{builder => flashbotsextra}/database_types.go | 12 +-
flashbotsextra/fetcher.go | 164 ++++++++++++++++++
go.sum | 2 +
miner/worker.go | 1 -
15 files changed, 290 insertions(+), 27 deletions(-)
create mode 100644 flashbotsextra/cmd/bundle_fetcher.go
rename {builder => flashbotsextra}/database.go (75%)
rename {builder => flashbotsextra}/database_test.go (99%)
rename {builder => flashbotsextra}/database_types.go (88%)
create mode 100644 flashbotsextra/fetcher.go
diff --git a/builder/block_submission_rate_limiter_test.go b/builder/block_submission_rate_limiter_test.go
index 9a0ce5b55d..4d30e75912 100644
--- a/builder/block_submission_rate_limiter_test.go
+++ b/builder/block_submission_rate_limiter_test.go
@@ -68,5 +68,4 @@ func TestLimit(t *testing.T) {
t.Error("chan was not ready")
}
}
-
}
diff --git a/builder/builder.go b/builder/builder.go
index b7287ecaac..32aa3716c8 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/flashbotsextra"
"github.com/ethereum/go-ethereum/log"
"github.com/flashbots/go-boost-utils/bls"
@@ -43,23 +44,22 @@ type IBuilder interface {
}
type Builder struct {
- ds IDatabaseService
+ ds flashbotsextra.IDatabaseService
beaconClient IBeaconClient
relay IRelay
eth IEthereumService
resubmitter Resubmitter
blockSubmissionRateLimiter *BlockSubmissionRateLimiter
-
- builderSecretKey *bls.SecretKey
- builderPublicKey boostTypes.PublicKey
- builderSigningDomain boostTypes.Domain
+ builderSecretKey *bls.SecretKey
+ builderPublicKey boostTypes.PublicKey
+ builderSigningDomain boostTypes.Domain
bestMu sync.Mutex
bestAttrs BuilderPayloadAttributes
bestBlockProfit *big.Int
}
-func NewBuilder(sk *bls.SecretKey, ds IDatabaseService, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
+func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)
diff --git a/builder/builder_test.go b/builder/builder_test.go
index 2f143bf446..b3a3f815d9 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/flashbotsextra"
"github.com/flashbots/go-boost-utils/bls"
boostTypes "github.com/flashbots/go-boost-utils/types"
"github.com/stretchr/testify/require"
@@ -74,7 +75,7 @@ func TestOnPayloadAttributes(t *testing.T) {
testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}
- builder := NewBuilder(sk, NilDbService{}, &testBeacon, &testRelay, bDomain, testEthService)
+ builder := NewBuilder(sk, flashbotsextra.NilDbService{}, &testBeacon, &testRelay, bDomain, testEthService)
builder.Start()
defer builder.Stop()
diff --git a/builder/local_relay_test.go b/builder/local_relay_test.go
index b101029b48..c30779a3fa 100644
--- a/builder/local_relay_test.go
+++ b/builder/local_relay_test.go
@@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/flashbotsextra"
"github.com/ethereum/go-ethereum/log"
"github.com/flashbots/go-boost-utils/bls"
boostTypes "github.com/flashbots/go-boost-utils/types"
@@ -29,7 +30,7 @@ func newTestBackend(t *testing.T, forkchoiceData *beacon.ExecutableDataV1, block
beaconClient := &testBeaconClient{validator: validator}
localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true)
ethService := &testEthereumService{synced: true, testExecutableData: forkchoiceData, testBlock: block}
- backend := NewBuilder(sk, NilDbService{}, beaconClient, localRelay, bDomain, ethService)
+ backend := NewBuilder(sk, flashbotsextra.NilDbService{}, beaconClient, localRelay, bDomain, ethService)
// service := NewService("127.0.0.1:31545", backend)
return backend, localRelay, validator
diff --git a/builder/resubmitter_test.go b/builder/resubmitter_test.go
index 516e5c7317..e60ff3ef77 100644
--- a/builder/resubmitter_test.go
+++ b/builder/resubmitter_test.go
@@ -9,7 +9,6 @@ import (
)
func TestResubmitter(t *testing.T) {
-
resubmitter := Resubmitter{}
pingCh := make(chan error)
diff --git a/builder/service.go b/builder/service.go
index 0d26cd6e48..f4b99413f3 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -8,7 +8,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/flashbotsextra"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
@@ -170,16 +172,27 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
return errors.New("neither local nor remote relay specified")
}
- ethereumService := NewEthereumService(backend)
-
// TODO: move to proper flags
- var ds IDatabaseService
- ds, err = NewDatabaseService(os.Getenv("FLASHBOTS_POSTGRES_DSN"))
- if err != nil {
- log.Error("could not connect to the DB", "err", err)
- ds = NilDbService{}
+ var ds flashbotsextra.IDatabaseService
+ dbDSN := os.Getenv("FLASHBOTS_POSTGRES_DSN")
+ if dbDSN != "" {
+ ds, err = flashbotsextra.NewDatabaseService(dbDSN)
+ if err != nil {
+ log.Error("could not connect to the DB", "err", err)
+ ds = flashbotsextra.NilDbService{}
+ }
+ } else {
+ log.Info("db dsn is not provided, starting nil db svc")
+ ds = flashbotsextra.NilDbService{}
}
+ // Bundle fetcher
+ mevBundleCh := make(chan []types.MevBundle)
+ blockNumCh := make(chan int64)
+ bundleFetcher := flashbotsextra.NewBundleFetcher(backend, ds, blockNumCh, mevBundleCh, true)
+ go bundleFetcher.Run()
+
+ ethereumService := NewEthereumService(backend)
builderBackend := NewBuilder(builderSk, ds, beaconClient, relay, builderSigningDomain, ethereumService)
builderService := NewService(cfg.ListenAddr, localRelay, builderBackend)
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 7c71fd47c9..9ad20c7398 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -180,7 +180,6 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
BeaconEndpoint: ctx.String(utils.BuilderBeaconEndpoint.Name),
RemoteRelayEndpoint: ctx.String(utils.BuilderRemoteRelayEndpoint.Name),
}
-
backend, eth := utils.RegisterEthService(stack, &cfg.Eth, bpConfig)
// Warn users to migrate if they have a legacy freezer format.
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 4d0008e81d..82509b4890 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -618,6 +618,15 @@ func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]t
return ret, nil
}
+// AddMevBundles adds a mev bundles to the pool
+func (pool *TxPool) AddMevBundles(mevBundles []types.MevBundle) error {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ pool.mevBundles = append(pool.mevBundles, mevBundles...)
+ return nil
+}
+
// AddMevBundle adds a mev bundle to the pool
func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
bundleHasher := sha3.NewLegacyKeccak256()
diff --git a/flashbotsextra/cmd/bundle_fetcher.go b/flashbotsextra/cmd/bundle_fetcher.go
new file mode 100644
index 0000000000..ff6e78be93
--- /dev/null
+++ b/flashbotsextra/cmd/bundle_fetcher.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+ "os"
+ "time"
+
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/flashbotsextra"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+func main() {
+ // Test bundle fetcher
+ log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+ mevBundleCh := make(chan []types.MevBundle)
+ blockNumCh := make(chan int64)
+ db, err := flashbotsextra.NewDatabaseService("postgres://postgres:postgres@localhost:5432/test?sslmode=disable")
+ if err != nil {
+ panic(err)
+ }
+ bundleFetcher := flashbotsextra.NewBundleFetcher(nil, db, blockNumCh, mevBundleCh, false)
+
+ go bundleFetcher.Run()
+ log.Info("waiting for mev bundles")
+ go func() {
+ blockNum := []int64{15232009, 15232008, 15232010}
+ for _, num := range blockNum {
+ <-time.After(time.Second)
+ blockNumCh <- num
+ }
+ }()
+ for bundles := range mevBundleCh {
+ for _, bundle := range bundles {
+ log.Info("bundle info", "blockNum", bundle.BlockNumber, "txsLength", len(bundle.Txs))
+ }
+ }
+}
diff --git a/builder/database.go b/flashbotsextra/database.go
similarity index 75%
rename from builder/database.go
rename to flashbotsextra/database.go
index f4799208e5..cd840b067a 100644
--- a/builder/database.go
+++ b/flashbotsextra/database.go
@@ -1,4 +1,4 @@
-package builder
+package flashbotsextra
import (
"context"
@@ -13,14 +13,24 @@ import (
_ "github.com/lib/pq"
)
+const (
+ highPrioLimitSize = 500
+ lowPrioLimitSize = 100
+)
+
type IDatabaseService interface {
ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace)
+ GetPriorityBundles(ctx context.Context, blockNum int64, isHighPrio bool) ([]DbBundle, error)
}
type NilDbService struct{}
func (NilDbService) ConsumeBuiltBlock(*types.Block, []types.SimulatedBundle, *boostTypes.BidTrace) {}
+func (NilDbService) GetPriorityBundles(ctx context.Context, blockNum int64, isHighPrio bool) ([]DbBundle, error) {
+ return []DbBundle{}, nil
+}
+
type DatabaseService struct {
db *sqlx.DB
@@ -28,6 +38,7 @@ type DatabaseService struct {
insertBlockBuiltBundleNoIdStmt *sqlx.NamedStmt
insertBlockBuiltBundleWithIdStmt *sqlx.NamedStmt
insertMissingBundleStmt *sqlx.NamedStmt
+ fetchPrioBundlesStmt *sqlx.NamedStmt
}
func NewDatabaseService(postgresDSN string) (*DatabaseService, error) {
@@ -56,17 +67,26 @@ func NewDatabaseService(postgresDSN string) (*DatabaseService, error) {
return nil, err
}
+ fetchPrioBundlesStmt, err := db.PrepareNamed("select bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase from bundles where is_high_prio = :is_high_prio and coinbase_diff*1e18/total_gas_used > 1000000000 and param_block_number = :param_block_number order by coinbase_diff/total_gas_used DESC limit :limit")
+ if err != nil {
+ return nil, err
+ }
return &DatabaseService{
db: db,
insertBuiltBlockStmt: insertBuiltBlockStmt,
insertBlockBuiltBundleNoIdStmt: insertBlockBuiltBundleNoIdStmt,
insertBlockBuiltBundleWithIdStmt: insertBlockBuiltBundleWithIdStmt,
insertMissingBundleStmt: insertMissingBundleStmt,
+ fetchPrioBundlesStmt: fetchPrioBundlesStmt,
}, nil
}
func (ds *DatabaseService) ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace) {
tx, err := ds.db.Beginx()
+ if err != nil {
+ log.Error("could not insert built block", "err", err)
+ return
+ }
blockData := BuiltBlock{
BlockNumber: block.NumberU64(),
@@ -134,3 +154,23 @@ func (ds *DatabaseService) ConsumeBuiltBlock(block *types.Block, bundles []types
log.Error("could not commit DB trasnaction", "err", err)
}
}
+func (ds *DatabaseService) GetPriorityBundles(ctx context.Context, blockNum int64, isHighPrio bool) ([]DbBundle, error) {
+ var bundles []DbBundle
+ tx, err := ds.db.Beginx()
+ if err != nil {
+ log.Error("failed to begin db tx for get priority bundles", "err", err)
+ return nil, err
+ }
+ arg := map[string]interface{}{"param_block_number": uint64(blockNum), "is_high_prio": isHighPrio, "limit": lowPrioLimitSize}
+ if isHighPrio {
+ arg["limit"] = highPrioLimitSize
+ }
+ if err = tx.NamedStmtContext(ctx, ds.fetchPrioBundlesStmt).SelectContext(ctx, &bundles, arg); err != nil {
+ return nil, err
+ }
+ err = tx.Commit()
+ if err != nil {
+ log.Error("could not commit GetPriorityBundles transaction", "err", err)
+ }
+ return bundles, nil
+}
diff --git a/builder/database_test.go b/flashbotsextra/database_test.go
similarity index 99%
rename from builder/database_test.go
rename to flashbotsextra/database_test.go
index 2ff2c7bb8a..1d3a7cce35 100644
--- a/builder/database_test.go
+++ b/flashbotsextra/database_test.go
@@ -1,4 +1,4 @@
-package builder
+package flashbotsextra
import (
"math/big"
diff --git a/builder/database_types.go b/flashbotsextra/database_types.go
similarity index 88%
rename from builder/database_types.go
rename to flashbotsextra/database_types.go
index 96965616b0..58d28c8cf5 100644
--- a/builder/database_types.go
+++ b/flashbotsextra/database_types.go
@@ -1,4 +1,4 @@
-package builder
+package flashbotsextra
import (
"math/big"
@@ -38,9 +38,9 @@ type DbBundle struct {
ParamSignedTxs string `db:"param_signed_txs"`
ParamBlockNumber uint64 `db:"param_block_number"`
- ParamTimestamp uint64 `db:"param_timestamp"`
+ ParamTimestamp *uint64 `db:"param_timestamp"`
ReceivedTimestamp time.Time `db:"received_timestamp"`
- ParamRevertingTxHashes string `db:"param_reverting_tx_hashes"`
+ ParamRevertingTxHashes *string `db:"param_reverting_tx_hashes"`
CoinbaseDiff string `db:"coinbase_diff"`
TotalGasUsed uint64 `db:"total_gas_used"`
@@ -54,7 +54,7 @@ func SimulatedBundleToDbBundle(bundle *types.SimulatedBundle) DbBundle {
for i, rTxHash := range bundle.OriginalBundle.RevertingTxHashes {
revertingTxHashes[i] = rTxHash.String()
}
-
+ paramRevertingTxHashes := strings.Join(revertingTxHashes, ",")
signedTxsStrings := make([]string, len(bundle.OriginalBundle.Txs))
for i, tx := range bundle.OriginalBundle.Txs {
signedTxsStrings[i] = tx.Hash().String()
@@ -65,8 +65,8 @@ func SimulatedBundleToDbBundle(bundle *types.SimulatedBundle) DbBundle {
ParamSignedTxs: strings.Join(signedTxsStrings, ","),
ParamBlockNumber: bundle.OriginalBundle.BlockNumber.Uint64(),
- ParamTimestamp: bundle.OriginalBundle.MinTimestamp,
- ParamRevertingTxHashes: strings.Join(revertingTxHashes, ","),
+ ParamTimestamp: &bundle.OriginalBundle.MinTimestamp,
+ ParamRevertingTxHashes: ¶mRevertingTxHashes,
CoinbaseDiff: new(big.Rat).SetFrac(bundle.TotalEth, big.NewInt(1e18)).FloatString(18),
TotalGasUsed: bundle.TotalGasUsed,
diff --git a/flashbotsextra/fetcher.go b/flashbotsextra/fetcher.go
new file mode 100644
index 0000000000..14484e6917
--- /dev/null
+++ b/flashbotsextra/fetcher.go
@@ -0,0 +1,164 @@
+package flashbotsextra
+
+import (
+ "context"
+ "errors"
+ "math/big"
+ "strings"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/log"
+ "golang.org/x/crypto/sha3"
+)
+
+type Fetcher interface {
+ Run() error
+}
+
+type bundleFetcher struct {
+ db IDatabaseService
+ backend *eth.Ethereum
+ blockNumCh chan int64
+ bundlesCh chan []types.MevBundle
+ shouldPushToTxPool bool // Added for testing
+}
+
+func NewBundleFetcher(backend *eth.Ethereum, db IDatabaseService, blockNumCh chan int64, bundlesCh chan []types.MevBundle, shouldPushToTxPool bool) *bundleFetcher {
+ return &bundleFetcher{
+ db: db,
+ backend: backend,
+ blockNumCh: blockNumCh,
+ bundlesCh: bundlesCh,
+ shouldPushToTxPool: shouldPushToTxPool,
+ }
+}
+
+func (b *bundleFetcher) Run() {
+ log.Info("Start bundle fetcher")
+ if b.shouldPushToTxPool {
+ eventCh := make(chan core.ChainHeadEvent)
+ b.backend.BlockChain().SubscribeChainHeadEvent(eventCh)
+ pushBlockNum := func() {
+ for blockNum := range eventCh {
+ b.blockNumCh <- blockNum.Block.Header().Number.Int64()
+ }
+ }
+ addMevBundle := func() {
+ log.Info("Start receiving mev bundles")
+ for bundles := range b.bundlesCh {
+ b.backend.TxPool().AddMevBundles(bundles)
+ }
+ }
+ go pushBlockNum()
+ go addMevBundle()
+ }
+ pushMevBundles := func(bundles []DbBundle) {
+ mevBundles := make([]types.MevBundle, 0)
+ for _, bundle := range bundles {
+ mevBundle, err := b.dbBundleToMevBundle(bundle)
+ if err != nil {
+ log.Error("failed to convert db bundle to mev bundle", "err", err)
+ continue
+ }
+ mevBundles = append(mevBundles, *mevBundle)
+ }
+ if len(mevBundles) > 0 {
+ b.bundlesCh <- mevBundles
+ }
+ }
+ go b.fetchAndPush(context.Background(), pushMevBundles)
+}
+
+func (b *bundleFetcher) fetchAndPush(ctx context.Context, pushMevBundles func(bundles []DbBundle)) {
+ var blockNum int64
+ lowPrioBundleTicker := time.NewTicker(time.Second * 2)
+ defer lowPrioBundleTicker.Stop()
+
+ for {
+ select {
+ case blockNum = <-b.blockNumCh:
+ ctxH, cancelH := context.WithTimeout(ctx, time.Second*3)
+ bundles, err := b.db.GetPriorityBundles(ctxH, blockNum, true)
+ cancelH()
+ if err != nil {
+ log.Error("failed to fetch high prio bundles", "err", err)
+ continue
+ }
+ log.Info("Fetching High prio bundles", "size", len(bundles), "blockNum", blockNum)
+ if len(bundles) != 0 {
+ pushMevBundles(bundles)
+ }
+
+ case <-lowPrioBundleTicker.C:
+ ctxL, cancelL := context.WithTimeout(ctx, time.Second*3)
+ bundles, err := b.db.GetPriorityBundles(ctxL, blockNum, false)
+ cancelL()
+ if err != nil {
+ log.Error("failed to fetch low prio bundles", "err", err)
+ continue
+ }
+ log.Info("Fetching low prio bundles", "len", len(bundles), "blockNum", blockNum)
+ if len(bundles) != 0 {
+ pushMevBundles(bundles)
+ }
+ case <-ctx.Done():
+ close(b.bundlesCh)
+ return
+ }
+ }
+}
+
+func (b *bundleFetcher) dbBundleToMevBundle(arg DbBundle) (*types.MevBundle, error) {
+ signedTxsStr := strings.Split(arg.ParamSignedTxs, ",")
+ if len(signedTxsStr) == 0 {
+ return nil, errors.New("bundle missing txs")
+ }
+ if arg.ParamBlockNumber == 0 {
+ return nil, errors.New("bundle missing blockNumber")
+ }
+
+ var txs types.Transactions
+ for _, txStr := range signedTxsStr {
+ decodedTx, err := hexutil.Decode(txStr)
+ if err != nil {
+ log.Error("could not decode bundle tx", "id", arg.DbId, "err", err)
+ continue
+ }
+ tx := new(types.Transaction)
+ if err := tx.UnmarshalBinary(decodedTx); err != nil {
+ log.Error("could not unmarshal bundle decoded tx", "id", arg.DbId, "err", err)
+ continue
+ }
+ txs = append(txs, tx)
+ }
+ var paramRevertingTxHashes []string
+ if arg.ParamRevertingTxHashes != nil {
+ paramRevertingTxHashes = strings.Split(*arg.ParamRevertingTxHashes, ",")
+ }
+ revertingTxHashesStrings := paramRevertingTxHashes
+ revertingTxHashes := make([]common.Hash, len(revertingTxHashesStrings))
+ for _, rTxHashStr := range revertingTxHashesStrings {
+ revertingTxHashes = append(revertingTxHashes, common.HexToHash(rTxHashStr))
+ }
+ var minTimestamp uint64
+ if arg.ParamTimestamp != nil {
+ minTimestamp = *arg.ParamTimestamp
+ }
+ bundleHasher := sha3.NewLegacyKeccak256()
+ for _, tx := range txs {
+ bundleHasher.Write(tx.Hash().Bytes())
+ }
+ bundleHash := common.BytesToHash(bundleHasher.Sum(nil))
+ return &types.MevBundle{
+ Txs: txs,
+ BlockNumber: new(big.Int).SetUint64(arg.ParamBlockNumber),
+ MinTimestamp: minTimestamp,
+ RevertingTxHashes: revertingTxHashes,
+ Hash: bundleHash,
+ }, nil
+}
diff --git a/go.sum b/go.sum
index 2bd0339c78..c6e0fcfdf7 100644
--- a/go.sum
+++ b/go.sum
@@ -159,6 +159,7 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -321,6 +322,7 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
diff --git a/miner/worker.go b/miner/worker.go
index 8f3ff96667..399b1459d8 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1389,7 +1389,6 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
log.Warn("Proposer payout create tx failed due to not enough balance", "profit", profit.String())
return errors.New("proposer payout create tx failed due to not enough balance"), nil
}
-
}
return nil, blockBundles
From 47140e7cc437acc730f6bf1c769238c255310375 Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Thu, 15 Sep 2022 11:32:20 +0300
Subject: [PATCH 41/83] increase payment tx gas limit
---
miner/worker.go | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/miner/worker.go b/miner/worker.go
index 399b1459d8..4a7c1eecf5 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -82,6 +82,8 @@ const (
// staleThreshold is the maximum depth of the acceptable stale block.
staleThreshold = 7
+
+ paymentTxGas = 25000
)
var (
@@ -1319,7 +1321,7 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
var builderCoinbaseBalanceBefore *big.Int
if validatorCoinbase != nil {
builderCoinbaseBalanceBefore = env.state.GetBalance(w.coinbase)
- if err := env.gasPool.SubGas(params.TxGas); err != nil {
+ if err := env.gasPool.SubGas(paymentTxGas); err != nil {
return err, nil
}
}
@@ -1366,7 +1368,7 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
log.Info("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
profit := new(big.Int).Sub(builderCoinbaseBalanceAfter, builderCoinbaseBalanceBefore)
- env.gasPool.AddGas(params.TxGas)
+ env.gasPool.AddGas(paymentTxGas)
if profit.Sign() == 1 {
tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit)
if err != nil {
@@ -1813,14 +1815,14 @@ func (w *worker) createProposerPayoutTx(env *environment, recipient *common.Addr
sender := w.coinbase.String()
log.Info(sender)
nonce := env.state.GetNonce(w.coinbase)
- fee := new(big.Int).Mul(big.NewInt(21000), env.header.BaseFee)
+ fee := new(big.Int).Mul(big.NewInt(paymentTxGas), env.header.BaseFee)
amount := new(big.Int).Sub(profit, fee)
if amount.Sign() == -1 {
return nil, errors.New("negative amount of proposer payout")
}
gasPrice := new(big.Int).Set(env.header.BaseFee)
chainId := w.chainConfig.ChainID
- log.Debug("createProposerPayoutTx", "sender", sender, "chainId", chainId.String(), "nonce", nonce, "amount", amount.String(), "gas", params.TxGas, "baseFee", env.header.BaseFee.String(), "fee", fee)
- tx := types.NewTransaction(nonce, *recipient, amount, params.TxGas, gasPrice, nil)
+ log.Debug("createProposerPayoutTx", "sender", sender, "chainId", chainId.String(), "nonce", nonce, "amount", amount.String(), "baseFee", env.header.BaseFee.String(), "fee", fee)
+ tx := types.NewTransaction(nonce, *recipient, amount, 25000, gasPrice, nil)
return types.SignTx(tx, types.LatestSignerForChainID(chainId), w.config.BuilderTxSigningKey)
}
From 78b79f64a80766d6e9fe73ec22ff571a8e0edf7a Mon Sep 17 00:00:00 2001
From: Bhakiyaraj Kalimuthu
Date: Mon, 19 Sep 2022 13:31:54 +0200
Subject: [PATCH 42/83] do not fetch bundle if empty block num during the
starup (#29)
* do not fetch bundle if empty block num during the startup
---
flashbotsextra/fetcher.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/flashbotsextra/fetcher.go b/flashbotsextra/fetcher.go
index 14484e6917..05e01c4a85 100644
--- a/flashbotsextra/fetcher.go
+++ b/flashbotsextra/fetcher.go
@@ -95,6 +95,9 @@ func (b *bundleFetcher) fetchAndPush(ctx context.Context, pushMevBundles func(bu
}
case <-lowPrioBundleTicker.C:
+ if blockNum == 0 {
+ continue
+ }
ctxL, cancelL := context.WithTimeout(ctx, time.Second*3)
bundles, err := b.db.GetPriorityBundles(ctxL, blockNum, false)
cancelL()
From 4e0811973b92a5adc61f507d75fb4ae6371fa53f Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Tue, 20 Sep 2022 14:08:14 +0200
Subject: [PATCH 43/83] Update gas limit in api check (#30)
---
eth/block-validation/api_test.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go
index 98f8646c02..bd7c306d70 100644
--- a/eth/block-validation/api_test.go
+++ b/eth/block-validation/api_test.go
@@ -101,7 +101,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
}
blockRequest.Message.Value = boostTypes.IntToU256(190526394825529)
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "inaccurate payment")
- blockRequest.Message.Value = boostTypes.IntToU256(190526394825530)
+ blockRequest.Message.Value = boostTypes.IntToU256(190277920613530)
require.NoError(t, api.ValidateBuilderSubmissionV1(blockRequest))
// TODO: test with contract calling blacklisted address
@@ -142,7 +142,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
invalidPayload.LogsBloom = boostTypes.Bloom{}
copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
blockRequest.ExecutionPayload = invalidPayload
- copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x48c4ee43556e149344878f5d1fee26b0b8d569e1697a4d9cce74034c6c45fac1")[:32])
+ copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0xbcefd7caec3624eb917a0a7055d6a6d5c2aeb71111adef5cc733577f7e6dd985")[:32])
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value")
}
From f4fad7d9a10999cb5914e1879497baab7a48f3f8 Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Wed, 21 Sep 2022 18:52:45 +0300
Subject: [PATCH 44/83] set proposer payment to 26k (#31)
---
eth/block-validation/api_test.go | 4 ++--
miner/worker.go | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go
index bd7c306d70..f7945399d6 100644
--- a/eth/block-validation/api_test.go
+++ b/eth/block-validation/api_test.go
@@ -101,7 +101,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
}
blockRequest.Message.Value = boostTypes.IntToU256(190526394825529)
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "inaccurate payment")
- blockRequest.Message.Value = boostTypes.IntToU256(190277920613530)
+ blockRequest.Message.Value = boostTypes.IntToU256(190215802060530)
require.NoError(t, api.ValidateBuilderSubmissionV1(blockRequest))
// TODO: test with contract calling blacklisted address
@@ -142,7 +142,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
invalidPayload.LogsBloom = boostTypes.Bloom{}
copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
blockRequest.ExecutionPayload = invalidPayload
- copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0xbcefd7caec3624eb917a0a7055d6a6d5c2aeb71111adef5cc733577f7e6dd985")[:32])
+ copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x65cded68b85277f489f22497731d8cece9e42f0429a250a5022a9417408f3998")[:32])
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value")
}
diff --git a/miner/worker.go b/miner/worker.go
index 4a7c1eecf5..2f3e1772e5 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -83,7 +83,7 @@ const (
// staleThreshold is the maximum depth of the acceptable stale block.
staleThreshold = 7
- paymentTxGas = 25000
+ paymentTxGas = 26000
)
var (
@@ -1823,6 +1823,6 @@ func (w *worker) createProposerPayoutTx(env *environment, recipient *common.Addr
gasPrice := new(big.Int).Set(env.header.BaseFee)
chainId := w.chainConfig.ChainID
log.Debug("createProposerPayoutTx", "sender", sender, "chainId", chainId.String(), "nonce", nonce, "amount", amount.String(), "baseFee", env.header.BaseFee.String(), "fee", fee)
- tx := types.NewTransaction(nonce, *recipient, amount, 25000, gasPrice, nil)
+ tx := types.NewTransaction(nonce, *recipient, amount, paymentTxGas, gasPrice, nil)
return types.SignTx(tx, types.LatestSignerForChainID(chainId), w.config.BuilderTxSigningKey)
}
From 2b1d810103226a9d96b934a61378960fdee656c6 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Wed, 21 Sep 2022 18:32:01 +0200
Subject: [PATCH 45/83] Adjust block number for bundle fetching (#32)
---
flashbotsextra/fetcher.go | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/flashbotsextra/fetcher.go b/flashbotsextra/fetcher.go
index 05e01c4a85..956698bc15 100644
--- a/flashbotsextra/fetcher.go
+++ b/flashbotsextra/fetcher.go
@@ -44,8 +44,8 @@ func (b *bundleFetcher) Run() {
eventCh := make(chan core.ChainHeadEvent)
b.backend.BlockChain().SubscribeChainHeadEvent(eventCh)
pushBlockNum := func() {
- for blockNum := range eventCh {
- b.blockNumCh <- blockNum.Block.Header().Number.Int64()
+ for currentBlockNum := range eventCh {
+ b.blockNumCh <- currentBlockNum.Block.Header().Number.Int64()
}
}
addMevBundle := func() {
@@ -75,37 +75,37 @@ func (b *bundleFetcher) Run() {
}
func (b *bundleFetcher) fetchAndPush(ctx context.Context, pushMevBundles func(bundles []DbBundle)) {
- var blockNum int64
+ var currentBlockNum int64
lowPrioBundleTicker := time.NewTicker(time.Second * 2)
defer lowPrioBundleTicker.Stop()
for {
select {
- case blockNum = <-b.blockNumCh:
+ case currentBlockNum = <-b.blockNumCh:
ctxH, cancelH := context.WithTimeout(ctx, time.Second*3)
- bundles, err := b.db.GetPriorityBundles(ctxH, blockNum, true)
+ bundles, err := b.db.GetPriorityBundles(ctxH, currentBlockNum+1, true)
cancelH()
if err != nil {
log.Error("failed to fetch high prio bundles", "err", err)
continue
}
- log.Info("Fetching High prio bundles", "size", len(bundles), "blockNum", blockNum)
+ log.Info("Fetching High prio bundles", "size", len(bundles), "currentlyBuiltBlockNum", currentBlockNum+1)
if len(bundles) != 0 {
pushMevBundles(bundles)
}
case <-lowPrioBundleTicker.C:
- if blockNum == 0 {
+ if currentBlockNum == 0 {
continue
}
ctxL, cancelL := context.WithTimeout(ctx, time.Second*3)
- bundles, err := b.db.GetPriorityBundles(ctxL, blockNum, false)
+ bundles, err := b.db.GetPriorityBundles(ctxL, currentBlockNum+1, false)
cancelL()
if err != nil {
log.Error("failed to fetch low prio bundles", "err", err)
continue
}
- log.Info("Fetching low prio bundles", "len", len(bundles), "blockNum", blockNum)
+ log.Info("Fetching low prio bundles", "len", len(bundles), "currentlyBuiltBlockNum", currentBlockNum+1)
if len(bundles) != 0 {
pushMevBundles(bundles)
}
From 97fa9c1431ea2a59e9b15f5fb57c8ca6fef94494 Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Thu, 8 Sep 2022 10:43:07 +0300
Subject: [PATCH 46/83] new greedy builder
---
builder/builder.go | 4 +-
core/types/transaction.go | 61 +++--
core/types/transaction_test.go | 8 +-
miner/algo_common.go | 354 +++++++++++++++++++++++++++++
miner/algo_common_test.go | 402 +++++++++++++++++++++++++++++++++
miner/algo_greedy.go | 89 ++++++++
miner/algo_greedy_test.go | 69 ++++++
miner/multi_worker.go | 17 +-
miner/worker.go | 160 ++++++++++---
9 files changed, 1109 insertions(+), 55 deletions(-)
create mode 100644 miner/algo_common.go
create mode 100644 miner/algo_common_test.go
create mode 100644 miner/algo_greedy.go
create mode 100644 miner/algo_greedy_test.go
diff --git a/builder/builder.go b/builder/builder.go
index 32aa3716c8..5824823169 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -146,8 +146,10 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
err = b.relay.SubmitBlock(&blockSubmitReq)
if err != nil {
- log.Error("could not submit block", "err", err)
+ log.Error("could not submit block", "err", err, "bundles", len(bundles))
return err
+ } else {
+ log.Info("could submit block", "bundles", len(bundles))
}
log.Info("submitted block", "header", block.Header(), "bid", blockBidMsg)
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 1a31046db8..abd3c61cc6 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -461,7 +461,8 @@ func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// TxWithMinerFee wraps a transaction with its gas price or effective miner gasTipCap
type TxWithMinerFee struct {
- tx *Transaction
+ Tx *Transaction
+ Bundle *SimulatedBundle
minerFee *big.Int
}
@@ -474,7 +475,16 @@ func NewTxWithMinerFee(tx *Transaction, baseFee *big.Int) (*TxWithMinerFee, erro
return nil, err
}
return &TxWithMinerFee{
- tx: tx,
+ Tx: tx,
+ minerFee: minerFee,
+ }, nil
+}
+
+// NewBundleWithMinerFee creates a wrapped bundle.
+func NewBundleWithMinerFee(bundle *SimulatedBundle, baseFee *big.Int) (*TxWithMinerFee, error) {
+ minerFee := bundle.MevGasPrice
+ return &TxWithMinerFee{
+ Bundle: bundle,
minerFee: minerFee,
}, nil
}
@@ -489,7 +499,14 @@ func (s TxByPriceAndTime) Less(i, j int) bool {
// deterministic sorting
cmp := s[i].minerFee.Cmp(s[j].minerFee)
if cmp == 0 {
- return s[i].tx.time.Before(s[j].tx.time)
+ if s[i].Tx != nil && s[j].Tx != nil {
+ return s[i].Tx.time.Before(s[j].Tx.time)
+ } else if s[i].Bundle != nil && s[j].Bundle != nil {
+ return s[i].Bundle.TotalGasUsed <= s[j].Bundle.TotalGasUsed
+ } else if s[i].Bundle != nil {
+ return false
+ }
+ return true
}
return cmp > 0
}
@@ -522,9 +539,16 @@ type TransactionsByPriceAndNonce struct {
//
// Note, the input map is reowned so the caller should not interact any more with
// if after providing it to the constructor.
-func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions, baseFee *big.Int) *TransactionsByPriceAndNonce {
+func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions, bundles []SimulatedBundle, baseFee *big.Int) *TransactionsByPriceAndNonce {
// Initialize a price and received time based heap with the head transactions
- heads := make(TxByPriceAndTime, 0, len(txs))
+ heads := make(TxByPriceAndTime, 0, len(txs)+len(bundles))
+ for i := range bundles {
+ wrapped, err := NewBundleWithMinerFee(&bundles[i], baseFee)
+ if err != nil {
+ continue
+ }
+ heads = append(heads, wrapped)
+ }
for from, accTxs := range txs {
acc, _ := Sender(signer, accTxs[0])
wrapped, err := NewTxWithMinerFee(accTxs[0], baseFee)
@@ -548,21 +572,23 @@ func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transa
}
// Peek returns the next transaction by price.
-func (t *TransactionsByPriceAndNonce) Peek() *Transaction {
+func (t *TransactionsByPriceAndNonce) Peek() *TxWithMinerFee {
if len(t.heads) == 0 {
return nil
}
- return t.heads[0].tx
+ return t.heads[0]
}
// Shift replaces the current best head with the next one from the same account.
func (t *TransactionsByPriceAndNonce) Shift() {
- acc, _ := Sender(t.signer, t.heads[0].tx)
- if txs, ok := t.txs[acc]; ok && len(txs) > 0 {
- if wrapped, err := NewTxWithMinerFee(txs[0], t.baseFee); err == nil {
- t.heads[0], t.txs[acc] = wrapped, txs[1:]
- heap.Fix(&t.heads, 0)
- return
+ if t.heads[0].Tx != nil {
+ acc, _ := Sender(t.signer, t.heads[0].Tx)
+ if txs, ok := t.txs[acc]; ok && len(txs) > 0 {
+ if wrapped, err := NewTxWithMinerFee(txs[0], t.baseFee); err == nil {
+ t.heads[0], t.txs[acc] = wrapped, txs[1:]
+ heap.Fix(&t.heads, 0)
+ return
+ }
}
}
heap.Pop(&t.heads)
@@ -661,6 +687,15 @@ type MevBundle struct {
Hash common.Hash
}
+func (b *MevBundle) RevertingHash(hash common.Hash) bool {
+ for _, revHash := range b.RevertingTxHashes {
+ if revHash == hash {
+ return true
+ }
+ }
+ return false
+}
+
type SimulatedBundle struct {
MevGasPrice *big.Int
TotalEth *big.Int
diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go
index 67e5b3cce3..47a6bc638e 100644
--- a/core/types/transaction_test.go
+++ b/core/types/transaction_test.go
@@ -320,11 +320,11 @@ func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) {
expectedCount += count
}
// Sort the transactions and cross check the nonce ordering
- txset := NewTransactionsByPriceAndNonce(signer, groups, baseFee)
+ txset := NewTransactionsByPriceAndNonce(signer, groups, nil, baseFee)
txs := Transactions{}
for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
- txs = append(txs, tx)
+ txs = append(txs, tx.Tx)
txset.Shift()
}
if len(txs) != expectedCount {
@@ -377,11 +377,11 @@ func TestTransactionTimeSort(t *testing.T) {
groups[addr] = append(groups[addr], tx)
}
// Sort the transactions and cross check the nonce ordering
- txset := NewTransactionsByPriceAndNonce(signer, groups, nil)
+ txset := NewTransactionsByPriceAndNonce(signer, groups, nil, nil)
txs := Transactions{}
for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
- txs = append(txs, tx)
+ txs = append(txs, tx.Tx)
txset.Shift()
}
if len(txs) != len(keys) {
diff --git a/miner/algo_common.go b/miner/algo_common.go
new file mode 100644
index 0000000000..a1314a3994
--- /dev/null
+++ b/miner/algo_common.go
@@ -0,0 +1,354 @@
+package miner
+
+import (
+ "errors"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/tracers/logger"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/params"
+ "math/big"
+ "sync/atomic"
+)
+
+const (
+ shiftTx = 1
+ popTx = 2
+)
+
+var errInterrupt = errors.New("miner worker interrupted")
+
+type environmentDiff struct {
+ baseEnvironment *environment
+ header *types.Header
+ gasPool *core.GasPool // available gas used to pack transactions
+ state *state.StateDB // apply state changes here
+ newProfit *big.Int
+ newTxs []*types.Transaction
+ newReceipts []*types.Receipt
+}
+
+func newEnvironmentDiff(env *environment) *environmentDiff {
+ gasPool := new(core.GasPool).AddGas(env.gasPool.Gas())
+ return &environmentDiff{
+ baseEnvironment: env,
+ header: types.CopyHeader(env.header),
+ gasPool: gasPool,
+ state: env.state.Copy(),
+ newProfit: new(big.Int),
+ }
+}
+
+func (e *environmentDiff) copy() *environmentDiff {
+ gasPool := new(core.GasPool).AddGas(e.gasPool.Gas())
+
+ return &environmentDiff{
+ baseEnvironment: e.baseEnvironment,
+ header: types.CopyHeader(e.header),
+ gasPool: gasPool,
+ state: e.state.Copy(),
+ newProfit: new(big.Int).Set(e.newProfit),
+ newTxs: e.newTxs[:],
+ newReceipts: e.newReceipts[:],
+ }
+}
+
+func (e *environmentDiff) applyToBaseEnv() {
+ env := e.baseEnvironment
+ env.gasPool = new(core.GasPool).AddGas(e.gasPool.Gas())
+ env.header = e.header
+ env.state = e.state
+ env.profit.Add(env.profit, e.newProfit)
+ env.tcount += len(e.newTxs)
+ env.txs = append(env.txs, e.newTxs...)
+ env.receipts = append(env.receipts, e.newReceipts...)
+}
+
+func checkInterrupt(i *int32) bool {
+ return i != nil && atomic.LoadInt32(i) != commitInterruptNone
+}
+
+// Simulate bundle on top of current state without modifying it
+// pending txs used to track if bundle tx is part of the mempool
+func simulateBundle(env *environment, bundle types.MevBundle, chData chainData, interrupt *int32) (types.SimulatedBundle, error) {
+ stateDB := env.state.Copy()
+ gasPool := new(core.GasPool).AddGas(env.header.GasLimit)
+
+ var totalGasUsed uint64
+ gasFees := big.NewInt(0)
+ ethSentToCoinbase := big.NewInt(0)
+
+ for i, tx := range bundle.Txs {
+ if checkInterrupt(interrupt) {
+ return types.SimulatedBundle{}, errInterrupt
+ }
+
+ if env.header.BaseFee != nil && tx.Type() == 2 {
+ // Sanity check for extremely large numbers
+ if tx.GasFeeCap().BitLen() > 256 {
+ return types.SimulatedBundle{}, core.ErrFeeCapVeryHigh
+ }
+ if tx.GasTipCap().BitLen() > 256 {
+ return types.SimulatedBundle{}, core.ErrTipVeryHigh
+ }
+ // Ensure gasFeeCap is greater than or equal to gasTipCap.
+ if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
+ return types.SimulatedBundle{}, core.ErrTipAboveFeeCap
+ }
+ }
+
+ stateDB.Prepare(tx.Hash(), i+env.tcount)
+ coinbaseBalanceBefore := stateDB.GetBalance(env.coinbase)
+
+ var tempGasUsed uint64
+ receipt, err := core.ApplyTransaction(chData.chainConfig, chData.chain, &env.coinbase, gasPool, stateDB, env.header, tx, &tempGasUsed, *chData.chain.GetVMConfig())
+ if err != nil {
+ return types.SimulatedBundle{}, err
+ }
+ if receipt.Status == types.ReceiptStatusFailed && !containsHash(bundle.RevertingTxHashes, receipt.TxHash) {
+ return types.SimulatedBundle{}, errors.New("failed tx")
+ }
+
+ totalGasUsed += receipt.GasUsed
+
+ _, err = types.Sender(env.signer, tx)
+ if err != nil {
+ return types.SimulatedBundle{}, err
+ }
+
+ // see NOTE below
+ //txInPendingPool := false
+ //if accountTxs, ok := pendingTxs[from]; ok {
+ // // check if tx is in pending pool
+ // txNonce := tx.Nonce()
+ //
+ // for _, accountTx := range accountTxs {
+ // if accountTx.Nonce() == txNonce {
+ // txInPendingPool = true
+ // break
+ // }
+ // }
+ //}
+
+ gasUsed := new(big.Int).SetUint64(receipt.GasUsed)
+ gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee)
+ if err != nil {
+ return types.SimulatedBundle{}, err
+ }
+ gasFeesTx := gasUsed.Mul(gasUsed, gasPrice)
+ coinbaseBalanceAfter := stateDB.GetBalance(env.coinbase)
+ coinbaseDelta := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
+ coinbaseDelta.Sub(coinbaseDelta, gasFeesTx)
+ ethSentToCoinbase.Add(ethSentToCoinbase, coinbaseDelta)
+
+ // NOTE - it differs from prod!, if changed - change in commit bundle too
+ //if !txInPendingPool {
+ // // If tx is not in pending pool, count the gas fees
+ // gasFees.Add(gasFees, gasFeesTx)
+ //}
+ gasFees.Add(gasFees, gasFeesTx)
+ }
+
+ totalEth := new(big.Int).Add(ethSentToCoinbase, gasFees)
+
+ return types.SimulatedBundle{
+ MevGasPrice: new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)),
+ TotalEth: totalEth,
+ EthSentToCoinbase: ethSentToCoinbase,
+ TotalGasUsed: totalGasUsed,
+ OriginalBundle: bundle,
+ }, nil
+}
+
+func applyTransactionWithBlacklist(signer types.Signer, config *params.ChainConfig, bc core.ChainContext, author *common.Address, gp *core.GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, blacklist map[common.Address]struct{}) (*types.Receipt, *state.StateDB, error) {
+ // short circuit if blacklist is empty
+ if len(blacklist) == 0 {
+ snap := statedb.Snapshot()
+ receipt, err := core.ApplyTransaction(config, bc, author, gp, statedb, header, tx, usedGas, cfg)
+ if err != nil {
+ statedb.RevertToSnapshot(snap)
+ }
+ return receipt, statedb, err
+ }
+
+ sender, err := signer.Sender(tx)
+ if err != nil {
+ return nil, statedb, err
+ }
+
+ if _, in := blacklist[sender]; in {
+ return nil, statedb, errors.New("blacklist violation, tx.sender")
+ }
+
+ if to := tx.To(); to != nil {
+ if _, in := blacklist[*to]; in {
+ return nil, statedb, errors.New("blacklist violation, tx.to")
+ }
+ }
+
+ touchTracer := logger.NewAccountTouchTracer()
+ cfg.Tracer = touchTracer
+ cfg.Debug = true
+
+ usedGasTmp := *usedGas
+ gasPoolTmp := new(core.GasPool).AddGas(gp.Gas())
+ stateCopy := statedb.Copy()
+ snap := stateCopy.Snapshot()
+
+ stateCopy.Prepare(tx.Hash(), statedb.TxIndex())
+ receipt, err := core.ApplyTransaction(config, bc, author, gasPoolTmp, stateCopy, header, tx, &usedGasTmp, cfg)
+ if err != nil {
+ stateCopy.RevertToSnapshot(snap)
+ *usedGas = usedGasTmp
+ *gp = *gasPoolTmp
+ return receipt, stateCopy, err
+ }
+
+ for _, address := range touchTracer.TouchedAddresses() {
+ if _, in := blacklist[address]; in {
+ return nil, statedb, errors.New("blacklist violation, tx trace")
+ }
+ }
+
+ *usedGas = usedGasTmp
+ *gp = *gasPoolTmp
+ return receipt, stateCopy, nil
+}
+
+// commit tx to envDiff
+func (envDiff *environmentDiff) commitTx(tx *types.Transaction, chData chainData) (*types.Receipt, int, error) {
+ header := envDiff.header
+ coinbase := &envDiff.baseEnvironment.coinbase
+ signer := envDiff.baseEnvironment.signer
+
+ gasPrice, err := tx.EffectiveGasTip(header.BaseFee)
+ if err != nil {
+ return nil, shiftTx, err
+ }
+
+ envDiff.state.Prepare(tx.Hash(), envDiff.baseEnvironment.tcount+len(envDiff.newTxs))
+
+ receipt, newState, err := applyTransactionWithBlacklist(signer, chData.chainConfig, chData.chain, coinbase,
+ envDiff.gasPool, envDiff.state, header, tx, &header.GasUsed, *chData.chain.GetVMConfig(), chData.blacklist)
+ envDiff.state = newState
+ if err != nil {
+ switch {
+ case errors.Is(err, core.ErrGasLimitReached):
+ // Pop the current out-of-gas transaction without shifting in the next from the account
+ from, _ := types.Sender(signer, tx)
+ log.Trace("Gas limit exceeded for current block", "sender", from)
+ return nil, popTx, err
+
+ case errors.Is(err, core.ErrNonceTooLow):
+ // New head notification data race between the transaction pool and miner, shift
+ from, _ := types.Sender(signer, tx)
+ log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
+ return nil, shiftTx, err
+
+ case errors.Is(err, core.ErrNonceTooHigh):
+ // Reorg notification data race between the transaction pool and miner, skip account =
+ from, _ := types.Sender(signer, tx)
+ log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
+ return nil, popTx, err
+
+ case errors.Is(err, core.ErrTxTypeNotSupported):
+ // Pop the unsupported transaction without shifting in the next from the account
+ from, _ := types.Sender(signer, tx)
+ log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
+ return nil, popTx, err
+
+ default:
+ // Strange error, discard the transaction and get the next in line (note, the
+ // nonce-too-high clause will prevent us from executing in vain).
+ log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
+ return nil, shiftTx, err
+ }
+ }
+
+ envDiff.newProfit = envDiff.newProfit.Add(envDiff.newProfit, gasPrice.Mul(gasPrice, big.NewInt(int64(receipt.GasUsed))))
+ envDiff.newTxs = append(envDiff.newTxs, tx)
+ envDiff.newReceipts = append(envDiff.newReceipts, receipt)
+ return receipt, shiftTx, nil
+}
+
+// Commit Bundle to env diff
+func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chData chainData, interrupt *int32) error {
+ coinbase := envDiff.baseEnvironment.coinbase
+ tmpEnvDiff := envDiff.copy()
+
+ coinbaseBalanceBefore := tmpEnvDiff.state.GetBalance(coinbase)
+
+ profitBefore := new(big.Int).Set(tmpEnvDiff.newProfit)
+ var gasUsed uint64
+
+ for _, tx := range bundle.OriginalBundle.Txs {
+ if tmpEnvDiff.header.BaseFee != nil && tx.Type() == 2 {
+ // Sanity check for extremely large numbers
+ if tx.GasFeeCap().BitLen() > 256 {
+ return core.ErrFeeCapVeryHigh
+ }
+ if tx.GasTipCap().BitLen() > 256 {
+ return core.ErrTipVeryHigh
+ }
+ // Ensure gasFeeCap is greater than or equal to gasTipCap.
+ if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
+ return core.ErrTipAboveFeeCap
+ }
+ }
+
+ if tx.Value().Sign() == -1 {
+ return core.ErrNegativeValue
+ }
+
+ _, err := tx.EffectiveGasTip(envDiff.header.BaseFee)
+ if err != nil {
+ return err
+ }
+
+ _, err = types.Sender(envDiff.baseEnvironment.signer, tx)
+ if err != nil {
+ return err
+ }
+
+ if checkInterrupt(interrupt) {
+ return errInterrupt
+ }
+
+ receipt, _, err := tmpEnvDiff.commitTx(tx, chData)
+
+ if err != nil {
+ log.Debug("Bundle tx error", "bundle", bundle.OriginalBundle.Hash, "tx", tx.Hash(), "err", err)
+ return err
+ }
+
+ if receipt.Status != types.ReceiptStatusSuccessful && !bundle.OriginalBundle.RevertingHash(tx.Hash()) {
+ log.Debug("Bundle tx failed", "bundle", bundle.OriginalBundle.Hash, "tx", tx.Hash(), "err", err)
+ return errors.New("bundle tx revert")
+ }
+
+ gasUsed += receipt.GasUsed
+ }
+ coinbaseBalanceAfter := tmpEnvDiff.state.GetBalance(coinbase)
+ coinbaseBalanceDelta := new(big.Int).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
+ tmpEnvDiff.newProfit.Add(profitBefore, coinbaseBalanceDelta)
+
+ bundleProfit := coinbaseBalanceDelta
+
+ bundleActualEffGP := bundleProfit.Div(bundleProfit, big.NewInt(int64(gasUsed)))
+ bundleSimEffGP := new(big.Int).Set(bundle.MevGasPrice)
+
+ // allow >-1% divergence
+ bundleActualEffGP.Mul(bundleActualEffGP, big.NewInt(100))
+ bundleSimEffGP.Mul(bundleSimEffGP, big.NewInt(99))
+
+ if bundleSimEffGP.Cmp(bundleActualEffGP) == 1 {
+ log.Debug("Bundle underpays after inclusion", "bundle", bundle.OriginalBundle.Hash)
+ return errors.New("bundle underpays")
+ }
+
+ *envDiff = *tmpEnvDiff
+ return nil
+}
diff --git a/miner/algo_common_test.go b/miner/algo_common_test.go
new file mode 100644
index 0000000000..d21610f4a4
--- /dev/null
+++ b/miner/algo_common_test.go
@@ -0,0 +1,402 @@
+package miner
+
+import (
+ "crypto/ecdsa"
+ "fmt"
+ mapset "github.com/deckarep/golang-set"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+ "math/big"
+ "testing"
+)
+
+const GasLimit uint64 = 30000000
+
+var (
+ // Pay proxy is a contract that sends msg.value to address specified in calldata[0..32]
+ payProxyAddress = common.HexToAddress("0x1100000000000000000000000000000000000000")
+ payProxyCode = hexutil.MustDecode("0x6000600060006000346000356000f1")
+)
+
+type signerList struct {
+ config *params.ChainConfig
+ signers []*ecdsa.PrivateKey
+ addresses []common.Address
+ nonces []uint64
+}
+
+func (sig signerList) signTx(i int, gas uint64, gasTipCap *big.Int, gasFeeCap *big.Int, to common.Address, value *big.Int, data []byte) *types.Transaction {
+ txData := &types.DynamicFeeTx{
+ ChainID: sig.config.ChainID,
+ Nonce: sig.nonces[i],
+ GasTipCap: gasTipCap,
+ GasFeeCap: gasFeeCap,
+ Gas: gas,
+ To: &to,
+ Value: value,
+ Data: data,
+ }
+ sig.nonces[i] += 1
+
+ return types.MustSignNewTx(sig.signers[i], types.LatestSigner(sig.config), txData)
+}
+
+func genSignerList(len int, config *params.ChainConfig) signerList {
+ res := signerList{
+ config: config,
+ signers: make([]*ecdsa.PrivateKey, len),
+ addresses: make([]common.Address, len),
+ nonces: make([]uint64, len),
+ }
+
+ for i := 0; i < len; i++ {
+ privKey, err := crypto.ToECDSA(crypto.Keccak256(big.NewInt(int64(i)).Bytes()))
+ if err != nil {
+ panic(fmt.Sprint("cant create priv key", err))
+ }
+ res.signers[i] = privKey
+ res.addresses[i] = crypto.PubkeyToAddress(privKey.PublicKey)
+ }
+ return res
+}
+
+func genGenesisAlloc(sign signerList, contractAddr []common.Address, contractCode [][]byte) core.GenesisAlloc {
+ genesisAlloc := make(core.GenesisAlloc)
+ for i := 0; i < len(sign.signers); i++ {
+ genesisAlloc[sign.addresses[i]] = core.GenesisAccount{
+ Balance: big.NewInt(1000000000000000000), // 1 ether
+ Nonce: sign.nonces[i],
+ }
+ }
+
+ for i, address := range contractAddr {
+ genesisAlloc[address] = core.GenesisAccount{
+ Balance: new(big.Int),
+ Code: contractCode[i],
+ }
+ }
+
+ return genesisAlloc
+}
+
+func genTestSetup() (*state.StateDB, chainData, signerList) {
+ config := params.AllEthashProtocolChanges
+ db := rawdb.NewMemoryDatabase()
+ signerList := genSignerList(10, config)
+
+ genesisAlloc := genGenesisAlloc(signerList, []common.Address{payProxyAddress}, [][]byte{payProxyCode})
+
+ gspec := &core.Genesis{
+ Config: config,
+ Alloc: genesisAlloc,
+ }
+ _ = gspec.MustCommit(db)
+
+ chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
+
+ stateDB, _ := state.New(chain.CurrentHeader().Root, state.NewDatabase(db), nil)
+
+ return stateDB, chainData{config, chain, nil}, signerList
+}
+
+func newEnvironment(data chainData, state *state.StateDB, coinbase common.Address, gasLimit uint64, baseFee *big.Int) *environment {
+ currentBlock := data.chain.CurrentBlock()
+ // Note the passed coinbase may be different with header.Coinbase.
+ return &environment{
+ signer: types.MakeSigner(data.chainConfig, currentBlock.Number()),
+ state: state,
+ gasPool: new(core.GasPool).AddGas(gasLimit),
+ coinbase: coinbase,
+ ancestors: mapset.NewSet(),
+ family: mapset.NewSet(),
+ header: &types.Header{
+ Coinbase: coinbase,
+ ParentHash: currentBlock.Hash(),
+ Number: new(big.Int).Add(currentBlock.Number(), big.NewInt(1)),
+ GasLimit: gasLimit,
+ GasUsed: 0,
+ BaseFee: baseFee,
+ Difficulty: big.NewInt(0),
+ },
+ uncles: make(map[common.Hash]*types.Header),
+ profit: new(big.Int),
+ }
+}
+
+func TestTxCommit(t *testing.T) {
+ statedb, chData, signers := genTestSetup()
+
+ env := newEnvironment(chData, statedb, signers.addresses[0], GasLimit, big.NewInt(1))
+ envDiff := newEnvironmentDiff(env)
+
+ tx := signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{})
+
+ receipt, i, err := envDiff.commitTx(tx, chData)
+ if err != nil {
+ t.Fatal("can't commit transaction:", err)
+ }
+ if receipt.Status != 1 {
+ t.Fatal("tx failed", receipt)
+ }
+ if i != shiftTx {
+ t.Fatal("incorrect shift value")
+ }
+
+ if env.tcount != 0 {
+ t.Fatal("env tcount modified")
+ }
+ if len(env.receipts) != 0 {
+ t.Fatal("env receipts modified")
+ }
+ if len(env.txs) != 0 {
+ t.Fatal("env txs modified")
+ }
+ if env.gasPool.Gas() != GasLimit {
+ t.Fatal("env gas pool modified")
+ }
+
+ if envDiff.gasPool.AddGas(receipt.GasUsed).Gas() != GasLimit {
+ t.Fatal("envDiff gas pool incorrect")
+ }
+ if envDiff.header.GasUsed != receipt.GasUsed {
+ t.Fatal("envDiff gas used is incorrect")
+ }
+ if len(envDiff.newReceipts) != 1 {
+ t.Fatal("envDiff receipts incorrect")
+ }
+ if len(envDiff.newTxs) != 1 {
+ t.Fatal("envDiff txs incorrect")
+ }
+}
+
+func TestBundleCommit(t *testing.T) {
+ statedb, chData, signers := genTestSetup()
+
+ env := newEnvironment(chData, statedb, signers.addresses[0], GasLimit, big.NewInt(1))
+ envDiff := newEnvironmentDiff(env)
+
+ tx1 := signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{})
+ tx2 := signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{})
+
+ bundle := types.MevBundle{
+ Txs: types.Transactions{tx1, tx2},
+ BlockNumber: env.header.Number,
+ }
+
+ simBundle, err := simulateBundle(env, bundle, chData, nil)
+ if err != nil {
+ t.Fatal("Failed to simulate bundle", err)
+ }
+
+ err = envDiff.commitBundle(&simBundle, chData, nil)
+ if err != nil {
+ t.Fatal("Failed to commit bundle", err)
+ }
+
+ if len(envDiff.newTxs) != 2 {
+ t.Fatal("Incorrect new txs")
+ }
+ if len(envDiff.newReceipts) != 2 {
+ t.Fatal("Incorrect receipts txs")
+ }
+ if envDiff.gasPool.AddGas(21000*2).Gas() != GasLimit {
+ t.Fatal("Gas pool incorrect update")
+ }
+}
+
+func TestErrorTxCommit(t *testing.T) {
+ statedb, chData, signers := genTestSetup()
+
+ env := newEnvironment(chData, statedb, signers.addresses[0], GasLimit, big.NewInt(1))
+ envDiff := newEnvironmentDiff(env)
+
+ signers.nonces[1] = 10
+ tx := signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{})
+
+ _, i, err := envDiff.commitTx(tx, chData)
+ if err == nil {
+ t.Fatal("committed incorrect transaction:", err)
+ }
+ if i != popTx {
+ t.Fatal("incorrect shift value")
+ }
+
+ if envDiff.gasPool.Gas() != GasLimit {
+ t.Fatal("envDiff gas pool incorrect")
+ }
+ if envDiff.header.GasUsed != 0 {
+ t.Fatal("envDiff gas used incorrect")
+ }
+ if envDiff.newProfit.Sign() != 0 {
+ t.Fatal("envDiff new profit incorrect")
+ }
+ if len(envDiff.newReceipts) != 0 {
+ t.Fatal("envDiff receipts incorrect")
+ }
+ if len(envDiff.newTxs) != 0 {
+ t.Fatal("envDiff txs incorrect")
+ }
+}
+
+func TestCommitTxOverGasLimit(t *testing.T) {
+ statedb, chData, signers := genTestSetup()
+
+ env := newEnvironment(chData, statedb, signers.addresses[0], 21000, big.NewInt(1))
+ envDiff := newEnvironmentDiff(env)
+
+ tx1 := signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{})
+ tx2 := signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{})
+
+ receipt, i, err := envDiff.commitTx(tx1, chData)
+ if err != nil {
+ t.Fatal("can't commit transaction:", err)
+ }
+ if receipt.Status != 1 {
+ t.Fatal("tx failed", receipt)
+ }
+ if i != shiftTx {
+ t.Fatal("incorrect shift value")
+ }
+
+ if envDiff.gasPool.Gas() != 0 {
+ t.Fatal("Env diff gas pool is not drained")
+ }
+
+ receipt, i, err = envDiff.commitTx(tx2, chData)
+ if err == nil {
+ t.Fatal("committed tx over gas limit")
+ }
+}
+
+func TestErrorBundleCommit(t *testing.T) {
+ statedb, chData, signers := genTestSetup()
+
+ env := newEnvironment(chData, statedb, signers.addresses[0], 21000*2, big.NewInt(1))
+ envDiff := newEnvironmentDiff(env)
+
+ // This tx will be included before bundle so bundle will fail because of gas limit
+ tx0 := signers.signTx(4, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{})
+
+ tx1 := signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{})
+ tx2 := signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{})
+
+ bundle := types.MevBundle{
+ Txs: types.Transactions{tx1, tx2},
+ BlockNumber: env.header.Number,
+ }
+
+ simBundle, err := simulateBundle(env, bundle, chData, nil)
+ if err != nil {
+ t.Fatal("Failed to simulate bundle", err)
+ }
+
+ _, _, err = envDiff.commitTx(tx0, chData)
+ if err != nil {
+ t.Fatal("Failed to commit tx0", err)
+ }
+
+ gasPoolBefore := *envDiff.gasPool
+ gasUsedBefore := envDiff.header.GasUsed
+ newProfitBefore := new(big.Int).Set(envDiff.newProfit)
+ balanceBefore := envDiff.state.GetBalance(signers.addresses[2])
+
+ err = envDiff.commitBundle(&simBundle, chData, nil)
+ if err == nil {
+ t.Fatal("Committed failed bundle", err)
+ }
+
+ if *envDiff.gasPool != gasPoolBefore {
+ t.Fatal("gasPool changed")
+ }
+
+ if envDiff.header.GasUsed != gasUsedBefore {
+ t.Fatal("gasUsed changed")
+ }
+
+ balanceAfter := envDiff.state.GetBalance(signers.addresses[2])
+ if balanceAfter.Cmp(balanceBefore) != 0 {
+ t.Fatal("balance changed")
+ }
+
+ if envDiff.newProfit.Cmp(newProfitBefore) != 0 {
+ t.Fatal("newProfit changed")
+ }
+
+ if len(envDiff.newTxs) != 1 {
+ t.Fatal("Incorrect new txs")
+ }
+ if len(envDiff.newReceipts) != 1 {
+ t.Fatal("Incorrect receipts txs")
+ }
+}
+
+func TestBlacklist(t *testing.T) {
+ statedb, chData, signers := genTestSetup()
+
+ env := newEnvironment(chData, statedb, signers.addresses[0], GasLimit, big.NewInt(1))
+ envDiff := newEnvironmentDiff(env)
+
+ blacklist := map[common.Address]struct{}{
+ signers.addresses[3]: {},
+ }
+ chData.blacklist = blacklist
+
+ gasPoolBefore := *envDiff.gasPool
+ gasUsedBefore := envDiff.header.GasUsed
+ balanceBefore := envDiff.state.GetBalance(signers.addresses[3])
+
+ tx := signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[3], big.NewInt(77), []byte{})
+ _, _, err := envDiff.commitTx(tx, chData)
+ if err == nil {
+ t.Fatal("committed blacklisted transaction: to")
+ }
+
+ tx = signers.signTx(3, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[1], big.NewInt(88), []byte{})
+ _, _, err = envDiff.commitTx(tx, chData)
+ if err == nil {
+ t.Fatal("committed blacklisted transaction: sender")
+ }
+
+ calldata := make([]byte, 32-20, 20)
+ calldata = append(calldata, signers.addresses[3].Bytes()...)
+
+ tx = signers.signTx(4, 40000, big.NewInt(0), big.NewInt(1), payProxyAddress, big.NewInt(99), calldata)
+ _, _, err = envDiff.commitTx(tx, chData)
+ fmt.Println("balance", envDiff.state.GetBalance(signers.addresses[3]))
+
+ if err == nil {
+ t.Fatal("committed blacklisted transaction: trace")
+ }
+
+ if *envDiff.gasPool != gasPoolBefore {
+ t.Fatal("gasPool changed")
+ }
+
+ if envDiff.header.GasUsed != gasUsedBefore {
+ t.Fatal("gasUsed changed")
+ }
+
+ if envDiff.newProfit.Sign() != 0 {
+ t.Fatal("newProfit changed")
+ }
+
+ if envDiff.state.GetBalance(signers.addresses[3]).Cmp(balanceBefore) != 0 {
+ t.Fatal("blacklisted balance changed")
+ }
+
+ if len(envDiff.newTxs) != 0 {
+ t.Fatal("newTxs changed")
+ }
+
+ if len(envDiff.newReceipts) != 0 {
+ t.Fatal("newReceipts changed")
+ }
+}
diff --git a/miner/algo_greedy.go b/miner/algo_greedy.go
new file mode 100644
index 0000000000..2a4b0917de
--- /dev/null
+++ b/miner/algo_greedy.go
@@ -0,0 +1,89 @@
+package miner
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+type chainData struct {
+ chainConfig *params.ChainConfig
+ chain *core.BlockChain
+ blacklist map[common.Address]struct{}
+}
+
+// / To use it:
+// / 1. Copy relevant data from the worker
+// / 2. Call buildBlock
+// / 2. If new bundles, txs arrive, call buildBlock again
+// / This struct lifecycle is tied to 1 block-building task
+type greedyBuilder struct {
+ inputEnvironment *environment
+ chainData chainData
+ interrupt *int32
+}
+
+func newGreedyBuilder(chain *core.BlockChain, chainConfig *params.ChainConfig, blacklist map[common.Address]struct{}, env *environment, interrupt *int32) *greedyBuilder {
+ return &greedyBuilder{
+ inputEnvironment: env,
+ chainData: chainData{chainConfig, chain, blacklist},
+ interrupt: interrupt,
+ }
+}
+
+func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transactions map[common.Address]types.Transactions) (*environment, []types.SimulatedBundle) {
+
+ env := b.inputEnvironment.copy()
+
+ orders := types.NewTransactionsByPriceAndNonce(env.signer, transactions, simBundles, env.header.BaseFee)
+ log.Debug("buildBlock", "totalBundles", len(simBundles))
+ for _, bundle := range simBundles {
+ log.Debug("buildBlock", "simBHash", bundle.OriginalBundle.Hash)
+ }
+ envDiff := newEnvironmentDiff(env)
+
+ usedBundles := make([]types.SimulatedBundle, 0)
+
+ for {
+ order := orders.Peek()
+ if order == nil {
+ break
+ }
+
+ if order.Tx != nil {
+ receipt, skip, err := envDiff.commitTx(order.Tx, b.chainData)
+ switch skip {
+ case shiftTx:
+ orders.Shift()
+ case popTx:
+ orders.Pop()
+ }
+
+ if err != nil {
+ log.Info("could not apply tx", "hash", order.Tx.Hash(), "err", err)
+ continue
+ }
+ effGapPrice, err := order.Tx.EffectiveGasTip(env.header.BaseFee)
+ if err == nil {
+ log.Trace("Included tx", "EGP", effGapPrice.String(), "gasUsed", receipt.GasUsed)
+ }
+ } else if order.Bundle != nil {
+ bundle := order.Bundle
+ //log.Debug("buildBlock considering bundle", "egp", bundle.MevGasPrice.String(), "hash", bundle.OriginalBundle.Hash)
+ err := envDiff.commitBundle(bundle, b.chainData, b.interrupt)
+ orders.Pop()
+ if err != nil {
+ log.Info("Could not apply bundle", "bundle", bundle.OriginalBundle.Hash, "err", err)
+ continue
+ }
+
+ log.Info("Included bundle", "bundleEGP", bundle.MevGasPrice.String(), "gasUsed", bundle.TotalGasUsed, "ethToCoinbase", ethIntToFloat(bundle.TotalEth))
+ usedBundles = append(usedBundles, *bundle)
+ }
+ }
+
+ envDiff.applyToBaseEnv()
+ return env, usedBundles
+}
diff --git a/miner/algo_greedy_test.go b/miner/algo_greedy_test.go
new file mode 100644
index 0000000000..b38704bc18
--- /dev/null
+++ b/miner/algo_greedy_test.go
@@ -0,0 +1,69 @@
+package miner
+
+import (
+ "fmt"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+ "math/big"
+ "testing"
+)
+
+func TestBuildBlockGasLimit(t *testing.T) {
+ statedb, chData, signers := genTestSetup()
+
+ env := newEnvironment(chData, statedb, signers.addresses[0], 21000, big.NewInt(1))
+
+ txs := make(map[common.Address]types.Transactions)
+
+ txs[signers.addresses[1]] = types.Transactions{
+ signers.signTx(1, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{}),
+ }
+ txs[signers.addresses[2]] = types.Transactions{
+ signers.signTx(2, 21000, big.NewInt(0), big.NewInt(1), signers.addresses[2], big.NewInt(0), []byte{}),
+ }
+
+ builder := newGreedyBuilder(chData.chain, chData.chainConfig, nil, env, nil)
+
+ result, _ := builder.buildBlock([]types.SimulatedBundle{}, txs)
+ log.Info("block built", "txs", len(result.txs), "gasPool", result.gasPool.Gas())
+ if result.tcount != 1 {
+ t.Fatal("Incorrect tx count")
+ }
+}
+
+func TestTxWithMinerFeeHeap(t *testing.T) {
+ statedb, chData, signers := genTestSetup()
+
+ env := newEnvironment(chData, statedb, signers.addresses[0], 21000, big.NewInt(1))
+
+ txs := make(map[common.Address]types.Transactions)
+
+ txs[signers.addresses[1]] = types.Transactions{
+ signers.signTx(1, 21000, big.NewInt(1), big.NewInt(5), signers.addresses[2], big.NewInt(0), []byte{}),
+ }
+ txs[signers.addresses[2]] = types.Transactions{
+ signers.signTx(2, 21000, big.NewInt(4), big.NewInt(5), signers.addresses[2], big.NewInt(0), []byte{}),
+ }
+
+ bundle1 := types.SimulatedBundle{MevGasPrice: big.NewInt(3), OriginalBundle: types.MevBundle{Hash: common.HexToHash("0xb1")}}
+ bundle2 := types.SimulatedBundle{MevGasPrice: big.NewInt(2), OriginalBundle: types.MevBundle{Hash: common.HexToHash("0xb2")}}
+
+ orders := types.NewTransactionsByPriceAndNonce(env.signer, txs, []types.SimulatedBundle{bundle2, bundle1}, env.header.BaseFee)
+
+ for {
+ order := orders.Peek()
+ if order == nil {
+ return
+ }
+
+ if order.Tx != nil {
+ fmt.Println("tx", order.Tx.Hash())
+ orders.Shift()
+ } else if order.Bundle != nil {
+ fmt.Println("bundle", order.Bundle.OriginalBundle.Hash)
+ orders.Pop()
+ }
+ }
+
+}
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index 4ab9a3619c..d1afb38bbd 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -94,7 +94,7 @@ type resChPair struct {
func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, error) {
resChans := []resChPair{}
- for _, worker := range append(w.workers, w.regularWorker) {
+ for _, worker := range append(w.workers) {
resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook)
if err != nil {
log.Error("could not start async block construction", "isFlashbotsWorker", worker.flashbots.isFlashbots, "#bundles", worker.flashbots.maxMergedBundles)
@@ -150,16 +150,13 @@ func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine cons
workers := []*worker{regularWorker}
- for i := 1; i <= config.MaxMergedBundles; i++ {
- workers = append(workers,
- newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
- isFlashbots: true,
- queue: queue,
- maxMergedBundles: i,
- }))
- }
+ workers = append(workers, newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
+ isFlashbots: true,
+ queue: queue,
+ maxMergedBundles: config.MaxMergedBundles,
+ }))
- log.Info("creating multi worker", "config.MaxMergedBundles", config.MaxMergedBundles, "config.TrustedRelays", config.TrustedRelays, "worker", len(workers))
+ log.Info("creating new merger worker", "config.MaxMergedBundles", config.MaxMergedBundles)
return &multiWorker{
regularWorker: regularWorker,
workers: workers,
diff --git a/miner/worker.go b/miner/worker.go
index 2f3e1772e5..cb7c2f3d76 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -244,6 +244,10 @@ type worker struct {
pendingMu sync.RWMutex
pendingTasks map[common.Hash]*task
+ bundleCacheMu sync.Mutex
+ bundleCacheHeaderHash common.Hash
+ bundleCache map[common.Hash]simulatedBundle
+
snapshotMu sync.RWMutex // The lock used to protect the snapshots below
snapshotBlock *types.Block
snapshotReceipts types.Receipts
@@ -347,6 +351,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
coinbase: builderCoinbase,
flashbots: flashbots,
+ bundleCache: make(map[common.Hash]simulatedBundle),
}
// Subscribe NewTxsEvent for tx pool
@@ -687,7 +692,7 @@ func (w *worker) mainLoop() {
acc, _ := types.Sender(w.current.signer, tx)
txs[acc] = append(txs[acc], tx)
}
- txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, w.current.header.BaseFee)
+ txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, nil, w.current.header.BaseFee)
tcount := w.current.tcount
w.commitTransactions(w.current, txset, nil)
@@ -1124,10 +1129,14 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
break
}
// Retrieve the next transaction and abort if all done
- tx := txs.Peek()
- if tx == nil {
+ order := txs.Peek()
+ if order == nil {
break
}
+ tx := order.Tx
+ if tx == nil {
+ continue
+ }
// Error may be ignored here. The error has already been checked
// during transaction acceptance is the transaction pool.
//
@@ -1351,13 +1360,13 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
}
if len(localTxs) > 0 {
- txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
+ txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, nil, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
return err, nil
}
}
if len(remoteTxs) > 0 {
- txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee)
+ txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, nil, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
return err, nil
}
@@ -1396,6 +1405,82 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
return nil, blockBundles
}
+// fillTransactionsAlgoWorker retrieves the pending transactions and bundles from the txpool and fills them
+// into the given sealing block.
+func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment, validatorCoinbase *common.Address) (error, []types.SimulatedBundle) {
+ // Split the pending transactions into locals and remotes
+ // Fill the block with all available pending transactions.
+ pending := w.eth.TxPool().Pending(true)
+ if env.gasPool == nil {
+ env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
+ }
+ var builderCoinbaseBalanceBefore *big.Int
+ if validatorCoinbase != nil {
+ builderCoinbaseBalanceBefore = env.state.GetBalance(w.coinbase)
+ if err := env.gasPool.SubGas(params.TxGas); err != nil {
+ return err, nil
+ }
+ }
+
+ var bundlesToConsider []types.SimulatedBundle
+ if w.flashbots.isFlashbots {
+ bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
+ if err != nil {
+ log.Error("Failed to fetch pending bundles", "err", err)
+ return err, nil
+ }
+
+ start := time.Now()
+ simBundles, err := w.simulateBundles(env, bundles, pending)
+ log.Debug("Simulated bundles", "time", time.Since(start), "bundles", len(bundles))
+ if err != nil {
+ log.Error("Failed to simulate flashbots bundles", "err", err)
+ return err, nil
+ }
+
+ bundlesToConsider = simBundles
+ }
+
+ buider := newGreedyBuilder(w.chain, w.chainConfig, w.blockList, env, interrupt)
+ start := time.Now()
+ newEnv, blockBundles := buider.buildBlock(bundlesToConsider, pending)
+ log.Debug("Build block", "time", time.Since(start), "gas used", newEnv.header.GasUsed)
+ *env = *newEnv
+
+ if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil {
+ builderCoinbaseBalanceAfter := env.state.GetBalance(w.coinbase)
+ log.Info("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
+
+ profit := new(big.Int).Sub(builderCoinbaseBalanceAfter, builderCoinbaseBalanceBefore)
+ env.gasPool.AddGas(params.TxGas)
+ if profit.Sign() == 1 {
+ tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit)
+ if err != nil {
+ log.Error("Proposer payout create tx failed", "err", err)
+ return fmt.Errorf("proposer payout create tx failed - %v", err), nil
+ }
+ if tx != nil {
+ log.Info("Proposer payout create tx succeeded, proceeding to commit tx")
+ _, err = w.commitTransaction(env, tx)
+ if err != nil {
+ log.Error("Proposer payout commit tx failed", "hash", tx.Hash().String(), "err", err)
+ return fmt.Errorf("proposer payout commit tx failed - %v", err), nil
+ }
+ log.Info("Proposer payout commit tx succeeded", "hash", tx.Hash().String())
+ env.tcount++
+ } else {
+ return errors.New("proposer payout create tx failed due to tx is nil"), nil
+ }
+ } else {
+ log.Warn("Proposer payout create tx failed due to not enough balance", "profit", profit.String())
+ return errors.New("proposer payout create tx failed due to not enough balance"), nil
+ }
+
+ }
+
+ return nil, blockBundles
+}
+
// generateWork generates a sealing block based on the given parameters.
func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
validatorCoinbase := params.coinbase
@@ -1411,7 +1496,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
var blockBundles []types.SimulatedBundle
if !params.noTxs {
start := time.Now()
- err, mergedBundles := w.fillTransactions(nil, work, &validatorCoinbase)
+ err, mergedBundles := w.fillTransactionsAlgoWorker(nil, work, &validatorCoinbase)
if err != nil {
return nil, err
}
@@ -1649,9 +1734,24 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
}
func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) ([]simulatedBundle, error) {
+ w.bundleCacheMu.Lock()
+ defer w.bundleCacheMu.Unlock()
+
simulatedBundles := []simulatedBundle{}
+ var bundleCache map[common.Hash]simulatedBundle
+ if w.bundleCacheHeaderHash == env.header.Hash() {
+ bundleCache = w.bundleCache
+ } else {
+ bundleCache = make(map[common.Hash]simulatedBundle)
+ }
+
for _, bundle := range bundles {
+ if simmed, ok := bundleCache[bundle.Hash]; ok {
+ simulatedBundles = append(simulatedBundles, simmed)
+ continue
+ }
+
if len(bundle.Txs) == 0 {
continue
}
@@ -1663,9 +1763,13 @@ func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pe
log.Debug("Error computing gas for a bundle", "error", err)
continue
}
+ bundleCache[bundle.Hash] = simmed
simulatedBundles = append(simulatedBundles, simmed)
}
+ w.bundleCacheHeaderHash = env.header.Hash()
+ w.bundleCache = bundleCache
+
return simulatedBundles, nil
}
@@ -1729,23 +1833,24 @@ func (w *worker) computeBundleGas(env *environment, bundle types.MevBundle, stat
totalGasUsed += receipt.GasUsed
- from, err := types.Sender(env.signer, tx)
- if err != nil {
- return simulatedBundle{}, err
- }
-
- txInPendingPool := false
- if accountTxs, ok := pendingTxs[from]; ok {
- // check if tx is in pending pool
- txNonce := tx.Nonce()
-
- for _, accountTx := range accountTxs {
- if accountTx.Nonce() == txNonce {
- txInPendingPool = true
- break
- }
- }
- }
+ // NOTE: see note below
+ //from, err := types.Sender(env.signer, tx)
+ //if err != nil {
+ // return simulatedBundle{}, err
+ //}
+ //
+ //txInPendingPool := false
+ //if accountTxs, ok := pendingTxs[from]; ok {
+ // // check if tx is in pending pool
+ // txNonce := tx.Nonce()
+ //
+ // for _, accountTx := range accountTxs {
+ // if accountTx.Nonce() == txNonce {
+ // txInPendingPool = true
+ // break
+ // }
+ // }
+ //}
gasUsed := new(big.Int).SetUint64(receipt.GasUsed)
gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee)
@@ -1758,10 +1863,11 @@ func (w *worker) computeBundleGas(env *environment, bundle types.MevBundle, stat
coinbaseDelta.Sub(coinbaseDelta, gasFeesTx)
ethSentToCoinbase.Add(ethSentToCoinbase, coinbaseDelta)
- if !txInPendingPool {
- // If tx is not in pending pool, count the gas fees
- gasFees.Add(gasFees, gasFeesTx)
- }
+ // NOTE: differs from prod, the reason is that it can't be manipulated anymore
+ //if !txInPendingPool {
+ // If tx is not in pending pool, count the gas fees
+ gasFees.Add(gasFees, gasFeesTx)
+ //}
}
totalEth := new(big.Int).Add(ethSentToCoinbase, gasFees)
From 982e38fc9c5aafb4ba88a5008aed1584a76df452 Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Fri, 23 Sep 2022 11:33:51 +0300
Subject: [PATCH 47/83] sim bundles in parallel (new merger) (#33)
* sim bundles in parallel
* add cache for failed bundles too
---
miner/worker.go | 65 ++++++++++++++++++++++++++++++--------------
miner/worker_test.go | 48 ++++++++++++++++++++++++++++++++
2 files changed, 93 insertions(+), 20 deletions(-)
diff --git a/miner/worker.go b/miner/worker.go
index cb7c2f3d76..538d4f03ec 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -246,7 +246,8 @@ type worker struct {
bundleCacheMu sync.Mutex
bundleCacheHeaderHash common.Hash
- bundleCache map[common.Hash]simulatedBundle
+ bundleCacheSuccess map[common.Hash]simulatedBundle
+ bundleCacheFailed map[common.Hash]struct{}
snapshotMu sync.RWMutex // The lock used to protect the snapshots below
snapshotBlock *types.Block
@@ -351,7 +352,8 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
coinbase: builderCoinbase,
flashbots: flashbots,
- bundleCache: make(map[common.Hash]simulatedBundle),
+ bundleCacheSuccess: make(map[common.Hash]simulatedBundle),
+ bundleCacheFailed: make(map[common.Hash]struct{}),
}
// Subscribe NewTxsEvent for tx pool
@@ -1737,38 +1739,61 @@ func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pe
w.bundleCacheMu.Lock()
defer w.bundleCacheMu.Unlock()
- simulatedBundles := []simulatedBundle{}
-
- var bundleCache map[common.Hash]simulatedBundle
+ var cacheSuccess map[common.Hash]simulatedBundle
+ var cacheFailed map[common.Hash]struct{}
if w.bundleCacheHeaderHash == env.header.Hash() {
- bundleCache = w.bundleCache
+ cacheSuccess = w.bundleCacheSuccess
+ cacheFailed = w.bundleCacheFailed
} else {
- bundleCache = make(map[common.Hash]simulatedBundle)
+ cacheSuccess = make(map[common.Hash]simulatedBundle)
+ cacheFailed = make(map[common.Hash]struct{})
}
- for _, bundle := range bundles {
- if simmed, ok := bundleCache[bundle.Hash]; ok {
- simulatedBundles = append(simulatedBundles, simmed)
+ simResult := make([]*simulatedBundle, len(bundles))
+
+ var wg sync.WaitGroup
+ for i, bundle := range bundles {
+ if simmed, ok := cacheSuccess[bundle.Hash]; ok {
+ simResult[i] = &simmed
continue
}
- if len(bundle.Txs) == 0 {
+ if _, ok := cacheFailed[bundle.Hash]; ok {
continue
}
- state := env.state.Copy()
- gasPool := new(core.GasPool).AddGas(env.header.GasLimit)
- simmed, err := w.computeBundleGas(env, bundle, state, gasPool, pendingTxs, 0)
- if err != nil {
- log.Debug("Error computing gas for a bundle", "error", err)
- continue
+ wg.Add(1)
+ go func(idx int, bundle types.MevBundle, state *state.StateDB) {
+ defer wg.Done()
+ if len(bundle.Txs) == 0 {
+ return
+ }
+ gasPool := new(core.GasPool).AddGas(env.header.GasLimit)
+ simmed, err := w.computeBundleGas(env, bundle, state, gasPool, pendingTxs, 0)
+
+ if err != nil {
+ log.Debug("Error computing gas for a bundle", "error", err)
+ return
+ }
+ simResult[idx] = &simmed
+ }(i, bundle, env.state.Copy())
+ }
+
+ wg.Wait()
+
+ simulatedBundles := make([]simulatedBundle, 0, len(bundles))
+ for i, bundle := range simResult {
+ if bundle != nil {
+ simulatedBundles = append(simulatedBundles, *bundle)
+ cacheSuccess[bundle.OriginalBundle.Hash] = *bundle
+ } else {
+ cacheFailed[bundles[i].Hash] = struct{}{}
}
- bundleCache[bundle.Hash] = simmed
- simulatedBundles = append(simulatedBundles, simmed)
}
w.bundleCacheHeaderHash = env.header.Hash()
- w.bundleCache = bundleCache
+ w.bundleCacheSuccess = cacheSuccess
+ w.bundleCacheFailed = cacheFailed
return simulatedBundles, nil
}
diff --git a/miner/worker_test.go b/miner/worker_test.go
index f590df7610..6b4187d5d7 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -673,3 +673,51 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
}
}
}
+
+func TestSimulateBundles(t *testing.T) {
+ w, _ := newTestWorker(t, ethashChainConfig, ethash.NewFaker(), rawdb.NewMemoryDatabase(), 0)
+ defer w.close()
+
+ env, err := w.prepareWork(&generateParams{gasLimit: 30000000})
+ if err != nil {
+ t.Fatalf("Failed to prepare work: %s", err)
+ }
+
+ signTx := func(nonce uint64) *types.Transaction {
+ tx, err := types.SignTx(types.NewTransaction(nonce, testUserAddress, big.NewInt(1000), params.TxGas, env.header.BaseFee, nil), types.HomesteadSigner{}, testBankKey)
+ if err != nil {
+ t.Fatalf("Failed to sign tx")
+ }
+ return tx
+ }
+
+ bundle1 := types.MevBundle{Txs: types.Transactions{signTx(0)}, Hash: common.HexToHash("0x01")}
+ // this bundle will fail
+ bundle2 := types.MevBundle{Txs: types.Transactions{signTx(1)}, Hash: common.HexToHash("0x02")}
+ bundle3 := types.MevBundle{Txs: types.Transactions{signTx(0)}, Hash: common.HexToHash("0x03")}
+
+ simBundles, err := w.simulateBundles(env, []types.MevBundle{bundle1, bundle2, bundle3}, nil)
+
+ if len(simBundles) != 2 {
+ t.Fatalf("Incorrect amount of sim bundles")
+ }
+
+ for _, simBundle := range simBundles {
+ if simBundle.OriginalBundle.Hash == common.HexToHash("0x02") {
+ t.Fatalf("bundle2 should fail")
+ }
+ }
+
+ // simulate 2 times to check cache
+ simBundles, err = w.simulateBundles(env, []types.MevBundle{bundle1, bundle2, bundle3}, nil)
+
+ if len(simBundles) != 2 {
+ t.Fatalf("Incorrect amount of sim bundles(cache)")
+ }
+
+ for _, simBundle := range simBundles {
+ if simBundle.OriginalBundle.Hash == common.HexToHash("0x02") {
+ t.Fatalf("bundle2 should fail(cache)")
+ }
+ }
+}
From 570b42ca6b73be15046b87a74e78ed5172ff7f9d Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Fri, 23 Sep 2022 18:51:41 +0200
Subject: [PATCH 48/83] Merge new merger algo and mev-geth
---
cmd/geth/main.go | 2 +-
cmd/utils/flags.go | 41 ++++------
eth/block-validation/api_test.go | 2 +-
miner/algo_common.go | 95 +----------------------
miner/algo_common_test.go | 96 ++++++++++++++++++++++-
miner/algo_greedy.go | 4 +
miner/miner.go | 9 ++-
miner/multi_worker.go | 50 +++++++++---
miner/worker.go | 128 ++++++++++++++++---------------
9 files changed, 236 insertions(+), 191 deletions(-)
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 9a13c436eb..d903c160be 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -130,12 +130,12 @@ var (
utils.LegacyMinerGasTargetFlag,
utils.MinerGasLimitFlag,
utils.MinerGasPriceFlag,
+ utils.MinerAlgoTypeFlag,
utils.MinerEtherbaseFlag,
utils.MinerExtraDataFlag,
utils.MinerRecommitIntervalFlag,
utils.MinerNoVerifyFlag,
utils.MinerMaxMergedBundlesFlag,
- utils.MinerTrustedRelaysFlag,
utils.MinerBlocklistFileFlag,
utils.NATFlag,
utils.NoDiscoverFlag,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 699e94e232..5daab254ad 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -547,6 +547,12 @@ var (
Value: ethconfig.Defaults.Miner.GasPrice,
Category: flags.MinerCategory,
}
+ MinerAlgoTypeFlag = &cli.StringFlag{
+ Name: "miner.algotype",
+ Usage: "Block building algorithm to use [=mev-geth] (mev-geth, greedy)",
+ Value: "mev-geth",
+ Category: flags.MinerCategory,
+ }
MinerEtherbaseFlag = &cli.StringFlag{
Name: "miner.etherbase",
Usage: "Public address for block mining rewards (default = first account)",
@@ -575,12 +581,6 @@ var (
Value: 3,
Category: flags.MinerCategory,
}
- MinerTrustedRelaysFlag = &cli.StringFlag{
- Name: "miner.trustedrelays",
- Usage: "flashbots - The Ethereum addresses of trusted relays for signature verification. The miner will accept signed bundles and other tasks from the relay, being reasonably certain about DDoS safety.",
- Value: "0x870e2734DdBe2Fba9864f33f3420d59Bc641f2be",
- Category: flags.MinerCategory,
- }
MinerBlocklistFileFlag = &cli.StringFlag{
Name: "miner.blocklist",
Usage: "flashbots - Path to JSON file with list of blocked addresses. Miner will ignore txs that touch mentioned addresses.",
@@ -1698,15 +1698,6 @@ func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
if ctx.IsSet(TxPoolPrivateLifetimeFlag.Name) {
cfg.PrivateTxLifetime = ctx.Duration(TxPoolPrivateLifetimeFlag.Name)
}
-
- addresses := strings.Split(ctx.String(MinerTrustedRelaysFlag.Name), ",")
- for _, address := range addresses {
- if trimmed := strings.TrimSpace(address); !common.IsHexAddress(trimmed) {
- Fatalf("Invalid account in --miner.trustedrelays: %s", trimmed)
- } else {
- cfg.TrustedRelays = append(cfg.TrustedRelays, common.HexToAddress(trimmed))
- }
- }
}
func setEthash(ctx *cli.Context, cfg *ethconfig.Config) {
@@ -1750,6 +1741,16 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
if ctx.IsSet(MinerGasPriceFlag.Name) {
cfg.GasPrice = flags.GlobalBig(ctx, MinerGasPriceFlag.Name)
}
+ if ctx.IsSet(MinerAlgoTypeFlag.Name) {
+ switch ctx.String(MinerAlgoTypeFlag.Name) {
+ case "greedy":
+ cfg.AlgoType = miner.ALGO_GREEDY
+ case "mev-geth":
+ cfg.AlgoType = miner.ALGO_MEV_GETH
+ default:
+ Fatalf("Invalid algo in --miner.algotype: %s", ctx.String(MinerAlgoTypeFlag.Name))
+ }
+ }
if ctx.IsSet(MinerRecommitIntervalFlag.Name) {
cfg.Recommit = ctx.Duration(MinerRecommitIntervalFlag.Name)
}
@@ -1762,16 +1763,6 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
cfg.MaxMergedBundles = ctx.Int(MinerMaxMergedBundlesFlag.Name)
- addresses := strings.Split(ctx.String(MinerTrustedRelaysFlag.Name), ",")
- for _, address := range addresses {
- if trimmed := strings.TrimSpace(address); !common.IsHexAddress(trimmed) {
- Fatalf("Invalid account in --miner.trustedrelays: %s", trimmed)
- } else {
- cfg.TrustedRelays = append(cfg.TrustedRelays, common.HexToAddress(trimmed))
- }
- }
- log.Info("Trusted relays set as", "addresses", cfg.TrustedRelays)
-
if ctx.IsSet(MinerBlocklistFileFlag.Name) {
bytes, err := os.ReadFile(ctx.String(MinerBlocklistFileFlag.Name))
if err != nil {
diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go
index f7945399d6..e93092467e 100644
--- a/eth/block-validation/api_test.go
+++ b/eth/block-validation/api_test.go
@@ -142,7 +142,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
invalidPayload.LogsBloom = boostTypes.Bloom{}
copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
blockRequest.ExecutionPayload = invalidPayload
- copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x65cded68b85277f489f22497731d8cece9e42f0429a250a5022a9417408f3998")[:32])
+ copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x08eaca07137419b4b7f8770530b2db870f6e1cc3eabd355b8f8301cec9fca5f3")[:32])
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value")
}
diff --git a/miner/algo_common.go b/miner/algo_common.go
index a1314a3994..d6c3f50392 100644
--- a/miner/algo_common.go
+++ b/miner/algo_common.go
@@ -2,6 +2,9 @@ package miner
import (
"errors"
+ "math/big"
+ "sync/atomic"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
@@ -10,8 +13,6 @@ import (
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
- "math/big"
- "sync/atomic"
)
const (
@@ -73,96 +74,6 @@ func checkInterrupt(i *int32) bool {
// Simulate bundle on top of current state without modifying it
// pending txs used to track if bundle tx is part of the mempool
-func simulateBundle(env *environment, bundle types.MevBundle, chData chainData, interrupt *int32) (types.SimulatedBundle, error) {
- stateDB := env.state.Copy()
- gasPool := new(core.GasPool).AddGas(env.header.GasLimit)
-
- var totalGasUsed uint64
- gasFees := big.NewInt(0)
- ethSentToCoinbase := big.NewInt(0)
-
- for i, tx := range bundle.Txs {
- if checkInterrupt(interrupt) {
- return types.SimulatedBundle{}, errInterrupt
- }
-
- if env.header.BaseFee != nil && tx.Type() == 2 {
- // Sanity check for extremely large numbers
- if tx.GasFeeCap().BitLen() > 256 {
- return types.SimulatedBundle{}, core.ErrFeeCapVeryHigh
- }
- if tx.GasTipCap().BitLen() > 256 {
- return types.SimulatedBundle{}, core.ErrTipVeryHigh
- }
- // Ensure gasFeeCap is greater than or equal to gasTipCap.
- if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
- return types.SimulatedBundle{}, core.ErrTipAboveFeeCap
- }
- }
-
- stateDB.Prepare(tx.Hash(), i+env.tcount)
- coinbaseBalanceBefore := stateDB.GetBalance(env.coinbase)
-
- var tempGasUsed uint64
- receipt, err := core.ApplyTransaction(chData.chainConfig, chData.chain, &env.coinbase, gasPool, stateDB, env.header, tx, &tempGasUsed, *chData.chain.GetVMConfig())
- if err != nil {
- return types.SimulatedBundle{}, err
- }
- if receipt.Status == types.ReceiptStatusFailed && !containsHash(bundle.RevertingTxHashes, receipt.TxHash) {
- return types.SimulatedBundle{}, errors.New("failed tx")
- }
-
- totalGasUsed += receipt.GasUsed
-
- _, err = types.Sender(env.signer, tx)
- if err != nil {
- return types.SimulatedBundle{}, err
- }
-
- // see NOTE below
- //txInPendingPool := false
- //if accountTxs, ok := pendingTxs[from]; ok {
- // // check if tx is in pending pool
- // txNonce := tx.Nonce()
- //
- // for _, accountTx := range accountTxs {
- // if accountTx.Nonce() == txNonce {
- // txInPendingPool = true
- // break
- // }
- // }
- //}
-
- gasUsed := new(big.Int).SetUint64(receipt.GasUsed)
- gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee)
- if err != nil {
- return types.SimulatedBundle{}, err
- }
- gasFeesTx := gasUsed.Mul(gasUsed, gasPrice)
- coinbaseBalanceAfter := stateDB.GetBalance(env.coinbase)
- coinbaseDelta := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
- coinbaseDelta.Sub(coinbaseDelta, gasFeesTx)
- ethSentToCoinbase.Add(ethSentToCoinbase, coinbaseDelta)
-
- // NOTE - it differs from prod!, if changed - change in commit bundle too
- //if !txInPendingPool {
- // // If tx is not in pending pool, count the gas fees
- // gasFees.Add(gasFees, gasFeesTx)
- //}
- gasFees.Add(gasFees, gasFeesTx)
- }
-
- totalEth := new(big.Int).Add(ethSentToCoinbase, gasFees)
-
- return types.SimulatedBundle{
- MevGasPrice: new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)),
- TotalEth: totalEth,
- EthSentToCoinbase: ethSentToCoinbase,
- TotalGasUsed: totalGasUsed,
- OriginalBundle: bundle,
- }, nil
-}
-
func applyTransactionWithBlacklist(signer types.Signer, config *params.ChainConfig, bc core.ChainContext, author *common.Address, gp *core.GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, blacklist map[common.Address]struct{}) (*types.Receipt, *state.StateDB, error) {
// short circuit if blacklist is empty
if len(blacklist) == 0 {
diff --git a/miner/algo_common_test.go b/miner/algo_common_test.go
index d21610f4a4..7ff498a06a 100644
--- a/miner/algo_common_test.go
+++ b/miner/algo_common_test.go
@@ -2,7 +2,11 @@ package miner
import (
"crypto/ecdsa"
+ "errors"
"fmt"
+ "math/big"
+ "testing"
+
mapset "github.com/deckarep/golang-set"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -14,8 +18,6 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
- "math/big"
- "testing"
)
const GasLimit uint64 = 30000000
@@ -33,6 +35,96 @@ type signerList struct {
nonces []uint64
}
+func simulateBundle(env *environment, bundle types.MevBundle, chData chainData, interrupt *int32) (types.SimulatedBundle, error) {
+ stateDB := env.state.Copy()
+ gasPool := new(core.GasPool).AddGas(env.header.GasLimit)
+
+ var totalGasUsed uint64
+ gasFees := big.NewInt(0)
+ ethSentToCoinbase := big.NewInt(0)
+
+ for i, tx := range bundle.Txs {
+ if checkInterrupt(interrupt) {
+ return types.SimulatedBundle{}, errInterrupt
+ }
+
+ if env.header.BaseFee != nil && tx.Type() == 2 {
+ // Sanity check for extremely large numbers
+ if tx.GasFeeCap().BitLen() > 256 {
+ return types.SimulatedBundle{}, core.ErrFeeCapVeryHigh
+ }
+ if tx.GasTipCap().BitLen() > 256 {
+ return types.SimulatedBundle{}, core.ErrTipVeryHigh
+ }
+ // Ensure gasFeeCap is greater than or equal to gasTipCap.
+ if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
+ return types.SimulatedBundle{}, core.ErrTipAboveFeeCap
+ }
+ }
+
+ stateDB.Prepare(tx.Hash(), i+env.tcount)
+ coinbaseBalanceBefore := stateDB.GetBalance(env.coinbase)
+
+ var tempGasUsed uint64
+ receipt, err := core.ApplyTransaction(chData.chainConfig, chData.chain, &env.coinbase, gasPool, stateDB, env.header, tx, &tempGasUsed, *chData.chain.GetVMConfig())
+ if err != nil {
+ return types.SimulatedBundle{}, err
+ }
+ if receipt.Status == types.ReceiptStatusFailed && !containsHash(bundle.RevertingTxHashes, receipt.TxHash) {
+ return types.SimulatedBundle{}, errors.New("failed tx")
+ }
+
+ totalGasUsed += receipt.GasUsed
+
+ _, err = types.Sender(env.signer, tx)
+ if err != nil {
+ return types.SimulatedBundle{}, err
+ }
+
+ // see NOTE below
+ //txInPendingPool := false
+ //if accountTxs, ok := pendingTxs[from]; ok {
+ // // check if tx is in pending pool
+ // txNonce := tx.Nonce()
+ //
+ // for _, accountTx := range accountTxs {
+ // if accountTx.Nonce() == txNonce {
+ // txInPendingPool = true
+ // break
+ // }
+ // }
+ //}
+
+ gasUsed := new(big.Int).SetUint64(receipt.GasUsed)
+ gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee)
+ if err != nil {
+ return types.SimulatedBundle{}, err
+ }
+ gasFeesTx := gasUsed.Mul(gasUsed, gasPrice)
+ coinbaseBalanceAfter := stateDB.GetBalance(env.coinbase)
+ coinbaseDelta := big.NewInt(0).Sub(coinbaseBalanceAfter, coinbaseBalanceBefore)
+ coinbaseDelta.Sub(coinbaseDelta, gasFeesTx)
+ ethSentToCoinbase.Add(ethSentToCoinbase, coinbaseDelta)
+
+ // NOTE - it differs from prod!, if changed - change in commit bundle too
+ //if !txInPendingPool {
+ // // If tx is not in pending pool, count the gas fees
+ // gasFees.Add(gasFees, gasFeesTx)
+ //}
+ gasFees.Add(gasFees, gasFeesTx)
+ }
+
+ totalEth := new(big.Int).Add(ethSentToCoinbase, gasFees)
+
+ return types.SimulatedBundle{
+ MevGasPrice: new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)),
+ TotalEth: totalEth,
+ EthSentToCoinbase: ethSentToCoinbase,
+ TotalGasUsed: totalGasUsed,
+ OriginalBundle: bundle,
+ }, nil
+}
+
func (sig signerList) signTx(i int, gas uint64, gasTipCap *big.Int, gasFeeCap *big.Int, to common.Address, value *big.Int, data []byte) *types.Transaction {
txData := &types.DynamicFeeTx{
ChainID: sig.config.ChainID,
diff --git a/miner/algo_greedy.go b/miner/algo_greedy.go
index 2a4b0917de..479254bf26 100644
--- a/miner/algo_greedy.go
+++ b/miner/algo_greedy.go
@@ -14,6 +14,10 @@ type chainData struct {
blacklist map[common.Address]struct{}
}
+type IBuilder interface {
+ buildBlock(simBundles []types.SimulatedBundle, transactions map[common.Address]types.Transactions) (*environment, []types.SimulatedBundle)
+}
+
// / To use it:
// / 1. Copy relevant data from the worker
// / 2. Call buildBlock
diff --git a/miner/miner.go b/miner/miner.go
index eadb5956b6..f634f81bc5 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -43,6 +43,13 @@ type Backend interface {
TxPool() *core.TxPool
}
+type AlgoType int
+
+const (
+ ALGO_MEV_GETH AlgoType = iota
+ ALGO_GREEDY
+)
+
// Config is the configuration parameters of mining.
type Config struct {
Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards (default = first account)
@@ -52,11 +59,11 @@ type Config struct {
GasFloor uint64 // Target gas floor for mined blocks.
GasCeil uint64 // Target gas ceiling for mined blocks.
GasPrice *big.Int // Minimum gas price for mining a transaction
+ AlgoType AlgoType // Algorithm to use for block building
Recommit time.Duration // The time interval for miner to re-create mining work.
Noverify bool // Disable remote mining solution verification(only useful in ethash).
BuilderTxSigningKey *ecdsa.PrivateKey // Signing key of builder coinbase to make transaction to validator
MaxMergedBundles int
- TrustedRelays []common.Address `toml:",omitempty"` // Trusted relay addresses to receive tasks from.
Blocklist []common.Address `toml:",omitempty"`
}
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index d1afb38bbd..e47ea12fca 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -141,22 +141,54 @@ func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64,
}
func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *multiWorker {
+ if config.AlgoType == ALGO_GREEDY {
+ return newMultiWorkerGreedy(config, chainConfig, engine, eth, mux, isLocalBlock, init)
+ } else {
+ return newMultiWorkerMevGeth(config, chainConfig, engine, eth, mux, isLocalBlock, init)
+ }
+}
+
+func newMultiWorkerGreedy(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *multiWorker {
queue := make(chan *task)
- regularWorker := newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
- isFlashbots: false,
- queue: queue,
+ greedyWorker := newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
+ isFlashbots: true,
+ queue: queue,
+ algoType: ALGO_GREEDY,
+ maxMergedBundles: config.MaxMergedBundles,
})
- workers := []*worker{regularWorker}
+ log.Info("creating new greedy worker")
+ return &multiWorker{
+ regularWorker: greedyWorker,
+ workers: []*worker{greedyWorker},
+ }
+}
- workers = append(workers, newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
- isFlashbots: true,
+func newMultiWorkerMevGeth(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *multiWorker {
+ queue := make(chan *task)
+
+ regularWorker := newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
+ isFlashbots: false,
queue: queue,
+ algoType: ALGO_MEV_GETH,
maxMergedBundles: config.MaxMergedBundles,
- }))
+ })
+
+ workers := []*worker{regularWorker}
+ if config.AlgoType == ALGO_MEV_GETH {
+ for i := 1; i <= config.MaxMergedBundles; i++ {
+ workers = append(workers,
+ newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
+ isFlashbots: true,
+ queue: queue,
+ algoType: ALGO_MEV_GETH,
+ maxMergedBundles: i,
+ }))
+ }
+ }
- log.Info("creating new merger worker", "config.MaxMergedBundles", config.MaxMergedBundles)
+ log.Info("creating multi worker", "config.MaxMergedBundles", config.MaxMergedBundles, "workers", len(workers))
return &multiWorker{
regularWorker: regularWorker,
workers: workers,
@@ -167,5 +199,5 @@ type flashbotsData struct {
isFlashbots bool
queue chan *task
maxMergedBundles int
- relayAddr common.Address
+ algoType AlgoType
}
diff --git a/miner/worker.go b/miner/worker.go
index 538d4f03ec..2557b45b7c 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -244,11 +244,6 @@ type worker struct {
pendingMu sync.RWMutex
pendingTasks map[common.Hash]*task
- bundleCacheMu sync.Mutex
- bundleCacheHeaderHash common.Hash
- bundleCacheSuccess map[common.Hash]simulatedBundle
- bundleCacheFailed map[common.Hash]struct{}
-
snapshotMu sync.RWMutex // The lock used to protect the snapshots below
snapshotBlock *types.Block
snapshotReceipts types.Receipts
@@ -297,28 +292,30 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
}
}
}
- log.Info("builderCoinbase", builderCoinbase.String())
+ log.Info("new worker", "builderCoinbase", builderCoinbase.String())
exitCh := make(chan struct{})
taskCh := make(chan *task)
- if flashbots.isFlashbots {
- // publish to the flashbots queue
- taskCh = flashbots.queue
- } else {
- // read from the flashbots queue
- go func() {
- for {
- select {
- case flashbotsTask := <-flashbots.queue:
+ if flashbots.algoType == ALGO_MEV_GETH {
+ if flashbots.isFlashbots {
+ // publish to the flashbots queue
+ taskCh = flashbots.queue
+ } else {
+ // read from the flashbots queue
+ go func() {
+ for {
select {
- case taskCh <- flashbotsTask:
+ case flashbotsTask := <-flashbots.queue:
+ select {
+ case taskCh <- flashbotsTask:
+ case <-exitCh:
+ return
+ }
case <-exitCh:
return
}
- case <-exitCh:
- return
}
- }
- }()
+ }()
+ }
}
blockList := make(map[common.Address]struct{})
@@ -352,8 +349,6 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
coinbase: builderCoinbase,
flashbots: flashbots,
- bundleCacheSuccess: make(map[common.Hash]simulatedBundle),
- bundleCacheFailed: make(map[common.Hash]struct{}),
}
// Subscribe NewTxsEvent for tx pool
@@ -372,7 +367,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
worker.wg.Add(2)
go worker.mainLoop()
go worker.newWorkLoop(recommit)
- if !flashbots.isFlashbots {
+ if flashbots.algoType != ALGO_MEV_GETH || !flashbots.isFlashbots {
// only mine if not flashbots
worker.wg.Add(2)
go worker.resultLoop()
@@ -1433,7 +1428,7 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment,
}
start := time.Now()
- simBundles, err := w.simulateBundles(env, bundles, pending)
+ simBundles, err := w.simulateBundles(env, bundles, nil) /* do not consider gas impact of mempool txs as bundles are treated as transactions wrt ordering */
log.Debug("Simulated bundles", "time", time.Since(start), "bundles", len(bundles))
if err != nil {
log.Error("Failed to simulate flashbots bundles", "err", err)
@@ -1443,9 +1438,9 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment,
bundlesToConsider = simBundles
}
- buider := newGreedyBuilder(w.chain, w.chainConfig, w.blockList, env, interrupt)
start := time.Now()
- newEnv, blockBundles := buider.buildBlock(bundlesToConsider, pending)
+ builder := newGreedyBuilder(w.chain, w.chainConfig, w.blockList, env, interrupt)
+ newEnv, blockBundles := builder.buildBlock(bundlesToConsider, pending)
log.Debug("Build block", "time", time.Since(start), "gas used", newEnv.header.GasUsed)
*env = *newEnv
@@ -1497,13 +1492,21 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
var blockBundles []types.SimulatedBundle
if !params.noTxs {
+ var err error
start := time.Now()
- err, mergedBundles := w.fillTransactionsAlgoWorker(nil, work, &validatorCoinbase)
+ switch w.flashbots.algoType {
+ case ALGO_GREEDY:
+ err, blockBundles = w.fillTransactionsAlgoWorker(nil, work, &validatorCoinbase)
+ case ALGO_MEV_GETH:
+ err, blockBundles = w.fillTransactions(nil, work, &validatorCoinbase)
+ default:
+ err, blockBundles = w.fillTransactions(nil, work, &validatorCoinbase)
+ }
+
if err != nil {
return nil, err
}
- blockBundles = mergedBundles
- log.Info("Filled block with transactions", "time", time.Since(start), "gas used", work.header.GasUsed)
+ log.Info("Filled block with transactions", "time", time.Since(start), "gas used", work.header.GasUsed, "txs", work.txs)
}
block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
if err != nil {
@@ -1735,15 +1738,22 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
}, mergedBundles, count, nil
}
+var (
+ g_bundleCacheMu sync.Mutex
+ g_bundleCacheHeaderHash common.Hash
+ g_bundleCacheSuccess map[common.Hash]simulatedBundle = make(map[common.Hash]simulatedBundle)
+ g_bundleCacheFailed map[common.Hash]struct{} = make(map[common.Hash]struct{})
+)
+
func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) ([]simulatedBundle, error) {
- w.bundleCacheMu.Lock()
- defer w.bundleCacheMu.Unlock()
+ g_bundleCacheMu.Lock()
+ defer g_bundleCacheMu.Unlock()
var cacheSuccess map[common.Hash]simulatedBundle
var cacheFailed map[common.Hash]struct{}
- if w.bundleCacheHeaderHash == env.header.Hash() {
- cacheSuccess = w.bundleCacheSuccess
- cacheFailed = w.bundleCacheFailed
+ if g_bundleCacheHeaderHash == env.header.Hash() {
+ cacheSuccess = g_bundleCacheSuccess
+ cacheFailed = g_bundleCacheFailed
} else {
cacheSuccess = make(map[common.Hash]simulatedBundle)
cacheFailed = make(map[common.Hash]struct{})
@@ -1791,9 +1801,9 @@ func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pe
}
}
- w.bundleCacheHeaderHash = env.header.Hash()
- w.bundleCacheSuccess = cacheSuccess
- w.bundleCacheFailed = cacheFailed
+ g_bundleCacheHeaderHash = env.header.Hash()
+ g_bundleCacheSuccess = cacheSuccess
+ g_bundleCacheFailed = cacheFailed
return simulatedBundles, nil
}
@@ -1858,24 +1868,23 @@ func (w *worker) computeBundleGas(env *environment, bundle types.MevBundle, stat
totalGasUsed += receipt.GasUsed
- // NOTE: see note below
- //from, err := types.Sender(env.signer, tx)
- //if err != nil {
- // return simulatedBundle{}, err
- //}
- //
- //txInPendingPool := false
- //if accountTxs, ok := pendingTxs[from]; ok {
- // // check if tx is in pending pool
- // txNonce := tx.Nonce()
- //
- // for _, accountTx := range accountTxs {
- // if accountTx.Nonce() == txNonce {
- // txInPendingPool = true
- // break
- // }
- // }
- //}
+ from, err := types.Sender(env.signer, tx)
+ if err != nil {
+ return simulatedBundle{}, err
+ }
+
+ txInPendingPool := false
+ if accountTxs, ok := pendingTxs[from]; ok {
+ // check if tx is in pending pool
+ txNonce := tx.Nonce()
+
+ for _, accountTx := range accountTxs {
+ if accountTx.Nonce() == txNonce {
+ txInPendingPool = true
+ break
+ }
+ }
+ }
gasUsed := new(big.Int).SetUint64(receipt.GasUsed)
gasPrice, err := tx.EffectiveGasTip(env.header.BaseFee)
@@ -1888,11 +1897,10 @@ func (w *worker) computeBundleGas(env *environment, bundle types.MevBundle, stat
coinbaseDelta.Sub(coinbaseDelta, gasFeesTx)
ethSentToCoinbase.Add(ethSentToCoinbase, coinbaseDelta)
- // NOTE: differs from prod, the reason is that it can't be manipulated anymore
- //if !txInPendingPool {
- // If tx is not in pending pool, count the gas fees
- gasFees.Add(gasFees, gasFeesTx)
- //}
+ if !txInPendingPool {
+ // If tx is not in pending pool, count the gas fees
+ gasFees.Add(gasFees, gasFeesTx)
+ }
}
totalEth := new(big.Int).Add(ethSentToCoinbase, gasFees)
From ba7664105604dd4007b39605f1573b5751e22236 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Fri, 23 Sep 2022 19:06:07 +0200
Subject: [PATCH 49/83] Enable portable blst
---
.github/workflows/go.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index f871a64839..ced95a5dc9 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -5,6 +5,10 @@ on:
pull_request:
branches: [ master ]
+env:
+ CGO_CFLAGS_ALLOW: "-O -D__BLST_PORTABLE__"
+ CGO_CFLAGS: "-O -D__BLST_PORTABLE__"
+
jobs:
build:
From 79c9121cc5705e8a97251a7a6c8ea187b23d9a38 Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Wed, 28 Sep 2022 18:23:57 +0300
Subject: [PATCH 50/83] Build on multiple tips (#35)
* remove unused beacon client from the builder
* build on multiple tips
* worker async generateWork
* rework bundle cache
* fix validate payload test in main
---
builder/block_submission_rate_limiter.go | 92 ---------
builder/block_submission_rate_limiter_test.go | 71 -------
builder/builder.go | 175 ++++++++++++------
builder/builder_test.go | 7 +-
builder/local_relay_test.go | 7 +-
builder/resubmit_utils.go | 64 +++++++
builder/resubmit_utils_test.go | 75 ++++++++
builder/resubmitter.go | 42 -----
builder/resubmitter_test.go | 55 ------
builder/service.go | 2 +-
eth/block-validation/api_test.go | 2 +-
miner/bundle_cache.go | 82 ++++++++
miner/bundle_cache_test.go | 68 +++++++
miner/multi_worker.go | 6 +
miner/worker.go | 58 ++----
miner/worker_test.go | 1 +
16 files changed, 442 insertions(+), 365 deletions(-)
delete mode 100644 builder/block_submission_rate_limiter.go
delete mode 100644 builder/block_submission_rate_limiter_test.go
create mode 100644 builder/resubmit_utils.go
create mode 100644 builder/resubmit_utils_test.go
delete mode 100644 builder/resubmitter.go
delete mode 100644 builder/resubmitter_test.go
create mode 100644 miner/bundle_cache.go
create mode 100644 miner/bundle_cache_test.go
diff --git a/builder/block_submission_rate_limiter.go b/builder/block_submission_rate_limiter.go
deleted file mode 100644
index ec83cc90b3..0000000000
--- a/builder/block_submission_rate_limiter.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package builder
-
-import (
- "context"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/core/types"
-)
-
-type blockRateLimitSubmission struct {
- resultCh chan bool
- block *types.Block
-}
-
-type BlockSubmissionRateLimiter struct {
- submissionsCh chan blockRateLimitSubmission
- started uint32
- ctx context.Context
- cancel context.CancelFunc
-}
-
-func NewBlockSubmissionRateLimiter() *BlockSubmissionRateLimiter {
- ctx, cancel := context.WithCancel(context.Background())
- r := &BlockSubmissionRateLimiter{
- submissionsCh: make(chan blockRateLimitSubmission),
- started: uint32(0),
- ctx: ctx,
- cancel: cancel,
- }
-
- return r
-}
-func (r *BlockSubmissionRateLimiter) Limit(block *types.Block) chan bool {
- resultCh := make(chan bool, 1)
- if atomic.LoadUint32(&r.started) != 1 {
- resultCh <- true
- return resultCh
- }
-
- select {
- case r.submissionsCh <- blockRateLimitSubmission{
- resultCh: resultCh,
- block: block,
- }:
- case <-r.ctx.Done():
- resultCh <- true
- }
- return resultCh
-}
-
-func (r *BlockSubmissionRateLimiter) Start() {
- if !atomic.CompareAndSwapUint32(&r.started, 0, 1) {
- return
- }
-
- go r.rateLimit()
-}
-
-func (r *BlockSubmissionRateLimiter) rateLimit() {
- for r.ctx.Err() == nil {
- // Beginning of the rate limit bucket
- bestSubmission := <-r.submissionsCh
-
- bucketCutoffCh := time.After(100 * time.Millisecond)
-
- bucketClosed := false
- for !bucketClosed {
- select {
- case <-r.ctx.Done():
- bucketClosed = true
- break
- case <-bucketCutoffCh:
- bucketClosed = true
- break
- case newSubmission := <-r.submissionsCh:
- if bestSubmission.block.Profit.Cmp(newSubmission.block.Profit) < 0 {
- bestSubmission.resultCh <- false
- bestSubmission = newSubmission
- } else {
- newSubmission.resultCh <- false
- }
- }
- }
-
- bestSubmission.resultCh <- true
- }
-}
-
-func (r *BlockSubmissionRateLimiter) Stop() {
- r.cancel()
-}
diff --git a/builder/block_submission_rate_limiter_test.go b/builder/block_submission_rate_limiter_test.go
deleted file mode 100644
index 4d30e75912..0000000000
--- a/builder/block_submission_rate_limiter_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package builder
-
-import (
- "math/big"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/stretchr/testify/require"
-)
-
-func TestLimit(t *testing.T) {
- rl := NewBlockSubmissionRateLimiter()
-
- // Check that before starting requests are passed through
- ch1 := rl.Limit(&types.Block{Profit: new(big.Int)})
- ch2 := rl.Limit(&types.Block{Profit: new(big.Int)})
- ch3 := rl.Limit(&types.Block{Profit: new(big.Int)})
-
- time.Sleep(200 * time.Millisecond)
-
- for _, ch := range []chan bool{ch1, ch2, ch3} {
- select {
- case shouldSubmit := <-ch:
- require.True(t, shouldSubmit)
- default:
- t.Error("chan was not ready")
- }
- }
-
- // Check that after starting requests are rate limited
- rl.Start()
-
- // Check that before starting requests are passed through
- ch1 = rl.Limit(&types.Block{Profit: new(big.Int)})
- ch2 = rl.Limit(&types.Block{Profit: new(big.Int)})
- ch3 = rl.Limit(&types.Block{Profit: big.NewInt(1)})
-
- time.Sleep(200 * time.Millisecond)
-
- for _, ch := range []chan bool{ch1, ch2, ch3} {
- select {
- case shouldSubmit := <-ch:
- if ch == ch3 {
- require.True(t, shouldSubmit)
- } else {
- require.False(t, shouldSubmit)
- }
- default:
- t.Error("chan was not ready")
- }
- }
-
- // Check that after stopping requests are passed through
- rl.Stop()
-
- ch1 = rl.Limit(&types.Block{Profit: new(big.Int)})
- ch2 = rl.Limit(&types.Block{Profit: new(big.Int)})
- ch3 = rl.Limit(&types.Block{Profit: new(big.Int)})
-
- time.Sleep(200 * time.Millisecond)
-
- for _, ch := range []chan bool{ch1, ch2, ch3} {
- select {
- case shouldSubmit := <-ch:
- require.True(t, shouldSubmit)
- default:
- t.Error("chan was not ready")
- }
- }
-}
diff --git a/builder/builder.go b/builder/builder.go
index 5824823169..ceb5732a5e 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -1,7 +1,9 @@
package builder
import (
+ "context"
"errors"
+ "golang.org/x/time/rate"
"math/big"
_ "os"
"sync"
@@ -44,66 +46,53 @@ type IBuilder interface {
}
type Builder struct {
- ds flashbotsextra.IDatabaseService
- beaconClient IBeaconClient
- relay IRelay
- eth IEthereumService
- resubmitter Resubmitter
- blockSubmissionRateLimiter *BlockSubmissionRateLimiter
- builderSecretKey *bls.SecretKey
- builderPublicKey boostTypes.PublicKey
- builderSigningDomain boostTypes.Domain
-
- bestMu sync.Mutex
- bestAttrs BuilderPayloadAttributes
- bestBlockProfit *big.Int
+ ds flashbotsextra.IDatabaseService
+ relay IRelay
+ eth IEthereumService
+ builderSecretKey *bls.SecretKey
+ builderPublicKey boostTypes.PublicKey
+ builderSigningDomain boostTypes.Domain
+
+ limiter *rate.Limiter
+
+ slotMu sync.Mutex
+ slot uint64
+ slotAttrs []BuilderPayloadAttributes
+ slotCtx context.Context
+ slotCtxCancel context.CancelFunc
}
-func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
+func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)
+ slotCtx, slotCtxCancel := context.WithCancel(context.Background())
return &Builder{
- ds: ds,
- beaconClient: bc,
- relay: relay,
- eth: eth,
- resubmitter: Resubmitter{},
- blockSubmissionRateLimiter: NewBlockSubmissionRateLimiter(),
- builderSecretKey: sk,
- builderPublicKey: pk,
-
+ ds: ds,
+ relay: relay,
+ eth: eth,
+ builderSecretKey: sk,
+ builderPublicKey: pk,
builderSigningDomain: builderSigningDomain,
- bestBlockProfit: big.NewInt(0),
+
+ limiter: rate.NewLimiter(rate.Every(time.Second), 1),
+ slot: 0,
+ slotCtx: slotCtx,
+ slotCtxCancel: slotCtxCancel,
}
}
func (b *Builder) Start() error {
- b.blockSubmissionRateLimiter.Start()
return nil
}
func (b *Builder) Stop() error {
- b.blockSubmissionRateLimiter.Stop()
return nil
}
func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
- b.bestMu.Lock()
- defer b.bestMu.Unlock()
-
- // Do not submit blocks that don't improve the profit
- if b.bestAttrs != *attrs {
- b.bestAttrs = *attrs
- b.bestBlockProfit.SetInt64(0)
- } else {
- if block.Profit.Cmp(b.bestBlockProfit) <= 0 {
- log.Info("Ignoring block that is not improving the profit")
- return nil
- }
- }
-
+ start := time.Now()
executableData := beacon.BlockToExecutableData(block)
payload, err := executableDataToExecutionPayload(executableData)
if err != nil {
@@ -152,9 +141,8 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
log.Info("could submit block", "bundles", len(bundles))
}
- log.Info("submitted block", "header", block.Header(), "bid", blockBidMsg)
+ log.Info("submitted block", "header", block.Header(), "bid", blockBidMsg, "time", time.Since(start))
- b.bestBlockProfit.Set(block.Profit)
return nil
}
@@ -188,29 +176,100 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
return errors.New("parent block not found in blocktree")
}
- blockHook := func(block *types.Block, bundles []types.SimulatedBundle) {
- select {
- case shouldSubmit := <-b.blockSubmissionRateLimiter.Limit(block):
- if !shouldSubmit {
- log.Info("Block rate limited", "blochHash", block.Hash())
- return
+ b.slotMu.Lock()
+ defer b.slotMu.Unlock()
+
+ if b.slot != attrs.Slot {
+ if b.slotCtxCancel != nil {
+ b.slotCtxCancel()
+ }
+
+ slotCtx, slotCtxCancel := context.WithTimeout(context.Background(), 12*time.Second)
+ b.slot = attrs.Slot
+ b.slotAttrs = nil
+ b.slotCtx = slotCtx
+ b.slotCtxCancel = slotCtxCancel
+ }
+
+ for _, currentAttrs := range b.slotAttrs {
+ if *attrs == currentAttrs {
+ log.Debug("ignoring known payload attribute", "slot", attrs.Slot, "hash", attrs.HeadHash)
+ return nil
+ }
+ }
+ b.slotAttrs = append(b.slotAttrs, *attrs)
+
+ go b.runBuildingJob(b.slotCtx, proposerPubkey, vd.FeeRecipient, attrs)
+ return nil
+}
+
+func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTypes.PublicKey, feeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) {
+ ctx, cancel := context.WithTimeout(slotCtx, 12*time.Second)
+ defer cancel()
+
+ // Submission queue for the given payload attributes
+ // multiple jobs can run for different attributes fot the given slot
+ // 1. When new block is ready we check if its profit is higher than profit of last best block
+ // if it is we set queueBest* to values of the new block and notify queueSignal channel.
+ // 2. Submission goroutine waits for queueSignal and submits queueBest* if its more valuable than
+ // queueLastSubmittedProfit keeping queueLastSubmittedProfit to be the profit of the last submission.
+ // Submission goroutine is globally rate limited to have fixed rate of submissions for all jobs.
+ var (
+ queueSignal = make(chan struct{}, 1)
+
+ queueMu sync.Mutex
+ queueLastSubmittedProfit = new(big.Int)
+ queueBestProfit = new(big.Int)
+ queueBestBlock *types.Block
+ queueBestBundles []types.SimulatedBundle
+ )
+
+ log.Debug("runBuildingJob", "slot", attrs.Slot, "parent", attrs.HeadHash)
+
+ submitBestBlock := func() {
+ queueMu.Lock()
+ if queueLastSubmittedProfit.Cmp(queueBestProfit) < 0 {
+ err := b.onSealedBlock(queueBestBlock, queueBestBundles, proposerPubkey, feeRecipient, attrs)
+ if err != nil {
+ log.Error("could not run sealed block hook", "err", err)
+ } else {
+ queueLastSubmittedProfit.Set(queueBestProfit)
}
- case <-time.After(200 * time.Millisecond):
- log.Info("Block rate limit timeout, submitting the block anyway")
}
+ queueMu.Unlock()
+ }
- err := b.onSealedBlock(block, bundles, proposerPubkey, vd.FeeRecipient, attrs)
- if err != nil {
- log.Error("could not run sealed block hook", "err", err)
+ // Empties queue, submits the best block for current job with rate limit (global for all jobs)
+ go runResubmitLoop(ctx, b.limiter, queueSignal, submitBestBlock)
+
+ // Populates queue with submissions that increase block profit
+ blockHook := func(block *types.Block, bundles []types.SimulatedBundle) {
+ if ctx.Err() != nil {
+ return
+ }
+
+ queueMu.Lock()
+ defer queueMu.Unlock()
+ if block.Profit.Cmp(queueBestProfit) > 0 {
+ queueBestBlock = block
+ queueBestBundles = bundles
+ queueBestProfit.Set(block.Profit)
+
+ select {
+ case queueSignal <- struct{}{}:
+ default:
+ }
}
}
- firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error {
- log.Info("Resubmitting build job")
- return b.eth.BuildBlock(attrs, blockHook)
+ // resubmits block builder requests every second
+ runRetryLoop(ctx, time.Second, func() {
+ log.Debug("retrying BuildBlock", "slot", attrs.Slot, "parent", attrs.HeadHash)
+ err := b.eth.BuildBlock(attrs, blockHook)
+ if err != nil {
+ log.Warn("Failed to build block", "err", err)
+ }
})
-
- return firstBlockResult
}
func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayload, error) {
diff --git a/builder/builder_test.go b/builder/builder_test.go
index b3a3f815d9..85afcaba31 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -75,12 +75,13 @@ func TestOnPayloadAttributes(t *testing.T) {
testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}
- builder := NewBuilder(sk, flashbotsextra.NilDbService{}, &testBeacon, &testRelay, bDomain, testEthService)
+ builder := NewBuilder(sk, flashbotsextra.NilDbService{}, &testRelay, bDomain, testEthService)
builder.Start()
defer builder.Stop()
err = builder.OnPayloadAttribute(testPayloadAttributes)
require.NoError(t, err)
+ time.Sleep(time.Second * 3)
require.NotNil(t, testRelay.submittedMsg)
expectedProposerPubkey, err := boostTypes.HexToPubkey(testBeacon.validator.Pk.String())
@@ -128,11 +129,11 @@ func TestOnPayloadAttributes(t *testing.T) {
// Clear the submitted message and check that the job will be ran again and but a new message will not be submitted since the profit is the same
testRelay.submittedMsg = nil
- time.Sleep(1200 * time.Millisecond)
+ time.Sleep(2200 * time.Millisecond)
require.Nil(t, testRelay.submittedMsg)
// Up the profit, expect to get the block
testEthService.testBlock.Profit.SetInt64(11)
- time.Sleep(1200 * time.Millisecond)
+ time.Sleep(2200 * time.Millisecond)
require.NotNil(t, testRelay.submittedMsg)
}
diff --git a/builder/local_relay_test.go b/builder/local_relay_test.go
index c30779a3fa..6ee0bfcc90 100644
--- a/builder/local_relay_test.go
+++ b/builder/local_relay_test.go
@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
+ "golang.org/x/time/rate"
"math/big"
"net/http"
"net/http/httptest"
@@ -30,9 +31,11 @@ func newTestBackend(t *testing.T, forkchoiceData *beacon.ExecutableDataV1, block
beaconClient := &testBeaconClient{validator: validator}
localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true)
ethService := &testEthereumService{synced: true, testExecutableData: forkchoiceData, testBlock: block}
- backend := NewBuilder(sk, flashbotsextra.NilDbService{}, beaconClient, localRelay, bDomain, ethService)
+ backend := NewBuilder(sk, flashbotsextra.NilDbService{}, localRelay, bDomain, ethService)
// service := NewService("127.0.0.1:31545", backend)
+ backend.limiter = rate.NewLimiter(rate.Inf, 0)
+
return backend, localRelay, validator
}
@@ -143,6 +146,7 @@ func TestGetHeader(t *testing.T) {
require.Equal(t, 204, rr.Code)
backend.OnPayloadAttribute(&BuilderPayloadAttributes{})
+ time.Sleep(2 * time.Second)
path = fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
rr = testRequest(t, relay, "GET", path, nil)
@@ -190,6 +194,7 @@ func TestGetPayload(t *testing.T) {
registerValidator(t, validator, relay)
backend.OnPayloadAttribute(&BuilderPayloadAttributes{})
+ time.Sleep(2 * time.Second)
path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String())
rr := testRequest(t, relay, "GET", path, nil)
diff --git a/builder/resubmit_utils.go b/builder/resubmit_utils.go
new file mode 100644
index 0000000000..749bbc2e65
--- /dev/null
+++ b/builder/resubmit_utils.go
@@ -0,0 +1,64 @@
+package builder
+
+import (
+ "context"
+ "github.com/ethereum/go-ethereum/log"
+ "golang.org/x/time/rate"
+ "time"
+)
+
+// runResubmitLoop checks for update signal and calls submit respecting provided rate limiter and context
+func runResubmitLoop(ctx context.Context, limiter *rate.Limiter, updateSignal chan struct{}, submit func()) {
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-updateSignal:
+ res := limiter.Reserve()
+ if !res.OK() {
+ log.Warn("resubmit loop failed to make limiter reservation")
+ return
+ }
+
+ // check if we could make submission before context ctxDeadline
+ if ctxDeadline, ok := ctx.Deadline(); ok {
+ delayDeadline := time.Now().Add(res.Delay())
+ if delayDeadline.After(ctxDeadline) {
+ res.Cancel()
+ return
+ }
+ }
+
+ delay := res.Delay()
+ if delay == 0 {
+ submit()
+ continue
+ }
+
+ t := time.NewTimer(delay)
+ select {
+ case <-t.C:
+ submit()
+ continue
+ case <-ctx.Done():
+ res.Cancel()
+ t.Stop()
+ return
+ }
+ }
+ }
+}
+
+// runRetryLoop calls retry periodically with the provided interval respecting context cancellation
+func runRetryLoop(ctx context.Context, interval time.Duration, retry func()) {
+ t := time.NewTicker(interval)
+ defer t.Stop()
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-t.C:
+ retry()
+ }
+ }
+}
diff --git a/builder/resubmit_utils_test.go b/builder/resubmit_utils_test.go
new file mode 100644
index 0000000000..d451097fd5
--- /dev/null
+++ b/builder/resubmit_utils_test.go
@@ -0,0 +1,75 @@
+package builder
+
+import (
+ "context"
+ "golang.org/x/time/rate"
+ "math/rand"
+ "sort"
+ "sync"
+ "testing"
+ "time"
+)
+
+type submission struct {
+ t time.Time
+ v int
+}
+
+func TestResubmitUtils(t *testing.T) {
+ const (
+ totalTime = time.Second
+ rateLimitTime = 100 * time.Millisecond
+ resubmitInterval = 10 * time.Millisecond
+ )
+
+ ctx, cancel := context.WithTimeout(context.Background(), totalTime)
+ defer cancel()
+ limiter := rate.NewLimiter(rate.Every(rateLimitTime), 1)
+
+ var (
+ signal = make(chan struct{}, 1)
+ subMu sync.Mutex
+ subLast int
+ subBest int
+ subAll []submission
+ )
+
+ go runResubmitLoop(ctx, limiter, signal, func() {
+ subMu.Lock()
+ defer subMu.Unlock()
+
+ if subBest > subLast {
+ subAll = append(subAll, submission{time.Now(), subBest})
+ subLast = subBest
+ }
+ })
+
+ runRetryLoop(ctx, resubmitInterval, func() {
+ subMu.Lock()
+ defer subMu.Unlock()
+
+ value := rand.Int()
+ if value > subBest {
+ subBest = value
+
+ select {
+ case signal <- struct{}{}:
+ default:
+ }
+ }
+ })
+
+ sorted := sort.SliceIsSorted(subAll, func(i, j int) bool {
+ return subAll[i].v < subAll[j].v
+ })
+ if !sorted {
+ t.Error("submissions are not sorted")
+ }
+
+ for i := 0; i < len(subAll)-1; i++ {
+ interval := subAll[i+1].t.Sub(subAll[i].t)
+ if interval+10*time.Millisecond < rateLimitTime {
+ t.Errorf("submissions are not rate limited: interval %s, limit %s", interval, rateLimitTime)
+ }
+ }
+}
diff --git a/builder/resubmitter.go b/builder/resubmitter.go
deleted file mode 100644
index e167badbab..0000000000
--- a/builder/resubmitter.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package builder
-
-import (
- "context"
- "sync"
- "time"
-)
-
-type Resubmitter struct {
- mu sync.Mutex
- cancel context.CancelFunc
-}
-
-func (r *Resubmitter) newTask(repeatFor time.Duration, interval time.Duration, fn func() error) error {
- repeatUntilCh := time.After(repeatFor)
-
- r.mu.Lock()
- if r.cancel != nil {
- r.cancel()
- }
- ctx, cancel := context.WithCancel(context.Background())
- r.cancel = cancel
- r.mu.Unlock()
-
- firstRunErr := fn()
-
- go func() {
- for ctx.Err() == nil {
- select {
- case <-ctx.Done():
- return
- case <-repeatUntilCh:
- cancel()
- return
- case <-time.After(interval):
- fn()
- }
- }
- }()
-
- return firstRunErr
-}
diff --git a/builder/resubmitter_test.go b/builder/resubmitter_test.go
deleted file mode 100644
index e60ff3ef77..0000000000
--- a/builder/resubmitter_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package builder
-
-import (
- "errors"
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestResubmitter(t *testing.T) {
- resubmitter := Resubmitter{}
-
- pingCh := make(chan error)
- go func() {
- res := resubmitter.newTask(time.Second, 100*time.Millisecond, func() error {
- return <-pingCh
- })
- require.ErrorContains(t, res, "xx")
- }()
-
- select {
- case pingCh <- errors.New("xx"):
- case <-time.After(time.Second):
- t.Error("timeout waiting for the function")
- }
-
- select {
- case pingCh <- nil:
- t.Error("function restarted too soon")
- default:
- }
-
- time.Sleep(200 * time.Millisecond)
-
- select {
- case pingCh <- nil:
- default:
- t.Error("function restarted too late")
- }
-
- time.Sleep(800 * time.Millisecond)
-
- select {
- case pingCh <- nil:
- default:
- t.Error("function restarted too late")
- }
-
- select {
- case pingCh <- nil:
- t.Error("function restarted after deadline")
- case <-time.After(200 * time.Millisecond):
- }
-}
diff --git a/builder/service.go b/builder/service.go
index f4b99413f3..6a1ace06d6 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -193,7 +193,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
go bundleFetcher.Run()
ethereumService := NewEthereumService(backend)
- builderBackend := NewBuilder(builderSk, ds, beaconClient, relay, builderSigningDomain, ethereumService)
+ builderBackend := NewBuilder(builderSk, ds, relay, builderSigningDomain, ethereumService)
builderService := NewService(cfg.ListenAddr, localRelay, builderBackend)
stack.RegisterAPIs([]rpc.API{
diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go
index e93092467e..f7945399d6 100644
--- a/eth/block-validation/api_test.go
+++ b/eth/block-validation/api_test.go
@@ -142,7 +142,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
invalidPayload.LogsBloom = boostTypes.Bloom{}
copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
blockRequest.ExecutionPayload = invalidPayload
- copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x08eaca07137419b4b7f8770530b2db870f6e1cc3eabd355b8f8301cec9fca5f3")[:32])
+ copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x65cded68b85277f489f22497731d8cece9e42f0429a250a5022a9417408f3998")[:32])
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value")
}
diff --git a/miner/bundle_cache.go b/miner/bundle_cache.go
new file mode 100644
index 0000000000..42a810bd21
--- /dev/null
+++ b/miner/bundle_cache.go
@@ -0,0 +1,82 @@
+package miner
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "sync"
+)
+
+const (
+ maxHeaders = 3
+)
+
+type BundleCache struct {
+ mu sync.Mutex
+ entries []*BundleCacheEntry
+}
+
+func NewBundleCache() *BundleCache {
+ return &BundleCache{
+ entries: make([]*BundleCacheEntry, maxHeaders),
+ }
+}
+
+func (b *BundleCache) GetBundleCache(header common.Hash) *BundleCacheEntry {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ for _, entry := range b.entries {
+ if entry != nil && entry.headerHash == header {
+ return entry
+ }
+ }
+ newEntry := newCacheEntry(header)
+ b.entries = b.entries[1:]
+ b.entries = append(b.entries, newEntry)
+
+ return newEntry
+}
+
+type BundleCacheEntry struct {
+ mu sync.Mutex
+ headerHash common.Hash
+ successfulBundles map[common.Hash]*simulatedBundle
+ failedBundles map[common.Hash]struct{}
+}
+
+func newCacheEntry(header common.Hash) *BundleCacheEntry {
+ return &BundleCacheEntry{
+ headerHash: header,
+ successfulBundles: make(map[common.Hash]*simulatedBundle),
+ failedBundles: make(map[common.Hash]struct{}),
+ }
+}
+
+func (c *BundleCacheEntry) GetSimulatedBundle(bundle common.Hash) (*types.SimulatedBundle, bool) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if simmed, ok := c.successfulBundles[bundle]; ok {
+ return simmed, true
+ }
+
+ if _, ok := c.failedBundles[bundle]; ok {
+ return nil, true
+ }
+
+ return nil, false
+}
+
+func (c *BundleCacheEntry) UpdateSimulatedBundles(result []*types.SimulatedBundle, bundles []types.MevBundle) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ for i, simBundle := range result {
+ bundleHash := bundles[i].Hash
+ if simBundle != nil {
+ c.successfulBundles[bundleHash] = simBundle
+ } else {
+ c.failedBundles[bundleHash] = struct{}{}
+ }
+ }
+}
diff --git a/miner/bundle_cache_test.go b/miner/bundle_cache_test.go
new file mode 100644
index 0000000000..71fcf376f6
--- /dev/null
+++ b/miner/bundle_cache_test.go
@@ -0,0 +1,68 @@
+package miner
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "testing"
+)
+
+func TestBundleCacheEntry(t *testing.T) {
+ entry := newCacheEntry(common.HexToHash("0x01"))
+
+ failingBundle := common.HexToHash("0xff")
+ successBundle := common.HexToHash("0xaa")
+
+ sim, found := entry.GetSimulatedBundle(failingBundle)
+ if sim != nil || found {
+ t.Errorf("found bundle in empty cache: %s", failingBundle)
+ }
+ sim, found = entry.GetSimulatedBundle(successBundle)
+ if sim != nil || found {
+ t.Errorf("found bundle in empty cache: %s", successBundle)
+ }
+
+ bundles := []types.MevBundle{types.MevBundle{Hash: failingBundle}, types.MevBundle{Hash: successBundle}}
+ simResult := []*types.SimulatedBundle{nil, &types.SimulatedBundle{OriginalBundle: bundles[1]}}
+ entry.UpdateSimulatedBundles(simResult, bundles)
+
+ sim, found = entry.GetSimulatedBundle(failingBundle)
+ if sim != nil || !found {
+ t.Error("incorrect failing bundle result")
+ }
+ sim, found = entry.GetSimulatedBundle(successBundle)
+ if sim != simResult[1] || !found {
+ t.Error("incorrect successful bundle result")
+ }
+}
+
+func TestBundleCache(t *testing.T) {
+ cache := NewBundleCache()
+
+ header1 := common.HexToHash("0x01")
+ header2 := common.HexToHash("0x02")
+ header3 := common.HexToHash("0x03")
+ header4 := common.HexToHash("0x04")
+
+ cache1 := cache.GetBundleCache(header1)
+ if cache1.headerHash != header1 {
+ t.Error("incorrect header cache")
+ }
+
+ cache2 := cache.GetBundleCache(header2)
+ if cache2.headerHash != header2 {
+ t.Error("incorrect header cache")
+ }
+
+ cache2Again := cache.GetBundleCache(header2)
+ if cache2 != cache2Again {
+ t.Error("header cache is not reused")
+ }
+
+ cache.GetBundleCache(header3)
+ cache.GetBundleCache(header4)
+
+ cache1Again := cache.GetBundleCache(header1)
+ if cache1 == cache1Again {
+ t.Error("cache1 should be removed after insertions")
+ }
+}
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index e47ea12fca..41ef0ea813 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -156,6 +156,7 @@ func newMultiWorkerGreedy(config *Config, chainConfig *params.ChainConfig, engin
queue: queue,
algoType: ALGO_GREEDY,
maxMergedBundles: config.MaxMergedBundles,
+ bundleCache: NewBundleCache(),
})
log.Info("creating new greedy worker")
@@ -168,11 +169,14 @@ func newMultiWorkerGreedy(config *Config, chainConfig *params.ChainConfig, engin
func newMultiWorkerMevGeth(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *multiWorker {
queue := make(chan *task)
+ bundleCache := NewBundleCache()
+
regularWorker := newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
isFlashbots: false,
queue: queue,
algoType: ALGO_MEV_GETH,
maxMergedBundles: config.MaxMergedBundles,
+ bundleCache: bundleCache,
})
workers := []*worker{regularWorker}
@@ -184,6 +188,7 @@ func newMultiWorkerMevGeth(config *Config, chainConfig *params.ChainConfig, engi
queue: queue,
algoType: ALGO_MEV_GETH,
maxMergedBundles: i,
+ bundleCache: bundleCache,
}))
}
}
@@ -200,4 +205,5 @@ type flashbotsData struct {
queue chan *task
maxMergedBundles int
algoType AlgoType
+ bundleCache *BundleCache
}
diff --git a/miner/worker.go b/miner/worker.go
index 2557b45b7c..db12d838a0 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -628,14 +628,16 @@ func (w *worker) mainLoop() {
}
case req := <-w.getWorkCh:
- block, err := w.generateWork(req.params)
- if err != nil {
- req.err <- err
- req.result <- nil
- } else {
- req.err <- nil
- req.result <- block
- }
+ go func() {
+ block, err := w.generateWork(req.params)
+ if err != nil {
+ req.err <- err
+ req.result <- nil
+ } else {
+ req.err <- nil
+ req.result <- block
+ }
+ }()
case ev := <-w.chainSideCh:
// Short circuit for duplicate side blocks
if _, exist := w.localUncles[ev.Block.Hash()]; exist {
@@ -1738,37 +1740,16 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
}, mergedBundles, count, nil
}
-var (
- g_bundleCacheMu sync.Mutex
- g_bundleCacheHeaderHash common.Hash
- g_bundleCacheSuccess map[common.Hash]simulatedBundle = make(map[common.Hash]simulatedBundle)
- g_bundleCacheFailed map[common.Hash]struct{} = make(map[common.Hash]struct{})
-)
-
func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) ([]simulatedBundle, error) {
- g_bundleCacheMu.Lock()
- defer g_bundleCacheMu.Unlock()
-
- var cacheSuccess map[common.Hash]simulatedBundle
- var cacheFailed map[common.Hash]struct{}
- if g_bundleCacheHeaderHash == env.header.Hash() {
- cacheSuccess = g_bundleCacheSuccess
- cacheFailed = g_bundleCacheFailed
- } else {
- cacheSuccess = make(map[common.Hash]simulatedBundle)
- cacheFailed = make(map[common.Hash]struct{})
- }
+ headerHash := env.header.Hash()
+ simCache := w.flashbots.bundleCache.GetBundleCache(headerHash)
simResult := make([]*simulatedBundle, len(bundles))
var wg sync.WaitGroup
for i, bundle := range bundles {
- if simmed, ok := cacheSuccess[bundle.Hash]; ok {
- simResult[i] = &simmed
- continue
- }
-
- if _, ok := cacheFailed[bundle.Hash]; ok {
+ if simmed, ok := simCache.GetSimulatedBundle(bundle.Hash); ok {
+ simResult[i] = simmed
continue
}
@@ -1791,20 +1772,15 @@ func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pe
wg.Wait()
+ simCache.UpdateSimulatedBundles(simResult, bundles)
+
simulatedBundles := make([]simulatedBundle, 0, len(bundles))
- for i, bundle := range simResult {
+ for _, bundle := range simResult {
if bundle != nil {
simulatedBundles = append(simulatedBundles, *bundle)
- cacheSuccess[bundle.OriginalBundle.Hash] = *bundle
- } else {
- cacheFailed[bundles[i].Hash] = struct{}{}
}
}
- g_bundleCacheHeaderHash = env.header.Hash()
- g_bundleCacheSuccess = cacheSuccess
- g_bundleCacheFailed = cacheFailed
-
return simulatedBundles, nil
}
diff --git a/miner/worker_test.go b/miner/worker_test.go
index 6b4187d5d7..4c49c91194 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -204,6 +204,7 @@ func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consens
w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, &flashbotsData{
isFlashbots: false,
queue: nil,
+ bundleCache: NewBundleCache(),
})
w.setEtherbase(testBankAddress)
return w, backend
From fade0b732f21b4185ed4d48765a91e4222015899 Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Wed, 28 Sep 2022 19:23:26 +0300
Subject: [PATCH 51/83] better logs (#36)
* miner logs
* builder logs
---
builder/builder.go | 7 ++-----
miner/algo_common.go | 8 ++++----
miner/algo_greedy.go | 6 +++---
miner/worker.go | 24 ++++++------------------
4 files changed, 15 insertions(+), 30 deletions(-)
diff --git a/builder/builder.go b/builder/builder.go
index ceb5732a5e..0272ddf39c 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -92,7 +92,6 @@ func (b *Builder) Stop() error {
}
func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
- start := time.Now()
executableData := beacon.BlockToExecutableData(block)
payload, err := executableDataToExecutionPayload(executableData)
if err != nil {
@@ -137,11 +136,9 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
if err != nil {
log.Error("could not submit block", "err", err, "bundles", len(bundles))
return err
- } else {
- log.Info("could submit block", "bundles", len(bundles))
}
- log.Info("submitted block", "header", block.Header(), "bid", blockBidMsg, "time", time.Since(start))
+ log.Info("submitted block", "slot", blockBidMsg.Slot, "value", blockBidMsg.Value.String(), "parent", blockBidMsg.ParentHash, "hash", block.Hash(), "bundles", len(bundles))
return nil
}
@@ -172,7 +169,7 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
parentBlock := b.eth.GetBlockByHash(attrs.HeadHash)
if parentBlock == nil {
- log.Info("Block hash not found in blocktree", "head block hash", attrs.HeadHash)
+ log.Warn("Block hash not found in blocktree", "head block hash", attrs.HeadHash)
return errors.New("parent block not found in blocktree")
}
diff --git a/miner/algo_common.go b/miner/algo_common.go
index d6c3f50392..db85457ce0 100644
--- a/miner/algo_common.go
+++ b/miner/algo_common.go
@@ -174,7 +174,7 @@ func (envDiff *environmentDiff) commitTx(tx *types.Transaction, chData chainData
default:
// Strange error, discard the transaction and get the next in line (note, the
// nonce-too-high clause will prevent us from executing in vain).
- log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
+ log.Trace("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
return nil, shiftTx, err
}
}
@@ -231,12 +231,12 @@ func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chDa
receipt, _, err := tmpEnvDiff.commitTx(tx, chData)
if err != nil {
- log.Debug("Bundle tx error", "bundle", bundle.OriginalBundle.Hash, "tx", tx.Hash(), "err", err)
+ log.Trace("Bundle tx error", "bundle", bundle.OriginalBundle.Hash, "tx", tx.Hash(), "err", err)
return err
}
if receipt.Status != types.ReceiptStatusSuccessful && !bundle.OriginalBundle.RevertingHash(tx.Hash()) {
- log.Debug("Bundle tx failed", "bundle", bundle.OriginalBundle.Hash, "tx", tx.Hash(), "err", err)
+ log.Trace("Bundle tx failed", "bundle", bundle.OriginalBundle.Hash, "tx", tx.Hash(), "err", err)
return errors.New("bundle tx revert")
}
@@ -256,7 +256,7 @@ func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chDa
bundleSimEffGP.Mul(bundleSimEffGP, big.NewInt(99))
if bundleSimEffGP.Cmp(bundleActualEffGP) == 1 {
- log.Debug("Bundle underpays after inclusion", "bundle", bundle.OriginalBundle.Hash)
+ log.Trace("Bundle underpays after inclusion", "bundle", bundle.OriginalBundle.Hash)
return errors.New("bundle underpays")
}
diff --git a/miner/algo_greedy.go b/miner/algo_greedy.go
index 479254bf26..d4912e4fa3 100644
--- a/miner/algo_greedy.go
+++ b/miner/algo_greedy.go
@@ -66,7 +66,7 @@ func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transacti
}
if err != nil {
- log.Info("could not apply tx", "hash", order.Tx.Hash(), "err", err)
+ log.Trace("could not apply tx", "hash", order.Tx.Hash(), "err", err)
continue
}
effGapPrice, err := order.Tx.EffectiveGasTip(env.header.BaseFee)
@@ -79,11 +79,11 @@ func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transacti
err := envDiff.commitBundle(bundle, b.chainData, b.interrupt)
orders.Pop()
if err != nil {
- log.Info("Could not apply bundle", "bundle", bundle.OriginalBundle.Hash, "err", err)
+ log.Trace("Could not apply bundle", "bundle", bundle.OriginalBundle.Hash, "err", err)
continue
}
- log.Info("Included bundle", "bundleEGP", bundle.MevGasPrice.String(), "gasUsed", bundle.TotalGasUsed, "ethToCoinbase", ethIntToFloat(bundle.TotalEth))
+ log.Trace("Included bundle", "bundleEGP", bundle.MevGasPrice.String(), "gasUsed", bundle.TotalGasUsed, "ethToCoinbase", ethIntToFloat(bundle.TotalEth))
usedBundles = append(usedBundles, *bundle)
}
}
diff --git a/miner/worker.go b/miner/worker.go
index db12d838a0..1f7227d469 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1373,30 +1373,25 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil {
builderCoinbaseBalanceAfter := env.state.GetBalance(w.coinbase)
- log.Info("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
+ log.Trace("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
profit := new(big.Int).Sub(builderCoinbaseBalanceAfter, builderCoinbaseBalanceBefore)
env.gasPool.AddGas(paymentTxGas)
if profit.Sign() == 1 {
tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit)
if err != nil {
- log.Error("Proposer payout create tx failed", "err", err)
return fmt.Errorf("proposer payout create tx failed - %v", err), nil
}
if tx != nil {
- log.Info("Proposer payout create tx succeeded, proceeding to commit tx")
_, err = w.commitTransaction(env, tx)
if err != nil {
- log.Error("Proposer payout commit tx failed", "hash", tx.Hash().String(), "err", err)
return fmt.Errorf("proposer payout commit tx failed - %v", err), nil
}
- log.Info("Proposer payout commit tx succeeded", "hash", tx.Hash().String())
env.tcount++
} else {
return errors.New("proposer payout create tx failed due to tx is nil"), nil
}
} else {
- log.Warn("Proposer payout create tx failed due to not enough balance", "profit", profit.String())
return errors.New("proposer payout create tx failed due to not enough balance"), nil
}
}
@@ -1443,35 +1438,30 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment,
start := time.Now()
builder := newGreedyBuilder(w.chain, w.chainConfig, w.blockList, env, interrupt)
newEnv, blockBundles := builder.buildBlock(bundlesToConsider, pending)
- log.Debug("Build block", "time", time.Since(start), "gas used", newEnv.header.GasUsed)
+ log.Debug("Build block", "time", time.Since(start), "gasUsed", newEnv.header.GasUsed)
*env = *newEnv
if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil {
builderCoinbaseBalanceAfter := env.state.GetBalance(w.coinbase)
- log.Info("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
+ log.Trace("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
profit := new(big.Int).Sub(builderCoinbaseBalanceAfter, builderCoinbaseBalanceBefore)
env.gasPool.AddGas(params.TxGas)
if profit.Sign() == 1 {
tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit)
if err != nil {
- log.Error("Proposer payout create tx failed", "err", err)
return fmt.Errorf("proposer payout create tx failed - %v", err), nil
}
if tx != nil {
- log.Info("Proposer payout create tx succeeded, proceeding to commit tx")
_, err = w.commitTransaction(env, tx)
if err != nil {
- log.Error("Proposer payout commit tx failed", "hash", tx.Hash().String(), "err", err)
return fmt.Errorf("proposer payout commit tx failed - %v", err), nil
}
- log.Info("Proposer payout commit tx succeeded", "hash", tx.Hash().String())
env.tcount++
} else {
return errors.New("proposer payout create tx failed due to tx is nil"), nil
}
} else {
- log.Warn("Proposer payout create tx failed due to not enough balance", "profit", profit.String())
return errors.New("proposer payout create tx failed due to not enough balance"), nil
}
@@ -1508,7 +1498,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
if err != nil {
return nil, err
}
- log.Info("Filled block with transactions", "time", time.Since(start), "gas used", work.header.GasUsed, "txs", work.txs)
+ log.Debug("Filled block with transactions", "time", time.Since(start), "gas used", work.header.GasUsed, "txs", len(work.txs))
}
block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
if err != nil {
@@ -1540,14 +1530,12 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
}
block.Profit.Set(lastTx.Value())
- log.Info("Block finalized and assembled", "blockProfit", block.Profit.String(), "proposer payment tx", lastTx, "receipt", receipt)
+ log.Info("Block finalized and assembled", "blockProfit", ethIntToFloat(block.Profit), "bundles", len(blockBundles), "gasUsed", block.GasUsed())
if params.onBlock != nil {
go params.onBlock(block, blockBundles)
}
- log.Info("Block finalized and assembled", "blockProfit", block.Profit.String(), "hash", block.Hash())
-
return block, nil
}
@@ -1937,7 +1925,7 @@ func (w *worker) createProposerPayoutTx(env *environment, recipient *common.Addr
}
gasPrice := new(big.Int).Set(env.header.BaseFee)
chainId := w.chainConfig.ChainID
- log.Debug("createProposerPayoutTx", "sender", sender, "chainId", chainId.String(), "nonce", nonce, "amount", amount.String(), "baseFee", env.header.BaseFee.String(), "fee", fee)
+ log.Trace("createProposerPayoutTx", "sender", sender, "chainId", chainId.String(), "nonce", nonce, "amount", amount.String(), "baseFee", env.header.BaseFee.String(), "fee", fee)
tx := types.NewTransaction(nonce, *recipient, amount, paymentTxGas, gasPrice, nil)
return types.SignTx(tx, types.LatestSignerForChainID(chainId), w.config.BuilderTxSigningKey)
}
From e5037ec6b68472c99271a6554b2b10d1a0eafcee Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Wed, 28 Sep 2022 19:43:14 +0300
Subject: [PATCH 52/83] squash more annoying logs (#37)
---
miner/algo_greedy.go | 4 ----
miner/worker.go | 1 -
2 files changed, 5 deletions(-)
diff --git a/miner/algo_greedy.go b/miner/algo_greedy.go
index d4912e4fa3..d1fd114625 100644
--- a/miner/algo_greedy.go
+++ b/miner/algo_greedy.go
@@ -42,10 +42,6 @@ func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transacti
env := b.inputEnvironment.copy()
orders := types.NewTransactionsByPriceAndNonce(env.signer, transactions, simBundles, env.header.BaseFee)
- log.Debug("buildBlock", "totalBundles", len(simBundles))
- for _, bundle := range simBundles {
- log.Debug("buildBlock", "simBHash", bundle.OriginalBundle.Hash)
- }
envDiff := newEnvironmentDiff(env)
usedBundles := make([]types.SimulatedBundle, 0)
diff --git a/miner/worker.go b/miner/worker.go
index 1f7227d469..6b2a377eda 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1916,7 +1916,6 @@ func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float {
func (w *worker) createProposerPayoutTx(env *environment, recipient *common.Address, profit *big.Int) (*types.Transaction, error) {
sender := w.coinbase.String()
- log.Info(sender)
nonce := env.state.GetNonce(w.coinbase)
fee := new(big.Int).Mul(big.NewInt(paymentTxGas), env.header.BaseFee)
amount := new(big.Int).Sub(profit, fee)
From cee22faa07fbd712091aeab4a3b78e2fabaeaabd Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Wed, 28 Sep 2022 20:26:56 +0300
Subject: [PATCH 53/83] Logs pt 3 (#38)
* log more info about bundles for all algos
* remove redundant build block log
* make fetch logs debug
---
builder/relay.go | 3 +--
flashbotsextra/fetcher.go | 4 ++--
miner/worker.go | 14 ++++++--------
3 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/builder/relay.go b/builder/relay.go
index 4d9e192c4c..4642707d41 100644
--- a/builder/relay.go
+++ b/builder/relay.go
@@ -103,8 +103,7 @@ func (r *RemoteRelay) updateValidatorsMap(currentSlot uint64, retries int) error
r.lastRequestedSlot = currentSlot
r.validatorsLock.Unlock()
- log.Info("Updated validators", "new", newMap, "for slot", currentSlot)
-
+ log.Info("Updated validators", "count", len(newMap), "slot", currentSlot)
return nil
}
diff --git a/flashbotsextra/fetcher.go b/flashbotsextra/fetcher.go
index 956698bc15..484aa1a4ee 100644
--- a/flashbotsextra/fetcher.go
+++ b/flashbotsextra/fetcher.go
@@ -89,7 +89,7 @@ func (b *bundleFetcher) fetchAndPush(ctx context.Context, pushMevBundles func(bu
log.Error("failed to fetch high prio bundles", "err", err)
continue
}
- log.Info("Fetching High prio bundles", "size", len(bundles), "currentlyBuiltBlockNum", currentBlockNum+1)
+ log.Debug("Fetching High prio bundles", "size", len(bundles), "currentlyBuiltBlockNum", currentBlockNum+1)
if len(bundles) != 0 {
pushMevBundles(bundles)
}
@@ -105,7 +105,7 @@ func (b *bundleFetcher) fetchAndPush(ctx context.Context, pushMevBundles func(bu
log.Error("failed to fetch low prio bundles", "err", err)
continue
}
- log.Info("Fetching low prio bundles", "len", len(bundles), "currentlyBuiltBlockNum", currentBlockNum+1)
+ log.Debug("Fetching low prio bundles", "len", len(bundles), "currentlyBuiltBlockNum", currentBlockNum+1)
if len(bundles) != 0 {
pushMevBundles(bundles)
}
diff --git a/miner/worker.go b/miner/worker.go
index 6b2a377eda..97548009c3 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1424,9 +1424,7 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment,
return err, nil
}
- start := time.Now()
simBundles, err := w.simulateBundles(env, bundles, nil) /* do not consider gas impact of mempool txs as bundles are treated as transactions wrt ordering */
- log.Debug("Simulated bundles", "time", time.Since(start), "bundles", len(bundles))
if err != nil {
log.Error("Failed to simulate flashbots bundles", "err", err)
return err, nil
@@ -1435,10 +1433,8 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment,
bundlesToConsider = simBundles
}
- start := time.Now()
builder := newGreedyBuilder(w.chain, w.chainConfig, w.blockList, env, interrupt)
newEnv, blockBundles := builder.buildBlock(bundlesToConsider, pending)
- log.Debug("Build block", "time", time.Since(start), "gasUsed", newEnv.header.GasUsed)
*env = *newEnv
if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil {
@@ -1472,6 +1468,7 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment,
// generateWork generates a sealing block based on the given parameters.
func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
+ start := time.Now()
validatorCoinbase := params.coinbase
// Set builder coinbase to be passed to beacon header
params.coinbase = w.coinbase
@@ -1485,7 +1482,6 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
var blockBundles []types.SimulatedBundle
if !params.noTxs {
var err error
- start := time.Now()
switch w.flashbots.algoType {
case ALGO_GREEDY:
err, blockBundles = w.fillTransactionsAlgoWorker(nil, work, &validatorCoinbase)
@@ -1498,7 +1494,6 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
if err != nil {
return nil, err
}
- log.Debug("Filled block with transactions", "time", time.Since(start), "gas used", work.header.GasUsed, "txs", len(work.txs))
}
block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
if err != nil {
@@ -1530,7 +1525,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
}
block.Profit.Set(lastTx.Value())
- log.Info("Block finalized and assembled", "blockProfit", ethIntToFloat(block.Profit), "bundles", len(blockBundles), "gasUsed", block.GasUsed())
+ log.Info("Block finalized and assembled", "blockProfit", ethIntToFloat(block.Profit), "txs", len(work.txs), "bundles", len(blockBundles), "gasUsed", block.GasUsed(), "time", time.Since(start))
if params.onBlock != nil {
go params.onBlock(block, blockBundles)
@@ -1729,6 +1724,7 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi
}
func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) ([]simulatedBundle, error) {
+ start := time.Now()
headerHash := env.header.Hash()
simCache := w.flashbots.bundleCache.GetBundleCache(headerHash)
@@ -1751,7 +1747,7 @@ func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pe
simmed, err := w.computeBundleGas(env, bundle, state, gasPool, pendingTxs, 0)
if err != nil {
- log.Debug("Error computing gas for a bundle", "error", err)
+ log.Trace("Error computing gas for a bundle", "error", err)
return
}
simResult[idx] = &simmed
@@ -1769,6 +1765,8 @@ func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pe
}
}
+ okBundles := len(bundles) - len(simulatedBundles)
+ log.Debug("Simulated bundles", "block", env.header.Number, "allBundles", len(bundles), "okBundles", okBundles, "time", time.Since(start))
return simulatedBundles, nil
}
From c5dae01675a66f6981673e141b183accaf69f6e1 Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Wed, 5 Oct 2022 14:09:37 +0300
Subject: [PATCH 54/83] New payment tx (#40)
* Rework payment tx
* move env access from worker
* add builder.dry-run
* Move proposer tx from fillTransactions
* Use one flag for validation blocklist
---
builder/builder.go | 25 +++--
builder/builder_test.go | 2 +-
builder/local_relay_test.go | 2 +-
builder/service.go | 17 ++-
cmd/geth/config.go | 4 +-
cmd/geth/main.go | 1 +
cmd/utils/flags.go | 8 +-
eth/block-validation/api.go | 9 +-
eth/block-validation/api_test.go | 4 +-
miner/algo_common.go | 112 ++++++++++++++++++++
miner/algo_common_test.go | 73 ++++++++++++-
miner/miner.go | 12 +++
miner/worker.go | 173 +++++++++++++------------------
miner/worker_test.go | 1 -
14 files changed, 317 insertions(+), 126 deletions(-)
diff --git a/builder/builder.go b/builder/builder.go
index 0272ddf39c..a20897477a 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -3,6 +3,7 @@ package builder
import (
"context"
"errors"
+ blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation"
"golang.org/x/time/rate"
"math/big"
_ "os"
@@ -49,6 +50,8 @@ type Builder struct {
ds flashbotsextra.IDatabaseService
relay IRelay
eth IEthereumService
+ dryRun bool
+ validator *blockvalidation.BlockValidationAPI
builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
builderSigningDomain boostTypes.Domain
@@ -62,7 +65,7 @@ type Builder struct {
slotCtxCancel context.CancelFunc
}
-func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
+func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService, dryRun bool, validator *blockvalidation.BlockValidationAPI) *Builder {
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)
@@ -72,6 +75,8 @@ func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRe
ds: ds,
relay: relay,
eth: eth,
+ dryRun: dryRun,
+ validator: validator,
builderSecretKey: sk,
builderPublicKey: pk,
builderSigningDomain: builderSigningDomain,
@@ -118,8 +123,6 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
Value: *value,
}
- go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)
-
signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
if err != nil {
log.Error("could not sign builder bid", "err", err)
@@ -132,10 +135,18 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
ExecutionPayload: payload,
}
- err = b.relay.SubmitBlock(&blockSubmitReq)
- if err != nil {
- log.Error("could not submit block", "err", err, "bundles", len(bundles))
- return err
+ if b.dryRun {
+ err = b.validator.ValidateBuilderSubmissionV1(&blockSubmitReq)
+ if err != nil {
+ log.Error("could not validate block", "err", err)
+ }
+ } else {
+ go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)
+ err = b.relay.SubmitBlock(&blockSubmitReq)
+ if err != nil {
+ log.Error("could not submit block", "err", err, "bundles", len(bundles))
+ return err
+ }
}
log.Info("submitted block", "slot", blockBidMsg.Slot, "value", blockBidMsg.Value.String(), "parent", blockBidMsg.ParentHash, "hash", block.Hash(), "bundles", len(bundles))
diff --git a/builder/builder_test.go b/builder/builder_test.go
index 85afcaba31..09d4c5b728 100644
--- a/builder/builder_test.go
+++ b/builder/builder_test.go
@@ -75,7 +75,7 @@ func TestOnPayloadAttributes(t *testing.T) {
testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}
- builder := NewBuilder(sk, flashbotsextra.NilDbService{}, &testRelay, bDomain, testEthService)
+ builder := NewBuilder(sk, flashbotsextra.NilDbService{}, &testRelay, bDomain, testEthService, false, nil)
builder.Start()
defer builder.Stop()
diff --git a/builder/local_relay_test.go b/builder/local_relay_test.go
index 6ee0bfcc90..9ba2bcc96e 100644
--- a/builder/local_relay_test.go
+++ b/builder/local_relay_test.go
@@ -31,7 +31,7 @@ func newTestBackend(t *testing.T, forkchoiceData *beacon.ExecutableDataV1, block
beaconClient := &testBeaconClient{validator: validator}
localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true)
ethService := &testEthereumService{synced: true, testExecutableData: forkchoiceData, testBlock: block}
- backend := NewBuilder(sk, flashbotsextra.NilDbService{}, localRelay, bDomain, ethService)
+ backend := NewBuilder(sk, flashbotsextra.NilDbService{}, localRelay, bDomain, ethService, false, nil)
// service := NewService("127.0.0.1:31545", backend)
backend.limiter = rate.NewLimiter(rate.Inf, 0)
diff --git a/builder/service.go b/builder/service.go
index 6a1ace06d6..c53fada894 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -3,6 +3,7 @@ package builder
import (
"errors"
"fmt"
+ blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation"
"net/http"
"os"
@@ -106,6 +107,7 @@ type BuilderConfig struct {
Enabled bool
EnableValidatorChecks bool
EnableLocalRelay bool
+ DryRun bool
BuilderSecretKey string
RelaySecretKey string
ListenAddr string
@@ -114,6 +116,7 @@ type BuilderConfig struct {
GenesisValidatorsRoot string
BeaconEndpoint string
RemoteRelayEndpoint string
+ ValidationBlocklist string
}
func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error {
@@ -172,6 +175,18 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
return errors.New("neither local nor remote relay specified")
}
+ var validator *blockvalidation.BlockValidationAPI
+ if cfg.DryRun {
+ var accessVerifier *blockvalidation.AccessVerifier
+ if cfg.ValidationBlocklist != "" {
+ accessVerifier, err = blockvalidation.NewAccessVerifierFromFile(cfg.ValidationBlocklist)
+ if err != nil {
+ return fmt.Errorf("failed to load validation blocklist %w", err)
+ }
+ }
+ validator = blockvalidation.NewBlockValidationAPI(backend, accessVerifier)
+ }
+
// TODO: move to proper flags
var ds flashbotsextra.IDatabaseService
dbDSN := os.Getenv("FLASHBOTS_POSTGRES_DSN")
@@ -193,7 +208,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
go bundleFetcher.Run()
ethereumService := NewEthereumService(backend)
- builderBackend := NewBuilder(builderSk, ds, relay, builderSigningDomain, ethereumService)
+ builderBackend := NewBuilder(builderSk, ds, relay, builderSigningDomain, ethereumService, cfg.DryRun, validator)
builderService := NewService(cfg.ListenAddr, localRelay, builderBackend)
stack.RegisterAPIs([]rpc.API{
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 9ad20c7398..a26192bcf7 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -171,6 +171,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
Enabled: ctx.IsSet(utils.BuilderEnabled.Name),
EnableValidatorChecks: ctx.IsSet(utils.BuilderEnableValidatorChecks.Name),
EnableLocalRelay: ctx.IsSet(utils.BuilderEnableLocalRelay.Name),
+ DryRun: ctx.IsSet(utils.BuilderDryRun.Name),
BuilderSecretKey: ctx.String(utils.BuilderSecretKey.Name),
RelaySecretKey: ctx.String(utils.BuilderRelaySecretKey.Name),
ListenAddr: ctx.String(utils.BuilderListenAddr.Name),
@@ -179,6 +180,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
GenesisValidatorsRoot: ctx.String(utils.BuilderGenesisValidatorsRoot.Name),
BeaconEndpoint: ctx.String(utils.BuilderBeaconEndpoint.Name),
RemoteRelayEndpoint: ctx.String(utils.BuilderRemoteRelayEndpoint.Name),
+ ValidationBlocklist: ctx.String(utils.BuilderBlockValidationBlacklistSourceFilePath.Name),
}
backend, eth := utils.RegisterEthService(stack, &cfg.Eth, bpConfig)
@@ -203,7 +205,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
// Configure log filter RPC API.
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
- if err := blockvalidationapi.Register(stack, eth, ctx); err != nil {
+ if err := blockvalidationapi.Register(stack, eth, ctx.String(utils.BuilderBlockValidationBlacklistSourceFilePath.Name)); err != nil {
utils.Fatalf("Failed to register the Block Validation API: %v", err)
}
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index d903c160be..efd0ace45c 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -166,6 +166,7 @@ var (
utils.BuilderEnabled,
utils.BuilderEnableValidatorChecks,
utils.BuilderEnableLocalRelay,
+ utils.BuilderDryRun,
utils.BuilderSecretKey,
utils.BuilderRelaySecretKey,
utils.BuilderListenAddr,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 5daab254ad..6f7d24c7f0 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -699,6 +699,10 @@ var (
Name: "builder.local_relay",
Usage: "Enable the local relay",
}
+ BuilderDryRun = &cli.BoolFlag{
+ Name: "builder.dry-run",
+ Usage: "Builder only validates blocks without submission to the relay",
+ }
BuilderSecretKey = &cli.StringFlag{
Name: "builder.secret_key",
Usage: "Builder key used for signing blocks",
@@ -1067,8 +1071,8 @@ var (
// Builder API flags
BuilderBlockValidationBlacklistSourceFilePath = &cli.StringFlag{
Name: "builder.validation_blacklist",
- Usage: "Path to file containing blacklisted addresses, json-encoded list of strings. Default assumes CWD is repo's root",
- Value: "ofac_blacklist.json",
+ Usage: "Path to file containing blacklisted addresses, json-encoded list of strings. Default assumes no blacklist",
+ Value: "",
Category: flags.EthCategory,
}
)
diff --git a/eth/block-validation/api.go b/eth/block-validation/api.go
index ca1ea29626..38984e2148 100644
--- a/eth/block-validation/api.go
+++ b/eth/block-validation/api.go
@@ -7,7 +7,6 @@ import (
"math/big"
"os"
- "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
@@ -17,8 +16,6 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
- "github.com/urfave/cli/v2"
-
boostTypes "github.com/flashbots/go-boost-utils/types"
)
@@ -81,11 +78,11 @@ func NewAccessVerifierFromFile(path string) (*AccessVerifier, error) {
}
// Register adds catalyst APIs to the full node.
-func Register(stack *node.Node, backend *eth.Ethereum, ctx *cli.Context) error {
+func Register(stack *node.Node, backend *eth.Ethereum, blockValidationBlocklistFile string) error {
var accessVerifier *AccessVerifier
- if ctx.IsSet(utils.BuilderBlockValidationBlacklistSourceFilePath.Name) {
+ if blockValidationBlocklistFile != "" {
var err error
- accessVerifier, err = NewAccessVerifierFromFile(ctx.String(utils.BuilderBlockValidationBlacklistSourceFilePath.Name))
+ accessVerifier, err = NewAccessVerifierFromFile(blockValidationBlocklistFile)
if err != nil {
return err
}
diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go
index f7945399d6..fa356bc240 100644
--- a/eth/block-validation/api_test.go
+++ b/eth/block-validation/api_test.go
@@ -101,7 +101,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
}
blockRequest.Message.Value = boostTypes.IntToU256(190526394825529)
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "inaccurate payment")
- blockRequest.Message.Value = boostTypes.IntToU256(190215802060530)
+ blockRequest.Message.Value = boostTypes.IntToU256(190526394825530)
require.NoError(t, api.ValidateBuilderSubmissionV1(blockRequest))
// TODO: test with contract calling blacklisted address
@@ -142,7 +142,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
invalidPayload.LogsBloom = boostTypes.Bloom{}
copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
blockRequest.ExecutionPayload = invalidPayload
- copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x65cded68b85277f489f22497731d8cece9e42f0429a250a5022a9417408f3998")[:32])
+ copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x272872d14b2a8a0454e747ed472d82d8d5ce342cfafd65fa7b77aa6de1c061d4")[:32])
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value")
}
diff --git a/miner/algo_common.go b/miner/algo_common.go
index db85457ce0..ef16c86617 100644
--- a/miner/algo_common.go
+++ b/miner/algo_common.go
@@ -1,7 +1,9 @@
package miner
import (
+ "crypto/ecdsa"
"errors"
+ "fmt"
"math/big"
"sync/atomic"
@@ -20,6 +22,8 @@ const (
popTx = 2
)
+var emptyCodeHash = common.HexToHash("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
+
var errInterrupt = errors.New("miner worker interrupted")
type environmentDiff struct {
@@ -263,3 +267,111 @@ func (envDiff *environmentDiff) commitBundle(bundle *types.SimulatedBundle, chDa
*envDiff = *tmpEnvDiff
return nil
}
+
+func estimatePayoutTxGas(env *environment, sender, receiver common.Address, prv *ecdsa.PrivateKey, chData chainData) (uint64, bool, error) {
+ if codeHash := env.state.GetCodeHash(receiver); codeHash == (common.Hash{}) || codeHash == emptyCodeHash {
+ return params.TxGas, true, nil
+ }
+ gasLimit := env.gasPool.Gas()
+
+ balance := new(big.Int).SetBytes([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
+ value := new(big.Int).SetBytes([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
+
+ diff := newEnvironmentDiff(env)
+ diff.state.SetBalance(sender, balance)
+ receipt, err := diff.commitPayoutTx(value, sender, receiver, gasLimit, prv, chData)
+ if err != nil {
+ return 0, false, err
+ }
+ return receipt.GasUsed, false, nil
+}
+
+func insertPayoutTx(env *environment, sender, receiver common.Address, gas uint64, isEOA bool, availableFunds *big.Int, prv *ecdsa.PrivateKey, chData chainData) (*types.Receipt, error) {
+ diff := newEnvironmentDiff(env)
+ applyTx := func(gas uint64) (*types.Receipt, error) {
+ fee := new(big.Int).Mul(env.header.BaseFee, new(big.Int).SetUint64(gas))
+ amount := new(big.Int).Sub(availableFunds, fee)
+ if amount.Sign() < 0 {
+ return nil, errors.New("not enough funds available")
+ }
+ rec, err := diff.commitPayoutTx(amount, sender, receiver, gas, prv, chData)
+ if err != nil {
+ return nil, fmt.Errorf("failed to commit payment tx: %w", err)
+ }
+ if rec.Status != types.ReceiptStatusSuccessful {
+ return nil, fmt.Errorf("payment tx failed")
+ }
+ return rec, nil
+ }
+
+ if isEOA {
+ rec, err := applyTx(gas)
+ if err != nil {
+ return nil, err
+ }
+ diff.applyToBaseEnv()
+ return rec, nil
+ }
+
+ var (
+ rec *types.Receipt
+ err error
+ )
+ for i := 0; i < 6; i++ {
+ rec, err = applyTx(gas)
+ if err != nil {
+ gas += 1000
+ } else {
+ break
+ }
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ diff.applyToBaseEnv()
+ return rec, nil
+}
+
+func (envDiff *environmentDiff) commitPayoutTx(amount *big.Int, sender, receiver common.Address, gas uint64, prv *ecdsa.PrivateKey, chData chainData) (*types.Receipt, error) {
+ senderBalance := envDiff.state.GetBalance(sender)
+
+ if gas < params.TxGas {
+ return nil, errors.New("not enough gas for intrinsic gas cost")
+ }
+
+ requiredBalance := new(big.Int).Mul(envDiff.header.BaseFee, new(big.Int).SetUint64(gas))
+ requiredBalance = requiredBalance.Add(requiredBalance, amount)
+ if requiredBalance.Cmp(senderBalance) > 0 {
+ return nil, errors.New("not enough balance")
+ }
+
+ signer := envDiff.baseEnvironment.signer
+ tx, err := types.SignNewTx(prv, signer, &types.DynamicFeeTx{
+ ChainID: chData.chainConfig.ChainID,
+ Nonce: envDiff.state.GetNonce(sender),
+ GasTipCap: new(big.Int),
+ GasFeeCap: envDiff.header.BaseFee,
+ Gas: gas,
+ To: &receiver,
+ Value: amount,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ txSender, err := signer.Sender(tx)
+ if err != nil {
+ return nil, err
+ }
+ if txSender != sender {
+ return nil, errors.New("incorrect sender private key")
+ }
+
+ receipt, _, err := envDiff.commitTx(tx, chData)
+ if err != nil {
+ return nil, err
+ }
+
+ return receipt, nil
+}
diff --git a/miner/algo_common_test.go b/miner/algo_common_test.go
index 7ff498a06a..42277613fa 100644
--- a/miner/algo_common_test.go
+++ b/miner/algo_common_test.go
@@ -4,6 +4,7 @@ import (
"crypto/ecdsa"
"errors"
"fmt"
+ "github.com/stretchr/testify/require"
"math/big"
"testing"
@@ -26,6 +27,9 @@ var (
// Pay proxy is a contract that sends msg.value to address specified in calldata[0..32]
payProxyAddress = common.HexToAddress("0x1100000000000000000000000000000000000000")
payProxyCode = hexutil.MustDecode("0x6000600060006000346000356000f1")
+ // log contract logs value that it receives
+ logContractAddress = common.HexToAddress("0x2200000000000000000000000000000000000000")
+ logContractCode = hexutil.MustDecode("0x346000523460206000a1")
)
type signerList struct {
@@ -184,7 +188,7 @@ func genTestSetup() (*state.StateDB, chainData, signerList) {
db := rawdb.NewMemoryDatabase()
signerList := genSignerList(10, config)
- genesisAlloc := genGenesisAlloc(signerList, []common.Address{payProxyAddress}, [][]byte{payProxyCode})
+ genesisAlloc := genGenesisAlloc(signerList, []common.Address{payProxyAddress, logContractAddress}, [][]byte{payProxyCode, logContractCode})
gspec := &core.Genesis{
Config: config,
@@ -492,3 +496,70 @@ func TestBlacklist(t *testing.T) {
t.Fatal("newReceipts changed")
}
}
+
+func TestPayoutTxUtils(t *testing.T) {
+ availableFunds := big.NewInt(50000000000000000) // 0.05 eth
+
+ statedb, chData, signers := genTestSetup()
+
+ env := newEnvironment(chData, statedb, signers.addresses[0], GasLimit, big.NewInt(1))
+
+ // Sending payment to the plain EOA
+ gas, isEOA, err := estimatePayoutTxGas(env, signers.addresses[1], signers.addresses[2], signers.signers[1], chData)
+ require.Equal(t, uint64(21000), gas)
+ require.True(t, isEOA)
+ require.NoError(t, err)
+
+ expectedPayment := new(big.Int).Sub(availableFunds, big.NewInt(21000))
+ balanceBefore := env.state.GetBalance(signers.addresses[2])
+ rec, err := insertPayoutTx(env, signers.addresses[1], signers.addresses[2], gas, isEOA, availableFunds, signers.signers[1], chData)
+ balanceAfter := env.state.GetBalance(signers.addresses[2])
+ require.NoError(t, err)
+ require.NotNil(t, rec)
+ require.Equal(t, types.ReceiptStatusSuccessful, rec.Status)
+ require.Equal(t, uint64(21000), rec.GasUsed)
+ require.True(t, new(big.Int).Sub(balanceAfter, balanceBefore).Cmp(expectedPayment) == 0)
+
+ // Sending payment to the contract that logs event of the amount
+ gas, isEOA, err = estimatePayoutTxGas(env, signers.addresses[1], logContractAddress, signers.signers[1], chData)
+ require.Equal(t, uint64(22025), gas)
+ require.False(t, isEOA)
+ require.NoError(t, err)
+
+ expectedPayment = new(big.Int).Sub(availableFunds, big.NewInt(22025))
+ balanceBefore = env.state.GetBalance(logContractAddress)
+ rec, err = insertPayoutTx(env, signers.addresses[1], logContractAddress, gas, isEOA, availableFunds, signers.signers[1], chData)
+ balanceAfter = env.state.GetBalance(logContractAddress)
+ require.NoError(t, err)
+ require.NotNil(t, rec)
+ require.Equal(t, types.ReceiptStatusSuccessful, rec.Status)
+ require.Equal(t, uint64(22025), rec.GasUsed)
+ require.True(t, new(big.Int).Sub(balanceAfter, balanceBefore).Cmp(expectedPayment) == 0)
+
+ // Try requesting less gas for contract tx. We request 21k gas, but we must pay 22025
+ // iteration logic should set gas limit to 23k
+ expectedPayment = new(big.Int).Sub(availableFunds, big.NewInt(23000))
+ balanceBefore = env.state.GetBalance(logContractAddress)
+ rec, err = insertPayoutTx(env, signers.addresses[1], logContractAddress, 21000, isEOA, availableFunds, signers.signers[1], chData)
+ balanceAfter = env.state.GetBalance(logContractAddress)
+ require.NoError(t, err)
+ require.NotNil(t, rec)
+ require.Equal(t, types.ReceiptStatusSuccessful, rec.Status)
+ require.Equal(t, uint64(22025), rec.GasUsed)
+ require.True(t, new(big.Int).Sub(balanceAfter, balanceBefore).Cmp(expectedPayment) == 0)
+
+ // errors
+
+ _, err = insertPayoutTx(env, signers.addresses[1], signers.addresses[2], 21000, true, availableFunds, signers.signers[2], chData)
+ require.ErrorContains(t, err, "incorrect sender private key")
+ _, err = insertPayoutTx(env, signers.addresses[1], logContractAddress, 23000, false, availableFunds, signers.signers[2], chData)
+ require.ErrorContains(t, err, "incorrect sender private key")
+
+ _, err = insertPayoutTx(env, signers.addresses[1], signers.addresses[2], 21000, true, big.NewInt(21000-1), signers.signers[1], chData)
+ require.ErrorContains(t, err, "not enough funds available")
+ _, err = insertPayoutTx(env, signers.addresses[1], logContractAddress, 23000, false, big.NewInt(23000-1), signers.signers[1], chData)
+ require.ErrorContains(t, err, "not enough funds available")
+
+ _, err = insertPayoutTx(env, signers.addresses[1], signers.addresses[2], 20000, true, availableFunds, signers.signers[1], chData)
+ require.ErrorContains(t, err, "not enough gas")
+}
diff --git a/miner/miner.go b/miner/miner.go
index f634f81bc5..1d0ed663b6 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -20,7 +20,10 @@ package miner
import (
"crypto/ecdsa"
"fmt"
+ "github.com/ethereum/go-ethereum/crypto"
"math/big"
+ "os"
+ "strings"
"sync"
"time"
@@ -82,6 +85,15 @@ type Miner struct {
}
func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, isLocalBlock func(header *types.Header) bool) *Miner {
+ if config.BuilderTxSigningKey == nil {
+ key := os.Getenv("BUILDER_TX_SIGNING_KEY")
+ if key, err := crypto.HexToECDSA(strings.TrimPrefix(key, "0x")); err != nil {
+ log.Error("Error parsing builder signing key from env", "err", err)
+ } else {
+ config.BuilderTxSigningKey = key
+ }
+ }
+
miner := &Miner{
eth: eth,
mux: mux,
diff --git a/miner/worker.go b/miner/worker.go
index 97548009c3..2b8a8852a4 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -17,14 +17,11 @@
package miner
import (
- "crypto/ecdsa"
"errors"
"fmt"
"math/big"
- "os"
"sort"
- "strings"
"sync"
"sync/atomic"
"time"
@@ -273,24 +270,11 @@ type worker struct {
}
func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool, flashbots *flashbotsData) *worker {
- var err error
var builderCoinbase common.Address
- key := os.Getenv("BUILDER_TX_SIGNING_KEY") // get builder private signing key
- if key == "" {
- log.Error("Builder signing key is empty, validator payout can not be done")
+ if config.BuilderTxSigningKey == nil {
+ log.Error("Builder tx signing key is not set")
} else {
- config.BuilderTxSigningKey, err = crypto.HexToECDSA(strings.TrimPrefix(key, "0x"))
- if err != nil {
- log.Error("Error creating builder tx signing key", "error", err)
- } else {
- publicKey := config.BuilderTxSigningKey.Public()
- publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
- if ok {
- builderCoinbase = crypto.PubkeyToAddress(*publicKeyECDSA)
- } else {
- log.Error("Cannot assert type, builder tx signing key")
- }
- }
+ builderCoinbase = crypto.PubkeyToAddress(config.BuilderTxSigningKey.PublicKey)
}
log.Info("new worker", "builderCoinbase", builderCoinbase.String())
exitCh := make(chan struct{})
@@ -1312,7 +1296,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
// fillTransactions retrieves the pending transactions from the txpool and fills them
// into the given sealing block. The transaction selection and ordering strategy can
// be customized with the plugin in the future.
-func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorCoinbase *common.Address) (error, []types.SimulatedBundle) {
+func (w *worker) fillTransactions(interrupt *int32, env *environment) (error, []types.SimulatedBundle) {
// Split the pending transactions into locals and remotes
// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
@@ -1326,13 +1310,6 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
}
- var builderCoinbaseBalanceBefore *big.Int
- if validatorCoinbase != nil {
- builderCoinbaseBalanceBefore = env.state.GetBalance(w.coinbase)
- if err := env.gasPool.SubGas(paymentTxGas); err != nil {
- return err, nil
- }
- }
var blockBundles []types.SimulatedBundle
if w.flashbots.isFlashbots {
@@ -1371,50 +1348,18 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC
}
}
- if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil {
- builderCoinbaseBalanceAfter := env.state.GetBalance(w.coinbase)
- log.Trace("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
-
- profit := new(big.Int).Sub(builderCoinbaseBalanceAfter, builderCoinbaseBalanceBefore)
- env.gasPool.AddGas(paymentTxGas)
- if profit.Sign() == 1 {
- tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit)
- if err != nil {
- return fmt.Errorf("proposer payout create tx failed - %v", err), nil
- }
- if tx != nil {
- _, err = w.commitTransaction(env, tx)
- if err != nil {
- return fmt.Errorf("proposer payout commit tx failed - %v", err), nil
- }
- env.tcount++
- } else {
- return errors.New("proposer payout create tx failed due to tx is nil"), nil
- }
- } else {
- return errors.New("proposer payout create tx failed due to not enough balance"), nil
- }
- }
-
return nil, blockBundles
}
// fillTransactionsAlgoWorker retrieves the pending transactions and bundles from the txpool and fills them
// into the given sealing block.
-func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment, validatorCoinbase *common.Address) (error, []types.SimulatedBundle) {
+func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment) (error, []types.SimulatedBundle) {
// Split the pending transactions into locals and remotes
// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
}
- var builderCoinbaseBalanceBefore *big.Int
- if validatorCoinbase != nil {
- builderCoinbaseBalanceBefore = env.state.GetBalance(w.coinbase)
- if err := env.gasPool.SubGas(params.TxGas); err != nil {
- return err, nil
- }
- }
var bundlesToConsider []types.SimulatedBundle
if w.flashbots.isFlashbots {
@@ -1437,32 +1382,6 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment,
newEnv, blockBundles := builder.buildBlock(bundlesToConsider, pending)
*env = *newEnv
- if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil {
- builderCoinbaseBalanceAfter := env.state.GetBalance(w.coinbase)
- log.Trace("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String())
-
- profit := new(big.Int).Sub(builderCoinbaseBalanceAfter, builderCoinbaseBalanceBefore)
- env.gasPool.AddGas(params.TxGas)
- if profit.Sign() == 1 {
- tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit)
- if err != nil {
- return fmt.Errorf("proposer payout create tx failed - %v", err), nil
- }
- if tx != nil {
- _, err = w.commitTransaction(env, tx)
- if err != nil {
- return fmt.Errorf("proposer payout commit tx failed - %v", err), nil
- }
- env.tcount++
- } else {
- return errors.New("proposer payout create tx failed due to tx is nil"), nil
- }
- } else {
- return errors.New("proposer payout create tx failed due to not enough balance"), nil
- }
-
- }
-
return nil, blockBundles
}
@@ -1482,15 +1401,21 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
var blockBundles []types.SimulatedBundle
if !params.noTxs {
var err error
+ paymentTxReserve, err := w.proposerTxPrepare(work, &validatorCoinbase)
+ if err != nil {
+ return nil, err
+ }
+
switch w.flashbots.algoType {
case ALGO_GREEDY:
- err, blockBundles = w.fillTransactionsAlgoWorker(nil, work, &validatorCoinbase)
+ err, blockBundles = w.fillTransactionsAlgoWorker(nil, work)
case ALGO_MEV_GETH:
- err, blockBundles = w.fillTransactions(nil, work, &validatorCoinbase)
+ err, blockBundles = w.fillTransactions(nil, work)
default:
- err, blockBundles = w.fillTransactions(nil, work, &validatorCoinbase)
+ err, blockBundles = w.fillTransactions(nil, work)
}
+ err = w.proposerTxCommit(work, &validatorCoinbase, paymentTxReserve)
if err != nil {
return nil, err
}
@@ -1562,7 +1487,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) {
}
// Fill pending transactions from the txpool
- err, _ = w.fillTransactions(interrupt, work, nil)
+ err, _ = w.fillTransactions(interrupt, work)
if err != nil && !errors.Is(err, errBlockInterruptedByRecommit) {
work.discard()
return
@@ -1912,17 +1837,59 @@ func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float {
return ethIntToFloat(feesWei)
}
-func (w *worker) createProposerPayoutTx(env *environment, recipient *common.Address, profit *big.Int) (*types.Transaction, error) {
- sender := w.coinbase.String()
- nonce := env.state.GetNonce(w.coinbase)
- fee := new(big.Int).Mul(big.NewInt(paymentTxGas), env.header.BaseFee)
- amount := new(big.Int).Sub(profit, fee)
- if amount.Sign() == -1 {
- return nil, errors.New("negative amount of proposer payout")
- }
- gasPrice := new(big.Int).Set(env.header.BaseFee)
- chainId := w.chainConfig.ChainID
- log.Trace("createProposerPayoutTx", "sender", sender, "chainId", chainId.String(), "nonce", nonce, "amount", amount.String(), "baseFee", env.header.BaseFee.String(), "fee", fee)
- tx := types.NewTransaction(nonce, *recipient, amount, paymentTxGas, gasPrice, nil)
- return types.SignTx(tx, types.LatestSignerForChainID(chainId), w.config.BuilderTxSigningKey)
+type proposerTxReservation struct {
+ builderBalance *big.Int
+ reservedGas uint64
+ isEOA bool
+}
+
+func (w *worker) proposerTxPrepare(env *environment, validatorCoinbase *common.Address) (*proposerTxReservation, error) {
+ if validatorCoinbase == nil || w.config.BuilderTxSigningKey == nil {
+ return nil, nil
+ }
+
+ w.mu.Lock()
+ sender := w.coinbase
+ w.mu.Unlock()
+ builderBalance := env.state.GetBalance(sender)
+
+ chainData := chainData{w.chainConfig, w.chain, w.blockList}
+ gas, isEOA, err := estimatePayoutTxGas(env, sender, *validatorCoinbase, w.config.BuilderTxSigningKey, chainData)
+ if err != nil {
+ return nil, fmt.Errorf("failed to estimate proposer payout gas: %w", err)
+ }
+
+ if err := env.gasPool.SubGas(gas); err != nil {
+ return nil, err
+ }
+
+ return &proposerTxReservation{
+ builderBalance: builderBalance,
+ reservedGas: gas,
+ isEOA: isEOA,
+ }, nil
+}
+
+func (w *worker) proposerTxCommit(env *environment, validatorCoinbase *common.Address, reserve *proposerTxReservation) error {
+ if reserve == nil || validatorCoinbase == nil {
+ return nil
+ }
+
+ w.mu.Lock()
+ sender := w.coinbase
+ w.mu.Unlock()
+ builderBalance := env.state.GetBalance(sender)
+
+ availableFunds := new(big.Int).Sub(builderBalance, reserve.builderBalance)
+ if availableFunds.Sign() <= 0 {
+ return errors.New("builder balance decreased")
+ }
+
+ env.gasPool.AddGas(reserve.reservedGas)
+ chainData := chainData{w.chainConfig, w.chain, w.blockList}
+ _, err := insertPayoutTx(env, sender, *validatorCoinbase, reserve.reservedGas, reserve.isEOA, availableFunds, w.config.BuilderTxSigningKey, chainData)
+ if err != nil {
+ return err
+ }
+ return nil
}
diff --git a/miner/worker_test.go b/miner/worker_test.go
index 4c49c91194..53b2c4446a 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -547,7 +547,6 @@ func TestGetSealingWorkPostMerge(t *testing.T) {
func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, postMerge bool) {
defer engine.Close()
- //os.Setenv("BUILDER_TX_SIGNING_KEY", "0xb9ee6b07275bb71da8823290b68b667c075482696576c05e7989dee7d29a5855")
w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
defer w.close()
From dbe3dff64b6596a25942cf8436001b9d02278a7c Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Fri, 7 Oct 2022 14:42:30 +0200
Subject: [PATCH 55/83] Improvements from greedy improve algo (#41)
* Backport improvements to the builder from incremental improvements
* Make linter happy
---
builder/builder.go | 5 +-
builder/local_relay_test.go | 3 +-
builder/relay.go | 2 -
builder/resubmit_utils.go | 3 +-
builder/resubmit_utils_test.go | 3 +-
builder/service.go | 3 +-
cmd/utils/flags.go | 9 +-
core/types/transaction.go | 13 ++
eth/block-validation/api_test.go | 7 +-
eth/tracers/logger/account_touch_tracer.go | 5 +-
miner/algo_common.go | 8 +-
miner/algo_common_test.go | 39 ++++-
miner/algo_greedy.go | 31 ++--
miner/algo_greedy_test.go | 6 +-
miner/bundle_cache.go | 3 +-
miner/bundle_cache_test.go | 3 +-
miner/miner.go | 14 +-
miner/multi_worker.go | 6 +-
miner/worker.go | 125 ++++++++++------
miner/worker_test.go | 164 ++++++++++++++++++---
20 files changed, 330 insertions(+), 122 deletions(-)
diff --git a/builder/builder.go b/builder/builder.go
index a20897477a..7f9758ceb0 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -3,13 +3,14 @@ package builder
import (
"context"
"errors"
- blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation"
- "golang.org/x/time/rate"
"math/big"
_ "os"
"sync"
"time"
+ blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation"
+ "golang.org/x/time/rate"
+
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
diff --git a/builder/local_relay_test.go b/builder/local_relay_test.go
index 9ba2bcc96e..53db30392a 100644
--- a/builder/local_relay_test.go
+++ b/builder/local_relay_test.go
@@ -4,13 +4,14 @@ import (
"bytes"
"encoding/json"
"fmt"
- "golang.org/x/time/rate"
"math/big"
"net/http"
"net/http/httptest"
"testing"
"time"
+ "golang.org/x/time/rate"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon"
diff --git a/builder/relay.go b/builder/relay.go
index 4642707d41..f5414ae280 100644
--- a/builder/relay.go
+++ b/builder/relay.go
@@ -29,8 +29,6 @@ func (r *testRelay) GetValidatorForSlot(nextSlot uint64) (ValidatorData, error)
r.requestedSlot = nextSlot
return r.validator, nil
}
-func (r *testRelay) handleRegisterValidator(w http.ResponseWriter, req *http.Request) {
-}
type RemoteRelay struct {
endpoint string
diff --git a/builder/resubmit_utils.go b/builder/resubmit_utils.go
index 749bbc2e65..4d95dab93a 100644
--- a/builder/resubmit_utils.go
+++ b/builder/resubmit_utils.go
@@ -2,9 +2,10 @@ package builder
import (
"context"
+ "time"
+
"github.com/ethereum/go-ethereum/log"
"golang.org/x/time/rate"
- "time"
)
// runResubmitLoop checks for update signal and calls submit respecting provided rate limiter and context
diff --git a/builder/resubmit_utils_test.go b/builder/resubmit_utils_test.go
index d451097fd5..f612c5a292 100644
--- a/builder/resubmit_utils_test.go
+++ b/builder/resubmit_utils_test.go
@@ -2,12 +2,13 @@ package builder
import (
"context"
- "golang.org/x/time/rate"
"math/rand"
"sort"
"sync"
"testing"
"time"
+
+ "golang.org/x/time/rate"
)
type submission struct {
diff --git a/builder/service.go b/builder/service.go
index c53fada894..08f8aa20e2 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -3,10 +3,11 @@ package builder
import (
"errors"
"fmt"
- blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation"
"net/http"
"os"
+ blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 6f7d24c7f0..aab20c84d5 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -1746,14 +1746,11 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
cfg.GasPrice = flags.GlobalBig(ctx, MinerGasPriceFlag.Name)
}
if ctx.IsSet(MinerAlgoTypeFlag.Name) {
- switch ctx.String(MinerAlgoTypeFlag.Name) {
- case "greedy":
- cfg.AlgoType = miner.ALGO_GREEDY
- case "mev-geth":
- cfg.AlgoType = miner.ALGO_MEV_GETH
- default:
+ algoType, err := miner.AlgoTypeFlagToEnum(ctx.String(MinerAlgoTypeFlag.Name))
+ if err != nil {
Fatalf("Invalid algo in --miner.algotype: %s", ctx.String(MinerAlgoTypeFlag.Name))
}
+ cfg.AlgoType = algoType
}
if ctx.IsSet(MinerRecommitIntervalFlag.Name) {
cfg.Recommit = ctx.Duration(MinerRecommitIntervalFlag.Name)
diff --git a/core/types/transaction.go b/core/types/transaction.go
index abd3c61cc6..b87549ac1f 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -571,6 +571,19 @@ func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transa
}
}
+func (t *TransactionsByPriceAndNonce) DeepCopy() *TransactionsByPriceAndNonce {
+ newT := &TransactionsByPriceAndNonce{
+ txs: make(map[common.Address]Transactions),
+ heads: append(TxByPriceAndTime{}, t.heads...),
+ signer: t.signer,
+ baseFee: new(big.Int).Set(t.baseFee),
+ }
+ for k, v := range t.txs {
+ newT.txs[k] = v
+ }
+ return newT
+}
+
// Peek returns the next transaction by price.
func (t *TransactionsByPriceAndNonce) Peek() *TxWithMinerFee {
if len(t.heads) == 0 {
diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go
index fa356bc240..9169abc775 100644
--- a/eth/block-validation/api_test.go
+++ b/eth/block-validation/api_test.go
@@ -41,9 +41,6 @@ var (
testValidatorKey, _ = crypto.HexToECDSA("28c3cd61b687fdd03488e167a5d84f50269df2a4c29a2cfb1390903aa775c5d0")
testValidatorAddr = crypto.PubkeyToAddress(testValidatorKey.PublicKey)
- testMinerKey, _ = crypto.HexToECDSA("28c3cd61b687fdd03488e167a5d84f50269df2a4c29a2cfb1390903aa775c5d0")
- testMinerAddr = crypto.PubkeyToAddress(testValidatorKey.PublicKey)
-
testBalance = big.NewInt(2e18)
)
@@ -57,7 +54,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
api := NewBlockValidationAPI(ethservice, nil)
parent := preMergeBlocks[len(preMergeBlocks)-1]
- api.eth.APIBackend.Miner().SetEtherbase(testMinerAddr)
+ api.eth.APIBackend.Miner().SetEtherbase(testValidatorAddr)
// This EVM code generates a log when the contract is created.
logCode := common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
@@ -142,7 +139,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {
invalidPayload.LogsBloom = boostTypes.Bloom{}
copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
blockRequest.ExecutionPayload = invalidPayload
- copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x272872d14b2a8a0454e747ed472d82d8d5ce342cfafd65fa7b77aa6de1c061d4")[:32])
+ copy(blockRequest.Message.BlockHash[:], hexutil.MustDecode("0x2ff468dee2e05f1f58744d5496f3ab22fdc23c8141f86f907b4b0f2c8e22afc4")[:32])
require.ErrorContains(t, api.ValidateBuilderSubmissionV1(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value")
}
diff --git a/eth/tracers/logger/account_touch_tracer.go b/eth/tracers/logger/account_touch_tracer.go
index 10d2fb32af..4c24779637 100644
--- a/eth/tracers/logger/account_touch_tracer.go
+++ b/eth/tracers/logger/account_touch_tracer.go
@@ -17,10 +17,11 @@
package logger
import (
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/vm"
"math/big"
"time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/vm"
)
type AccountTouchTracer struct {
diff --git a/miner/algo_common.go b/miner/algo_common.go
index ef16c86617..9c39993612 100644
--- a/miner/algo_common.go
+++ b/miner/algo_common.go
@@ -26,6 +26,12 @@ var emptyCodeHash = common.HexToHash("c5d2460186f7233c927e7db2dcc703c0e500b653ca
var errInterrupt = errors.New("miner worker interrupted")
+type chainData struct {
+ chainConfig *params.ChainConfig
+ chain *core.BlockChain
+ blacklist map[common.Address]struct{}
+}
+
type environmentDiff struct {
baseEnvironment *environment
header *types.Header
@@ -51,7 +57,7 @@ func (e *environmentDiff) copy() *environmentDiff {
gasPool := new(core.GasPool).AddGas(e.gasPool.Gas())
return &environmentDiff{
- baseEnvironment: e.baseEnvironment,
+ baseEnvironment: e.baseEnvironment.copy(),
header: types.CopyHeader(e.header),
gasPool: gasPool,
state: e.state.Copy(),
diff --git a/miner/algo_common_test.go b/miner/algo_common_test.go
index 42277613fa..b94198608f 100644
--- a/miner/algo_common_test.go
+++ b/miner/algo_common_test.go
@@ -4,10 +4,11 @@ import (
"crypto/ecdsa"
"errors"
"fmt"
- "github.com/stretchr/testify/require"
"math/big"
"testing"
+ "github.com/stretchr/testify/require"
+
mapset "github.com/deckarep/golang-set"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -366,10 +367,8 @@ func TestCommitTxOverGasLimit(t *testing.T) {
t.Fatal("Env diff gas pool is not drained")
}
- receipt, i, err = envDiff.commitTx(tx2, chData)
- if err == nil {
- t.Fatal("committed tx over gas limit")
- }
+ _, _, err = envDiff.commitTx(tx2, chData)
+ require.Error(t, err, "committed tx over gas limit")
}
func TestErrorBundleCommit(t *testing.T) {
@@ -497,6 +496,36 @@ func TestBlacklist(t *testing.T) {
}
}
+func TestGetSealingWorkAlgos(t *testing.T) {
+ t.Cleanup(func() {
+ testConfig.AlgoType = ALGO_MEV_GETH
+ })
+
+ for _, algoType := range []AlgoType{ALGO_MEV_GETH, ALGO_GREEDY} {
+ local := new(params.ChainConfig)
+ *local = *ethashChainConfig
+ local.TerminalTotalDifficulty = big.NewInt(0)
+ testConfig.AlgoType = algoType
+ testGetSealingWork(t, local, ethash.NewFaker(), true)
+ }
+}
+
+func TestGetSealingWorkAlgosWithProfit(t *testing.T) {
+ t.Cleanup(func() {
+ testConfig.AlgoType = ALGO_MEV_GETH
+ testConfig.BuilderTxSigningKey = nil
+ })
+
+ for _, algoType := range []AlgoType{ALGO_GREEDY} {
+ var err error
+ testConfig.BuilderTxSigningKey, err = crypto.GenerateKey()
+ require.NoError(t, err)
+ testConfig.AlgoType = algoType
+ t.Logf("running for %d", algoType)
+ testBundles(t)
+ }
+}
+
func TestPayoutTxUtils(t *testing.T) {
availableFunds := big.NewInt(50000000000000000) // 0.05 eth
diff --git a/miner/algo_greedy.go b/miner/algo_greedy.go
index d1fd114625..51b76a76cc 100644
--- a/miner/algo_greedy.go
+++ b/miner/algo_greedy.go
@@ -8,16 +8,6 @@ import (
"github.com/ethereum/go-ethereum/params"
)
-type chainData struct {
- chainConfig *params.ChainConfig
- chain *core.BlockChain
- blacklist map[common.Address]struct{}
-}
-
-type IBuilder interface {
- buildBlock(simBundles []types.SimulatedBundle, transactions map[common.Address]types.Transactions) (*environment, []types.SimulatedBundle)
-}
-
// / To use it:
// / 1. Copy relevant data from the worker
// / 2. Call buildBlock
@@ -37,14 +27,8 @@ func newGreedyBuilder(chain *core.BlockChain, chainConfig *params.ChainConfig, b
}
}
-func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transactions map[common.Address]types.Transactions) (*environment, []types.SimulatedBundle) {
-
- env := b.inputEnvironment.copy()
-
- orders := types.NewTransactionsByPriceAndNonce(env.signer, transactions, simBundles, env.header.BaseFee)
- envDiff := newEnvironmentDiff(env)
-
- usedBundles := make([]types.SimulatedBundle, 0)
+func (b *greedyBuilder) mergeOrdersIntoEnvDiff(envDiff *environmentDiff, orders *types.TransactionsByPriceAndNonce) []types.SimulatedBundle {
+ usedBundles := []types.SimulatedBundle{}
for {
order := orders.Peek()
@@ -65,7 +49,7 @@ func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transacti
log.Trace("could not apply tx", "hash", order.Tx.Hash(), "err", err)
continue
}
- effGapPrice, err := order.Tx.EffectiveGasTip(env.header.BaseFee)
+ effGapPrice, err := order.Tx.EffectiveGasTip(envDiff.baseEnvironment.header.BaseFee)
if err == nil {
log.Trace("Included tx", "EGP", effGapPrice.String(), "gasUsed", receipt.GasUsed)
}
@@ -84,6 +68,13 @@ func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transacti
}
}
+ return usedBundles
+}
+
+func (b *greedyBuilder) buildBlock(simBundles []types.SimulatedBundle, transactions map[common.Address]types.Transactions) (*environment, []types.SimulatedBundle) {
+ orders := types.NewTransactionsByPriceAndNonce(b.inputEnvironment.signer, transactions, simBundles, b.inputEnvironment.header.BaseFee)
+ envDiff := newEnvironmentDiff(b.inputEnvironment.copy())
+ usedBundles := b.mergeOrdersIntoEnvDiff(envDiff, orders)
envDiff.applyToBaseEnv()
- return env, usedBundles
+ return envDiff.baseEnvironment, usedBundles
}
diff --git a/miner/algo_greedy_test.go b/miner/algo_greedy_test.go
index b38704bc18..8f7f99d7ba 100644
--- a/miner/algo_greedy_test.go
+++ b/miner/algo_greedy_test.go
@@ -2,11 +2,12 @@ package miner
import (
"fmt"
+ "math/big"
+ "testing"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
- "math/big"
- "testing"
)
func TestBuildBlockGasLimit(t *testing.T) {
@@ -65,5 +66,4 @@ func TestTxWithMinerFeeHeap(t *testing.T) {
orders.Pop()
}
}
-
}
diff --git a/miner/bundle_cache.go b/miner/bundle_cache.go
index 42a810bd21..2d6bf18537 100644
--- a/miner/bundle_cache.go
+++ b/miner/bundle_cache.go
@@ -1,9 +1,10 @@
package miner
import (
+ "sync"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
- "sync"
)
const (
diff --git a/miner/bundle_cache_test.go b/miner/bundle_cache_test.go
index 71fcf376f6..77de101af6 100644
--- a/miner/bundle_cache_test.go
+++ b/miner/bundle_cache_test.go
@@ -1,9 +1,10 @@
package miner
import (
+ "testing"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
- "testing"
)
func TestBundleCacheEntry(t *testing.T) {
diff --git a/miner/miner.go b/miner/miner.go
index 1d0ed663b6..3d1215dfc2 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -19,8 +19,8 @@ package miner
import (
"crypto/ecdsa"
+ "errors"
"fmt"
- "github.com/ethereum/go-ethereum/crypto"
"math/big"
"os"
"strings"
@@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
@@ -53,6 +54,17 @@ const (
ALGO_GREEDY
)
+func AlgoTypeFlagToEnum(algoString string) (AlgoType, error) {
+ switch algoString {
+ case "mev-geth":
+ return ALGO_MEV_GETH, nil
+ case "greedy":
+ return ALGO_GREEDY, nil
+ default:
+ return ALGO_MEV_GETH, errors.New("algo not recognized")
+ }
+}
+
// Config is the configuration parameters of mining.
type Config struct {
Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards (default = first account)
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index 41ef0ea813..ca18ef3c83 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -94,7 +94,7 @@ type resChPair struct {
func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, error) {
resChans := []resChPair{}
- for _, worker := range append(w.workers) {
+ for _, worker := range w.workers {
resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook)
if err != nil {
log.Error("could not start async block construction", "isFlashbotsWorker", worker.flashbots.isFlashbots, "#bundles", worker.flashbots.maxMergedBundles)
@@ -141,7 +141,7 @@ func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64,
}
func newMultiWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(header *types.Header) bool, init bool) *multiWorker {
- if config.AlgoType == ALGO_GREEDY {
+ if config.AlgoType != ALGO_MEV_GETH {
return newMultiWorkerGreedy(config, chainConfig, engine, eth, mux, isLocalBlock, init)
} else {
return newMultiWorkerMevGeth(config, chainConfig, engine, eth, mux, isLocalBlock, init)
@@ -154,7 +154,7 @@ func newMultiWorkerGreedy(config *Config, chainConfig *params.ChainConfig, engin
greedyWorker := newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, init, &flashbotsData{
isFlashbots: true,
queue: queue,
- algoType: ALGO_GREEDY,
+ algoType: config.AlgoType,
maxMergedBundles: config.MaxMergedBundles,
bundleCache: NewBundleCache(),
})
diff --git a/miner/worker.go b/miner/worker.go
index 2b8a8852a4..e2c7f72661 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -79,8 +79,6 @@ const (
// staleThreshold is the maximum depth of the acceptable stale block.
staleThreshold = 7
-
- paymentTxGas = 26000
)
var (
@@ -276,6 +274,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
} else {
builderCoinbase = crypto.PubkeyToAddress(config.BuilderTxSigningKey.PublicKey)
}
+
log.Info("new worker", "builderCoinbase", builderCoinbase.String())
exitCh := make(chan struct{})
taskCh := make(chan *task)
@@ -1307,9 +1306,6 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment) (error, []
localTxs[account] = txs
}
}
- if env.gasPool == nil {
- env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
- }
var blockBundles []types.SimulatedBundle
if w.flashbots.isFlashbots {
@@ -1357,25 +1353,9 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment)
// Split the pending transactions into locals and remotes
// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
- if env.gasPool == nil {
- env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
- }
-
- var bundlesToConsider []types.SimulatedBundle
- if w.flashbots.isFlashbots {
- bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
- if err != nil {
- log.Error("Failed to fetch pending bundles", "err", err)
- return err, nil
- }
-
- simBundles, err := w.simulateBundles(env, bundles, nil) /* do not consider gas impact of mempool txs as bundles are treated as transactions wrt ordering */
- if err != nil {
- log.Error("Failed to simulate flashbots bundles", "err", err)
- return err, nil
- }
-
- bundlesToConsider = simBundles
+ bundlesToConsider, err := w.getSimulatedBundles(env)
+ if err != nil {
+ return err, nil
}
builder := newGreedyBuilder(w.chain, w.chainConfig, w.blockList, env, interrupt)
@@ -1385,6 +1365,27 @@ func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment)
return nil, blockBundles
}
+func (w *worker) getSimulatedBundles(env *environment) ([]types.SimulatedBundle, error) {
+ if !w.flashbots.isFlashbots {
+ return nil, nil
+ }
+
+ bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
+ if err != nil {
+ log.Error("Failed to fetch pending bundles", "err", err)
+ return nil, err
+ }
+
+ // TODO: consider interrupt
+ simBundles, err := w.simulateBundles(env, bundles, nil) /* do not consider gas impact of mempool txs as bundles are treated as transactions wrt ordering */
+ if err != nil {
+ log.Error("Failed to simulate flashbots bundles", "err", err)
+ return nil, err
+ }
+
+ return simBundles, nil
+}
+
// generateWork generates a sealing block based on the given parameters.
func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
start := time.Now()
@@ -1398,28 +1399,53 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
}
defer work.discard()
- var blockBundles []types.SimulatedBundle
- if !params.noTxs {
- var err error
- paymentTxReserve, err := w.proposerTxPrepare(work, &validatorCoinbase)
+ finalizeFn := func(env *environment, blockBundles []types.SimulatedBundle) (*types.Block, error) {
+ block, err := w.finalizeBlock(env, validatorCoinbase)
if err != nil {
+ log.Error("could not finalize block", "err", err)
return nil, err
}
- switch w.flashbots.algoType {
- case ALGO_GREEDY:
- err, blockBundles = w.fillTransactionsAlgoWorker(nil, work)
- case ALGO_MEV_GETH:
- err, blockBundles = w.fillTransactions(nil, work)
- default:
- err, blockBundles = w.fillTransactions(nil, work)
+ log.Info("Block finalized and assembled", "blockProfit", ethIntToFloat(block.Profit), "txs", len(env.txs), "bundles", len(blockBundles), "gasUsed", block.GasUsed(), "time", time.Since(start))
+ if params.onBlock != nil {
+ go params.onBlock(block, blockBundles)
}
- err = w.proposerTxCommit(work, &validatorCoinbase, paymentTxReserve)
- if err != nil {
- return nil, err
- }
+ return block, nil
+ }
+
+ if params.noTxs {
+ return finalizeFn(work, nil)
+ }
+
+ paymentTxReserve, err := w.proposerTxPrepare(work, &validatorCoinbase)
+ if err != nil {
+ return nil, err
+ }
+
+ var blockBundles []types.SimulatedBundle
+ switch w.flashbots.algoType {
+ case ALGO_GREEDY:
+ err, blockBundles = w.fillTransactionsAlgoWorker(nil, work)
+ case ALGO_MEV_GETH:
+ err, blockBundles = w.fillTransactions(nil, work)
+ default:
+ err, blockBundles = w.fillTransactions(nil, work)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ err = w.proposerTxCommit(work, &validatorCoinbase, paymentTxReserve)
+ if err != nil {
+ return nil, err
}
+
+ return finalizeFn(work, blockBundles)
+}
+
+func (w *worker) finalizeBlock(work *environment, validatorCoinbase common.Address) (*types.Block, error) {
block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
if err != nil {
return nil, err
@@ -1431,6 +1457,16 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
return block, nil
}
+ blockProfit, err := w.checkProposerPayment(work, validatorCoinbase)
+ if err != nil {
+ return nil, err
+ }
+
+ block.Profit = blockProfit
+ return block, nil
+}
+
+func (w *worker) checkProposerPayment(work *environment, validatorCoinbase common.Address) (*big.Int, error) {
if len(work.txs) == 0 {
return nil, errors.New("no proposer payment tx")
} else if len(work.receipts) == 0 {
@@ -1445,18 +1481,11 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
}
lastTxTo := lastTx.To()
if lastTxTo == nil || *lastTxTo != validatorCoinbase {
- log.Error("last transaction is not to the proposer!", "err", err, "lastTx", lastTx)
+ log.Error("last transaction is not to the proposer!", "lastTx", lastTx)
return nil, errors.New("last transaction is not proposer payment")
}
- block.Profit.Set(lastTx.Value())
- log.Info("Block finalized and assembled", "blockProfit", ethIntToFloat(block.Profit), "txs", len(work.txs), "bundles", len(blockBundles), "gasUsed", block.GasUsed(), "time", time.Since(start))
-
- if params.onBlock != nil {
- go params.onBlock(block, blockBundles)
- }
-
- return block, nil
+ return new(big.Int).Set(lastTx.Value()), nil
}
// commitWork generates several new sealing tasks based on the parent block
diff --git a/miner/worker_test.go b/miner/worker_test.go
index 53b2c4446a..6baeea4b86 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -17,6 +17,7 @@
package miner
import (
+ "crypto/ecdsa"
"errors"
"math/big"
"math/rand"
@@ -38,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
+ "github.com/stretchr/testify/require"
)
const (
@@ -60,6 +62,13 @@ var (
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
testBankFunds = big.NewInt(1000000000000000000)
+ testAddress1Key, _ = crypto.GenerateKey()
+ testAddress1 = crypto.PubkeyToAddress(testAddress1Key.PublicKey)
+ testAddress2Key, _ = crypto.GenerateKey()
+ testAddress2 = crypto.PubkeyToAddress(testAddress2Key.PublicKey)
+ testAddress3Key, _ = crypto.GenerateKey()
+ testAddress3 = crypto.PubkeyToAddress(testAddress3Key.PublicKey)
+
testUserKey, _ = crypto.GenerateKey()
testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey)
@@ -71,6 +80,8 @@ var (
Recommit: time.Second,
GasCeil: params.GenesisGasLimit,
}
+
+ defaultGenesisAlloc = core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}
)
func init() {
@@ -117,10 +128,10 @@ type testWorkerBackend struct {
uncleBlock *types.Block
}
-func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend {
+func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, alloc core.GenesisAlloc, n int) *testWorkerBackend {
var gspec = core.Genesis{
Config: chainConfig,
- Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
+ Alloc: alloc,
}
switch e := engine.(type) {
@@ -187,24 +198,24 @@ func (b *testWorkerBackend) newRandomUncle() *types.Block {
return blocks[0]
}
-func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
+func (b *testWorkerBackend) newRandomTx(creation bool, to common.Address, amt int64, key *ecdsa.PrivateKey, additionalGasLimit uint64, gasPrice *big.Int) *types.Transaction {
var tx *types.Transaction
- gasPrice := big.NewInt(10 * params.InitialBaseFee)
if creation {
- tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey)
+ tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(crypto.PubkeyToAddress(key.PublicKey)), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, key)
} else {
- tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey)
+ tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(crypto.PubkeyToAddress(key.PublicKey)), to, big.NewInt(amt), params.TxGas+additionalGasLimit, gasPrice, nil), types.HomesteadSigner{}, key)
}
return tx
}
-func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) {
- backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
+func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, alloc core.GenesisAlloc, blocks int) (*worker, *testWorkerBackend) {
+ backend := newTestWorkerBackend(t, chainConfig, engine, db, alloc, blocks)
backend.txPool.AddLocals(pendingTxs)
w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, &flashbotsData{
- isFlashbots: false,
+ isFlashbots: testConfig.AlgoType != ALGO_MEV_GETH,
queue: nil,
bundleCache: NewBundleCache(),
+ algoType: testConfig.AlgoType,
})
w.setEtherbase(testBankAddress)
return w, backend
@@ -234,7 +245,7 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
}
chainConfig.LondonBlock = big.NewInt(0)
- w, b := newTestWorker(t, chainConfig, engine, db, 0)
+ w, b := newTestWorker(t, chainConfig, engine, db, defaultGenesisAlloc, 0)
defer w.close()
// This test chain imports the mined blocks.
@@ -256,8 +267,8 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
w.start()
for i := 0; i < 5; i++ {
- b.txPool.AddLocal(b.newRandomTx(true))
- b.txPool.AddLocal(b.newRandomTx(false))
+ b.txPool.AddLocal(b.newRandomTx(true, testUserAddress, 0, testBankKey, 0, big.NewInt(10*params.InitialBaseFee)))
+ b.txPool.AddLocal(b.newRandomTx(false, testUserAddress, 1000, testBankKey, 0, big.NewInt(10*params.InitialBaseFee)))
w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()})
w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()})
@@ -283,7 +294,7 @@ func TestEmptyWorkClique(t *testing.T) {
func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
defer engine.Close()
- w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
+ w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0)
defer w.close()
var (
@@ -329,7 +340,7 @@ func TestStreamUncleBlock(t *testing.T) {
ethash := ethash.NewFaker()
defer ethash.Close()
- w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1)
+ w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 1)
defer w.close()
var taskCh = make(chan struct{})
@@ -387,7 +398,7 @@ func TestRegenerateMiningBlockClique(t *testing.T) {
func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
defer engine.Close()
- w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
+ w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0)
defer w.close()
var taskCh = make(chan struct{}, 3)
@@ -447,7 +458,7 @@ func TestAdjustIntervalClique(t *testing.T) {
func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
defer engine.Close()
- w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
+ w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0)
defer w.close()
w.skipSealHook = func(task *task) bool {
@@ -547,7 +558,7 @@ func TestGetSealingWorkPostMerge(t *testing.T) {
func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, postMerge bool) {
defer engine.Close()
- w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
+ w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0)
defer w.close()
w.setExtra([]byte{0x01, 0x02})
@@ -675,7 +686,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
}
func TestSimulateBundles(t *testing.T) {
- w, _ := newTestWorker(t, ethashChainConfig, ethash.NewFaker(), rawdb.NewMemoryDatabase(), 0)
+ w, _ := newTestWorker(t, ethashChainConfig, ethash.NewFaker(), rawdb.NewMemoryDatabase(), defaultGenesisAlloc, 0)
defer w.close()
env, err := w.prepareWork(&generateParams{gasLimit: 30000000})
@@ -697,6 +708,7 @@ func TestSimulateBundles(t *testing.T) {
bundle3 := types.MevBundle{Txs: types.Transactions{signTx(0)}, Hash: common.HexToHash("0x03")}
simBundles, err := w.simulateBundles(env, []types.MevBundle{bundle1, bundle2, bundle3}, nil)
+ require.NoError(t, err)
if len(simBundles) != 2 {
t.Fatalf("Incorrect amount of sim bundles")
@@ -710,6 +722,7 @@ func TestSimulateBundles(t *testing.T) {
// simulate 2 times to check cache
simBundles, err = w.simulateBundles(env, []types.MevBundle{bundle1, bundle2, bundle3}, nil)
+ require.NoError(t, err)
if len(simBundles) != 2 {
t.Fatalf("Incorrect amount of sim bundles(cache)")
@@ -721,3 +734,118 @@ func TestSimulateBundles(t *testing.T) {
}
}
}
+
+func testBundles(t *testing.T) {
+ db := rawdb.NewMemoryDatabase()
+ chainConfig := params.AllEthashProtocolChanges
+ engine := ethash.NewFaker()
+
+ chainConfig.LondonBlock = big.NewInt(0)
+
+ genesisAlloc := core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}
+
+ nExtraKeys := 5
+ extraKeys := make([]*ecdsa.PrivateKey, nExtraKeys)
+ for i := 0; i < nExtraKeys; i++ {
+ pk, _ := crypto.GenerateKey()
+ address := crypto.PubkeyToAddress(pk.PublicKey)
+ extraKeys[i] = pk
+ genesisAlloc[address] = core.GenesisAccount{Balance: testBankFunds}
+ }
+
+ nSearchers := 5
+ searcherPrivateKeys := make([]*ecdsa.PrivateKey, nSearchers)
+ for i := 0; i < nSearchers; i++ {
+ pk, _ := crypto.GenerateKey()
+ address := crypto.PubkeyToAddress(pk.PublicKey)
+ searcherPrivateKeys[i] = pk
+ genesisAlloc[address] = core.GenesisAccount{Balance: testBankFunds}
+ }
+
+ for _, address := range []common.Address{testAddress1, testAddress2, testAddress3} {
+ genesisAlloc[address] = core.GenesisAccount{Balance: testBankFunds}
+ }
+
+ w, b := newTestWorker(t, chainConfig, engine, db, genesisAlloc, 0)
+ w.setEtherbase(crypto.PubkeyToAddress(testConfig.BuilderTxSigningKey.PublicKey))
+ defer w.close()
+
+ // This test chain imports the mined blocks.
+ db2 := rawdb.NewMemoryDatabase()
+ b.genesis.MustCommit(db2)
+ chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil, nil)
+ defer chain.Stop()
+
+ // Ignore empty commit here for less noise.
+ w.skipSealHook = func(task *task) bool {
+ return len(task.receipts) == 0
+ }
+
+ // Wait for mined blocks.
+ sub := w.mux.Subscribe(core.NewMinedBlockEvent{})
+ defer sub.Unsubscribe()
+
+ rand.Seed(10)
+
+ for i := 0; i < 2; i++ {
+ commonTxs := []*types.Transaction{
+ b.newRandomTx(false, testBankAddress, 1e15, testAddress1Key, 0, big.NewInt(100*params.InitialBaseFee)),
+ b.newRandomTx(false, testBankAddress, 1e15, testAddress2Key, 0, big.NewInt(110*params.InitialBaseFee)),
+ b.newRandomTx(false, testBankAddress, 1e15, testAddress3Key, 0, big.NewInt(120*params.InitialBaseFee)),
+ }
+
+ searcherTxs := make([]*types.Transaction, len(searcherPrivateKeys)*2)
+ for i, pk := range searcherPrivateKeys {
+ searcherTxs[2*i] = b.newRandomTx(false, testBankAddress, 1, pk, 0, big.NewInt(150*params.InitialBaseFee))
+ searcherTxs[2*i+1] = b.newRandomTx(false, testBankAddress, 1+1, pk, 0, big.NewInt(150*params.InitialBaseFee))
+ }
+
+ nBundles := 2 * len(searcherPrivateKeys)
+ // two bundles per searcher, i and i+1
+ bundles := make([]*types.MevBundle, nBundles)
+ for i := 0; i < nBundles; i++ {
+ bundles[i] = new(types.MevBundle)
+ bundles[i].Txs = append(bundles[i].Txs, searcherTxs[i])
+ }
+
+ // common transactions in 10% of the bundles, randomly
+ for i := 0; i < nBundles/10; i++ {
+ randomCommonIndex := rand.Intn(len(commonTxs))
+ randomBundleIndex := rand.Intn(nBundles)
+ bundles[randomBundleIndex].Txs = append(bundles[randomBundleIndex].Txs, commonTxs[randomCommonIndex])
+ }
+
+ // additional lower profit transactions in 10% of the bundles, randomly
+ for _, extraKey := range extraKeys {
+ tx := b.newRandomTx(false, testBankAddress, 1, extraKey, 0, big.NewInt(20*params.InitialBaseFee))
+ randomBundleIndex := rand.Intn(nBundles)
+ bundles[randomBundleIndex].Txs = append(bundles[randomBundleIndex].Txs, tx)
+ }
+
+ blockNumber := big.NewInt(0).Add(chain.CurrentBlock().Number(), big.NewInt(1))
+ for _, bundle := range bundles {
+ err := b.txPool.AddMevBundle(bundle.Txs, blockNumber, 0, 0, nil)
+ require.NoError(t, err)
+ }
+
+ blockCh, errCh, err := w.getSealingBlock(chain.CurrentBlock().Hash(), chain.CurrentHeader().Time+12, testUserAddress, 0, common.Hash{}, false, false, nil)
+ require.NoError(t, err)
+ select {
+ case block := <-blockCh:
+ state, err := chain.State()
+ require.NoError(t, err)
+ balancePre := state.GetBalance(testUserAddress)
+ if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
+ t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err)
+ }
+ state, err = chain.StateAt(block.Root())
+ require.NoError(t, err)
+ balancePost := state.GetBalance(testUserAddress)
+ t.Log("Balances", balancePre, balancePost)
+ case err := <-errCh:
+ require.NoError(t, err)
+ case <-time.After(3 * time.Second): // Worker needs 1s to include new changes.
+ t.Fatalf("timeout")
+ }
+ }
+}
From 9a628299261d0f268e6fbbc4cde0e308e55df194 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Tue, 11 Oct 2022 16:34:36 +0200
Subject: [PATCH 56/83] Do not commit failing payment txs (#43)
---
miner/algo_common.go | 45 ++++++++++++++++++++++++---------------
miner/algo_common_test.go | 8 +++++--
2 files changed, 34 insertions(+), 19 deletions(-)
diff --git a/miner/algo_common.go b/miner/algo_common.go
index 9c39993612..90f4b2a79e 100644
--- a/miner/algo_common.go
+++ b/miner/algo_common.go
@@ -293,25 +293,24 @@ func estimatePayoutTxGas(env *environment, sender, receiver common.Address, prv
}
func insertPayoutTx(env *environment, sender, receiver common.Address, gas uint64, isEOA bool, availableFunds *big.Int, prv *ecdsa.PrivateKey, chData chainData) (*types.Receipt, error) {
- diff := newEnvironmentDiff(env)
- applyTx := func(gas uint64) (*types.Receipt, error) {
+ applyTx := func(envDiff *environmentDiff, gas uint64) (*types.Receipt, error) {
fee := new(big.Int).Mul(env.header.BaseFee, new(big.Int).SetUint64(gas))
amount := new(big.Int).Sub(availableFunds, fee)
if amount.Sign() < 0 {
return nil, errors.New("not enough funds available")
}
- rec, err := diff.commitPayoutTx(amount, sender, receiver, gas, prv, chData)
+ rec, err := envDiff.commitPayoutTx(amount, sender, receiver, gas, prv, chData)
if err != nil {
return nil, fmt.Errorf("failed to commit payment tx: %w", err)
- }
- if rec.Status != types.ReceiptStatusSuccessful {
+ } else if rec.Status != types.ReceiptStatusSuccessful {
return nil, fmt.Errorf("payment tx failed")
}
return rec, nil
}
if isEOA {
- rec, err := applyTx(gas)
+ diff := newEnvironmentDiff(env)
+ rec, err := applyTx(diff, gas)
if err != nil {
return nil, err
}
@@ -319,24 +318,36 @@ func insertPayoutTx(env *environment, sender, receiver common.Address, gas uint6
return rec, nil
}
- var (
- rec *types.Receipt
- err error
- )
+ var err error
for i := 0; i < 6; i++ {
- rec, err = applyTx(gas)
+ diff := newEnvironmentDiff(env)
+ var rec *types.Receipt
+ rec, err = applyTx(diff, gas)
if err != nil {
gas += 1000
- } else {
- break
+ continue
+ }
+
+ if gas == rec.GasUsed {
+ diff.applyToBaseEnv()
+ return rec, nil
+ }
+
+ exactEnvDiff := newEnvironmentDiff(env)
+ exactRec, err := applyTx(exactEnvDiff, rec.GasUsed)
+ if err != nil {
+ diff.applyToBaseEnv()
+ return rec, nil
}
+ exactEnvDiff.applyToBaseEnv()
+ return exactRec, nil
}
- if err != nil {
- return nil, err
+ if err == nil {
+ return nil, errors.New("could not estimate gas")
}
- diff.applyToBaseEnv()
- return rec, nil
+
+ return nil, err
}
func (envDiff *environmentDiff) commitPayoutTx(amount *big.Int, sender, receiver common.Address, gas uint64, prv *ecdsa.PrivateKey, chData chainData) (*types.Receipt, error) {
diff --git a/miner/algo_common_test.go b/miner/algo_common_test.go
index b94198608f..84fd4a9357 100644
--- a/miner/algo_common_test.go
+++ b/miner/algo_common_test.go
@@ -548,6 +548,7 @@ func TestPayoutTxUtils(t *testing.T) {
require.Equal(t, types.ReceiptStatusSuccessful, rec.Status)
require.Equal(t, uint64(21000), rec.GasUsed)
require.True(t, new(big.Int).Sub(balanceAfter, balanceBefore).Cmp(expectedPayment) == 0)
+ require.Equal(t, env.state.GetNonce(signers.addresses[1]), uint64(1))
// Sending payment to the contract that logs event of the amount
gas, isEOA, err = estimatePayoutTxGas(env, signers.addresses[1], logContractAddress, signers.signers[1], chData)
@@ -564,10 +565,10 @@ func TestPayoutTxUtils(t *testing.T) {
require.Equal(t, types.ReceiptStatusSuccessful, rec.Status)
require.Equal(t, uint64(22025), rec.GasUsed)
require.True(t, new(big.Int).Sub(balanceAfter, balanceBefore).Cmp(expectedPayment) == 0)
+ require.Equal(t, env.state.GetNonce(signers.addresses[1]), uint64(2))
// Try requesting less gas for contract tx. We request 21k gas, but we must pay 22025
- // iteration logic should set gas limit to 23k
- expectedPayment = new(big.Int).Sub(availableFunds, big.NewInt(23000))
+ expectedPayment = new(big.Int).Sub(availableFunds, big.NewInt(22025))
balanceBefore = env.state.GetBalance(logContractAddress)
rec, err = insertPayoutTx(env, signers.addresses[1], logContractAddress, 21000, isEOA, availableFunds, signers.signers[1], chData)
balanceAfter = env.state.GetBalance(logContractAddress)
@@ -576,6 +577,7 @@ func TestPayoutTxUtils(t *testing.T) {
require.Equal(t, types.ReceiptStatusSuccessful, rec.Status)
require.Equal(t, uint64(22025), rec.GasUsed)
require.True(t, new(big.Int).Sub(balanceAfter, balanceBefore).Cmp(expectedPayment) == 0)
+ require.Equal(t, env.state.GetNonce(signers.addresses[1]), uint64(3))
// errors
@@ -591,4 +593,6 @@ func TestPayoutTxUtils(t *testing.T) {
_, err = insertPayoutTx(env, signers.addresses[1], signers.addresses[2], 20000, true, availableFunds, signers.signers[1], chData)
require.ErrorContains(t, err, "not enough gas")
+
+ require.Equal(t, env.state.GetNonce(signers.addresses[1]), uint64(3))
}
From baf280f50cb58cb7f1bcff46fe968bcde33c3e94 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Mon, 17 Oct 2022 12:31:29 +0200
Subject: [PATCH 57/83] More bundle visibility (#42)
Use batch queries for upserting data on built blocks. Include more data on the built blocks - sealing time, orderflow cutoff time, and include data for all bundles considered.
---
builder/builder.go | 35 +++--
builder/eth_service.go | 10 +-
builder/eth_service_test.go | 2 +-
flashbotsextra/database.go | 226 ++++++++++++++++++++++---------
flashbotsextra/database_test.go | 92 ++++++++++++-
flashbotsextra/database_types.go | 7 +
miner/miner.go | 7 +-
miner/multi_worker.go | 4 +-
miner/worker.go | 85 ++++++------
9 files changed, 344 insertions(+), 124 deletions(-)
diff --git a/builder/builder.go b/builder/builder.go
index 7f9758ceb0..d750576923 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -97,7 +97,7 @@ func (b *Builder) Stop() error {
return nil
}
-func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
+func (b *Builder) onSealedBlock(block *types.Block, ordersClosedAt time.Time, sealedAt time.Time, commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
executableData := beacon.BlockToExecutableData(block)
payload, err := executableDataToExecutionPayload(executableData)
if err != nil {
@@ -142,15 +142,15 @@ func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBun
log.Error("could not validate block", "err", err)
}
} else {
- go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)
+ go b.ds.ConsumeBuiltBlock(block, ordersClosedAt, sealedAt, commitedBundles, allBundles, &blockBidMsg)
err = b.relay.SubmitBlock(&blockSubmitReq)
if err != nil {
- log.Error("could not submit block", "err", err, "bundles", len(bundles))
+ log.Error("could not submit block", "err", err, "#commitedBundles", len(commitedBundles))
return err
}
}
- log.Info("submitted block", "slot", blockBidMsg.Slot, "value", blockBidMsg.Value.String(), "parent", blockBidMsg.ParentHash, "hash", block.Hash(), "bundles", len(bundles))
+ log.Info("submitted block", "slot", blockBidMsg.Slot, "value", blockBidMsg.Value.String(), "parent", blockBidMsg.ParentHash, "hash", block.Hash(), "#commitedBundles", len(commitedBundles))
return nil
}
@@ -212,6 +212,14 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
return nil
}
+type blockQueueEntry struct {
+ block *types.Block
+ ordersCloseTime time.Time
+ sealedAt time.Time
+ commitedBundles []types.SimulatedBundle
+ allBundles []types.SimulatedBundle
+}
+
func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTypes.PublicKey, feeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) {
ctx, cancel := context.WithTimeout(slotCtx, 12*time.Second)
defer cancel()
@@ -229,8 +237,7 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
queueMu sync.Mutex
queueLastSubmittedProfit = new(big.Int)
queueBestProfit = new(big.Int)
- queueBestBlock *types.Block
- queueBestBundles []types.SimulatedBundle
+ queueBestEntry blockQueueEntry
)
log.Debug("runBuildingJob", "slot", attrs.Slot, "parent", attrs.HeadHash)
@@ -238,7 +245,8 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
submitBestBlock := func() {
queueMu.Lock()
if queueLastSubmittedProfit.Cmp(queueBestProfit) < 0 {
- err := b.onSealedBlock(queueBestBlock, queueBestBundles, proposerPubkey, feeRecipient, attrs)
+ err := b.onSealedBlock(queueBestEntry.block, queueBestEntry.ordersCloseTime, queueBestEntry.sealedAt, queueBestEntry.commitedBundles, queueBestEntry.allBundles, proposerPubkey, feeRecipient, attrs)
+
if err != nil {
log.Error("could not run sealed block hook", "err", err)
} else {
@@ -252,16 +260,23 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
go runResubmitLoop(ctx, b.limiter, queueSignal, submitBestBlock)
// Populates queue with submissions that increase block profit
- blockHook := func(block *types.Block, bundles []types.SimulatedBundle) {
+ blockHook := func(block *types.Block, ordersCloseTime time.Time, commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle) {
if ctx.Err() != nil {
return
}
+ sealedAt := time.Now()
+
queueMu.Lock()
defer queueMu.Unlock()
if block.Profit.Cmp(queueBestProfit) > 0 {
- queueBestBlock = block
- queueBestBundles = bundles
+ queueBestEntry = blockQueueEntry{
+ block: block,
+ ordersCloseTime: ordersCloseTime,
+ sealedAt: sealedAt,
+ commitedBundles: commitedBundles,
+ allBundles: allBundles,
+ }
queueBestProfit.Set(block.Profit)
select {
diff --git a/builder/eth_service.go b/builder/eth_service.go
index 48490b0ce5..47ef090d5c 100644
--- a/builder/eth_service.go
+++ b/builder/eth_service.go
@@ -9,10 +9,11 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/miner"
)
type IEthereumService interface {
- BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error
+ BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback miner.BlockHookFn) error
GetBlockByHash(hash common.Hash) *types.Block
Synced() bool
}
@@ -22,10 +23,11 @@ type testEthereumService struct {
testExecutableData *beacon.ExecutableDataV1
testBlock *types.Block
testBundlesMerged []types.SimulatedBundle
+ testAllBundles []types.SimulatedBundle
}
-func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error {
- sealedBlockCallback(t.testBlock, t.testBundlesMerged)
+func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback miner.BlockHookFn) error {
+ sealedBlockCallback(t.testBlock, time.Now(), t.testBundlesMerged, t.testAllBundles)
return nil
}
@@ -41,7 +43,7 @@ func NewEthereumService(eth *eth.Ethereum) *EthereumService {
return &EthereumService{eth: eth}
}
-func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error {
+func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback miner.BlockHookFn) error {
// Send a request to generate a full block in the background.
// The result can be obtained via the returned channel.
resCh, err := s.eth.Miner().GetSealingBlockAsync(attrs.HeadHash, uint64(attrs.Timestamp), attrs.SuggestedFeeRecipient, attrs.GasLimit, attrs.Random, false, sealedBlockCallback)
diff --git a/builder/eth_service_test.go b/builder/eth_service_test.go
index 9443a19f08..e13a05e288 100644
--- a/builder/eth_service_test.go
+++ b/builder/eth_service_test.go
@@ -93,7 +93,7 @@ func TestBuildBlock(t *testing.T) {
service := NewEthereumService(ethservice)
service.eth.APIBackend.Miner().SetEtherbase(common.Address{0x05, 0x11})
- err := service.BuildBlock(testPayloadAttributes, func(block *types.Block, _ []types.SimulatedBundle) {
+ err := service.BuildBlock(testPayloadAttributes, func(block *types.Block, _ time.Time, _ []types.SimulatedBundle, _ []types.SimulatedBundle) {
executableData := beacon.BlockToExecutableData(block)
require.Equal(t, common.Address{0x05, 0x11}, executableData.FeeRecipient)
require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random)
diff --git a/flashbotsextra/database.go b/flashbotsextra/database.go
index cd840b067a..1375e9214a 100644
--- a/flashbotsextra/database.go
+++ b/flashbotsextra/database.go
@@ -19,13 +19,14 @@ const (
)
type IDatabaseService interface {
- ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace)
+ ConsumeBuiltBlock(block *types.Block, OrdersClosedAt time.Time, sealedAt time.Time, commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace)
GetPriorityBundles(ctx context.Context, blockNum int64, isHighPrio bool) ([]DbBundle, error)
}
type NilDbService struct{}
-func (NilDbService) ConsumeBuiltBlock(*types.Block, []types.SimulatedBundle, *boostTypes.BidTrace) {}
+func (NilDbService) ConsumeBuiltBlock(block *types.Block, _ time.Time, _ time.Time, _ []types.SimulatedBundle, _ []types.SimulatedBundle, _ *boostTypes.BidTrace) {
+}
func (NilDbService) GetPriorityBundles(ctx context.Context, blockNum int64, isHighPrio bool) ([]DbBundle, error) {
return []DbBundle{}, nil
@@ -34,11 +35,9 @@ func (NilDbService) GetPriorityBundles(ctx context.Context, blockNum int64, isHi
type DatabaseService struct {
db *sqlx.DB
- insertBuiltBlockStmt *sqlx.NamedStmt
- insertBlockBuiltBundleNoIdStmt *sqlx.NamedStmt
- insertBlockBuiltBundleWithIdStmt *sqlx.NamedStmt
- insertMissingBundleStmt *sqlx.NamedStmt
- fetchPrioBundlesStmt *sqlx.NamedStmt
+ insertBuiltBlockStmt *sqlx.NamedStmt
+ insertMissingBundleStmt *sqlx.NamedStmt
+ fetchPrioBundlesStmt *sqlx.NamedStmt
}
func NewDatabaseService(postgresDSN string) (*DatabaseService, error) {
@@ -47,47 +46,116 @@ func NewDatabaseService(postgresDSN string) (*DatabaseService, error) {
return nil, err
}
- insertBuiltBlockStmt, err := db.PrepareNamed("insert into built_blocks (block_number, profit, slot, hash, gas_limit, gas_used, base_fee, parent_hash, proposer_pubkey, proposer_fee_recipient, builder_pubkey, timestamp, timestamp_datetime) values (:block_number, :profit, :slot, :hash, :gas_limit, :gas_used, :base_fee, :parent_hash, :proposer_pubkey, :proposer_fee_recipient, :builder_pubkey, :timestamp, to_timestamp(:timestamp)) returning block_id")
+ insertBuiltBlockStmt, err := db.PrepareNamed("insert into built_blocks (block_number, profit, slot, hash, gas_limit, gas_used, base_fee, parent_hash, proposer_pubkey, proposer_fee_recipient, builder_pubkey, timestamp, timestamp_datetime, orders_closed_at, sealed_at) values (:block_number, :profit, :slot, :hash, :gas_limit, :gas_used, :base_fee, :parent_hash, :proposer_pubkey, :proposer_fee_recipient, :builder_pubkey, :timestamp, to_timestamp(:timestamp), :orders_closed_at, :sealed_at) returning block_id")
if err != nil {
return nil, err
}
- insertBlockBuiltBundleNoIdStmt, err := db.PrepareNamed("insert into built_blocks_bundles (block_id, bundle_id) select :block_id, id from bundles where bundle_hash = :bundle_hash and param_block_number = :block_number returning bundle_id")
+ insertMissingBundleStmt, err := db.PrepareNamed("insert into bundles (bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase) values (:bundle_hash, :param_signed_txs, :param_block_number, :param_timestamp, :received_timestamp, :param_reverting_tx_hashes, :coinbase_diff, :total_gas_used, :state_block_number, :gas_fees, :eth_sent_to_coinbase) on conflict (bundle_hash, param_block_number) do nothing returning id")
if err != nil {
return nil, err
}
- insertBlockBuiltBundleWithIdStmt, err := db.PrepareNamed("insert into built_blocks_bundles (block_id, bundle_id) select :block_id, :bundle_id returning bundle_id")
+ fetchPrioBundlesStmt, err := db.PrepareNamed("select bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase from bundles where is_high_prio = :is_high_prio and coinbase_diff*1e18/total_gas_used > 1000000000 and param_block_number = :param_block_number order by coinbase_diff/total_gas_used DESC limit :limit")
if err != nil {
return nil, err
}
+ return &DatabaseService{
+ db: db,
+ insertBuiltBlockStmt: insertBuiltBlockStmt,
+ insertMissingBundleStmt: insertMissingBundleStmt,
+ fetchPrioBundlesStmt: fetchPrioBundlesStmt,
+ }, nil
+}
- insertMissingBundleStmt, err := db.PrepareNamed("insert into bundles (bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase) values (:bundle_hash, :param_signed_txs, :param_block_number, :param_timestamp, :received_timestamp, :param_reverting_tx_hashes, :coinbase_diff, :total_gas_used, :state_block_number, :gas_fees, :eth_sent_to_coinbase) on conflict (bundle_hash, param_block_number) do nothing returning id")
+func Min(l int, r int) int {
+ if l < r {
+ return l
+ }
+ return r
+}
+
+func (ds *DatabaseService) getBundleIds(ctx context.Context, blockNumber uint64, bundles []types.SimulatedBundle) (map[string]uint64, error) {
+ if len(bundles) == 0 {
+ return nil, nil
+ }
+
+ bundleIdsMap := make(map[string]uint64, len(bundles))
+
+ // Batch by 500
+ requestsToMake := [][]string{make([]string, 0, Min(500, len(bundles)))}
+ cRequestInd := 0
+ for i, bundle := range bundles {
+ if i != 0 && i%500 == 0 {
+ cRequestInd += 1
+ requestsToMake = append(requestsToMake, make([]string, 0, Min(500, len(bundles)-i)))
+ }
+ requestsToMake[cRequestInd] = append(requestsToMake[cRequestInd], bundle.OriginalBundle.Hash.String())
+ }
+
+ for _, request := range requestsToMake {
+ query, args, err := sqlx.In("select id, bundle_hash from bundles where param_block_number = ? and bundle_hash in (?)", blockNumber, request)
+ if err != nil {
+ return nil, err
+ }
+ query = ds.db.Rebind(query)
+
+ queryRes := []struct {
+ Id uint64 `db:"id"`
+ BundleHash string `db:"bundle_hash"`
+ }{}
+ err = ds.db.SelectContext(ctx, &queryRes, query, args...)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, row := range queryRes {
+ bundleIdsMap[row.BundleHash] = row.Id
+ }
+ }
+
+ return bundleIdsMap, nil
+}
+
+// TODO: cache locally for current block!
+func (ds *DatabaseService) getBundleIdsAndInsertMissingBundles(ctx context.Context, blockNumber uint64, bundles []types.SimulatedBundle) (map[string]uint64, error) {
+ bundleIdsMap, err := ds.getBundleIds(ctx, blockNumber, bundles)
if err != nil {
return nil, err
}
- fetchPrioBundlesStmt, err := db.PrepareNamed("select bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase from bundles where is_high_prio = :is_high_prio and coinbase_diff*1e18/total_gas_used > 1000000000 and param_block_number = :param_block_number order by coinbase_diff/total_gas_used DESC limit :limit")
+ toRetry := []types.SimulatedBundle{}
+ for _, bundle := range bundles {
+ bundleHashString := bundle.OriginalBundle.Hash.String()
+ if _, found := bundleIdsMap[bundleHashString]; found {
+ continue
+ }
+
+ var bundleId uint64
+ missingBundleData := SimulatedBundleToDbBundle(&bundle) // nolint: gosec
+ err = ds.insertMissingBundleStmt.GetContext(ctx, &bundleId, missingBundleData) // not using the tx as it relies on the unique constraint!
+ if err == nil {
+ bundleIdsMap[bundleHashString] = bundleId
+ } else if err == sql.ErrNoRows /* conflict, someone else inserted the bundle before we could */ {
+ toRetry = append(toRetry, bundle)
+ } else {
+ log.Error("could not insert missing bundle", "err", err)
+ }
+ }
+
+ retriedBundleIds, err := ds.getBundleIds(ctx, blockNumber, toRetry)
if err != nil {
return nil, err
}
- return &DatabaseService{
- db: db,
- insertBuiltBlockStmt: insertBuiltBlockStmt,
- insertBlockBuiltBundleNoIdStmt: insertBlockBuiltBundleNoIdStmt,
- insertBlockBuiltBundleWithIdStmt: insertBlockBuiltBundleWithIdStmt,
- insertMissingBundleStmt: insertMissingBundleStmt,
- fetchPrioBundlesStmt: fetchPrioBundlesStmt,
- }, nil
-}
-func (ds *DatabaseService) ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace) {
- tx, err := ds.db.Beginx()
- if err != nil {
- log.Error("could not insert built block", "err", err)
- return
+ for hash, id := range retriedBundleIds {
+ bundleIdsMap[hash] = id
}
+ return bundleIdsMap, nil
+}
+
+func (ds *DatabaseService) insertBuildBlock(tx *sqlx.Tx, ctx context.Context, block *types.Block, bidTrace *boostTypes.BidTrace, ordersClosedAt time.Time, sealedAt time.Time) (uint64, error) {
blockData := BuiltBlock{
BlockNumber: block.NumberU64(),
Profit: new(big.Rat).SetFrac(block.Profit, big.NewInt(1e18)).FloatString(18),
@@ -101,52 +169,88 @@ func (ds *DatabaseService) ConsumeBuiltBlock(block *types.Block, bundles []types
ProposerFeeRecipient: bidTrace.ProposerFeeRecipient.String(),
BuilderPubkey: bidTrace.BuilderPubkey.String(),
Timestamp: block.Time(),
+ OrdersClosedAt: ordersClosedAt.UTC(),
+ SealedAt: sealedAt.UTC(),
}
- ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
- defer cancel()
var blockId uint64
- if err = tx.NamedStmtContext(ctx, ds.insertBuiltBlockStmt).GetContext(ctx, &blockId, blockData); err != nil {
+ if err := tx.NamedStmtContext(ctx, ds.insertBuiltBlockStmt).GetContext(ctx, &blockId, blockData); err != nil {
log.Error("could not insert built block", "err", err)
- tx.Rollback()
+ return 0, err
+ }
+
+ return blockId, nil
+}
+
+func (ds *DatabaseService) insertBuildBlockBundleIds(tx *sqlx.Tx, ctx context.Context, blockId uint64, bundleIds []uint64) error {
+ if len(bundleIds) == 0 {
+ return nil
+ }
+
+ toInsert := make([]blockAndBundleId, len(bundleIds))
+ for i, bundleId := range bundleIds {
+ toInsert[i] = blockAndBundleId{blockId, bundleId}
+ }
+
+ _, err := tx.NamedExecContext(ctx, "insert into built_blocks_bundles (block_id, bundle_id) values (:block_id, :bundle_id)", toInsert)
+ return err
+}
+
+func (ds *DatabaseService) insertAllBlockBundleIds(tx *sqlx.Tx, ctx context.Context, blockId uint64, bundleIdsMap map[string]uint64) error {
+ if len(bundleIdsMap) == 0 {
+ return nil
+ }
+
+ toInsert := make([]blockAndBundleId, 0, len(bundleIdsMap))
+ for _, bundleId := range bundleIdsMap {
+ toInsert = append(toInsert, blockAndBundleId{blockId, bundleId})
+ }
+
+ _, err := tx.NamedExecContext(ctx, "insert into built_blocks_all_bundles (block_id, bundle_id) values (:block_id, :bundle_id)", toInsert)
+ return err
+}
+
+func (ds *DatabaseService) ConsumeBuiltBlock(block *types.Block, ordersClosedAt time.Time, sealedAt time.Time, commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace) {
+ ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
+ defer cancel()
+
+ bundleIdsMap, err := ds.getBundleIdsAndInsertMissingBundles(ctx, block.NumberU64(), allBundles)
+ if err != nil {
+ log.Error("could not insert bundles", "err", err)
+ }
+
+ tx, err := ds.db.Beginx()
+ if err != nil {
+ log.Error("could not open DB transaction", "err", err)
return
}
- for _, bundle := range bundles {
- bundleData := BuiltBlockBundle{
- BlockId: blockId,
- BundleId: nil,
- BlockNumber: blockData.BlockNumber,
- BundleHash: bundle.OriginalBundle.Hash.String(),
- }
+ blockId, err := ds.insertBuildBlock(tx, ctx, block, bidTrace, ordersClosedAt, sealedAt)
+ if err != nil {
+ tx.Rollback()
+ log.Error("could not insert built block", "err", err)
+ return
+ }
- var bundleId uint64
- err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData)
- if err == nil {
- continue
+ commitedBundlesIds := make([]uint64, 0, len(commitedBundles))
+ for _, bundle := range commitedBundles {
+ if id, found := bundleIdsMap[bundle.OriginalBundle.Hash.String()]; found {
+ commitedBundlesIds = append(commitedBundlesIds, id)
}
+ }
- if err != sql.ErrNoRows {
- log.Error("could not insert bundle", "err", err)
- // Try anyway
- }
+ err = ds.insertBuildBlockBundleIds(tx, ctx, blockId, commitedBundlesIds)
+ if err != nil {
+ tx.Rollback()
+ log.Error("could not insert built block bundles", "err", err)
+ return
+ }
- missingBundleData := SimulatedBundleToDbBundle(&bundle)
- err = ds.insertMissingBundleStmt.GetContext(ctx, &bundleId, missingBundleData) // not using the tx as it relies on the unique constraint!
- if err == nil {
- bundleData.BundleId = &bundleId
- _, err = tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleWithIdStmt).ExecContext(ctx, bundleData)
- if err != nil {
- log.Error("could not insert built block bundle after inserting missing bundle", "err", err)
- }
- } else if err == sql.ErrNoRows /* conflict, someone else inserted the bundle before we could */ {
- if err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData); err != nil {
- log.Error("could not insert bundle on retry", "err", err)
- continue
- }
- } else {
- log.Error("could not insert missing bundle", "err", err)
- }
+ err = ds.insertAllBlockBundleIds(tx, ctx, blockId, bundleIdsMap)
+ if err != nil {
+ tx.Rollback()
+ log.Error("could not insert built block all bundles", "err", err)
+ return
}
err = tx.Commit()
diff --git a/flashbotsextra/database_test.go b/flashbotsextra/database_test.go
index 1d3a7cce35..7b9efaf1a0 100644
--- a/flashbotsextra/database_test.go
+++ b/flashbotsextra/database_test.go
@@ -4,6 +4,7 @@ import (
"math/big"
"os"
"testing"
+ "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
@@ -20,13 +21,22 @@ func TestDatabaseBlockInsertion(t *testing.T) {
ds, err := NewDatabaseService(dsn)
require.NoError(t, err)
- _, err = ds.db.Exec("insert into bundles (id, param_block_number, bundle_hash) values (10, 20, '0x1078')")
+ _, err = ds.db.Exec("delete from built_blocks_bundles where block_id = (select block_id from built_blocks where hash = '0x9cc3ee47d091fea38c0187049cae56abe4e642eeb06c4832f06ec59f5dbce7ab')")
+ require.NoError(t, err)
+
+ _, err = ds.db.Exec("delete from built_blocks_all_bundles where block_id = (select block_id from built_blocks where hash = '0x9cc3ee47d091fea38c0187049cae56abe4e642eeb06c4832f06ec59f5dbce7ab')")
+ require.NoError(t, err)
+
+ _, err = ds.db.Exec("delete from built_blocks where hash = '0x9cc3ee47d091fea38c0187049cae56abe4e642eeb06c4832f06ec59f5dbce7ab'")
+ require.NoError(t, err)
+
+ _, err = ds.db.Exec("delete from bundles where bundle_hash in ('0x0978000000000000000000000000000000000000000000000000000000000000', '0x1078000000000000000000000000000000000000000000000000000000000000', '0x0979000000000000000000000000000000000000000000000000000000000000', '0x1080000000000000000000000000000000000000000000000000000000000000')")
require.NoError(t, err)
block := types.NewBlock(
&types.Header{
ParentHash: common.HexToHash("0xafafafa"),
- Number: big.NewInt(132),
+ Number: big.NewInt(12),
GasLimit: uint64(10000),
GasUsed: uint64(1000),
Time: 16000000,
@@ -47,6 +57,7 @@ func TestDatabaseBlockInsertion(t *testing.T) {
Hash: common.Hash{0x09, 0x78},
},
}
+
simBundle2 := types.SimulatedBundle{
MevGasPrice: big.NewInt(90),
TotalEth: big.NewInt(110),
@@ -54,18 +65,87 @@ func TestDatabaseBlockInsertion(t *testing.T) {
TotalGasUsed: uint64(1000),
OriginalBundle: types.MevBundle{
Txs: types.Transactions{types.NewTransaction(uint64(51), common.Address{0x61}, big.NewInt(109), uint64(167), big.NewInt(433), []byte{})},
- BlockNumber: big.NewInt(20),
+ BlockNumber: big.NewInt(12),
MinTimestamp: uint64(1000020),
RevertingTxHashes: []common.Hash{common.Hash{0x11, 0x17}},
Hash: common.Hash{0x10, 0x78},
},
}
+ var bundle2Id uint64
+ ds.db.Get(&bundle2Id, "insert into bundles (bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase) values (:bundle_hash, :param_signed_txs, :param_block_number, :param_timestamp, :received_timestamp, :param_reverting_tx_hashes, :coinbase_diff, :total_gas_used, :state_block_number, :gas_fees, :eth_sent_to_coinbase) on conflict (bundle_hash, param_block_number) do nothing returning id", SimulatedBundleToDbBundle(&simBundle2))
+
+ simBundle3 := types.SimulatedBundle{
+ MevGasPrice: big.NewInt(91),
+ TotalEth: big.NewInt(111),
+ EthSentToCoinbase: big.NewInt(101),
+ TotalGasUsed: uint64(101),
+ OriginalBundle: types.MevBundle{
+ Txs: types.Transactions{types.NewTransaction(uint64(51), common.Address{0x62}, big.NewInt(20), uint64(68), big.NewInt(44), []byte{})},
+ BlockNumber: big.NewInt(12),
+ MinTimestamp: uint64(1000021),
+ RevertingTxHashes: []common.Hash{common.Hash{0x10, 0x18}},
+ Hash: common.Hash{0x09, 0x79},
+ },
+ }
+
+ simBundle4 := types.SimulatedBundle{
+ MevGasPrice: big.NewInt(92),
+ TotalEth: big.NewInt(112),
+ EthSentToCoinbase: big.NewInt(102),
+ TotalGasUsed: uint64(1002),
+ OriginalBundle: types.MevBundle{
+ Txs: types.Transactions{types.NewTransaction(uint64(52), common.Address{0x62}, big.NewInt(110), uint64(168), big.NewInt(434), []byte{})},
+ BlockNumber: big.NewInt(12),
+ MinTimestamp: uint64(1000022),
+ RevertingTxHashes: []common.Hash{common.Hash{0x11, 0x19}},
+ Hash: common.Hash{0x10, 0x80},
+ },
+ }
+
+ var bundle4Id uint64
+ ds.db.Get(&bundle4Id, "insert into bundles (bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase) values (:bundle_hash, :param_signed_txs, :param_block_number, :param_timestamp, :received_timestamp, :param_reverting_tx_hashes, :coinbase_diff, :total_gas_used, :state_block_number, :gas_fees, :eth_sent_to_coinbase) on conflict (bundle_hash, param_block_number) do nothing returning id", SimulatedBundleToDbBundle(&simBundle4))
+
bidTrace := &boostTypes.BidTrace{}
- ds.ConsumeBuiltBlock(block, []types.SimulatedBundle{simBundle1, simBundle2}, bidTrace)
+ ocAt := time.Now().Add(-time.Hour).UTC()
+ sealedAt := time.Now().Add(-30 * time.Minute).UTC()
+ ds.ConsumeBuiltBlock(block, ocAt, sealedAt, []types.SimulatedBundle{simBundle1, simBundle2}, []types.SimulatedBundle{simBundle1, simBundle2, simBundle3, simBundle4}, bidTrace)
var dbBlock BuiltBlock
- ds.db.Get(&dbBlock, "select * from built_blocks where hash = '0x24e6998e4d2b4fd85f7f0d27ac4b87aaf0ec18e156e4b6e194ab5dadee0cd004'")
- t.Logf("block %v", dbBlock)
+ require.NoError(t, ds.db.Get(&dbBlock, "select block_id, block_number, profit, slot, hash, gas_limit, gas_used, base_fee, parent_hash, timestamp, timestamp_datetime, orders_closed_at, sealed_at from built_blocks where hash = '0x9cc3ee47d091fea38c0187049cae56abe4e642eeb06c4832f06ec59f5dbce7ab'"))
+ require.Equal(t, BuiltBlock{
+ BlockId: dbBlock.BlockId,
+ BlockNumber: 12,
+ Profit: "0.000000000000000010",
+ Slot: 0,
+ Hash: block.Hash().String(),
+ GasLimit: block.GasLimit(),
+ GasUsed: block.GasUsed(),
+ BaseFee: 7,
+ ParentHash: "0x000000000000000000000000000000000000000000000000000000000afafafa",
+ Timestamp: 16000000,
+ TimestampDatetime: dbBlock.TimestampDatetime,
+ OrdersClosedAt: dbBlock.OrdersClosedAt,
+ SealedAt: dbBlock.SealedAt,
+ }, dbBlock)
+
+ require.True(t, dbBlock.TimestampDatetime.Equal(time.Unix(16000000, 0)))
+ require.Equal(t, ocAt.Truncate(time.Millisecond), dbBlock.OrdersClosedAt.UTC().Truncate(time.Millisecond))
+ require.Equal(t, sealedAt.Truncate(time.Millisecond), dbBlock.SealedAt.UTC().Truncate(time.Millisecond))
+
+ var bundles []DbBundle
+ ds.db.Select(&bundles, "select bundle_hash, param_signed_txs, param_block_number, param_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase from bundles order by param_timestamp")
+ require.Len(t, bundles, 4)
+ require.Equal(t, []DbBundle{SimulatedBundleToDbBundle(&simBundle1), SimulatedBundleToDbBundle(&simBundle2), SimulatedBundleToDbBundle(&simBundle3), SimulatedBundleToDbBundle(&simBundle4)}, bundles)
+
+ var commitedBundles []string
+ require.NoError(t, ds.db.Select(&commitedBundles, "select b.bundle_hash as bundle_hash from built_blocks_bundles bbb inner join bundles b on b.id = bbb.bundle_id where bbb.block_id = $1 order by b.param_timestamp", dbBlock.BlockId))
+ require.Len(t, commitedBundles, 2)
+ require.Equal(t, []string{simBundle1.OriginalBundle.Hash.String(), simBundle2.OriginalBundle.Hash.String()}, commitedBundles)
+
+ var allBundles []string
+ require.NoError(t, ds.db.Select(&allBundles, "select b.bundle_hash as bundle_hash from built_blocks_all_bundles bbb inner join bundles b on b.id = bbb.bundle_id where bbb.block_id = $1 order by b.param_timestamp", dbBlock.BlockId))
+ require.Len(t, allBundles, 4)
+ require.Equal(t, []string{simBundle1.OriginalBundle.Hash.String(), simBundle2.OriginalBundle.Hash.String(), simBundle3.OriginalBundle.Hash.String(), simBundle4.OriginalBundle.Hash.String()}, allBundles)
}
diff --git a/flashbotsextra/database_types.go b/flashbotsextra/database_types.go
index 58d28c8cf5..560d616996 100644
--- a/flashbotsextra/database_types.go
+++ b/flashbotsextra/database_types.go
@@ -23,6 +23,8 @@ type BuiltBlock struct {
BuilderPubkey string `db:"builder_pubkey"`
Timestamp uint64 `db:"timestamp"`
TimestampDatetime time.Time `db:"timestamp_datetime"`
+ OrdersClosedAt time.Time `db:"orders_closed_at"`
+ SealedAt time.Time `db:"sealed_at"`
}
type BuiltBlockBundle struct {
@@ -49,6 +51,11 @@ type DbBundle struct {
EthSentToCoinbase string `db:"eth_sent_to_coinbase"`
}
+type blockAndBundleId struct {
+ BlockId uint64 `db:"block_id"`
+ BundleId uint64 `db:"bundle_id"`
+}
+
func SimulatedBundleToDbBundle(bundle *types.SimulatedBundle) DbBundle {
revertingTxHashes := make([]string, len(bundle.OriginalBundle.RevertingTxHashes))
for i, rTxHash := range bundle.OriginalBundle.RevertingTxHashes {
diff --git a/miner/miner.go b/miner/miner.go
index 3d1215dfc2..4eb06a213d 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -276,18 +276,21 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
return miner.worker.regularWorker.pendingLogsFeed.Subscribe(ch)
}
+// Accepts the block, time at which orders were taken, bundles which were used to build the block and all bundles that were considered for the block
+type BlockHookFn = func(*types.Block, time.Time, []types.SimulatedBundle, []types.SimulatedBundle)
+
// GetSealingBlockAsync requests to generate a sealing block according to the
// given parameters. Regardless of whether the generation is successful or not,
// there is always a result that will be returned through the result channel.
// The difference is that if the execution fails, the returned result is nil
// and the concrete error is dropped silently.
-func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, error) {
+func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, blockHook BlockHookFn) (chan *types.Block, error) {
return miner.worker.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, false, blockHook)
}
// GetSealingBlockSync creates a sealing block according to the given parameters.
// If the generation is failed or the underlying work is already closed, an error
// will be returned.
-func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, blockHook func(*types.Block, []types.SimulatedBundle)) (*types.Block, error) {
+func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, blockHook BlockHookFn) (*types.Block, error) {
return miner.worker.GetSealingBlockSync(parent, timestamp, coinbase, gasLimit, random, noTxs, false, blockHook)
}
diff --git a/miner/multi_worker.go b/miner/multi_worker.go
index ca18ef3c83..45ffb67f07 100644
--- a/miner/multi_worker.go
+++ b/miner/multi_worker.go
@@ -91,7 +91,7 @@ type resChPair struct {
errCh chan error
}
-func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, error) {
+func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook BlockHookFn) (chan *types.Block, error) {
resChans := []resChPair{}
for _, worker := range w.workers {
@@ -128,7 +128,7 @@ func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64,
return resCh, nil
}
-func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (*types.Block, error) {
+func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook BlockHookFn) (*types.Block, error) {
resCh, err := w.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook)
if err != nil {
return nil, err
diff --git a/miner/worker.go b/miner/worker.go
index e2c7f72661..6d1be8a08a 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1194,16 +1194,16 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
// generateParams wraps various of settings for generating sealing task.
type generateParams struct {
- timestamp uint64 // The timstamp for sealing task
- forceTime bool // Flag whether the given timestamp is immutable or not
- parentHash common.Hash // Parent block hash, empty means the latest chain head
- coinbase common.Address // The fee recipient address for including transaction
- gasLimit uint64 // The validator's requested gas limit target
- random common.Hash // The randomness generated by beacon chain, empty before the merge
- noUncle bool // Flag whether the uncle block inclusion is allowed
- noExtra bool // Flag whether the extra field assignment is allowed
- noTxs bool // Flag whether an empty block without any transaction is expected
- onBlock func(*types.Block, []types.SimulatedBundle) // Callback to call for each produced block
+ timestamp uint64 // The timstamp for sealing task
+ forceTime bool // Flag whether the given timestamp is immutable or not
+ parentHash common.Hash // Parent block hash, empty means the latest chain head
+ coinbase common.Address // The fee recipient address for including transaction
+ gasLimit uint64 // The validator's requested gas limit target
+ random common.Hash // The randomness generated by beacon chain, empty before the merge
+ noUncle bool // Flag whether the uncle block inclusion is allowed
+ noExtra bool // Flag whether the extra field assignment is allowed
+ noTxs bool // Flag whether an empty block without any transaction is expected
+ onBlock BlockHookFn // Callback to call for each produced block
}
// prepareWork constructs the sealing task according to the given parameters,
@@ -1295,7 +1295,8 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
// fillTransactions retrieves the pending transactions from the txpool and fills them
// into the given sealing block. The transaction selection and ordering strategy can
// be customized with the plugin in the future.
-func (w *worker) fillTransactions(interrupt *int32, env *environment) (error, []types.SimulatedBundle) {
+// Returns error if any, otherwise the bundles that made it into the block and all bundles that passed simulation
+func (w *worker) fillTransactions(interrupt *int32, env *environment) (error, []types.SimulatedBundle, []types.SimulatedBundle) {
// Split the pending transactions into locals and remotes
// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
@@ -1308,61 +1309,67 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment) (error, []
}
var blockBundles []types.SimulatedBundle
+ var allBundles []types.SimulatedBundle
if w.flashbots.isFlashbots {
bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
if err != nil {
log.Error("Failed to fetch pending transactions", "err", err)
- return err, nil
+ return err, nil, nil
}
- bundleTxs, bundle, mergedBundles, numBundles, err := w.generateFlashbotsBundle(env, bundles, pending)
+ var bundleTxs types.Transactions
+ var resultingBundle simulatedBundle
+ var mergedBundles []types.SimulatedBundle
+ var numBundles int
+ bundleTxs, resultingBundle, mergedBundles, numBundles, allBundles, err = w.generateFlashbotsBundle(env, bundles, pending)
if err != nil {
log.Error("Failed to generate flashbots bundle", "err", err)
- return err, nil
+ return err, nil, nil
}
- log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(bundle.TotalEth), "gasUsed", bundle.TotalGasUsed, "bundleScore", bundle.MevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles)
+ log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(resultingBundle.TotalEth), "gasUsed", resultingBundle.TotalGasUsed, "bundleScore", resultingBundle.MevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles)
if len(bundleTxs) == 0 {
- return errors.New("no bundles to apply"), nil
+ return errors.New("no bundles to apply"), nil, nil
}
if err := w.commitBundle(env, bundleTxs, interrupt); err != nil {
- return err, nil
+ return err, nil, nil
}
blockBundles = mergedBundles
- env.profit.Add(env.profit, bundle.EthSentToCoinbase)
+ env.profit.Add(env.profit, resultingBundle.EthSentToCoinbase)
}
if len(localTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, nil, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
- return err, nil
+ return err, nil, nil
}
}
if len(remoteTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, nil, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
- return err, nil
+ return err, nil, nil
}
}
- return nil, blockBundles
+ return nil, blockBundles, allBundles
}
// fillTransactionsAlgoWorker retrieves the pending transactions and bundles from the txpool and fills them
// into the given sealing block.
-func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment) (error, []types.SimulatedBundle) {
+// Returns error if any, otherwise the bundles that made it into the block and all bundles that passed simulation
+func (w *worker) fillTransactionsAlgoWorker(interrupt *int32, env *environment) (error, []types.SimulatedBundle, []types.SimulatedBundle) {
// Split the pending transactions into locals and remotes
// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
bundlesToConsider, err := w.getSimulatedBundles(env)
if err != nil {
- return err, nil
+ return err, nil, nil
}
builder := newGreedyBuilder(w.chain, w.chainConfig, w.blockList, env, interrupt)
newEnv, blockBundles := builder.buildBlock(bundlesToConsider, pending)
*env = *newEnv
- return nil, blockBundles
+ return nil, blockBundles, bundlesToConsider
}
func (w *worker) getSimulatedBundles(env *environment) ([]types.SimulatedBundle, error) {
@@ -1399,7 +1406,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
}
defer work.discard()
- finalizeFn := func(env *environment, blockBundles []types.SimulatedBundle) (*types.Block, error) {
+ finalizeFn := func(env *environment, orderCloseTime time.Time, blockBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle) (*types.Block, error) {
block, err := w.finalizeBlock(env, validatorCoinbase)
if err != nil {
log.Error("could not finalize block", "err", err)
@@ -1408,14 +1415,14 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
log.Info("Block finalized and assembled", "blockProfit", ethIntToFloat(block.Profit), "txs", len(env.txs), "bundles", len(blockBundles), "gasUsed", block.GasUsed(), "time", time.Since(start))
if params.onBlock != nil {
- go params.onBlock(block, blockBundles)
+ go params.onBlock(block, orderCloseTime, blockBundles, allBundles)
}
return block, nil
}
if params.noTxs {
- return finalizeFn(work, nil)
+ return finalizeFn(work, time.Now(), nil, nil)
}
paymentTxReserve, err := w.proposerTxPrepare(work, &validatorCoinbase)
@@ -1424,13 +1431,15 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
}
var blockBundles []types.SimulatedBundle
+ var allBundles []types.SimulatedBundle
+ orderCloseTime := time.Now()
switch w.flashbots.algoType {
case ALGO_GREEDY:
- err, blockBundles = w.fillTransactionsAlgoWorker(nil, work)
+ err, blockBundles, allBundles = w.fillTransactionsAlgoWorker(nil, work)
case ALGO_MEV_GETH:
- err, blockBundles = w.fillTransactions(nil, work)
+ err, blockBundles, allBundles = w.fillTransactions(nil, work)
default:
- err, blockBundles = w.fillTransactions(nil, work)
+ err, blockBundles, allBundles = w.fillTransactions(nil, work)
}
if err != nil {
@@ -1442,7 +1451,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, error) {
return nil, err
}
- return finalizeFn(work, blockBundles)
+ return finalizeFn(work, orderCloseTime, blockBundles, allBundles)
}
func (w *worker) finalizeBlock(work *environment, validatorCoinbase common.Address) (*types.Block, error) {
@@ -1516,7 +1525,7 @@ func (w *worker) commitWork(interrupt *int32, noempty bool, timestamp int64) {
}
// Fill pending transactions from the txpool
- err, _ = w.fillTransactions(interrupt, work)
+ err, _, _ = w.fillTransactions(interrupt, work)
if err != nil && !errors.Is(err, errBlockInterruptedByRecommit) {
work.discard()
return
@@ -1570,7 +1579,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
// getSealingBlock generates the sealing block based on the given parameters.
// The generation result will be passed back via the given channel no matter
// the generation itself succeeds or not.
-func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, chan error, error) {
+func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook BlockHookFn) (chan *types.Block, chan error, error) {
var (
resCh = make(chan *types.Block, 1)
errCh = make(chan error, 1)
@@ -1608,17 +1617,18 @@ func (w *worker) isTTDReached(header *types.Header) bool {
type simulatedBundle = types.SimulatedBundle
-func (w *worker) generateFlashbotsBundle(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, []types.SimulatedBundle, int, error) {
+func (w *worker) generateFlashbotsBundle(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, []types.SimulatedBundle, int, []types.SimulatedBundle, error) {
simulatedBundles, err := w.simulateBundles(env, bundles, pendingTxs)
if err != nil {
- return nil, simulatedBundle{}, nil, 0, err
+ return nil, simulatedBundle{}, nil, 0, nil, err
}
sort.SliceStable(simulatedBundles, func(i, j int) bool {
return simulatedBundles[j].MevGasPrice.Cmp(simulatedBundles[i].MevGasPrice) < 0
})
- return w.mergeBundles(env, simulatedBundles, pendingTxs)
+ bundleTxs, bundle, mergedBundles, numBundles, err := w.mergeBundles(env, simulatedBundles, pendingTxs)
+ return bundleTxs, bundle, mergedBundles, numBundles, simulatedBundles, err
}
func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, []types.SimulatedBundle, int, error) {
@@ -1719,8 +1729,7 @@ func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pe
}
}
- okBundles := len(bundles) - len(simulatedBundles)
- log.Debug("Simulated bundles", "block", env.header.Number, "allBundles", len(bundles), "okBundles", okBundles, "time", time.Since(start))
+ log.Debug("Simulated bundles", "block", env.header.Number, "allBundles", len(bundles), "okBundles", len(simulatedBundles), "time", time.Since(start))
return simulatedBundles, nil
}
From 12041db831df150020af32d083f8f3cc1cf06155 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Tue, 25 Oct 2022 11:00:36 +0200
Subject: [PATCH 58/83] Cleanup and include old mev geth prs (#45)
* Remove old infra scripts
* core: tx_pool not return `error` in `MevBundles()`
* Adjust worker to not consider error returned from MevBundles
* Update web3ext.go
* internal/ethapi: Fix context not being used
* fix: dev: add defer privateTx.Stop()
Co-authored-by: eugene
Co-authored-by: 0x2 <75550506+Code0x2@users.noreply.github.com>
Co-authored-by: Nicolas Gotchac
Co-authored-by: Jolly Zhao
---
core/tx_pool.go | 5 +-
infra/Dockerfile.node | 23 ----
infra/Dockerfile.updater | 23 ----
infra/start-mev-geth-node.sh | 99 -----------------
infra/start-mev-geth-updater.sh | 183 --------------------------------
internal/ethapi/api.go | 63 ++++++-----
internal/web3ext/web3ext.go | 5 +
miner/worker.go | 13 +--
8 files changed, 48 insertions(+), 366 deletions(-)
delete mode 100644 infra/Dockerfile.node
delete mode 100644 infra/Dockerfile.updater
delete mode 100755 infra/start-mev-geth-node.sh
delete mode 100755 infra/start-mev-geth-updater.sh
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 82509b4890..d53bb4559f 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -363,6 +363,7 @@ func (pool *TxPool) loop() {
defer report.Stop()
defer evict.Stop()
defer journal.Stop()
+ defer privateTx.Stop()
// Notify tests that the init phase is done
close(pool.initDoneCh)
@@ -587,7 +588,7 @@ func (pool *TxPool) AllMevBundles() []types.MevBundle {
// MevBundles returns a list of bundles valid for the given blockNumber/blockTimestamp
// also prunes bundles that are outdated
-func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]types.MevBundle, error) {
+func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) []types.MevBundle {
pool.mu.Lock()
defer pool.mu.Unlock()
@@ -615,7 +616,7 @@ func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]t
}
pool.mevBundles = bundles
- return ret, nil
+ return ret
}
// AddMevBundles adds a mev bundles to the pool
diff --git a/infra/Dockerfile.node b/infra/Dockerfile.node
deleted file mode 100644
index 7868453eba..0000000000
--- a/infra/Dockerfile.node
+++ /dev/null
@@ -1,23 +0,0 @@
-# Build Geth in a stock Go builder container
-FROM golang:1.15-alpine as builder
-
-RUN apk add --no-cache make gcc musl-dev linux-headers git
-
-ADD . /go-ethereum
-RUN cd /go-ethereum && GO111MODULE=on go run build/ci.go install ./cmd/geth
-
-# Pull Geth into a second stage deploy alpine container
-FROM alpine:latest
-
-ENV PYTHONUNBUFFERED=1
-RUN apk add --update --no-cache groff less python3 curl jq ca-certificates && ln -sf python3 /usr/bin/python
-RUN python3 -m ensurepip
-RUN pip3 install --no-cache --upgrade pip setuptools awscli
-
-COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
-
-COPY ./infra/start-mev-geth-node.sh /root/start-mev-geth-node.sh
-RUN chmod 755 /root/start-mev-geth-node.sh
-
-EXPOSE 8545 8546 30303 30303/udp
-ENTRYPOINT ["/root/start-mev-geth-node.sh"]
diff --git a/infra/Dockerfile.updater b/infra/Dockerfile.updater
deleted file mode 100644
index 808f55aa2b..0000000000
--- a/infra/Dockerfile.updater
+++ /dev/null
@@ -1,23 +0,0 @@
-# Build Geth in a stock Go builder container
-FROM golang:1.15-alpine as builder
-
-RUN apk add --no-cache make gcc musl-dev linux-headers git
-
-ADD . /go-ethereum
-RUN cd /go-ethereum && GO111MODULE=on go run build/ci.go install ./cmd/geth
-
-# Pull Geth into a second stage deploy alpine container
-FROM alpine:latest
-
-ENV PYTHONUNBUFFERED=1
-RUN apk add --update --no-cache groff less python3 curl jq ca-certificates && ln -sf python3 /usr/bin/python
-RUN python3 -m ensurepip
-RUN pip3 install --no-cache --upgrade pip setuptools awscli
-
-COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
-
-COPY ./infra/start-mev-geth-updater.sh /root/start-mev-geth-updater.sh
-RUN chmod 755 /root/start-mev-geth-updater.sh
-
-EXPOSE 8545 8546 30303 30303/udp
-ENTRYPOINT ["/root/start-mev-geth-updater.sh"]
diff --git a/infra/start-mev-geth-node.sh b/infra/start-mev-geth-node.sh
deleted file mode 100755
index 45ef0c5197..0000000000
--- a/infra/start-mev-geth-node.sh
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/bin/sh -x
-# Starts the Mev-Geth node client
-# Written by Luke Youngblood, luke@blockscale.net
-
-# network=mainnet # normally set by environment
-# syncmode=fast # normally set by environment
-# rpcport=8545 # normally set by environment
-# wsport=8546 # normally set by environment
-# netport=30303 # normally set by environment
-
-init_node() {
- # You can put any commands you would like to run to initialize the node here.
- echo Initializing node...
-}
-
-start_node() {
- if [ $network = "goerli" ]
- then
- geth \
- --port $netport \
- --http \
- --http.addr 0.0.0.0 \
- --http.port $rpcport \
- --http.api eth,net,web3 \
- --http.vhosts '*' \
- --http.corsdomain '*' \
- --graphql \
- --graphql.corsdomain '*' \
- --graphql.vhosts '*' \
- --ws \
- --ws.addr 0.0.0.0 \
- --ws.port $wsport \
- --ws.api eth,net,web3 \
- --ws.origins '*' \
- --syncmode $syncmode \
- --gcmode archive \
- --cache 4096 \
- --maxpeers $connections \
- --goerli
- if [ $? -ne 0 ]
- then
- echo "Node failed to start; exiting."
- exit 1
- fi
- else
- geth \
- --port $netport \
- --http \
- --http.addr 0.0.0.0 \
- --http.port $rpcport \
- --http.api eth,net,web3 \
- --http.vhosts '*' \
- --http.corsdomain '*' \
- --graphql \
- --graphql.corsdomain '*' \
- --graphql.vhosts '*' \
- --ws \
- --ws.addr 0.0.0.0 \
- --ws.port $wsport \
- --ws.api eth,net,web3 \
- --ws.origins '*' \
- --syncmode $syncmode \
- --gcmode archive \
- --cache 4096 \
- --snapshot=false \
- --maxpeers $connections
- if [ $? -ne 0 ]
- then
- echo "Node failed to start; exiting."
- exit 1
- fi
- fi
-}
-
-s3_sync() {
- # Determine data directory
- if [ $network = "goerli" ]
- then
- datadir=/root/.ethereum/goerli/geth/chaindata
- else
- datadir=/root/.ethereum/geth/chaindata
- fi
- # If the current1 key exists, node1 is the most current set of blockchain data
- echo "A 404 error below is expected and nothing to be concerned with."
- aws s3api head-object --request-payer requester --bucket $chainbucket --key current1
- if [ $? -eq 0 ]
- then
- s3key=node1
- else
- s3key=node2
- fi
- aws s3 sync --only-show-errors --request-payer requester --region $region s3://$chainbucket/$s3key $datadir
-}
-
-# main
-
-init_node
-s3_sync
-start_node
diff --git a/infra/start-mev-geth-updater.sh b/infra/start-mev-geth-updater.sh
deleted file mode 100755
index abad72fab9..0000000000
--- a/infra/start-mev-geth-updater.sh
+++ /dev/null
@@ -1,183 +0,0 @@
-#!/bin/sh -x
-# Starts the Mev-Geth updater client
-# Written by Luke Youngblood, luke@blockscale.net
-
-# netport=30303 # normally set by environment
-
-init_node() {
- # Initialization steps can go here
- echo Initializing node...
- aws configure set default.s3.max_concurrent_requests 64
- aws configure set default.s3.max_queue_size 20000
-}
-
-start_node() {
- if [ $network = "goerli" ]
- then
- geth \
- --port $netport \
- --syncmode $syncmode \
- --cache 4096 \
- --gcmode archive \
- --maxpeers $connections \
- --goerli &
- if [ $? -ne 0 ]
- then
- echo "Node failed to start; exiting."
- exit 1
- fi
- else
- geth \
- --port $netport \
- --syncmode $syncmode \
- --cache 4096 \
- --gcmode archive \
- --maxpeers $connections &
- if [ $? -ne 0 ]
- then
- echo "Node failed to start; exiting."
- exit 1
- fi
- fi
-}
-
-s3_sync_down() {
- # Determine data directory
- if [ $network = "goerli" ]
- then
- datadir=/root/.ethereum/goerli/geth/chaindata
- else
- datadir=/root/.ethereum/geth/chaindata
- fi
-
- # If the current1 object exists, node1 is the key we should download
- echo "A 404 error below is expected and nothing to be concerned with."
- aws s3api head-object --bucket $chainbucket --key current1
- if [ $? -eq 0 ]
- then
- echo "current1 key exists; downloading node1"
- s3key=node1
- else
- echo "current1 key doesn't exist; downloading node2"
- s3key=node2
- fi
-
- aws s3 sync --region $region --only-show-errors s3://$chainbucket/$s3key $datadir
- if [ $? -ne 0 ]
- then
- echo "aws s3 sync command failed; exiting."
- exit 2
- fi
-}
-
-kill_node() {
- tries=0
- while [ ! -z `ps -ef |grep geth|grep -v geth-updater|grep -v grep|awk '{print $1}'` ]
- do
- ps -ef |grep geth|grep -v geth-updater|grep -v grep
- pid=`ps -ef |grep geth|grep -v geth-updater|grep -v grep|awk '{print $1}'`
- kill $pid
- sleep 30
- echo "Waiting for the node to shutdown cleanly... try number $tries"
- let "tries+=1"
- if [ $tries -gt 29 ]
- then
- echo "Node has not stopped cleanly after $tries, forcibly killing."
- ps -ef |grep geth|grep -v geth-updater|grep -v grep
- pid=`ps -ef |grep geth|grep -v geth-updater|grep -v grep|awk '{print $1}'`
- kill -9 $pid
- fi
- if [ $tries -gt 30 ]
- then
- echo "Node has not stopped cleanly after $tries, exiting..."
- exit 3
- fi
- done
-}
-
-s3_sync_up() {
- # Determine data directory
- if [ $network = "goerli" ]
- then
- datadir=/root/.ethereum/goerli/geth/chaindata
- else
- datadir=/root/.ethereum/geth/chaindata
- fi
-
- # If the current1 object exists, node1 is the folder that clients will download, so we should update node2
- aws s3api head-object --bucket $chainbucket --key current1
- if [ $? -eq 0 ]
- then
- echo "current1 key exists; updating node2"
- s3key=node2
- else
- echo "current1 key doesn't exist; updating node1"
- s3key=node1
- fi
-
- aws s3 sync --delete --region $region --only-show-errors --acl public-read $datadir s3://$chainbucket/$s3key
- if [ $? -ne 0 ]
- then
- echo "aws s3 sync upload command failed; exiting."
- exit 4
- fi
-
- if [ "$s3key" = "node2" ]
- then
- echo "Removing current1 key, as the node2 key was just updated."
- aws s3 rm --region $region s3://$chainbucket/current1
- if [ $? -ne 0 ]
- then
- echo "aws s3 rm command failed; retrying."
- sleep 5
- aws s3 rm --region $region s3://$chainbucket/current1
- if [ $? -ne 0 ]
- then
- echo "aws s3 rm command failed; exiting."
- exit 5
- fi
- fi
- else
- echo "Touching current1 key, as the node1 key was just updated."
- touch ~/current1
- aws s3 cp --region $region --acl public-read ~/current1 s3://$chainbucket/
- if [ $? -ne 0 ]
- then
- echo "aws s3 cp command failed; retrying."
- sleep 5
- aws s3 cp --region $region --acl public-read ~/current1 s3://$chainbucket/
- if [ $? -ne 0 ]
- then
- echo "aws s3 cp command failed; exiting."
- exit 6
- fi
- fi
- fi
-}
-
-continuous() {
- # This function continuously stops the node every hour
- # and syncs the chain data with S3, then restarts the node.
- while true
- do
- echo "Sleeping for 60 minutes at `date`..."
- sleep 3600
- echo "Cleanly shutting down the node so we can update S3 with the latest chaindata at `date`..."
- kill_node
- echo "Syncing chain data to S3 at `date`..."
- s3_sync_up
- echo "Restarting the node after syncing to S3 at `date`..."
- start_node
- done
-}
-
-# main
-
-echo "Initializing the node at `date`..."
-init_node
-echo "Syncing initial chain data with stored chain data in S3 at `date`..."
-s3_sync_down
-echo "Starting the node at `date`..."
-start_node
-echo "Starting the continuous loop at `date`..."
-continuous
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 0e101c5dd3..5d25203e51 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -2150,6 +2150,19 @@ func (s *BundleAPI) CallBundle(ctx context.Context, args CallBundleArgs) (map[st
timeoutMilliSeconds = *args.Timeout
}
timeout := time.Millisecond * time.Duration(timeoutMilliSeconds)
+
+ // Setup context so it may be cancelled the call has completed
+ // or, in case of unmetered gas, setup a context with a timeout.
+ var cancel context.CancelFunc
+ if timeout > 0 {
+ ctx, cancel = context.WithTimeout(ctx, timeout)
+ } else {
+ ctx, cancel = context.WithCancel(ctx)
+ }
+ // Make sure the context is cancelled when the call has completed
+ // this makes sure resources are cleaned up.
+ defer cancel()
+
state, parent, err := s.b.StateAndHeaderByNumberOrHash(ctx, args.StateBlockNumberOrHash)
if state == nil || err != nil {
return nil, err
@@ -2188,18 +2201,6 @@ func (s *BundleAPI) CallBundle(ctx context.Context, args CallBundleArgs) (map[st
BaseFee: baseFee,
}
- // Setup context so it may be cancelled the call has completed
- // or, in case of unmetered gas, setup a context with a timeout.
- var cancel context.CancelFunc
- if timeout > 0 {
- ctx, cancel = context.WithTimeout(ctx, timeout)
- } else {
- ctx, cancel = context.WithCancel(ctx)
- }
- // Make sure the context is cancelled when the call has completed
- // this makes sure resources are cleaned up.
- defer cancel()
-
vmconfig := vm.Config{}
// Setup the gas pool (also for unmetered requests)
@@ -2214,6 +2215,11 @@ func (s *BundleAPI) CallBundle(ctx context.Context, args CallBundleArgs) (map[st
var totalGasUsed uint64
gasFees := new(big.Int)
for i, tx := range txs {
+ // Check if the context was cancelled (eg. timed-out)
+ if err := ctx.Err(); err != nil {
+ return nil, err
+ }
+
coinbaseBalanceBeforeTx := state.GetBalance(coinbase)
state.Prepare(tx.Hash(), i)
@@ -2303,6 +2309,20 @@ func (s *BundleAPI) EstimateGasBundle(ctx context.Context, args EstimateGasBundl
}
timeout := time.Millisecond * time.Duration(timeoutMS)
+ // Setup context so it may be cancelled when the call
+ // has completed or, in case of unmetered gas, setup
+ // a context with a timeout
+ var cancel context.CancelFunc
+ if timeout > 0 {
+ ctx, cancel = context.WithTimeout(ctx, timeout)
+ } else {
+ ctx, cancel = context.WithCancel(ctx)
+ }
+
+ // Make sure the context is cancelled when the call has completed
+ // This makes sure resources are cleaned up
+ defer cancel()
+
state, parent, err := s.b.StateAndHeaderByNumberOrHash(ctx, args.StateBlockNumberOrHash)
if state == nil || err != nil {
return nil, err
@@ -2327,20 +2347,6 @@ func (s *BundleAPI) EstimateGasBundle(ctx context.Context, args EstimateGasBundl
BaseFee: parent.BaseFee,
}
- // Setup context so it may be cancelled when the call
- // has completed or, in case of unmetered gas, setup
- // a context with a timeout
- var cancel context.CancelFunc
- if timeout > 0 {
- ctx, cancel = context.WithTimeout(ctx, timeout)
- } else {
- ctx, cancel = context.WithCancel(ctx)
- }
-
- // Make sure the context is cancelled when the call has completed
- // This makes sure resources are cleaned up
- defer cancel()
-
// RPC Call gas cap
globalGasCap := s.b.RPCGasCap()
@@ -2359,6 +2365,11 @@ func (s *BundleAPI) EstimateGasBundle(ctx context.Context, args EstimateGasBundl
// Feed each of the transactions into the VM ctx
// And try and estimate the gas used
for i, txArgs := range args.Txs {
+ // Check if the context was cancelled (eg. timed-out)
+ if err := ctx.Err(); err != nil {
+ return nil, err
+ }
+
// Since its a txCall we'll just prepare the
// state with a random hash
var randomHash common.Hash
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index d80b6b8ac2..b3a7e5fb53 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -606,6 +606,11 @@ web3._extend({
call: 'eth_sendBundle',
params: 1,
}),
+ new web3._extend.Method({
+ name: 'callBundle',
+ call: 'eth_callBundle',
+ params: 6
+ }),
],
properties: [
new web3._extend.Property({
diff --git a/miner/worker.go b/miner/worker.go
index 6d1be8a08a..ca448d3807 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1311,16 +1311,13 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment) (error, []
var blockBundles []types.SimulatedBundle
var allBundles []types.SimulatedBundle
if w.flashbots.isFlashbots {
- bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
- if err != nil {
- log.Error("Failed to fetch pending transactions", "err", err)
- return err, nil, nil
- }
+ bundles := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
var bundleTxs types.Transactions
var resultingBundle simulatedBundle
var mergedBundles []types.SimulatedBundle
var numBundles int
+ var err error
bundleTxs, resultingBundle, mergedBundles, numBundles, allBundles, err = w.generateFlashbotsBundle(env, bundles, pending)
if err != nil {
log.Error("Failed to generate flashbots bundle", "err", err)
@@ -1377,11 +1374,7 @@ func (w *worker) getSimulatedBundles(env *environment) ([]types.SimulatedBundle,
return nil, nil
}
- bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
- if err != nil {
- log.Error("Failed to fetch pending bundles", "err", err)
- return nil, err
- }
+ bundles := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time)
// TODO: consider interrupt
simBundles, err := w.simulateBundles(env, bundles, nil) /* do not consider gas impact of mempool txs as bundles are treated as transactions wrt ordering */
From a4fd793719026947665668d9b8a400e105859765 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Tue, 25 Oct 2022 17:28:42 +0200
Subject: [PATCH 59/83] Increase submission rate to 2 blocks per second (#47)
---
builder/builder.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/builder/builder.go b/builder/builder.go
index d750576923..adada18749 100644
--- a/builder/builder.go
+++ b/builder/builder.go
@@ -82,7 +82,7 @@ func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRe
builderPublicKey: pk,
builderSigningDomain: builderSigningDomain,
- limiter: rate.NewLimiter(rate.Every(time.Second), 1),
+ limiter: rate.NewLimiter(rate.Every(time.Millisecond), 510),
slot: 0,
slotCtx: slotCtx,
slotCtxCancel: slotCtxCancel,
@@ -287,7 +287,7 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
}
// resubmits block builder requests every second
- runRetryLoop(ctx, time.Second, func() {
+ runRetryLoop(ctx, 500*time.Millisecond, func() {
log.Debug("retrying BuildBlock", "slot", attrs.Slot, "parent", attrs.HeadHash)
err := b.eth.BuildBlock(attrs, blockHook)
if err != nil {
From 6152cfc704213859a3b43ad0e5fa04586d3eb3e7 Mon Sep 17 00:00:00 2001
From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
Date: Thu, 3 Nov 2022 10:15:41 +0100
Subject: [PATCH 60/83] Add disable bundle fetcher flag (#48)
---
builder/service.go | 11 +++++++----
cmd/geth/config.go | 1 +
cmd/geth/main.go | 1 +
cmd/utils/flags.go | 4 ++++
4 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/builder/service.go b/builder/service.go
index 08f8aa20e2..2c311b1b33 100644
--- a/builder/service.go
+++ b/builder/service.go
@@ -108,6 +108,7 @@ type BuilderConfig struct {
Enabled bool
EnableValidatorChecks bool
EnableLocalRelay bool
+ DisableBundleFetcher bool
DryRun bool
BuilderSecretKey string
RelaySecretKey string
@@ -203,10 +204,12 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error
}
// Bundle fetcher
- mevBundleCh := make(chan []types.MevBundle)
- blockNumCh := make(chan int64)
- bundleFetcher := flashbotsextra.NewBundleFetcher(backend, ds, blockNumCh, mevBundleCh, true)
- go bundleFetcher.Run()
+ if !cfg.DisableBundleFetcher {
+ mevBundleCh := make(chan []types.MevBundle)
+ blockNumCh := make(chan int64)
+ bundleFetcher := flashbotsextra.NewBundleFetcher(backend, ds, blockNumCh, mevBundleCh, true)
+ go bundleFetcher.Run()
+ }
ethereumService := NewEthereumService(backend)
builderBackend := NewBuilder(builderSk, ds, relay, builderSigningDomain, ethereumService, cfg.DryRun, validator)
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index a26192bcf7..f18f140639 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -171,6 +171,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
Enabled: ctx.IsSet(utils.BuilderEnabled.Name),
EnableValidatorChecks: ctx.IsSet(utils.BuilderEnableValidatorChecks.Name),
EnableLocalRelay: ctx.IsSet(utils.BuilderEnableLocalRelay.Name),
+ DisableBundleFetcher: ctx.IsSet(utils.BuilderDisableBundleFetcher.Name),
DryRun: ctx.IsSet(utils.BuilderDryRun.Name),
BuilderSecretKey: ctx.String(utils.BuilderSecretKey.Name),
RelaySecretKey: ctx.String(utils.BuilderRelaySecretKey.Name),
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index efd0ace45c..df1ded7dd9 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -166,6 +166,7 @@ var (
utils.BuilderEnabled,
utils.BuilderEnableValidatorChecks,
utils.BuilderEnableLocalRelay,
+ utils.BuilderDisableBundleFetcher,
utils.BuilderDryRun,
utils.BuilderSecretKey,
utils.BuilderRelaySecretKey,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index aab20c84d5..fde7c8265d 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -699,6 +699,10 @@ var (
Name: "builder.local_relay",
Usage: "Enable the local relay",
}
+ BuilderDisableBundleFetcher = &cli.BoolFlag{
+ Name: "builder.no_bundle_fetcher",
+ Usage: "Disable the bundle fetcher",
+ }
BuilderDryRun = &cli.BoolFlag{
Name: "builder.dry-run",
Usage: "Builder only validates blocks without submission to the relay",
From 5d7d71427d8bccc082bd63048dd864a97dca38ec Mon Sep 17 00:00:00 2001
From: Vitaly Drogan
Date: Fri, 18 Nov 2022 18:04:06 +0200
Subject: [PATCH 61/83] update readme (#49)
* update readme
* add bundle movement (#50)
* Add builder diagram
Co-authored-by: Bhakiyaraj Kalimuthu
Co-authored-by: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com>
---
README.md | 100 +++++++++++++++++++++++++++----
docs/builder/builder-diagram.png | Bin 0 -> 2936973 bytes
2 files changed, 90 insertions(+), 10 deletions(-)
create mode 100644 docs/builder/builder-diagram.png
diff --git a/README.md b/README.md
index 99d8fbcf64..742dc50a29 100644
--- a/README.md
+++ b/README.md
@@ -28,20 +28,100 @@ Local relay is enabled by default and overwrites remote relay data. This is only
* Does not accept external blocks
* Does not have payload cache, only the latest block is available
-## Usage
+# Usage
Configure geth for your network, it will become the block builder.
-Builder API options:
+Builder-related options:
```
$ geth --help
BUILDER API OPTIONS:
- --builder.secret_key value Builder key used for signing blocks (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_SECRET_KEY]
- --builder.relay_secret_key value Builder local relay API key used for signing headers (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_RELAY_SECRET_KEY]
- --builder.listen_addr value Listening address for builder endpoint (default: ":28545") [$BUILDER_LISTEN_ADDR]
- --builder.genesis_fork_version value Genesis fork version. For kiln use 0x70000069 (default: "0x00000000") [$BUILDER_GENESIS_FORK_VERSION]
- --builder.bellatrix_fork_version value Bellatrix fork version. For kiln use 0x70000071 (default: "0x02000000") [$BUILDER_BELLATRIX_FORK_VERSION]
- --builder.genesis_validators_root value Genesis validators root of the network. For kiln use 0x99b09fcd43e5905236c370f184056bec6e6638cfc31a323b304fc4aa789cb4ad (default: "0x0000000000000000000000000000000000000000000000000000000000000000") [$BUILDER_GENESIS_VALIDATORS_ROOT]
- --builder.beacon_endpoint value Beacon endpoint to connect to for beacon chain data (default: "http://127.0.0.1:5052") [$BUILDER_BEACON_ENDPOINT]
- --builder.remote_relay_endpoint value Relay endpoint to connect to for validator registration data, if not provided will expose validator registration locally [$BUILDER_REMOTE_RELAY_ENDPOINT]
+ --builder (default: false)
+ Enable the builder
+ --builder.beacon_endpoint value (default: "http://127.0.0.1:5052")
+ --builder.bellatrix_fork_version value (default: "0x02000000")
+ --builder.dry-run (default: false)
+ --builder.genesis_fork_version value (default: "0x00000000")
+ --builder.genesis_validators_root value (default: "0x0000000000000000000000000000000000000000000000000000000000000000")
+ --builder.listen_addr value (default: ":28545")
+ Listening address for builder endpoint [$BUILDER_LISTEN_ADDR]
+ --builder.local_relay (default: false)
+ --builder.no_bundle_fetcher (default: false)
+ --builder.relay_secret_key value (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11")
+ --builder.remote_relay_endpoint value
+ --builder.secret_key value (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11")
+ --builder.validator_checks (default: false)
+ --builder.validation_blacklist value
+ --miner.algotype value (default: "mev-geth")
+ --miner.blocklist value
+ --miner.extradata value
```
+
+Environment variables:
+```
+BUILDER_TX_SIGNING_KEY - private key of the builder used to sign payment transaction
+```
+
+## Details of the implementation
+
+There are two parts of the builder.
+
+1. `./builder` responsible for communicating with the relay
+2. `./miner` responsible for producing blocks
+
+### `builder` module
+
+Main logic of the builder is in the `builder.go` file.
+
+Builder is driven by the modified consensus client that calls `OnPayloadAttribute` indicating that block should be produced.
+After requesting additional validator data from the relay builder starts building job with `runBuildingJob`.
+Building job continuously makes a request to the `miner` with the correct parameters and submits produced block.
+
+* Builder retries build block requests every second on average.
+* If the job is running but a new one is submitted for a different slot we cancel previous job.
+* All jobs have 12s deadline.
+* If new request is submitted for the same slot as before but with different parameters, we run these jobs in parallel.
+ It is possible to receive multiple requests from CL for the same slot but for different parent blocks if there is a possibility
+ of a missed block.
+* All submissions to the relay are rate limited at 2 req/s
+* Only blocks that have more profit than the previous best submissions for the particular job are submitted.
+
+Additional features of the builder:
+* Builder can submit data about build blocks to the database. It stores block data, included bundles, and all considered bundles.
+ Implemented in `flashbotsextra.IDatabaseService`.
+* It's possible to run local relay in the same process
+* It can validate blocks instead of submitting them to the relay. (see `--builder.dry-run`)
+
+### `miner` module
+
+Miner is responsible for block creation. Request from the `builder` is routed to the `worker.go` where
+`generateWork` does the job of creating a block.
+
+* Coinbase of the block is set to the address of the block proposer, fee recipient of the validator receives its eth
+ in the last tx in the block.
+* We reserve gas for the proposer payment using `proposerTxPrepare` and commit proposer payment after txs are added with
+ `proposerTxCommit`. We do it in a way so all fees received by the block builder are sent to the fee recipient.
+* Transaction insertion is done in `fillTransactionsAlgoWorker` \ `fillTransactions`. Depending on the algorithm selected.
+ Algo worker (greedy) inserts bundles whenever they belong in the block by effective gas price but default method inserts bundles on top of the block.
+ (see `--miner.algo`)
+* Worker is also responsible for simulating bundles. Bundles are simulated in parallel and results are cached for the particular parent block.
+* `algo_greedy.go` implements logic of the block building. Bundles and transactions are sorted in the order of effective gas price then
+ we try to insert everything into to block until gas limit is reached. Failing bundles are reverted during the insertion but txs are not.
+* Builder can filter transactions touching a particular set of addresses.
+ If a bundle or transaction touches one of the addresses it is skipped. (see `--miner.blocklist` flag)
+
+## Bundle Movement
+
+There are two ways bundles are moved to builders
+
+1. via API -`sendBundle`
+2. via Data Base - `flashbotsextra.IDatabaseService`
+
+### `fetcher` service
+* Fetcher service is part of `flashbotsextra.IDatabaseService` which is responsible for fetching the bundles from db and pushing into mev bundles queue which will be processed by builder.
+* Fetcher is a background process which fetches high priority and low priority bundles from db.
+* Fetcher fetches `500` high priority bundles on every head change, and `100` low priority bundles in the interval of every `2 seconds`.
+
+## Block builder diagram
+
+
diff --git a/docs/builder/builder-diagram.png b/docs/builder/builder-diagram.png
new file mode 100644
index 0000000000000000000000000000000000000000..d1b4e66f75344ff8f7d590f6ac0656d740a1e3f8
GIT binary patch
literal 2936973
zcmeFZWk8f&*EVdR41zEN3PU@z0uBh$CEbW9Eh!)(AQIAu4xyAt3P^`acSuPK2uL>q
z(%lW;KE1@}y55`ndG6=$_rCuIn0=mmuf6tK$2yL+d39S+>f!~m3#U$&(B$iA_-%s&Ad&3nx8qn2Nf@Do$6nU>ZoD
z^oAy0^g_sEBb>gl;9*g6nQQIjSLHsII+6ynYKZEG*Us;)Mz=2Q#PA>O#W+p1l;sXh
zSaS|J9UV>aw(`d03bd~6MUO14YeuQukP$gU_~tnl-Idcg(*N$`t=Oe_pQ5=#{K!*i>$`0tW(q1y9>*2`nyf>kG!)e
zo(L_#*BxD|*v!vNQc)7TxiQnhD7ajCcIIBDst?DM549F;+_lIj~8GmtHjWR2*(_1?UEzoos$b6#c+H
zj?(^z`^?_R(cz6;r|nU%j?W_pi|b%gtZPN)1$_*tf`y8sL(?CX?#y>;EUd1_p;uO&
zHv3FEcIPrHa_Ut+w@~J0M*S7=)RoU%c*rc>r
zZk0}%V{Nnnec92$l2XOaH_K3sm*;SaF_kw=+Y{G?kJf}ozvo};XmvYW%i9`ulG?6y
z+aISk&Hct@w)PYg?mSLDf~?P0B6D&?RUS;B-DK7#znR$`mds_4i}rt)qJHm*PuXug
zJhby%4qe7cY1gZ-T-$X0GYh5j@|U6Fl5T~+{61?Cgt1Wa5Nz4MZT#^^_YAR9=<7AP
z{7a=A7s|kBI?>H+bZ-EdiA@8_D^bFSxb_*CU`$5~|I*;230R-E7m1FR_fw3dc8FJ|I2uw6qSs38pB`NMMQ})_IY=Q)ANlJ
z0z<48;dW<=P{{R){nZcTC|tMo1nQeal8OQ=H8#65DfL@>3x&GiNT+H}EJKmQ1^BSc
z=cRI|-L6d6d|8Llo1TROgT>agcOUd6q^A4OOmqY-K3l#Svzc@Wm#sc6*gTkQa!#FbL8B)1bohRw-Q^Z&3x`r?eFm3Bqws(
z+(E`6>ZDXTO@pQ)#hxq*OLMees=(o=SQ$nEHWwHqIu
zf028T&5v4GXp_cJDd*n1Mc@E=9g6(5^zh=>SsmRsTLrfUd*}SbKgeLRjgo+?Z_T`<
z-q(xZwKlfbLtM*IiV-ruZye&>s`KDzui_}Ow`l=*kAhidnH}Gc=V#lKBP~i~GkU^j
z)@M2r{B|^!$_h2FFG~Dg$@}2vXEYhjkv{Y%rF=fwQ!D2Jasz3lk{M&1Z}=hE<7?Bn
z`X45&&*!#!>WMw93vCDCi8$^`UjQD$&rr=sv|+$*z~w!)^(>z
zH~MfR(=Fc-f3E@+iWpnjUu#wJR-@zbqvS7`0Rf*Mja=o$Q3YG%)L|mD_6}yN-X5Z+
z^Nz}Ge+BmH6|Cz{RV2RqSkId+^S-<}7aOhfqS%j&OFv_9$NK
zb^^EU>h)Jj&Ib9*juXtfq%V5jpu`BBLh~>CBeLHZuB<;&q7kPNMd}#QAIzHw%TXUp
zOGO@12|3&MKhFKuUh1$W>P(wRk7Jovw%N~2={Oy?!i_@Vg!P3-+mF41AvD^(g!f0+
z!8t1J(~t7HUHpw}An|*GR7!rgCbPhoQ0`xG4+KX`8q5CF#F70;ZXA@q8{>PMJCS`T
zKEgvn%W~@dAK2zpOQ-(|#qNvpBB#^&3o~qk{xy-r)*~v3UY9k(q|~bj>@HOmmT#on
z)F7lVm6F?a`qiGcoT+y2)v9owwnO0+mQs|4$*PUEOs5xBr!hcbM&mq1yz2W?bv8aF
zDfU^_Q+6C3j2zWYL70YbKC3!nQ?iO8_Q=DNhuj%gC;$a^igT
z`Z)ZS%KLv_EHRr8^QxzakCOQ-y0-=4FvV{C>@HmVM=JVj?Kb>pu&JG&l#IP*Zg0}r
z%vtYLQA)pkAgf+;usMLfZ`*Z`Iatj}QF430aW1`j&9AHnZVl3imul{ZEiyxbJObw*
zXJ5t4%WQ5IR+L?(d!S{s4JJseqnF4H^4W|(|Dtr6lqu~)pX=wonoHigJ
z<%J8zcfEf$Oggec)S6RRwqS0$`tz$o{Wz~3h|N;d-wN8vhnGEzhw}$>u(Sw2i~@v&
znP!ew`GdVJrBKwov%t#
zMU)dmex@BH)6R@#uZ?qb@)PdZR7mQ4;6PtjS~FcYQcYi8{
zg 7_aipv`Rw}8J}SnjC!&1gz8vkTx(BoCm1hZTiWUTvfA23Pz(uxRs(B~YA+eID
zmgvNZ+lQT&-e;8W&Jv5^7Mx47r?nbfYZZ>^)^$55P}Gg7*Qc-7`xPx(wc%5`h#S;|7;i0eCz(VWdjuSwK--OJi}j>`8I
zaS?N)Uep!^BxLnaRGk3bomGjET^_L?_X{g=MYQRxMf4(sE~@AVZ{o
zQD(`Z)u~+x6dW}|dJ8g3u3MkQvS_|V3-V*$cfBWOQGa7M;<5#gkcK^&Uy!h`+g;Z^
z+R%L^SPt9U{_&E)t|)vSZP%?~Qo92S#29O$$601-gO-{R%#UYt0;{+smU0Ub)XG
zoEieR4@l+a^c_Snw6X9!{LyOxkPn5(;)*eo4*Q
z6fcEvZnrJ39bv=2nN*siea0SL_Wf5z>r-uU0`I*s1{>Uh;S%rw=HRE?iA-M{g(d
zq?Pgd6zf(&?wUM}T;tl&$j3P{FmEmM{CoRDOtGjEg(x2O_aKrh^g(qWKD+;V6)u|7
zfne;O=Q9n%s=b9aF#-k5w;Jn@?cs8bzJ;M
zf0!2aPJ>i?Lm+Jjm_!(a$G4?Kw^7d2PF8_DVJ-!;L}M4?C~(g5*v@utOMR%?#gKG^
z;*%6c9
zI$YWgKWJNK(^3@`aqddh$!{*Jg_pzf)nnk~IG1-;0^9^JEp;_pZ2KS>QpW!b)v|b3
z=SLpqiAUNlFETy|0w3!pEAtsL9cwp$W+FTuC7Snj;4krsiiOq;KeYd_!s}EXI8BYI
z=KNirI`V-!F?#rHTE$6-Bcc-EK6t#0meWfBfw(qjuEmk9XhS#?9*4*O_7DIIPwxIk
zSqv8)=R~I4!CW#+U+z382Ne<|{Vt69t^*8Di{n*}3
z?E2NaZKUcdq(nWH%?&^t;tOs6*v7NW2EZm=BfRb@TNQHnaUvAoT(+yJ6+xk`0$}Io
z_8(tfR#!g(C5l{+uA9s54{TwiD9y^Adpptt?qULedLIBTEGvkxd_1C`p+OG&n@Ke1ACUyA&;ULOe!(U`z5Q5
z%pKH6+x5Q?i)lx)gtxFjDNn(EwV9t_UbNfc&M(lJa!E(cZzrAUVd)V31-xbZJzG$x
z8AwU;6#)1f1msCkInb51^S$k5#>Y==LRSb-+O*wj3b{CJ>8RY1X0-F}Wl8$LEnAEr&{7G?vg
z4yj&=)rayy3<|QA4ozC^%%Id|2&fqgQT0!v&a%`ob4p|-ff4*Ri483<$T#?t1@GRcIvx;x?)44V`T3PD2hxmhiqMn2~;VqjteV2
z7P{&>&BKBacpM5(J4dJIi!7hB^{pCD>E7G1sn|D<&{l&60{Bl>gtY^5L8^v
z*Ls>n{0`(-+`FVe8MKkTDSHAq0c^=oR3)GpXeel@3@}gW5)=N6%K+oj=knjtpU4If
zPA6jZhRuUat?+q1M-D14`Ax5~h8`Qx9C2I3%;(LB+;Nrr&qe9+!kR&r@FV45Pz
zw5?x*7*jw6m(jWZP5#&bT~GnLvAX%_-^}1mrK*;B2$jRnz`Pc|xkMZy2AgU!P5R$&
zZ>0fm&+Q-RKZ$cVjgxlfjHP`wc{?avgCNAXeO!b|$C^Rdu7AN~=7fp`aJPJ?-2tD-
zkEmNo3{sSZI3&JXuUK9j?>w_8n6<>TL&othPM$sC8<9divQiS{?r*m{@@f9n%!Zh`=`VGs~cv~08T~~6FqgZeg5Wc0K$XtkRg-)=ENn5
z$YD*kypPN%U2U{k{(_Aw{d@mT#S-bT09
zmbasp%yDGyR{}@ipjqH#qC6Itun3|Fe^rP8YbP282y&Ip-Cvr0k)PFYY^pSTF#FV(
zIXx$92I}Kj5Zhw2GZDrp${YP`*ugP2eT6vt)L-M>TYwX$uNC;wpHRyIqpH8c$|#@H79pf9BsQkqZyDv%|J>$
z{ELjKaC0;L;~4J5pv3B9|HrUD_H@K@m%Cm}CkPFnWa(as2`7Sfwpk1Fpxg@&zQeINgW4Ek#{0^S;XmZXQ~76Z+jtc
z5;TsTukAiqwrx*nzRusr{MSZ)gGB-4H?vYtt>i<3R_xKX_Otn8Bau`{JE1sviYW{_r6w7Q36k2fL906H+fF(xO6GaG|5
zP_4H$XszV9V5S@CFSI+oKAjMd3~{`_=odg$%KieA%cIr&Ru@?!j*pWW#E(bBF5Wyj
z;HcudgEE%)B9iUO5a6w5enIYskgYntGR8y=A8r<=0AOU1A6)J8$8z~l&6d8IlXvp^
z7p^?n=u-1~!Uf3U_54g1)*;uu1=9e*P3d}SH|Gc;7so2GZ~qlT$RPJ){A1dGY-Pz!
zch`g6B*5TZMJ4-@b6>wm#>oWLKoEO4R5Glpcv%o_r#z|3pr4C$Ed8paZl8P@p!)Mn(TH+lCOq=epBlT;gu>(ZN8xN%WpEj{}oFx!=V>f73zYaL$;(?yN3&&Bj
z%?gAUN|UK~C)@cxrV`Yxkr2JgY2Kf=4$A9m3*a~R6Mqt`9NXpQEhyFA-ikQ+8L1(a
zHLr*|roShQM*uh1N$=S-IR2)MAb9=#%x^5&$A&rn%U_2Z8@9eMz|E?e$8`Aa&-PX3
zK&q$kpP&D+ogkXX1TiH2V)4O|F#XvOC>Y~)cc#Q-TyCpW>yYPlvN$6DM%Vh06jJ}w)F4+*$j~s2)KNVsIEFb%YTOjARp-g
z3PnECzLvS&(seDeMN3+a;g2m<&j6qha_T$FpU3@eZ$*@@WC5I-!fsfX$iG;WpmKL>
z%m=L~y!$<%o%(Rui_>PhO;MQv_qWdUMr7*>n2BKjcg+)La!*kYD#8Hwpa=@uo@?vW
zmrRd)0V=vzwVywEJ6`AKvpNrwbd$Agitr@BS#p4mc~}DSdyRv%Asq-s
z<~oIf*v>#~QnK%c;@6CArqg{VMf0`Zdm4*e=*44-_qrrVYd$x_YeW8K;NKr(P2eIt
zpMmNVacyr_t%7d>(0nUPkU~fXsFO$7+219}lOUCF#*^GaeWAyq^vCwfIBgW&3`xbd
zL_er_`&l}GbR7xdQXY%`JX6!&EPaJ3AZH27Q1A8Dx1&LMzy5{5^~pO2fM2amt1oiP
zgPQU|$#lF=f2oBNKGVV>U_lZ=^ehxiDR7wo#PTeXWb4#lrUe-v>}xGq^Vo3^`mN@ky>_rPdD86h!4#_MqE3iXt
z>ND$@^xS`TzaOw911v
zd&ov5vd4Vk6owBYApRMm-0^8!wJeJ18+(8Zjz2Zj{)#zukU9Uh@oy&wb=RdfKGSM-
zmJ|Gh?s?@?NDYVZxyx!$j69$couJz67qY-0ML&FjG-HS^Nv1-HMq%lp
zbF6bTVY;pkyFk#p>^0~x^&X%CJ5A$v5V+;))qBb5u=;a(zN$J^=)P6$*JbX^>eMa(^*I
z`TE-L++bq|Qr7!250;n{QkGz7i!XrphBPJDqAT4Nuu(lT-a@iV!0_GAt6*|k{
zprbh+T%l5N+1pfr$m?C;tiy40Y0W`Q)iR9lgMmPzmgGh4YX44S{Clm{JUKT|yz?M!
z=NC5)h-Tec0Z|TUWix)3&W05ABg?gW_yoDTm{_3KV|#85KwM-Mh3{+!gdS|_NKFz!
z>=70(2QoLxWivO%FNT%AUmpn@JVul%(P^&4
zE2#Qc_H3g9OJ9ha$o@x?{v9^-ZX&K=MkLA2FU2=Q+E2HR(*oE$bdnUc3+DmQr|?=o
z<_PywIn4(`B?r_MklYRdLd2p~cbdj%SwWAEQ<55>nB+pJ5cqKqfy{t=I#K8VFl4Z}
zS7p`seABV;K(Z~ER^CHpj7?3oPfm7N4`?RYU5BW}1>k=O;~aKY%f3bj`-cZ(Bo)%2
zlu!aj*RA3R$O^$IX;a+_IQR~<1zq~m>}|^IInZR_`hr|X*W0M`Y}{M}ZGd)TwZ-Oq
zpDAzyzc>CAdl5aQ3{dw9ezY6tme5>49BsKBg$-{O6wkRIJq0YY_xIfAMX#6q~z(OktLGya#C{^c^^%DbemTn0Xz
z-S8|vX6^4m}yv;LW!Kl%zcGGcQ44dmT
z7fEl>LA*?93cLaCHmlON=i|~1nm*7s_we;Ex=)82`s3*1Sf+uJEAi^3F^D{J{t2Qm
z(NuOla?b~~sOr{=8o=eMZxluMEwCqlRF(GzJMYW77cr%irsIG23D_?ux
zS>blnYkhI42$BFV8ZX(`^T)Tq?s_NNCuMV!89b7KQYF-VVBl6-XD8+@Mn}i;0P(re
zp-IKU-vz3-z`5UqOI)Qj_x>Qq`>`5QmubeH0aCtU7b1tCG&@EiuqOYPIo&f9LDEYt
zjGZ{S6@mxMw_UA#HKdmS7lE}zjTQZkdqe<~KgOdI9pUhI@E~|2ylc5#be$%RDjqTR
zl}Jh8`@A-WtcJ3NyfjsItVpeN>@q4GF8UPYJjD4GAOPuOrf8KjpuUY2{xjrzZxK~b
zfQUB(Df5teAN+1^+_Y;rXHACs3P)=Yye?r0(*QmKw*hya4J(Afqz%)@G%zA{+Aq!EVk2
zCP6t%M5RTL~L2O2c4@?x-xA`U^-
zZl`K7Z>S>ze;vRVDer)vV!}{z725|M-E_EJFU%k=IxAbQyVu{-UN5wn&A?vB>gkI9
zjfk3|OlLi(&UHejb7VO!dPT=!B4qSi*PZFw&`Reytr1zMJFAL+re65assrUs1v&Zn
zlBDvvC8p9g5I3^n&9b6xK)RqAgeqRBJr2IIyiZee1?QG;+iPzYih=Ogum;|FvF_oV
zKw+xK-ntumpAlTkpli;%DLX(zF76CsWjWT+jay&>PPI~19eH7irG~oZ%iCXlN`S)o
zKs2mxPG2K{VGu73CDi9yL00>~(RrbSD8iNQsvum;Jjc@uLrbpDU@xIQGqJ?#4gxf9
zKOzHTF5byP^=M;f{G#W!J%G3v6LptIse?%YaD%tYp_nD-p*bCry$Dpnn8zm6xP)kb
zTQdSP1Dyv-)pPI`NI!&`W^w(l)=RDdAe_q
zYIFe5N;pc3m|`-Mu^$^Hl|VK_4P-d%7Z~jOMnfb)g$oqpN58b69F42r=yOWX7q^!bn1$mYN|~7B*bvFHMEcAC
z{uWAV(R?Dj6If8|lU}|EOQ{uG2CYq|pLD;braz^HjVXAFV2@PnPoTdwx|@tc?TtyF
z#Ez=?@lls8g{nMh%11wTgO(uGxh0a{0iZ<|1c2NE--#27zgQ`vE9)pKDa;-5HuY#s
z+e`S%CofVE1_&x+0>Sm$dr--F
z4O;G|0YlEOAXCvqBiW3-#Z%D(X#$ux;%~$TXMp17joH}yf+`th_eddk2ufCbPbNyZ
z7e6|r7Nd6nsmzGi+1??rH`Y@v7l$SA#yM)fb`!qn1_(WK**KEhQU_+ZHq}aC3`tNO
zrg=CtbTni=Lx62;hx9S?TL&_E)Gsba>m@oWWxrL{nvle*ei>N0j4TCpj@XqXN}e#m
z{#hcYq)`c}BH8vA=WsI$R9S4Ri1|A*M36XGUEcJuLyNuERX9Kh&plmFlGB
zq>fglEA4>C!Tox@e3o#oHXIuKl#-Tq9eJ4GXF!m<8M***+4EB#B4-I|1TkcyTr_mb
z)L3XN+R_|p41sv*mt5bn7FdG!6vZf`{tK8l(woO~OU=c=l2_m(ysy_}9;8JX2tLE^
zeO@Tg$~BamZX&lVW#2Rv8x$QByZZoLMChMQZ<({}yG&N|8E?h6pvuV)?w6FdFFs#w
z_NHXq-HXXf`gN>?y;Sd&+oSoOPc%0x#PaBzjiD$Yaho-ce_;06I(F4#!qt`KYJz|Q
zn(qwW&X~-7(DFoX@aY%=gh0GkQg=?;Nj5~D@fNn%88y0B!QGd0>WbzMc1dqRQCu3b
zQ3+3J6~bsmr-gjAT@ao~;trRkt9@W-PH}2+`l@YI6e=P_d>AG_&*~}NouU-8ePJmP
zsxm|mP_l@{z#LizmuWHT3#iwffjKBoWz`y*)mt@Vni~Ro>$PZCc7Y@qskgfK2hZiA
z&3-eqhmT+_d;q?=?Egs#o8pNGCX%j{c8uZ(HslS%U~9REvQ;zV;lo1h<57}h=AN-0
zS3IR(J@;(r)_if)rKUT?(o7qN?kmi|c!{fv;fW=F8X*XH5;z~^Sx8<*in_TXLYqiy
zK#bzRDeH@V+A#u`FqP`RWM!*?V}(HY<+Mv;ED}rS1*P1sK-f?^>$q9Dgb)gvotmNd
zP6(P*Vn0AL5H2c}{lReKndejOpkJql<#VkMiI*1<)je+>`{!LL5LAVDXq0b$Fzlsw
zTPG7&eqpnpk(lqTUa@df)=?L50{3!JR|*O&DPADQ$l|*|+j9pCf0a>=C23E+%0>5i
z(0uZk5O$55uB{;hFq^m{$c(wzQk{oVDMS3;GC`iAxX7jKyv
zB~D!BenF>W19OL9f%h|7NndZl5x7R*)*?_C6d>sxbJhr50Fk@E6A9T7`1VJm4xW|9
z#~SEOh)7GXWyw6DDj`(&z=CPne>q1|Gi(OqzE3~O>KVQfT3BlIPJ$tgUPsq{FgsJz
zu*w?mH0;ZVbPSKAD@@`3)%<%F<%}5GG17`4I2s`>CrYsg`U@$?;*On_5<2kps`-j1
zIIDE*SuD6T>>Sh~o(wwF2q+uAsFV<$-Yhaax>3b9hVb&D9%Tou#Lo&z`?LA2vabJn
zNE-)u=0>w!VfRVT)Vq2H^Mkeq6}Ka_^HoXqzoeRVb+CNFf|>N9la~KbQ4grmirmBY
zhGZZ=raF0lJyz*Xs1AAlR3c{-LHs0vJGLn-DqD
zctT}+#r<`q1j+s2U-T{LOk{Qe9Sy|4JGB9EJN+G?BS)JxEr%1(5#4PB4`^}fcxMv*
zE(5gvt&bA8sk8hs2uM|c!~Fiua^fVyT|`1}u*hoEW%FRkO&C-I;{WNu-*wzyQrmyW
znE$Z=81erumYlSIS5E)ido&_Sw9DB6E-VY60eNP@(#@$K1R^KRjr2bUci~DbwkE%x
z!-tdn@r_6ieHN%ClOW9kD%576`x-sz-t@;sja~@w4y_6AvVZL7pFI;Xx$^vZHz>W<
zL4zf`%Z!}vES^LT-m!Dp+ySTIeV#tc$!c3=BVcn1o;pNC{x!K+e#jMD(#?kcyxSTkMFg^iF
zY|1Ml#^hhDBxvASn6J%uK3-DVEwCgrczM{#oe=6SSj!^hHy_>YxA?r!4iIMX?)CYQ
zL=YE$@Zz^v|lME+(V@DZpSpl0X5%Ihgxw4K2aDNg^d
z6w_ruzOq3#bIQOy6a^ri8jvuT{o#W3Y{7jjt@n9l|HcQ%XtN-yM#yCsgB@|EpUmt$
z`ghaDG$CXyaSjdXldW}`emRc7g=u4Ph+m*hk#8pP^0$^h_durWputw-nm11#OpgU_
zmBi#d{2sp!(b6t}#<}eG$0Z(y0441Gzfszy;Jx6!R0n9i>fjo99OMs}0V-t^=BvN`
zQKTM1UA~bf$6D#%2xaimhW%H=B-EwZ0kv!>g8KZrs-q9{@8OlRpi`uP&(!H;rO5rJ
zq#kIaRKO*!q&R|p8+>T&3lqBP~dknfXa{qUB
zd(x>al{6LOCS;!Tl>zG34gHVp#T`PerOp^ELR}L=i<}z5#%1#bV5eObY
zz;}S^g8WQBxL?6U`Nyl<0IR{t?{(s9MFOt;GIvUhD<4)KXmkD`)!8R|ys>TUV9u1|
zlN!g1_@|_I-?tK63<`?#LQYuyoh`amU9r>s3*
z!spJ?a9NKuZ|QUdl+=uOoNWKh^zQ+{y-WOuobK)&$NiQ14!~~*mxGIF8Of=Rq&R7g
zXMm!OFLtz===@N~&cR>1+>m$z&ndGRFKj6%ohqL}BJ48D?loSQU0)5;-nK7+TU8!D
zcVdLnac}t$PZZ3T5%Nok`4JnVJ}L2Im>T&lzr29wsB-43ZgtmHm+s&ax|l9ko;KYa
zbYL$(zq!#?LS}f2Ch_FK=#)s`L&BUFny6jcNYm*1vB{TOSSuAkJn0A%F4o
z!dtoPGxN3g7gzJ81WcJbLf)xq^~}nT6xLTK(n~H}PHH%tZJ=Qw>9l?O@SMiu>5)v!
z$P|=4YeT&Fk!us<`8@hhx5N7Sv`m&`-K`dit*26iT%0lyt*FMTisH4&lv^6xS5~;I
z6y&1iom;k=#wYSv@L4xvjBp&Zi3n2MHIR>FZfla#{3zX7Q>iF-5i5P?>CN@vNkojW
zSUYLk3%EFSvhITy=9?EAu>6hikgq+*hni;LR1mYi1Wwp{GyA?)|BvJw*F9g{R-ba(f;rsy(EnfHz$5zlBcs--$R~9KZiDqsg#RJ8(BI
zY$&HO_lD{uH8|cg(49M|?-f#FyNtUunU$mjEZ9mn=RVso<#gk|^mgT1{P*#Mu5qoEhqovytrOT;o6dT0F?9LJ
zHW3!|-{|JniR9CxuzW={qAyO(_uzJisW*;G?y2v0gpjp9f$x{DHpSP<`EVu?bdgW+
zQtfw`G|QjmNRULFp%wPrsC#48L`EH4rbbN4w%$WNF*R8*AG3GA3>(kWX_q8s9eA@nWtK#InWu{smleQUP%Y%6C%T!NrDmC
zUN^dN?*vB*u^Vi^N$P*=dJ)^7lqXgY#OpN}0Tb(IE(WH&yz{cJ@>3p!bFp08(VF$}K2zBnJF
z6Tv4~Xh|Q;p4k$?!MM?6=1c-z&wCS9+56hUpabZL!F0QP;&~9e)_4(`<&iYM&eaZM9
zp$iQf3x@TC_{pze8__;7yhg>eVP0nfX`YUt-BL{CGY|2EONtr
zXN?`!Dm~}>$bz%B!7tD@T|dOV#D4G+?b-)K^_aD9`EruBHz_jXsu`j#-a?}p8GP}}
z#*52!PXz?nipI0=5S}07Elxw)`AiW~AhE4;zZ5C6RFhvgyD?p|8dM*woq~FKKA*|?
z_T*KS+~v1(sx^|cavE3N41+~{7ZdBuR1PKMuo8p@fAkmX#u!Vw&3@te5rNzi`|}+D#H6ZT)j@@D)-b|;SEpz1tEJj1I3YPY#8o(*O-wE#qaZcrg=4jPm(@TOBHsgl;lb
zqH5#wmi?H`B>%i~hW>jp^b}+Csw?Dg=A?BxRi3F7Uh7
zLiVoT{W%`#U)hOVLpZ}PPEFnLkkV>#Q(RQ?uF`fpd?7n1ekvkps#_MXk;d#W5*E0A
zZ!y)|U_VhXLD*AW3&&2|u;C5Lw5mqNit;+x}RJ~ox;;ZivkB|=q`qqDHa!UzY>oO@sM!pDx`r(79n
z_lH;WZ5Fy!OM^_(p0|8-Mf-%RZWY?P;E^FoG;ciT_Kd!k>Z8?|D>#}RdM~-#zR=Em
z_v%!qb!E8dC*76%8GR9_5Gebc(@857zVt{LGC?`EZoTK`0_8W#^C${ZlP*vC%K2iD
zJ>)8K@rW4xnyTIorfp7APqG^kZ!bPV){>ti7OH1bRc+~TTh7v>=h;Ap`n~pxKTU(>
zS^Mam@bJYh9cGD2oR>~5XG4)__PUE-CBN70U_usX=c?!evFo>*Jd?|JqQ>44EkquP
zH6tVZRz02}Q_apR;JvGT(!7G;VkY05-Lu@-iTa=>C)$x1mR$RK=Tn-TsHpz@<4Z!*
zVrU~otnzj&euoJh|I>}M5KfLT6IHJMfJXB{CXuSsFQ2aLGDQ_xr0rY6?bsAmQ}_0`
zi7roT*&C$^8rbJ{6C^lga4)FOe!DsStTW;J=a*I@6(Xe{M{qq?{M@6gDtWsO5|xm0
zGq}yvNP4*)QtH})6v9``Xk@QqbJ~nn?J3O*@6M_7SVYptF@mic4N4d0F;@gVB@s
zCiP14W=2XrU(RB4Z3y}vjh+r1NG|T>Mwju-B;_e?(l8mtE3zs0I01E6@Q0$)_nxBj
zFX|lUd_Uo}t{*hNb>(!^C;skSz#`sH3DfZN+8M{S+G;dEiyKgQ!MrPSeKDAFASk
z(>@M(ZHJv<4|pFk
z@MwXBkDWySfmEl*xMp=I%&;?ZvtC`8sa0(QwZveeZ}d~MTI>l*SxVg3_Gz@YywSc=
zL$qM9Rdb0M_fQWHi4r5`{n#tw=-0*0>m05vt+rA%w^-u+q>LF*-o}f|DK4H}0)+x{ur)8st30w
z#9SHtp)5}2drnA<>3;Yg#XfX-6hB7k+Zva!exr#!(x7Ke#Q{A#Y^v?DTgy%nve9Jf?Uj{D*FkrA1
z5w_!`V-Y!8?O8V!?8h#1zxcYw^%iUT7%M#6Q9fUVN%FdD)@HUbAEVtLTGw@aa9%Pj
z|FO4&`^kh9V~KTp*5kwFc(X+U+*N1N7uFf%Ys5XI!NSzhQ(BERz50*8MMpaPEPv6a
z(3d<}E<_WsXX@aZA+UDMD(I!mCdY80+J5YQdTv81^O}#YAyafWGJlho`KeYv%jdFTl6BS=Qw=(GM7LExULw=N4=QRoBHwjE@Ta<^ta+P
z-wi!wN_#}VAD^UJP~Z3yb+)?l4w~tSfW85l3dZlNXR_qDjSK(dF7iS6+uJ
z``e~yv_B4`+|X=j>PfSyjC8r_nWa!D7=h=6KfY{FDbR0H}ECj_=q8UG}v4&B!Bl{C5^AnzLyF91W(dC
zKPUU#S^O!L$ya^Su-v*VS)?<2tUgm3^-^cpio0^LkT#)b)Yrb)zHky-yt7(d$k6c}
z->2;(J}Zgk_@l4h)=RV3g|aIZp@izU$xB-~CrE
z(;_{a*$$;DQfn>|dVV48d+A;-tra|Hh*!tsjmUT14-yt7F%aVo&E6l}mN8vWM!IX5
z>ypZ?$a~*baH!q<5fOXo1xH=gKIMU#I9Iu{=U7M;2tvF9;zD@2YepB3BE#2iP4_=|X&V_~uG(VVpQnL>2V0%r`|3dPq>MeG$Ksl>VT4=W*@H_uSqZ**
zl#<-O-;rvhPDrAU*yaIo1@@gGcE)K|tdlrc@H;op=VtS}De8C#M=j$`zaPMu^#r7n
zFqB^M6fG6=d_c=R?#=tK{io?@?H#;16<%R-dIUS$AVGr*!(w5uFu4pVce6t<>A2xd
zse9L{?=j`Foa($EdFY=V4Oib72Ny?7qMcCbg_{k=Ynm&>UwW0wm`GeG(D;qde<;@w
z`MwWna9=50)r=j9ezw?W3X~yJdE&N~?Yh2|p&u-zp$-w7sEX5lrxZ1soE3){D>w!H
zhU{OR&h5OMk&uI;W`AD7p|fI9>D_YkYvf9edR3b&b=8yCg>n;dH)$G`OPzCyd9dPk
zpFC}?;Nb!E-KM1?PLSZn1gY6z^MyH&!|V_ZTLg8oZ%?
zS4+jyMorp^YGZRkt1^9(yPY)KllbKWIt4ll{IE>j2<-v;UR7z3D`19Pp3}V
zuA+}#zm_8{V!Ufk`AELG2ethmE?Hpjj!E=a^4KxgEtgjBg9l#5U
z={`Q2l;wR#upQ<2W1$z_nqZH<_d+0A*7qaJ{NxS1rf7ATp4jSOV>}tgHG?u*8Fqh^
z_jS+4^H~SRnyTjVO^QnPW;tI}jp8I4D{?GJ0tnCc0~0wkZ8r*^3K`WI=20|l>shD^
zj|?2(f81u&m^lnT5jtNMRu6j
z=0QYOcvIs6X3BgLD_YPVKi0Q1vYcRVwWV^fYK=n2jyyo)X{#h&@)5C>VAV6@$)J>$
z=2EAdf@1AQ#oS?2@6_!t>tA8E-u+YUlg$VrycWG^EI
z6sNHr3;ctC2D4@3Q#WIIx4rhI#EKC&UBDq*GR58;qE}r)0}?i@Q0@10+n1D7pCkpzE7X~-hXYcyHr<`KN|afR>;NhIiG<$)5(wS@uxP+
zig?Z9^|KokPLU)_CfFj));^8re9FIYS~_MX(9=a4MOSUC(h3=xhJXuqNkD7+L
z<`E?eTb5y>>Ef*{8=+BKxMWsRHP=e}9nvS(Jg@B$8KoB+sPX-1r224Utwy|Y`rz(y
ziORn2=g6KiNB7ZEG#=sc*7}jaQU)D`4+~bwk-2Sg$JGygtg#nRz$Em$EB)xHGIG
z^$n+7#%+x6?i<;Of!?J!VzNZ%I$VajsGK{c#EA9={j16_4Y?~ew{B>*W*^QUAW{`ys!xpbB00FrbSa`gB_f^S#liMe@RIWoe!LCrs+KwA<{D3@Yo`v}V8UpJVj3
zU;Z#Mfc+9r(gcq&N!TC@3o9l>oK$19lr590P94K@$v~oogdHn+UCB49vjW3D=8X3(
zO72Jx@3Onk+=_-v5qj5hy+fcd#Fbi}-MG7|IAokR<3j5_hrXH%E~+1gwtiJ=*Sy8}u{y#uS&m4yTO2OMIh9>GsHt
zEtNXB$PP>E%XQXmj!i_XUQ@V{j-mYRWH{1VP`A;yxGs$)r;U-VL8&30$%WKnqtzGv
zU
zyI#|J&OG{pAN48gnS+&xh1RSH??-AQ3An_1DtE>W6LwcFZ8w`d?yFTP6GE@N&Yd^e
zeXxNSEYaWm;?VOsWwic|y0~5)!nPi+B=l2G_+y>9Yepjr6Nf{{zUa@7IIOiDp5>yH
z%Y*tY%yA=C*6if54h5sVjZWv3BI(B%x3Gi~$|;mwX7=uu6BhRoeZ1h&Fhx%=>0Bq&
z`nvfGh0oR{&S}d*Ie26-;x2ANPOslHN7J(^Rdils(bJT?b87
z8im9t0w7+55pMAEZAw6%FACiN3et^-Tvair+nph2MS}Jwf&A;Wr+$Qi2;(IpKO`wvyrlfk^%pirY?V
zAH*YXA|8`!-sCohUPNM7bAll;_CuyioRBV-B2_=Oq?ykZ&bc
z&CcZ+)@;!Q9>U>`&0?wiGZwJa<_!ixd5+_~j{8!zfP`?zl^5Id`I&|LOIdtH-5GQ1
z=KE89;yQf3etriisd$|dE_P%r8p`u?zCThP=74ziVDUok3+0)GJjz~w9K+ms{dK=g
zx7~VcI_I2o@_kDWv}WK}(;o_Gs|mE1k$?mwAc5{nfCrvN&xkNY=7&U9|22lyDXaO*
z%N7sHJh<^t%R?`l?P5qMMjXiTa?GmYu%WYZuC{WI7aAV!co2-0e!S4~;+e}-M3;IY
z0>cgusP#sDUVf+7jGm3H#6vSLe)TK!DH{occzviEK8pw1+6!BwX9=rj5gS%=$#BMyOe(8ptCvXjC8$E$6tAE#Smu6E
zg_j}R7b~GLIQL4dcIE}4S(`;1`D)cCF9P)j=rFG2`Zd-8V;0vdhRG^y5`B-2qbq|(
z3H?L9h!b3c{T`|iPOO3_T+pmA3>9M%FEbebc-Y5a!wdd8tmY?#NB)i%l;Ty!`Q;^%
zYvAp`n*%SPFoF$b4DXK2rx>z$dBONRKfl;voMVhoj#z2UOKe`PkI?{w4Cj|U1VRX6
zqwJapRKlxK#!0!21`X^N;!a778?F-`5EvhbL!T4IEyiZOw*l=1hst0-tZDC+*dvG4
zwD}s9xLS!d97lPnBZ2~B2Sa8NI_P_`S$u9!Ui@HJ?XQrplULsIa)`H3Nu100AI=MH
zO~m3Qix+?T124D5h>oZ_JsYD)RVa~4^gw*743e|MsD+W8@^M~eki>Y%%Xbx?jdLqe
z1h1(|IOAs-zi{H^dF)2@EA!`Op(gKfk=qzCvp$gz#w6N>wyuojjP1c8a_|#oVYsUa
z2^b+T#)dF~0WFMEOT5G=Uoo0i9y)o2ZHST>A}db_UR?Q(zFl6LD{qi&Y?z0q$f#yw
zGE8ByVSb@6b)~Nqv6D82BlX8{gCT)cq|7_C4NUlqF;%Thtwc%c3l|dotAvq(X))Gd
z$mE*!E9SX2eYl94T$`8c@cPMPfzHENsP+e`#83>OjD^_Gq9)E@G-K>@U5peMub2ZE
zKa{6;UV$5(Jg0g=jyYy(zRsu-c{Od#I8r=F_`5#DAVzqMGzbgzd&e;M)0POLB}@%<
z9l|R5Jm0-IadNPkUR%XZnM&9#45<{Wv?oRun9wIM8Wo`fL(uq~57(|Y_QSo1;-y~j
zj1X*@Wf7F9cibzorxRnAd99?lSLE*qM-<_l@=xa2VoV`?PlPAL*co#Zq5679}Z
z@Wo>wSD+XJ%U&=c5N6{c0!3lUIGURK3u7ycNi}aS#!l=3lE)MGk|OMLuZ^&idAxFu
zugX^$8DT@;Luh7vb8n~2+`C@Ppt5%{DN-!JLSs+l`gNX81Vg9OcW954Ar%bWGO#Vr^5sMg4
z{EpBVq1WguTTf7{qXM!wl)cQ><7D6nkEsh(U
zg^D?O97Ra9aFkgRHkI;*xWMyTohM~HgJuC@UcT=YK_=!sJS%GVW}Z*E4&@4gDL;$E
zxUPL)c>q;HQRP9&Jr|Fj>RBI_)q=6ga}CiVJYZl^^Q?*e*lOVkDSIrs%FmT0+*x%S
zs`xXz~IT0yfW$+>3ao;JW;cIK*+JN#8{~xq^mmw151x%;0SgNvc_zx`<2i!4k}$R5!HXAH)HUBnd3M8#Fcz{^i!ph&
zE%P&VWQ@=*l)XHIFn2P>m`lpN9G+Zj{amMbhRO`ferBtl9)@Gg9({hd(SP;Y|OjV
zGennKSVNdmF8WXvzOPz{$Z@m@eT?Hv87F4X+G=q`<#9~qc)r;nhpen$oQSv{-iY}b
zuy|Avc9QqqSin{DuBHwwZefhUuNq4{mlO6ki_3ZZu;7A3nYo{`-~`T;jk3apdr4tV
zyRc6j^EuB-+*=uoPiN2E5VZ1V+-q1k84E`;f1Vdg;y%pvH(W1=;jGPFe8;^D@sjf@
zQL%sZ43WnGKPn%_4X5k5;Z92*gIhd9*X}D^r+DS_
z{Kqpq&*L%Xayhv#@LmKzm@t=CwqIiTWB>pl07*naR1M~3=E%JNSS=(aLO$2qVBK|M
zu_ooJyhC|6<9%ar)3aRkz051_Uo4m#K0MBe1tCQ#Ep
zoX>U2UX3HNH{VaMzB>K>_rFi)op)XuJ-WI5qN&0CWt9?8KwG7F&0GQ!kbnfbJAvbm
zKR!M5(8Fnm9e0X{$m%7N4QUB0Q@za1nNtZ9<$*bjZu!M9LMlWEk8E(Ni7(+f5gU0E
ztD8-JtCtF*M#Kif`9Y7*N;ruUmH@+1{YxX@e<5~HmesAAxL}|E6?x<
zqD-~@DdL4Db1Hkfl)Cbu9fqvTgw>5~sLSd(%34;<@*>RN#RDSp$`70K^20R``0O`O
zycZClSjD?!4}xAz;p2Gu#W2kSbcAa|D9SIG3-b$fC1O``V|4MdSR$GVP@lZ%7W;Gp}rV#d6^0^Hw#x0
zrX#7f6?1CIk2YVTeP}C_+19j`~N)mf8w+>PT5(zzW%7IH3O_>X&^<$~?~t^~gNe
z5q_+MqT+lEF4QCDDOM%tRQ5`tjVq%s;$iljs;z|P7}}JV?&3YcXAyoZkE`PK2bT~;
zbKBsJv_Uo!ltdq!SPKs`I=4xz#IAX#aZQAeA{x_oFmN&7Fn?jlt_*4mqQB91=QDP)
zp@@7b2klJ4;KC;27$@sjqf&n6F#26F>@#jSmzQb`L?soEFpOVtEnZ#pqbkhN!q`s%
z7R-NTY%?cgaAv+L;vDlZ!ajKsrl+(;na}AbjQt`Waei4{Tp2hjb0x+#uDkxa**I9U
z#8nsdq-rlxwV)si({+6s(ThGs
z+h9N`-qVaJ#Oersux!3%Z1T)P`}o
zPqlAUEU}7l68C`oUzy+eu6V^1b{re$BiQ6~s)ZAkF*QV(+V|vI^5?k%<0AKu5B_1^h&6SsJCFqnz0|+1(gkvwAT)$gxy*&$P|H;PrA?f4$?;rDh2s=DW=Cbh|
zK|RXngyQ7y;zb<&CC1dexEEBRKj}Bs80g&_FSN=7t%xOfx%3ZDsVv&$d6RJ;3q@j4
z3t{W3#U8v9@N8JxqKGm)f6yn30E0IiV-crILpXB+-l^poxbp0+!bb8PeVIjKl&S2m2p9SR*Wq|X?jo3TFJK-o
z{eU(tV?xFsS#At?t{C!!sJLWsw$bPcn`_cU{GM3F!c%H^O>Fz?25NG==CNwi(P7yom4Q77lqmfZUo5C3_7LX!t<
zSu9!Krd)@1uHN66@0nMt(3l)w-c?vw!~KpvO5Z6V0;vb{75#(uE8~oN4RZlwg=g3@
zm(!*tEFkj(=W(s@;;nhy@qEMkJooCd&Yp11PC9QsRy3}+D?&mx31&V7YF8q2}~=2@Pr*+&fVxxBAY
z*V4b}N9FmFdPQUv?sv?``DeUm6QUQ7!Q4Mt#6h2>Kh!7MpMJ)B4fSX2Oq+p+-<Ddn#`w^hcUpMOA62ZTxOmnnEOkl
z0-n|Ij%EyzXHy+(p{mIf9^5M`A)S3-vrV8b>}80DFo`|ahNNqMc1^nJrkm0kXMQ7X
zywQfu>hB+`hkydw>f!8FBp?9^NT8b%*mc)k(=*RJleXXf4e=tx%QZ%@@@}g21AeQW*6QAef`HlzEvN8_C6%W`LILIdqJ*>(N
z!$cM%^2*v7GpkK+ixIZOj^~m*gf4kduR_aUEGywCO0!W`URdi5T%1pyoX@q&YUGF!
zPpTQL_`HOI3^6U&fro8Y%TAv`2!jD(Wafp4dc`W-nvuO+ql8r{_2%V~G8N86=pyg1
zA?5!o536CI%pb+3%dmzQHeSkzp$p?vZFMbWsKkfr#REeb8*sBaH`=IXtm53tRJ9~j
zW)(-jVpQ_sxYB0DSXe~X5_W}rn!;`7-^&YkP-7pE0Jr)I1Gwm45~a=ih;Pab9^2~(P|}li~$Ua
zxm;X>pBTJnWzi#EYHNM2>=9P_1g#X;tr-ESTjlvyt+Fm5FRNAOx&8A`1m*e{&1z49
zd|f`Tb>7(jtJc=+Gg28ttGyP;hrb8qm$SKq4+>*qwOX~T`YwjWG8QV)x$+z+OiLX~
znd(=rmVHDRYenQP##qWw!s*2-<=|hH7egB;=s&}BHRU2Qi(RV(f
z7)&bbGVDt~T<{MrAw;YJDjBL0!LNJeU4NnaYbh$5(Sq)q0%63uN&qENxJj7D2t_&1q#$
zDE+7OXU<_fhcP{WS&3;?UnpK9rEivSOXOF^H2GEYD~y`&hg8ka%|n%9Oy*^rW6M4q
z^?fMjyL(UQ7Uo(1*ua~bCm>b2G{n>aBavXZ}+N8}UZsWKo3
zuiCwVKkDbSYJ6AWj$lWBtjd8wuGiX_W^AVXW8_Wy#zVb3$!yECP3#w;8M4b7Jh&M2Q&lweiM$S&gI06SJB}D-k8?
zUKo`o)Ip#=6zG{g|X^8pF_B%!kTvM-yd(PDkL#=tL~+h0kiB2
zR*mhdO+q-%*I;1@pI4sIOYQ?0run-pDk<&BIfUH8@KgE)V*~>m-V0?uulhuu1M=|*
z=#-cKL-{dyAr`ZEBv#im2hkoFx9|YYC*p}h$Hg#AUaatDUV|lKWLd4xA_nTiLI~!!
zQllKZGEYuY))|CL74ge8lH`M#7L)!idFQR^eq*d4Cda55pe@YYcoC
z<>(t2^rqxFa%dK7IG4}k`R4!GySJeK&g(AhKiiTm$&w;j*2#A4q%&M~rWsmVdeMvC
z(4h$qghCh?+Bh9(7}8#pyKvEIX9^h#bbuTn!3W};9S(64AjFu`b~-@Yncj3dy@4c_
zkFtD_EK8Q)k(EPSX{=e_<-M_t`XFcm#&pLir{!istgESIEI4r$m
zddwa2kRy(B4%AuKADupx3QOnUBM-f0_;+cf!eXHZ1)uST#3qhy5_rH?8q8H%1-u~!~ET4Hz5?GQX
z5~!|B`dEVGoXZ=GebbD!u$^S5pby7)o!9~}?&W-!m@v-aBQS5Qg}?Ri0P&(q838#9-!=tomdSPt295)~L;yzxj9R?g0T>nBj^nT1<+j&lyP
z607)UleW53|dCUccWwFd;!$TF8T)o)IZpmPw}vlUYj@
zaI^Q4*o4Pj*55vmprFF6a>P;iOnAq0y3T?XlChdVS3MFx_#3^zx-}??MpNaNF_3eu
zs`t6`13!^L)*Y|XH_Xo-u*W@a{eIXesW5ZihDR(4tRG+iYAm`9SylVoqN1|y1t_{cw
zD58T3#a`B#ykI{y(R7pNHkiJ;*V+ve91WkNBlLp%AwlH4k+dt}`E}td-~9)F|HGFL
zfB07}KlY%6
z%b)(!e|nTV5Kx@=g>LBF8T&H!DE!mJ8T?w5Aqo?PZnBrgfY~fS%2sj28RZLCP(wWi
zXACLI7e%xUVcuw
zhRze1I|31uM`Pwx*sQ$tii)DiNTayQcpL}A(M-32LWTl`kx^3TI-`VQIh8gCEZ7?4
zjIxEaG}m=LZ1t39aQHAbIUzYZ{k}?jAQGK7YKP3jI**Yxjo!DXl=m36f?2Kt0
zp`7jl#|wXVYk;gXh%<|U)%CU(*48=?{K>#N>0cA0NOe%=%`r4tsj5ki^}v%H;UG6S
zkwFm&I^b_cvtA1A6URaF$r!ozLX9M!aaujY!CB~(W3bbU$DYgYt>Tf%ZfuP5>G>ca
zk$cdL!(+0(lXa--51-VL;(Pi7$zXjjFnQf0>m&kAv=yRVyM^^iwyO-`1Lkd<1aXv3
zPoo4EnvD#WZD6h@$YmVKdOm_6rqU`6pK)AcltMSk{6yoaf<5E|r^_17w=gE2!;$O$
zhm&c23#$gI?u+A0>^hsPJwPr_5GpGPcFCT${}{d2)Ve*83~UB>1BG9HHuC1)^!SP6
zgd=9;PG}+UfDiGnwexJeA~=I?;DGADwHH9fJdp8QCzt1s;T?H_zi#{MedFj8v^_M?
zVk!*B;rHf|vF5Yuy?s^Cg{*I%E>4PXxsTz$ncW+FUcP9RDqa#4c`TW{ljSU!85)7Y
zB!ik6?=#Ni4djD^N+lYHdFa5IV9%JJ-iqjLoSbXxU`g^tc!10?UUCt=tXnH@RzBQ0
z@32QWE;xr8R4^{Imrd-PY9n;}&Kx+uRY4Sbkke$H_nejslh=y8yW*VTaNx{DtL-ne
z8OF0e1z9paXNLDSE4NOWI`Hk$r;_n48D#rqtaHz^Ig|xS&hWPShH=K&SIy3LKj*5z
z5=hAIDKgEO##v>(@s{s{F`9FI0xTOWb2*>!i2eR#psJkPh3xcqYryeBmT>m;5@_KV
zH_kdyrzaIzDd3-H_VdE8ZW-0`4-Zy`aPx5xNwo@UN7fixPVx43>od8b=K#>PqQN21!`;i)}
zci6jM&c17QxA&k?gJccrv=WkB0DsoYKA#Gh?1R$CIMIZI)0)(gR){43PJm?`->)0F
z!{LhNcmfNN`3uP{_!V8tz6edNd#kf2$irEm`{-3o1QJ*?H#~oa^nM~xUjhv;foIKr
zGB@-p4A;t-ynkrAeFM?15N4C4lzTWD8}LR?b5&Vt-8t{i&eB!}d(T#=t_~<@@ztD-
z##9;U`KpUK@0}xs8WUKdd#E_@?yaB-bh+xZ{4R**{QO$ZD^(x@itBs@-F5dsveJp{
zr&j9>Ebg&J))fEJ5$HLBcQ=VW`5m;Xir||nl+cY#BxlfL`gBFUqLVeTkI%uX|LVW~
zZ!Z7azxd~u2OfOO<^At_|LBi&^RDSsg&dVR=+lZ_YoWpy&kuz0k+>j8gi+6xF6Q9O
z{ap5yz(fPLWJUYZ_B?H1;>`(e+Mo6sS_`U8^)Bc#`gZtu?Se1g$gDyE;?=Vpv)2SA
z&N`qmaqJ7uJ7X#%M;0A{E3`8%YwygiZq*83YtkNy>l=yJwQM
zLJ#{)a!sqldp@F;9PHl>9Hr7Z`iu1;+t@T;%pPz);DJ7ejzVW{U%Ro(GP#wdWP5Z8
zdR^$`ck5BTp3E^1nu7Zp{G0%J~#f^C!v?;PV!3Lw>@$NlQ&^zg0pk(Y?W5gBiIV~*?t;ZflZnVZA%d){X)^br;KyMb8Ge7+QY
z*#0oCm943$i%u#?*)_rs_L3kzeW&(3zHvs5zksbC!DcS#JP?&&*z>G~wADX!(Mu#*
zkfFZcp50_J-5Y;7!`bN0vD$MIQ|=DE`AJ;p_QfxzWx)ZTTz?3D{K=eCANfbWe);hq
z|MAP?*8tj$-uG($a1Efnn$*6&>q>zu1+EmhQs6a5fj{vl{zR(Je%Iws{i#1SN-L+~
z1jM4;)lou`qBJrT8K`T-aDv=-WG8{(IiQ20VV!%;2)r~{a5ns9MNudVSJt2rV}ld(
z9Dq6-p~m2M#_cMao9TUDj59`h9Y~yLoI(p%z$v&;dJKXwKH!$0ApI!(Uy!jKr%Meb
z<1=3d8V6l7&V9~U@ot}WzKp8EZ`8p_(Pp6feS#mMF-HgktHzJ<4|aH-g+}PzV3|q_
zO0T&t43=!Pg#|g|^P`tQqaYMx$+eI%>l|hmWm+@%%zyeg4QyH9yy5^v
zJIcLxkMlm?lTmJLHDt}fdYC%}zYg;;Dne&Yw+7E{vC_Bq0q4zD&MbC&8$?gz?*sM(
z|8RJGE_?}I?5laVEX8qZ<>!H0JRl^{o-@jLDe!n~ofblJU
z5f7QytUtJz(4A4$AizSg=s^JsKh8`|%ri&5q@Mo@IFP{boGOSIuWLAGA0FAL1F0Ct
z-59zDY|{2A6Zm}7IQ5JNU$y7jIH|*@f;7_;*7`9SnkF7WlcHaDxW#V1jRVGRJ$~ASJ{Xbb&}!7_I?BP!ht$f
zh^lGACv{wG70LFEs*EHXWVPbgGqjQ{cmX!q#?*g#o6bU5nC@Xy3?KiF4MkzfHg;7ancT8~nSO`Ld1^M9e-xD|9P#mP!?YgZ9p_r|hH2HqP_#
z&-Bc|3;CP_hhww*voa5?n@06@(oDrncv5B32AVdL8|?%=$-ly5*>}eHHUDOA&OS2F
zSXHICrti)nKdr5M$~&7mZr?7Q$oVHTFMZL#s@1{75y$C--_hvY@6ma7UxI>l1aaIs
zTPu_8Kj%EhI)0jfCg|pjY5HMio@jy(tuv=O+5Tt(AUz*GO}=G(k{QnQQvsto>I}DQ
zPJ%IHsFw8sU~?~6l@6S93oS3alDVfe2}OZW#L4h(GiVs3)a
z_5<2Vhh(b@f(Ab7hNk)4UKVJ4>;y^Y{tYw-5g<@Zb>dGlQO-$@$X0yNr6ypXd+kkp
zXD#h%a&c+;^w8J$IU$?aQ`y5A*WeLH?>Ig)?vYnF2@cOrrHJ$T3~g+Wfk;Jrw8kga
zw>*Of3q?G=38xR{t|OXHC3v3U%-HIyg-~qeNzXFpvzqF+A8=Qdk=CCqJ)arVcXa{-
zw@`cTSLtKVqFsa8h3Q*(R)MnZ1?Bz28$Miv%2PS6Uk*z14meSh=v(U1P)
zWb!|D`S1VAkIdY*+F%`-E`if845XnxO&b{BL>vzOt(Gyi?%N3xWh~WUNB@t!w(qT{
zvub+IW*qiB8E{kCEJ-f!ZbkGYr)2zSdp_Ts+4L3r=@z>38|nA>h07h`F>+!mLGxX_
zwUw>uuXxCWR`}WTg1em7WWF^ftEbW<^if$pNoPj_T&u(E5A^4l#uL+bNuQtSRnDrUiRa_%&BIL%xK!{oTHKg
z&`!V-O}EN4RZkha;GKIx`U~XaN8cYPJ~~AP+@6-`R&)e=OYf=%Ud^4{y*E7Hy^lWl
z$$qq-tMk}TP2NKTXK?$c+GF@`l4_3R8;*TroQl(tu?yul@^6w5K+NeScAIQM2lEc&
zQn|P?VguPt(m!X$r;jvKwaeGom*xo*A5u(Ms~Tp+8+&{humcWuh(F-Nl5CX+1kiSf5bo3$w_
z0@>-jaW;~d=z-tJBlK~`(qrr`V;hu$%#q!O&dzLm3+-BsgzxNSiER4G-n49R-iJr&
z?d*=qBWKsZXCLRi@1sxqfi;0ek`P_Jfmbqc;i8i;6*`+Ix{WR;0BgPTEa#y4T06bz
zj8j4|Xl}9uj~fH|(qMM00nvxOjeca3=TD!$oTokW_KcO(w^ifDK6d$>$@-*$*j-y;
z5?;u5mA(>)bH8!Y>Fpab$K@PDD{FjC`X!GiSuf}R@_zaihIgzP`O7q}7a}Rvv=u3g(ln|Epqc7cif?X=O^WA|6XJ6(F8)S~mqi2S5hupB1?ddx9+>soy
zP}s{GO^(4!=f0@s`D?%O;mfb&zaL8g?UA?MCJx;Qt?Li3I0dc&v{#(O*B4wVaHYVN
z0#^#WmMQR__k925-%0@OzxdC8a2)x?_vh?lXb2imIE#nRaNzV4P-;MAl*cGsl+(x4
zYvz14qmrYgN|)^2=_eBrkA0xj{50^NeWV=y1DZcKlG3vXVK{BS#
z8Ood{0}GaW8EVajWE{$9Zl)e5&SbjfK4WV|8>P~_D4UdNilZ?P>`yXVH=|CrGyM2K
z(aW$RpAt^OS5VH%&EZeD%9K!+(Z;|Mv<{GBD{mM!*?h9E$jW(@L8*uJ{7*>u-siPCWC%H|7MRzZbs!#AiNx`Sy1_eq)f~6~>zj
ze;NmepFQC{Dpc_LR&^#5I<&J-CfoHe#@eTXgVlb-`_t%K=493YAACOjEA$&MpE^u8
z!&cz!8?$e3j9*TXBO}xrw_;^_za36$&Ld7KG`6lBRf0=0TQ|#i`;x2^NoMc%b$LmU
zD)tBkLXadL^F`SM;J=tGvG(S32=bPWb#WvXyANPlSPI!=l32}hUxNKRVIaY}|5
z-f0}x9t{SL&b*Fl(vfL-pCHH>!bgvr=|$yvS(k0JuJVZE_0~woE1ysSk%Wcw-p5zw
zM)u2EF0Wcs>toHQQgGm&UKqK|>ELgSbu?~I!#bzPsfPp;Ggp^6$QIuoJ=^!C*KH;5
zy*B|-vTuV$=|LEp)&b%D1J`yqFUG-_@#86UbTRA)O6$mApWbz3)Dc7??{E43g`*$j
zI(;^2;VZMR1frd94d9Sf*7==}rz(4rN>(0$OKPP^og5q?f)VJ@>eW_Ul#L1Bj>hvH93gGl5^BLaO{&+leHe%FPP)}@w?;$G~1x>I&1}l
z(UolMo+BS7z^$r1{M*Z!&Jk+g(2W|@n_x-Sfg=<>c3v+|Wy#xfmOJxY=t;)EA(`;_
zgAV2y=HERzj?s)2e;5Pa@q1-E*-#yfqfkE{veg{ApUDIPMfWxM;r)^rI3iVHOtr~T
z`PHh8_P*Wf#0{o{S0jH*YkcBoJ^DC0w+dxpP_2pTn5XHnISc(>eRYDtDlId(R-+oz
zClY|zB&LOlbkP}{TXWeo+Wgc
zg!4w$duQnQt%H-}d#mVF
zLrlPP&XrC6vhOCqt}kZRz*%yWL>1l^AjAjT%WUCoRr29)`?gmTsOT@}1UlDxH)+DExcV0-wcVD7`Bmy+FhtXrI%@5s>^Zu?Rb)c_vvO3WO
zqw;JtKaUAddM18AL(fAuXSVaHK|1rAAY;ZPcxF9~Z>m|d_R~W%W59oNzC<3NB@S)6aq-EYlV*o$m52gxB@;k^)w
zoA9$$eAH`Be9vx$7}+%aH6up^)$Kjwx3}@e1jdiEZTSgLes0EHx#4sCM8-N-&9`$j
z2SEgOptT_H*+Ar<^{t#RPP{eAHT3h`>ueOv_t^&T68OyePoU#C2R3-~#hYrX+IhzG
zFQ542R4~uEMUTsWWbz~}NG3P}B_ksh;3!D&+OHeQCrRG>NwCx
zFzmtnO)!lt!Q=L_b8lo~c*HYrgT!A;qSv#RZ$$1}J9I*$ox`?VsEo13=!-7~hB!Rn
z@?PI7H18s&yl(aFqX+vy5Zbp{XR^uO^c^&{7kqc13G*4y?R&z%BMXo77JnC>o6iDI
zdIlSD;q$XjO#%x4PSsw{eb2q^WwwY0_VL<52mJ)&1-oMFHi6lGK)31teN^|NpY@)e
zoEhVw0!5aWw+bCxpMm5sH{SS8Mz3yy)m8~kKrv%-;nk_ER*{#vcMSF_`LVs6HzAcz
zMS_^1FJ7|`tSR1JX!(Vl|0nIs7@_84ZKY
z9=cOXAGkkoD^b+f0TV-dpcw8Pql@w1AQu=J#vvoeI4Il$W20Gn6kE_kXV{KrnVuma
zCxbG|IhdTc#o0eW#5}A0JzCM$=bjy2oN1J2&D<&U42EZtH7LtcHeemTg_mK_oO_B`
zx4})1GH$L@Q6`X@&l()8k-N}V&&}GyTQ`Tzpyuq^;0Oon*Hv%iISruTUu)bxW@}8v
z_{=&EOoi&gJkKw6T>a(~pT0c!;JwKfkJB#i+N3E5-*$
z?NsxPlS|-hA%i&bIXefEig6P@v|j{27)ArZlkd8}0hECoMW^ghV;|^_BO96100Hz_)Jb(JW7NYn7?*%3v3?
zvPONL&l#wkLGL&@ee4CsV;#GtN1b%`Jsulp5+hx2$k2wNf8)fKZJKL>vf)3UPe3p4
zn!Z=zYfxF5ZQpg*I^cGkaTc?+JOjQ}J)SX8WpQMC
zXOkg({^@z&W*XNKPd?op`GHp6Dfro{TY&|0ob3NNpzWIm8XFX=)0o`HPg8BoX`FGN
zt9uv1b`Ael2o9`RWG?5)xf2O=kO;Z
zOJsnb`wThP$Pc{b=YiJE|DFu}@Se}Czra36-UgQLIG}2`51xH@xq#Q$xfnd(Li89AgqBe@>t)yg1NX
zM>XnHB}V2?l2GX4+3gu-Y{tJmU=pCzmnQG(HPC%|7Tu;}^#{2oKETArt7pDqxMzJB%N{4V+`vimdzjpZ(zFAO6FC
zaCyr^4_*HHPyFNr?jWV3m6{(~ry^|tr!S9Mt1BlEJ)>1CB89X}L=Zx0maxmw@74HC5XRRikQu9ad9
z#@b&~=?-o;IuZV9(7$k%co1Z0&o}EYxg)hjFR8La53*0?UH2WD2}%pDttsJ5Q7Kle4!(p=0DFX8Kf15dAOi9D`F3WzM6#LegoN6^}N
zU`zsv=b%)f`^tazNnu#+!S>SHzPse5u>?9%a_EuNG>K)ocTgPYU!z}STw|AkFT8tX
zmAN~g&X>Ta#17Ef{%(&Oi-4k}%*tH)7Jk?Y*hRp=OH;{v1oG@3_Y7oq_Eqic;T`AY
z5m>A(V1Jo^kz6`1diV<&|Jca~N`_#v#8>+ExsRgr0gu=#=&ia9KifyvfL<__NdmMP
zAiY~6nCeryn5y)9Gxte2&3%YOd8`Brh()hx5qq
z_*de~v>iB-UVPVnGR7NdWOiA`)#L>9vCkzpOyX*wlL
zt>|C)YvgeFegdL-?<9B%F6Dn@hwpkm
zR0n(K2F_8!N!ID{N8d90z2p4GQJHr}3vy1G+qn&g_e_tnymMeokG(|}-x(cjt06Z)
z_w{d*Z~Ehg2GL>2rQI)^D6;wo=;K9TDBZ^uM+jcAO7&=gCG3h
z<^At}|K(f0<@UUJ$)>vg@`_U68bEtR$$NeHl>%1^Tq$s+z-yfX-}imrcllla&hNgw
z=RNP4jNN9fPtXRWNfanOOF&pGeAg{THzk!qOUdIvUL$t#%_(UhKhBwgoIziRQ7dD=
za2`{U!Z{Jcg|Sz~o>9Nhc7Y^094IxE)}gw_uXjvF*HO6v>PQA|9sZPP#yLk22NWf@
z)i)I2kEe$Rr`I`~l2O8-HPoD>Bg{|B^Mdt4vXw8YTJwVpULouJA6=!Y?yLXO*
zcs;~5l)>5vVlDh$42>gmaeDHAi&U{QMvH*OW{hsd+6FhL@(#>H=HSU_bgm%roavn+
z_{3nGY(80Rpq*45uF*n?H}8R2$usM?D^yr3lIrlQp|icZWbxfK8LEY?vu+JWGBUj9
zHdqh^9_%6L!NWnw=y#osI=@@{=}UL?eDj%i8v9rB4E%$?CYw04^v-6D+Y16Kh0SOt
zpWrN7l}>F6k8jR!rLyc*%i!CyBNGHv`;7R`RITN`!{>1XF=h*0!vGd6`0VFn#0k!1
zoDB0;K^7`=`bB~20UefoEO_v6_MmDNd{*bEeczrE9g}+*pBuGjwA23
zCqNhp%y2@qGG_WX#G%1)$hk65UIK)iPsg5W8sig2w5;_Bb;4
zfd}TC%6RQ<2DKp7IH0qhoY{5G_1xtQ5PY{s>j1}h0+1ZD4L%wt`NsJv@XGPI&{Ux3
zKu@K&VLt2L=NPu1w&%tsLu~Lo&D{m72P$gi-}C@HV3^Ds%nvxb*&}&oX@qy*G7hRu
z;PCkq3A}-NI#)FL+b6Qlf=bTSI)l*18Trn)KQgl1_nooEgx5H6$a(wwwz>(lV&H#r
zh6o7kIl2Kgf%nP0&;7GcG6(15HlBa|@Ln%8Diqgw-+oeN|KV6G?~&O8oyO&=gLv0k
zb>a5ZC^Rf5i#d)Ab&edB1u9;LzTqu<+WElgQ{BKG6}YtT&yaM2bafD&VZplhIHt%c
za=aPc*0RvXb@aK%`DYdjp{4RpkV?FkzK||PD~(2VoK!rRMF;t^r5Mso2tLvqrOMp`;2TO`|!2
z%cV*UvAa)=rH-Lfz*N8KB@y-t!O__4=c2#`5gXQ&3|
zTKU`nluBUd5&kVK8hKC%Tj!v?N~EAEo2HfYs;==keQV&w$;BhEjn7|t-s*q3P|GHS
zmka4ze#F1zD!!|X8d&H+o2vLeC;E*ZlJ&P9<}$(0jBk2;g%_r}<*;KGo^yLdIa3zV
zVyj9d6Ocy|&<2WfAkMdPjSfx+a0X02r|`)Q%wh<_9wHBF!!*dMO3FLRtMp*wu`dMH
z1_Dt~;(&~8Vhx&9H$oG0nSO(lps+-p^;eWpqqdclR*_DnLpp|mA(bwr8w4DrVL-Y;
zq)Vh5=}xJk8)k;?j-i{O;qyJ;IqUroo*(vF&))ZSM?i(bq=rh8k0EPold#Lw#%wRG
z^@;ZC_@kc=%)i|69JU&M9*d}J-aVJyk>71dWLI(NnpcNlHl?y}82L7#^ZFoE}a
zvEALlWnpBDmN`pF)CNfM5Qval(GmE=jVOiTh}HAsgs6@B{^N=*|QYf(Ck_-&It&rHEXwm+P~
z)Q3^Et7~0_E5fbjG$%yOajw&)w+%??=ci&qY;6m7pb4=P7E8>GJ^rGK1Deg3Oa2F`
zn{y+bH?mo!w0gQ*SlY-DP<$8`+r}F)0bkUWk;_tKX#nbI!65&KFxQ##bUw+s`_$Y3
zsvN5Lm`aqQwSwu{U=;G~GCP!-WFa#~J4Uf%7&4&UIsr|;DDy+L-@}vsjpzC#{hMs_
z!O33Q%N7njxD3@vNBhV4IaJs8@%{(v|A+4z55KwI@Vk#O@r57X5OMwc&p!>W4WRKh
z&R%J)lcDVt57Gj$6O-;l{hp8db)J*y
z2ORUF`oKtY8;)QL_rTm2AHFKLwW!H9Q8r~G;RMg7
z?0P->@fOa}6XT@zhx(e6@F|bXFeid_iZ7_YS_nba_~5{r$=7dDUuvAo;}CmP%-fZQ
zW9a+EmWNQ5jp&d()I6CF?X#=nzY%x|(vXC_(04#WmY1XnGR-h(0AR@LQhcLq^`}Lw
zZjkU&aS2;M2Vj>J&8D^hZ?xxC)$9@8=!VFzy)IYE^Dey}1*%lsp$r-q{Fkz5z?rxm
z&|QY0?ZWXea4jSta!t%a%)&=l{rZO!wMN&d4O&!6H`a?rxmO<_$~w}5Tvt2DTh&9z(=Z
z@*ozB+S|U;z$TieE@enl>ph=MOxYo7m-nhXa1P3jwAF{mCGfXK-ks7`U=s1GJJs47
zkgS`af3byht=f3~LAY2%fg7YFCW16PR_&RG&!W2XsD*Bo9q)%05A`9(nyicFq
z;@;cR3qEfg
zEN{Z*r5T&l{qO$aZ$Cng_cS%K-gW3g=Namz)|>4*obEN)g|&~W(CAp9T-_&d=lT_I
zLAarpTphB>$Z>!@Bkg2QvODncj`hOIv9}e2;`B$GM69M>%N$ioe@8Se!p-V(7a)&l
zmtqrmX?wDSf}bu2oQqN0F5OLc)v~k$)IHMWx?66bxMa@p#3j!Ifq|Bq2{@Txny7M$
z(_1SQe*od4F*+67fRDGa-oY^%VA;f2j#!m$e^3ODvsDAfkTV|e6!*Tsel_q)j8BK~
zV~uA##rh(rfZd%Xlnv6fJL56H=`G<{Ul`5|Zg<^w1ns{@OWm%Y+Y0zYX~ot?Ks*AY
zj1z=CQ|$@)xS>7_+=i^wJ{hIw?}+OivcDptJ$F-Ap^rRq2nwd6Uh##1-G{fde4LEk
zaU!P>VcvrwKSsw*081CnB4{eGta8~AidTj6V5xT@?FSS#VbMG9^~{1)7A7ieSlol3
zXk!@8x7vczgQjzvrv?rIBV$Lcg(sv<&wRsGXJ~)t>15pIOySsxqs}$≀4+KtT;^
z%CLgG88Z)fibXlB4%Nt#&?4I
z@KKTgai|?lKXFl%@jgYSoN5%YDBM+6MmSq$4m*UQe`CW|942r6zB1l{ehOn3=0%}~
zC$#pKkHqADAm(v)ONQ^P_0XXBZGK$b;1y-2Jh*gpZ(ZDNQH<&vJb
zHyKi;EG6L!im-IJJEsCVcj%8lRI@Q)fD}9*&*Pp!A(Fq~@PU{WcA?GBg3K&cj}tcl
zAPz6<69qz2SD--}6cEO|I-rx6pPm1ZW%ON&uOS)_-dv048mdK9MlC+%x|^1B1%C7H
zl+#5N``1_&H0%qySF-CxN}J5Fw7h3vK3+}nt!N56uSF0OmDM}BL2zi{&3dJ_0TuDQ
zh}=+uiY}k(UG;O;0E4U6)_E)H{V{^EC0ZdNu@9uR<^yqw5l*dYHEAN7=ZohDwW|rj
z)%{H|nQ7i+tdBCklY=0%L_rH2o7)&oXTqo$-pn9fDtbLpyXp0!%(z(w)``8+F5C%*
zMm00`GX^~Dc@(022O1pZW*c&W^*A7ZJAHu?@60E=MTm$M5$X{OTp2%kUhQ0js@O<8
zD<2B@KQ93ONQrgziNl!I2}LT-(0r{0Qsfz5RqNW+JHKq#o5lIdbLL;`HXFM)wglpL
znkC5)(y1ThX(vVD?|VwX>mpi{J1StlCzX-sG794MPxkj8iCz_@?HwcH%3Laug0q_`
z|L!Oc(%>&tsV((>LK`)-Q`|yLQqBVgCb=`?iP-GRO{Mc9@G2H2`BeV%#{JL9181Ak
z(TdR7%-w|G*h^ul=WX+tFs7^=oTW1G58j-{OMs5bUb(QR=Jwu51+A>?QifAN9V60b
znYLlNL@O;m6-puAK^$*zptlb|)CJL2Cyv#=yjc*5n_Kpx9PxuO-F8aSg$h|UvcC1)
zD)&A=2tZ7lr}_vudtP$fofr7Iiuae|1(kL=pvT}H-ZzcqAU@}=RX)yF8u{K9`tF_K
zwV#|9&$nSA>;9cnS6F17yG=%0vI3BtV}lem@R{?B$fx8Fg`Lv;%EhK~QU
zbOm%$b4`fuM<=v;K-&-@>$~td%3Kpl@GESTMg2#L%rddL`%h>KGG%QA;S$6hUr*Z#4vHAIy;Zjj}jJ8mlIu
z^pdlPu<9ScM>B90=T{;B17>2Kcb1yGHoR@9n&HKot`(#n4{_ABm7`2%v9
zk_rmZUS;t|E+Z08)!ktslS-B8jpC!V8E(hEAM0wy^>U50gI8SqYY)Y2
zz7+||JcWI$dzrA1Wtz@=bG29tGbEo3VMsfeV<2}J6@deESe`ip#6%#W{S=+oT<2@8
zv-AplFiTk9$%aeGV)D?BR^$ObPXV0nIW_Dr1ar_q{IuAqY(-kZUZSiE;`G`!=9v#S
zlt(oU_y+6s8%DEChxXi1b|Nv~qZTdW?IIaBF?B10=$M6N-V%nHl-0IB$bpqS6Wq`b
zyACv%900<|u=M1}Q*T+?*}MticNg=kq*m49YF9(oTRbvy=WvVr!#*%Kdvo5w_88N{`f0!E`vP{AD(Gs&7t@WrX(LtAPmFvA
zs6c)*0yP2`8ygib4&3cm)%9)WdE_mLxC=a(GrnVVpH&(U0cRU!+%Ad2YMfAX;05o@
z5>oTO5+^kw8Fefos@UV~7aK*TL6)zj-#Jhk3VhuUODex%7F*0Lbrl+hp)xXKmZPpR
z6(r|wKHrE<@i|daPKY3OMxq3$fp1VQCF0#)uqI&X$^6Bx2z&g1*xk}!eXYQ+1XXWd
z54+DJU(;ovD_V3V%;jCz-OI?vj#|8E>%KeS(CXQYxvUqaL~Pc*Ze0(Ch}X@?am09o
zIUvwX+~|{U+pAp+`}9+yJEYFUY7$h-kRMB$FR8=m4cJn}-^n>+gSvq8?Z}R3(~lg7
zuNEozJK^Z|lYsVLym2vT(T6b~jPHEz7~kg$J-|KQ4saqIe;hG*89HqDNR-gb-{Dq%
zH)_}uYNI%B<;VZdfi~ttdg6>>;A2tj-h=1O*{6+)2rqzX
zh6h~i?qDbW+7*pFCxcyJKSp@RtDGxy_0x2~OG)Helq)?RX)XqopaQrHIc$$}k-lYF;8Fs5wTa^c(Fjec)$*IY5UR;sgp*SkZe$I|Nf
z`}8`=*tJ6S^6`-BYE(h`U`x3Q+o%1av2WTjfwZA8B|l|qbf((qdcIuKmC&JT7HJav
zF1F3^M)rtY_)$VfD!~;Yd@|4`hZ!d5mPzWH;mf_MAYp0(I2SU*EJOWfxBj@b5m8;fV{mPaZmCA}Cchr#b56T(TXah;LQ2`lUGKx(QQsLp`z&Wl
zFZ55Xff_3I{ExSp-H2EGI$=hrG3T1untS8;)OG)IvPEUNmCpNtzCg{@Idl%(u=Sn9
ztxSU7)ey$0MXdEP9D>og`IUDt?+50S2s-a}92yw<{v5{pa{=y79xZTU8>|ylIX=N3
zmgw$sxdd7O)e^?tW-0^CjXjufMZ8e`y0xXiCWNDJ){+PAvQRpMRJYh<4
zX9G)9XfR|o0zQ>(J@=ESi9Jg6k$rmW*6Ib9;B#8jXIagBz#h6tzqca1_z7p>)plM{
zp}QUVv;stt=^dZZc9u4NZ~*D#+V}dsw#9^=FYng+w#Gm;0m!NHqjaZvB^bL_kV_Wx
z?x9G-U`^(N?~-^DeU&^d(a1`~;@NrowNlbQS&E2xZG-HeM=xz+b?Ra5pJea4>*KTV
zU&%fa&2Ka2K5p3WI-~vgEc5s-2K+VMI$MtQ8r>|G_UkWS%g4jJuD5MfTvcOa%LQ{q
zzocN_hK|SwZ!+G!2YlMf=<0PO-9d9;^>WMlcQp*BvdrRZcwk2(PPk(me>lUaORRO%qQDe+55GQEZU*QipZUOgB$-P5Tc)+@w?{HQW2L_rJ!`LIzUHJ
z)^xl3`7YlwaK1I>bhzl#Sg%62S%;&A)y5LAoZMl0?)b26&0~i*`G&&p+upf!{&E;g
zbmaM0ptFDkEm%2-30es!2-^C{^WAAZtF#Qt@M54S3I;r;yth$Kve{y{z@B2jS08
zDX5i^WA_sC)YZdJuUwY#P-4h;{m;=|C@twamv`Gpfs|B1-e{F?3HAKj`<*hgE*aMa
zq$+tE_`r@SD6F8@?dd^cA&eKcHXT(NUNPCyQ9XhMTBKzRX)=?dr)D`4heV(-fL4nz
z*7DbEC1RIR01E@vq-tOP+N3`|UI#rhl_OfC4D
zVmg#dX9|FVVi%VKF~z@A%eGyq1^yZWXdr9qW?^{Y3!%i5ky5Y$)D;#bfDgNmRrc|H
zaLM)x-qMzIOED};*~24o9<24wLYX=^;S(;32hza*T}`n_cSl$%6Bv#dYfcLSh0cuh
z&JQeBjM)E{02`GCg4LTYsyZg9xj{-|S@+)LlIYq$ead+4s3?LSk^=elK2bV(v6S71tQ715zJ{v~j8|b#o;;&J-1{l@+{naO<
zWg#?D#yrP8L)6vR)=~<#h($&;yD)eno?fBh@!B{enCuQ@W9Ji@!2j_d73{WzU5|MJ
z$eP_wLQJv2mre7Xz%gu1*%iq=8gr7#w8)1tqz*B_#!oOBpf|Dutl_xAp|>6H()pT|
zP?)jNVO;Qfd_DtRg}NvGJIZ|}N*0|!^m+CLl=8Z*TvwKbFNObmNy>(N{p038kNT99
zY%NI^9+=6ohVWzwNf7PK4;T%sfpGVnRiq
zI#br@n=ucQQ%%w0F$ar$ubs~*=mU=W?i5R=b8eRscp0gSHYfqg$_R2%iON_Dem
zGIQr6JQyEb&JyQje-XXMa!h7*qU@LkTJTndiim2F+$_!xb}Eby`c1SD99Z@#_DPV7
z1b?TUepK3-!JU!<0bV~#?FeBxP1vWlUHyX_;)|p$(Y|)l@#Lyq~Ha6!q
zM=TF9fJCHJ9=IhiuVFW07r&E$fm~Pb7;!I*db|`ncn$jO$8vo8c4euJdc2IBv?3-}
zG{q_@**le7T2KxseiVnh)ORexmfbiSH+|0*t&kv68DWG8X^6N|*3$DWbW^Z4H9;`x
zcTNSjCQ=xD+VDVPh$G8;1fQ>0)LzmvU{am9y5=x*dgvs`oUn?gx&B&Bu4kCQBn$sR
zKZr9rSlg&Hl$zaQ(NhT*QP@tvh8bmfxCJdw56o@)s6RhB7cu*zP@wXXbA(-Qf4S>p
zeE*Kjc1}h|3uJ1!Lnnyd38N{>E`b9@%{IyLpvq@;Npg;&1LbOISm58FE*+{|?cbh7
zO{8?wsavVS`wFKb7BrL`qn8f;CDj4o3P^C7!xr&l55x3A^9>a+7dd-VBFeO2>5zP^
z8LXrl2K`>KUeGVlVYejosq4CtvSvRr^BSC^rK1zuW42QAtP~`#>QT5V<`?{d?tW@$
zJV06NRl%jC#j8{SHUdx|O!SC5oiUUCYDcd8g$uXQ7kCTCBVS`zw|$mv+Y?lXFG~E!
zBdl6wc^v(0c}+t=ug#G-tsIlm6J9=|7_+~Kt%AMRqwQ<2=J6Pv%chyR0U3M~vXJ2u
zUC5L~{rq-p%DnW6?&PJ?;!G6AV
zbm&yYbA+YOb^PT({oqJKYEYe%?N@pKNBd5(eKO%``UH^_zU_%kgtw
zP1c+c!y>6axqXH(l^)CV&x~Wgz6|pss>Ou~dja%cUL=a4|zuA31bMA9qY1&ThM&9M~a(x_JO099V#`JY(P9hsb~Nd6wvoM$5Y^Bn#66wFrg
zHF^L
zrAcgq2MCf_uho}VL=gkAfSGT^eiw_YN8Bu3FKYblDOZo=?-!4Rn59*7e^4{qD2qEC
zJd#ZS#xYX?SN`S52oKCD?r!T|pk3_I%E4vV5>J
z2G?EvaXtqKFJbM3#5+|!3kK3