├── .env.example ├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── api ├── api_test.go ├── blocks.go └── transactions.go ├── blockcheck ├── blockcheck.go ├── errorsummary.go ├── failedtx.go ├── minererrors.go └── statsservice.go ├── cmd ├── block-watch │ ├── README.md │ ├── discord.go │ └── main.go ├── blocksim │ ├── README.md │ └── main.go ├── examples │ └── blocks-api │ │ └── main.go ├── experiments │ ├── unclecheck2 │ │ └── main.go │ └── unclecounts │ │ └── main.go └── history-check │ ├── README.md │ └── main.go ├── common ├── bundle.go ├── minerearnings.go ├── utils.go └── utils_test.go ├── flashbotsutils └── flashbotsutils.go ├── go.mod └── go.sum /.env.example: -------------------------------------------------------------------------------- 1 | export ETH_NODE="/server/geth.ipc" 2 | export MEVGETH_NODE="" 3 | export DISCORD_WEBHOOK="" 4 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.16.x] 8 | os: [ubuntu-latest, macos-latest, windows-latest] 9 | runs-on: ${{ matrix.os }} 10 | 11 | steps: 12 | - name: Install Go 13 | uses: actions/setup-go@v2 14 | with: 15 | go-version: ${{ matrix.go-version }} 16 | 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | 20 | - name: Lint 21 | run: gofmt -d ./ 22 | 23 | - name: Test 24 | run: go test ./... 25 | 26 | - name: Build 27 | run: go build -o flashbots-failed-tx cmd/flashbots-failed-tx/main.go 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env* 2 | /cmd/test 3 | /tmp 4 | /out.txt 5 | /out*.txt 6 | /*.txt 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Chris Hager 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Utilities for [Flashbots](https://github.com/flashbots/pm) 2 | 3 | * Go API client for the [mev-blocks API](https://blocks.flashbots.net/) for information about Flashbots blocks and transactions 4 | * Detect bundle errors: (a) out of order, (b) lower gas fee than lowest non-fb tx 5 | * Detect failed Flashbots and other 0-gas transactions (can run over history or in 'watch' mode, webserver that serves recent detections) 6 | * Various related utilities 7 | 8 | Uses: 9 | 10 | * https://github.com/ethereum/go-ethereum 11 | * https://github.com/metachris/go-ethutils 12 | 13 | Related: 14 | 15 | * https://github.com/metachris/flashbots-rpc (callBundle, etc) 16 | 17 | 18 | Good starting points: 19 | 20 | * `cmd/api-test/main.go` 21 | * `cmd/block-watch/main.go` 22 | 23 | Reach out: [twitter.com/metachris](https://twitter.com/metachris) 24 | 25 | --- 26 | 27 | ## Flashbots Blocks & Transactions API 28 | 29 | https://blocks.flashbots.net/ 30 | 31 | Installation: 32 | 33 | ```bash 34 | go get github.com/metachris/flashbots/api 35 | ``` 36 | 37 | Usage: 38 | 39 | ```go 40 | // Blocks API: default 41 | block, err := api.GetBlocks(nil) 42 | 43 | // Blocks API: options 44 | opts := api.GetBlocksOptions{BlockNumber: 12527162} 45 | block, err := api.GetBlocks(&opts) 46 | 47 | // Transactions API: default 48 | txs, err := GetTransactions(nil) 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /api/api_test.go: -------------------------------------------------------------------------------- 1 | // Client for [Flashbots mev-blocks API](https://blocks.flashbots.net/) 2 | package api_test 3 | 4 | import ( 5 | "testing" 6 | 7 | "github.com/metachris/flashbots/api" 8 | ) 9 | 10 | func TestBlocksApi(t *testing.T) { 11 | opts := api.GetBlocksOptions{} 12 | if opts.ToUriQuery() != "" { 13 | t.Error("Should be empty, is", opts.ToUriQuery()) 14 | } 15 | 16 | opts = api.GetBlocksOptions{BlockNumber: 123} 17 | if opts.ToUriQuery() != "?block_number=123" { 18 | t.Error("Wrong ToUriQuery:", opts, opts.ToUriQuery()) 19 | } 20 | 21 | opts = api.GetBlocksOptions{BlockNumber: 123, Miner: "xxx"} 22 | if opts.ToUriQuery() != "?block_number=123&miner=xxx" { 23 | t.Error("Wrong ToUriQuery:", opts, opts.ToUriQuery()) 24 | } 25 | 26 | opts = api.GetBlocksOptions{BlockNumber: 12527162} 27 | block, err := api.GetBlocks(&opts) 28 | if err != nil { 29 | t.Error(err) 30 | } 31 | 32 | if len(block.Blocks) != 1 { 33 | t.Error("Wrong amount of blocks:", len(block.Blocks)) 34 | } 35 | 36 | if len(block.GetTxMap()) != 18 { 37 | t.Error("Wrong amount of tx:", len(block.GetTxMap())) 38 | } 39 | 40 | tx1 := "0x50aa84a35a999f7dbfed2d72c44712742edbfa12dfdeb33904e3fe7244791eed" 41 | if !block.HasTx(tx1) { 42 | t.Error("Should be a failed Flashbots tx", tx1) 43 | } 44 | } 45 | 46 | func TestTransactionsApi(t *testing.T) { 47 | opts := api.GetTransactionsOptions{} 48 | if opts.ToUriQuery() != "" { 49 | t.Error("Should be empty, is", opts.ToUriQuery()) 50 | } 51 | 52 | // opts = GetTransactionsOptions{} 53 | txs, err := api.GetTransactions(nil) 54 | if err != nil { 55 | t.Error(err) 56 | } 57 | 58 | if len(txs.Transactions) != 100 { 59 | t.Error("Wrong amount of tx:", len(txs.Transactions)) 60 | } 61 | 62 | txs, err = api.GetTransactions(&api.GetTransactionsOptions{Limit: 5}) 63 | if err != nil { 64 | t.Error(err) 65 | } 66 | 67 | if len(txs.Transactions) != 5 { 68 | t.Error("Wrong amount of tx:", len(txs.Transactions), "wanted:", 5) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /api/blocks.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type FlashbotsBlock struct { 11 | BlockNumber int64 `json:"block_number"` 12 | Miner string `json:"miner"` 13 | MinerReward string `json:"miner_reward"` 14 | CoinbaseTransfers string `json:"coinbase_transfers"` 15 | 16 | GasUsed int64 `json:"gas_used"` 17 | GasPrice string `json:"gas_price"` 18 | Transactions []FlashbotsTransaction `json:"transactions"` 19 | } 20 | 21 | // HasTx returns true if the transaction hash is included in any of the blocks of the API response 22 | func (b FlashbotsBlock) HasTx(hash string) bool { 23 | for _, tx := range b.Transactions { 24 | if tx.Hash == hash { 25 | return true 26 | } 27 | } 28 | return false 29 | } 30 | 31 | type GetBlocksOptions struct { 32 | BlockNumber int64 33 | Miner string 34 | From string 35 | Before int64 36 | Limit int64 37 | } 38 | 39 | func (b GetBlocksOptions) ToUriQuery() string { 40 | args := []string{} 41 | if b.BlockNumber > 0 { 42 | args = append(args, fmt.Sprintf("block_number=%d", b.BlockNumber)) 43 | } 44 | if b.Miner != "" { 45 | args = append(args, fmt.Sprintf("miner=%s", b.Miner)) 46 | } 47 | if b.From != "" { 48 | args = append(args, fmt.Sprintf("from=%s", b.From)) 49 | } 50 | if b.Before > 0 { 51 | args = append(args, fmt.Sprintf("before=%d", b.Before)) 52 | } 53 | if b.Limit > 0 { 54 | args = append(args, fmt.Sprintf("limit=%d", b.Limit)) 55 | } 56 | 57 | s := strings.Join(args, "&") 58 | if len(s) > 0 { 59 | s = "?" + s 60 | } 61 | 62 | return s 63 | } 64 | 65 | type GetBlocksResponse struct { 66 | LatestBlockNumber int64 `json:"latest_block_number"` 67 | Blocks []FlashbotsBlock `json:"blocks"` 68 | } 69 | 70 | // GetTxMap returns a map of all transactions, indexed by hash 71 | func (r *GetBlocksResponse) GetTxMap() map[string]FlashbotsTransaction { 72 | res := make(map[string]FlashbotsTransaction) 73 | for _, b := range r.Blocks { 74 | for _, t := range b.Transactions { 75 | res[t.Hash] = t 76 | } 77 | } 78 | return res 79 | } 80 | 81 | // HasTx returns true if the transaction hash is included in any of the blocks of the API response 82 | func (r *GetBlocksResponse) HasTx(hash string) bool { 83 | txMap := r.GetTxMap() 84 | _, exists := txMap[hash] 85 | return exists 86 | } 87 | 88 | // GetBlocks returns the 100 most recent flashbots blocks. This also contains a list of transactions that were 89 | // part of the flashbots bundle. 90 | // https://blocks.flashbots.net/v1/blocks 91 | func GetBlocks(options *GetBlocksOptions) (response GetBlocksResponse, err error) { 92 | url := "https://blocks.flashbots.net/v1/blocks" 93 | if options != nil { 94 | url = url + options.ToUriQuery() 95 | } 96 | 97 | resp, err := http.Get(url) 98 | if err != nil { 99 | err := fmt.Errorf("mev-blocks api request error: %s - %w", url, err) 100 | return response, err 101 | } 102 | 103 | if resp.StatusCode >= 400 { 104 | err := fmt.Errorf("mev-blocks api response status code error: %s - %s", resp.Status, url) 105 | return response, err 106 | } 107 | 108 | err = json.NewDecoder(resp.Body).Decode(&response) 109 | if err != nil { 110 | err := fmt.Errorf("mev-blocks api response decode error: %s - %w", url, err) 111 | return response, err 112 | } 113 | 114 | return response, nil 115 | } 116 | -------------------------------------------------------------------------------- /api/transactions.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | BundleTypeFlashbots = "flashbots" 12 | BundleTypeRogue = "rogue" 13 | ) 14 | 15 | type FlashbotsTransaction struct { 16 | Hash string `json:"transaction_hash"` 17 | TxIndex int64 `json:"tx_index"` 18 | BundleType string `json:"bundle_type"` 19 | BundleIndex int64 `json:"bundle_index"` 20 | BlockNumber int64 `json:"block_number"` 21 | EoaAddress string `json:"eoa_address"` 22 | ToAddress string `json:"to_address"` 23 | GasUsed int64 `json:"gas_used"` 24 | GasPrice string `json:"gas_price"` 25 | CoinbaseTransfer string `json:"coinbase_transfer"` 26 | TotalMinerReward string `json:"total_miner_reward"` 27 | } 28 | 29 | type GetTransactionsOptions struct { 30 | Before int64 // Filter transactions to before this block number (exclusive, does not include this block number). Default value: latest 31 | Limit int64 // Number of transactions that are returned 32 | } 33 | 34 | func (opts GetTransactionsOptions) ToUriQuery() string { 35 | args := []string{} 36 | if opts.Before > 0 { 37 | args = append(args, fmt.Sprintf("before=%d", opts.Before)) 38 | } 39 | if opts.Limit > 0 { 40 | args = append(args, fmt.Sprintf("limit=%d", opts.Limit)) 41 | } 42 | 43 | s := strings.Join(args, "&") 44 | if len(s) > 0 { 45 | s = "?" + s 46 | } 47 | 48 | return s 49 | } 50 | 51 | type TransactionsResponse struct { 52 | LatestBlockNumber int64 `json:"latest_block_number"` 53 | Transactions []FlashbotsTransaction `json:"transactions"` 54 | } 55 | 56 | // GetTransactions returns the 100 most recent flashbots transactions. Use the before query param to 57 | // filter to transactions before a given block number. 58 | // https://blocks.flashbots.net/#api-Flashbots-GetV1Transactions 59 | func GetTransactions(options *GetTransactionsOptions) (response TransactionsResponse, err error) { 60 | url := "https://blocks.flashbots.net/v1/transactions" 61 | if options != nil { 62 | url = url + options.ToUriQuery() 63 | } 64 | 65 | resp, err := http.Get(url) 66 | if err != nil { 67 | return response, err 68 | } 69 | 70 | err = json.NewDecoder(resp.Body).Decode(&response) 71 | if err != nil { 72 | return response, err 73 | } 74 | 75 | return response, nil 76 | } 77 | -------------------------------------------------------------------------------- /blockcheck/blockcheck.go: -------------------------------------------------------------------------------- 1 | package blockcheck 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/big" 7 | "sort" 8 | "time" 9 | 10 | ethcommon "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/core/types" 12 | "github.com/metachris/flashbots/api" 13 | "github.com/metachris/flashbots/common" 14 | "github.com/metachris/go-ethutils/addresslookup" 15 | "github.com/metachris/go-ethutils/blockswithtx" 16 | "github.com/metachris/go-ethutils/utils" 17 | ) 18 | 19 | var ( 20 | ErrFlashbotsApiDoesntHaveThatBlockYet = errors.New("flashbots API latest height < requested block height") 21 | ) 22 | 23 | var ThresholdBiggestBundlePercentPriceDiff float32 = 50 24 | var ThresholdBundleIsPayingLessThanLowestTxPercentDiff float32 = 50 25 | 26 | var AddressLookup *addresslookup.AddressLookupService 27 | var AddressesUpdated time.Time 28 | 29 | var FlashbotsBlockCache map[int64]api.FlashbotsBlock = make(map[int64]api.FlashbotsBlock) 30 | 31 | type ErrorCounts struct { 32 | FailedFlashbotsTx uint64 33 | Failed0GasTx uint64 34 | BundlePaysMoreThanPrevBundle uint64 35 | BundleHasLowerFeeThanLowestNonFbTx uint64 36 | BundleHas0Fee uint64 37 | BundleHasNegativeFee uint64 38 | } 39 | 40 | func (ec *ErrorCounts) Add(counts ErrorCounts) { 41 | ec.FailedFlashbotsTx += counts.FailedFlashbotsTx 42 | ec.Failed0GasTx += counts.Failed0GasTx 43 | ec.BundlePaysMoreThanPrevBundle += counts.BundlePaysMoreThanPrevBundle 44 | ec.BundleHasLowerFeeThanLowestNonFbTx += counts.BundleHasLowerFeeThanLowestNonFbTx 45 | ec.BundleHas0Fee += counts.BundleHas0Fee 46 | ec.BundleHasNegativeFee += counts.BundleHasNegativeFee 47 | } 48 | 49 | type BlockCheck struct { 50 | Number int64 51 | Miner string 52 | MinerName string 53 | SkipFlashbotsApi bool 54 | 55 | BlockWithTxReceipts *blockswithtx.BlockWithTxReceipts 56 | EthBlock *types.Block 57 | FlashbotsApiBlock *api.FlashbotsBlock 58 | FlashbotsTransactions []api.FlashbotsTransaction 59 | Bundles []*common.Bundle 60 | 61 | // Collection of errors 62 | Errors []string 63 | FailedTx map[string]*FailedTx 64 | 65 | // Helpers to filter later in user code 66 | BiggestBundlePercentPriceDiff float32 // on order error, max % difference to previous bundle 67 | BundleIsPayingLessThanLowestTxPercentDiff float32 68 | 69 | HasBundleWith0EffectiveGasPrice bool 70 | HasFailedFlashbotsTx bool 71 | HasFailed0GasTx bool 72 | 73 | TriggerAlertOnFailedTx bool 74 | ManualHasSeriousError bool // manually set by specific error conditions 75 | 76 | ErrorCounter ErrorCounts 77 | } 78 | 79 | func CheckBlock(blockWithTx *blockswithtx.BlockWithTxReceipts, skipFlashbotsApi bool) (blockCheck *BlockCheck, err error) { 80 | // Init / update AddressLookup service 81 | if AddressLookup == nil { 82 | AddressLookup = addresslookup.NewAddressLookupService(nil) 83 | err = AddressLookup.AddAllAddresses() 84 | if err != nil { 85 | return blockCheck, err 86 | } 87 | AddressesUpdated = time.Now() 88 | } else { // after 5 minutes, update addresses 89 | timeSinceAddressUpdate := time.Since(AddressesUpdated) 90 | if timeSinceAddressUpdate.Seconds() > 60*5 { 91 | AddressLookup.ClearCache() 92 | AddressLookup.AddAllAddresses() 93 | AddressesUpdated = time.Now() 94 | } 95 | } 96 | 97 | // Lookup miner details 98 | minerAddr, _ := AddressLookup.GetAddressDetail(blockWithTx.Block.Coinbase().Hex()) 99 | 100 | // Create check result 101 | check := BlockCheck{ 102 | BlockWithTxReceipts: blockWithTx, 103 | EthBlock: blockWithTx.Block, 104 | FlashbotsTransactions: make([]api.FlashbotsTransaction, 0), 105 | SkipFlashbotsApi: skipFlashbotsApi, 106 | 107 | Number: blockWithTx.Block.Number().Int64(), 108 | Miner: blockWithTx.Block.Coinbase().Hex(), 109 | MinerName: minerAddr.Name, 110 | Bundles: make([]*common.Bundle, 0), 111 | ErrorCounter: ErrorCounts{}, 112 | } 113 | 114 | err = check.QueryFlashbotsApi() 115 | if err != nil { 116 | return blockCheck, err 117 | } 118 | 119 | check.CreateBundles() 120 | check.Check() 121 | return &check, nil 122 | } 123 | 124 | func (b *BlockCheck) AddError(msg string) { 125 | b.Errors = append(b.Errors, msg) 126 | } 127 | 128 | func (b *BlockCheck) HasErrors() bool { 129 | return len(b.Errors) > 0 130 | } 131 | 132 | func (b *BlockCheck) HasSeriousErrors() bool { 133 | // Failed tx 134 | if len(b.FailedTx) > 0 { 135 | return true 136 | } 137 | 138 | if b.ManualHasSeriousError { 139 | return true 140 | } 141 | 142 | // Bundle percent price diff 143 | if b.BiggestBundlePercentPriceDiff >= ThresholdBiggestBundlePercentPriceDiff { 144 | return true 145 | } 146 | 147 | // Bundle lower than lowest non-fb tx 148 | if b.BundleIsPayingLessThanLowestTxPercentDiff >= ThresholdBundleIsPayingLessThanLowestTxPercentDiff { 149 | return true 150 | } 151 | 152 | return false 153 | } 154 | 155 | func (b *BlockCheck) HasLessSeriousErrors() bool { 156 | // Failed tx 157 | if len(b.FailedTx) > 0 { 158 | return true 159 | } 160 | 161 | // Bundle percent price diff 162 | if b.BiggestBundlePercentPriceDiff >= 25 { 163 | return true 164 | } 165 | 166 | // Bundle lower than lowest non-fb tx 167 | if b.BundleIsPayingLessThanLowestTxPercentDiff >= 25 { 168 | return true 169 | } 170 | 171 | return false 172 | } 173 | 174 | // AddBundle adds the bundle and sorts them by Index 175 | func (b *BlockCheck) AddBundle(bundle *common.Bundle) { 176 | b.Bundles = append(b.Bundles, bundle) 177 | 178 | // Bring bundles into order 179 | sort.SliceStable(b.Bundles, func(i, j int) bool { 180 | return b.Bundles[i].Index < b.Bundles[j].Index 181 | }) 182 | } 183 | 184 | func (b *BlockCheck) QueryFlashbotsApi() error { 185 | cachedBlock, found := FlashbotsBlockCache[b.Number] 186 | if found { 187 | // fmt.Println(11) 188 | b.FlashbotsApiBlock = &cachedBlock 189 | b.FlashbotsTransactions = b.FlashbotsApiBlock.Transactions 190 | return nil 191 | } 192 | 193 | if b.SkipFlashbotsApi { 194 | if b.FlashbotsApiBlock == nil { 195 | b.FlashbotsApiBlock = &api.FlashbotsBlock{} 196 | } 197 | return nil 198 | } 199 | 200 | // API call to flashbots 201 | opts := api.GetBlocksOptions{BlockNumber: b.Number} 202 | flashbotsResponse, err := api.GetBlocks(&opts) 203 | if err != nil { 204 | return err 205 | } 206 | 207 | // Return an error if API doesn't have the block yet 208 | if flashbotsResponse.LatestBlockNumber < b.Number { 209 | return ErrFlashbotsApiDoesntHaveThatBlockYet 210 | } 211 | 212 | // fmt.Println("22", b.Number, len(flashbotsResponse.Blocks)) // TODO 213 | 214 | if len(flashbotsResponse.Blocks) != 1 { 215 | return nil 216 | } 217 | 218 | b.FlashbotsApiBlock = &flashbotsResponse.Blocks[0] 219 | b.FlashbotsTransactions = b.FlashbotsApiBlock.Transactions 220 | return nil 221 | } 222 | 223 | func (b *BlockCheck) CreateBundles() { 224 | if b.FlashbotsApiBlock == nil { 225 | return 226 | } 227 | 228 | // Clear old bundles 229 | b.Bundles = make([]*common.Bundle, 0) 230 | 231 | // Create the bundles from all Flashbots transactions in this block 232 | bundles := make(map[int64]*common.Bundle) 233 | for _, tx := range b.FlashbotsApiBlock.Transactions { 234 | bundleIndex := tx.BundleIndex 235 | bundle, exists := bundles[bundleIndex] 236 | if !exists { 237 | bundle = common.NewBundle() 238 | bundle.Index = bundleIndex 239 | bundles[bundleIndex] = bundle 240 | } 241 | 242 | // Update bundle information 243 | bundle.Transactions = append(bundle.Transactions, tx) 244 | 245 | txMinerReward := new(big.Int) 246 | txMinerReward.SetString(tx.TotalMinerReward, 10) 247 | 248 | txCoinbaseTransfer := new(big.Int) 249 | txCoinbaseTransfer.SetString(tx.CoinbaseTransfer, 10) 250 | 251 | txGasUsed := big.NewInt(tx.GasUsed) 252 | 253 | bundle.TotalMinerReward = new(big.Int).Add(bundle.TotalMinerReward, txMinerReward) 254 | bundle.TotalCoinbaseTransfer = new(big.Int).Add(bundle.TotalCoinbaseTransfer, txCoinbaseTransfer) 255 | bundle.TotalGasUsed = new(big.Int).Add(bundle.TotalGasUsed, txGasUsed) 256 | 257 | bundle.CoinbaseDivGasUsed = new(big.Int).Div(bundle.TotalCoinbaseTransfer, bundle.TotalGasUsed) 258 | bundle.RewardDivGasUsed = new(big.Int).Div(bundle.TotalMinerReward, bundle.TotalGasUsed) 259 | } 260 | 261 | // Add bundles to the block 262 | for _, bundle := range bundles { 263 | b.AddBundle(bundle) 264 | } 265 | } 266 | 267 | func (b *BlockCheck) IsFlashbotsTx(hash string) bool { 268 | for _, tx := range b.FlashbotsTransactions { 269 | if tx.Hash == hash { 270 | return true 271 | } 272 | } 273 | return false 274 | } 275 | 276 | // Check analyzes the Flashbots bundles and adds errors when issues are found 277 | func (b *BlockCheck) Check() { 278 | // fmt.Println(b.Number, 1, b.FlashbotsApiBlock, len(b.FlashbotsTransactions)) 279 | numBundles := len(b.Bundles) 280 | 281 | // Check 0: contains failed Flashbots or 0-gas tx 282 | b.checkBlockForFailedTx() 283 | // fmt.Println(b.Number, 2, len(b.Errors)) 284 | 285 | // Check 1: do all bundles exists or are there gaps? 286 | for i := 0; i < numBundles; i++ { 287 | if b.Bundles[int64(i)] == nil { 288 | b.AddError(fmt.Sprintf("- error: missing bundle # %d in block %d", i, b.Number)) 289 | } 290 | } 291 | // fmt.Println(b.Number, 3, len(b.Errors)) 292 | 293 | // Check 2: are the bundles in the correct order? 294 | lastCoinbaseDivGasused := big.NewInt(-1) 295 | lastRewardDivGasused := big.NewInt(-1) 296 | for i := 0; i < numBundles; i++ { 297 | bundle := b.Bundles[int64(i)] 298 | 299 | // if not first bundle, and value larger than from last bundle, print the error 300 | if lastCoinbaseDivGasused.Int64() == -1 { 301 | // nothing to do on the first bundle 302 | } else { 303 | percentDiff := new(big.Float).Quo(new(big.Float).SetInt(bundle.RewardDivGasUsed), new(big.Float).SetInt(lastRewardDivGasused)) 304 | percentDiff = new(big.Float).Sub(percentDiff, big.NewFloat(1)) 305 | percentDiff = new(big.Float).Mul(percentDiff, big.NewFloat(100)) 306 | bundle.PercentPriceDiff = percentDiff 307 | 308 | if bundle.CoinbaseDivGasUsed.Cmp(lastCoinbaseDivGasused) == 1 && 309 | bundle.RewardDivGasUsed.Cmp(lastRewardDivGasused) == 1 && 310 | bundle.CoinbaseDivGasUsed.Cmp(lastRewardDivGasused) == 1 { 311 | 312 | msg := fmt.Sprintf("bundle %d pays %v%s more than previous bundle\n", bundle.Index, percentDiff.Text('f', 2), "%") 313 | b.AddError(msg) 314 | b.ErrorCounter.BundlePaysMoreThanPrevBundle += 1 315 | bundle.IsOutOfOrder = true 316 | diffFloat, _ := percentDiff.Float32() 317 | if diffFloat > b.BiggestBundlePercentPriceDiff { 318 | b.BiggestBundlePercentPriceDiff = diffFloat 319 | } 320 | } 321 | } 322 | 323 | lastCoinbaseDivGasused = bundle.CoinbaseDivGasUsed 324 | lastRewardDivGasused = bundle.RewardDivGasUsed 325 | } 326 | // fmt.Println(b.Number, 4, len(b.Errors)) 327 | 328 | // Check 3: bundle effective gas price > lowest tx gas price 329 | // step 1. find lowest non-fb-tx gas price 330 | lowestGasPrice := big.NewInt(-1) 331 | lowestGasPriceTxHash := "" 332 | for _, tx := range b.EthBlock.Transactions() { 333 | isFlashbotsTx := b.IsFlashbotsTx(tx.Hash().String()) 334 | if isFlashbotsTx { 335 | continue 336 | } 337 | 338 | if lowestGasPrice.Int64() == -1 || tx.GasPrice().Cmp(lowestGasPrice) == -1 { 339 | if utils.IsBigIntZero(tx.GasPrice()) && len(tx.Data()) > 0 { // don't count Flashbots-like tx 340 | continue 341 | } 342 | lowestGasPrice = tx.GasPrice() 343 | lowestGasPriceTxHash = tx.Hash().Hex() 344 | } 345 | } 346 | 347 | // step 2. check gas prices and fees 348 | for _, bundle := range b.Bundles { 349 | if bundle.RewardDivGasUsed.Cmp(ethcommon.Big0) == -1 { // negative fee 350 | bundle.IsNegativeEffectiveGasPrice = true 351 | msg := fmt.Sprintf("bundle %d has negative effective-gas-price (%v)\n", bundle.Index, common.BigIntToEString(bundle.RewardDivGasUsed, 4)) 352 | b.AddError(msg) 353 | b.ErrorCounter.BundleHasNegativeFee += 1 354 | b.ManualHasSeriousError = true 355 | 356 | } else if utils.IsBigIntZero(bundle.RewardDivGasUsed) { // 0 fee 357 | bundle.Is0EffectiveGasPrice = true 358 | msg := fmt.Sprintf("bundle %d has 0 effective-gas-price\n", bundle.Index) 359 | b.AddError(msg) 360 | b.ErrorCounter.BundleHas0Fee += 1 361 | b.HasBundleWith0EffectiveGasPrice = true 362 | b.ManualHasSeriousError = true 363 | 364 | } else if bundle.RewardDivGasUsed.Cmp(lowestGasPrice) == -1 { // lower fee than lowest non-fb TX 365 | bundle.IsPayingLessThanLowestTx = true 366 | 367 | // calculate percent difference: 368 | fCur := new(big.Float).SetInt(bundle.RewardDivGasUsed) 369 | fLow := new(big.Float).SetInt(lowestGasPrice) 370 | diffPercent1 := new(big.Float).Quo(fCur, fLow) 371 | diffPercent2 := new(big.Float).Sub(big.NewFloat(1), diffPercent1) 372 | diffPercent := new(big.Float).Mul(diffPercent2, big.NewFloat(100)) 373 | 374 | msg := fmt.Sprintf("bundle %d has %s%s lower effective-gas-price (%v) than [lowest non-fb transaction]() (%v)\n", bundle.Index, diffPercent.Text('f', 2), "%", common.BigIntToEString(bundle.RewardDivGasUsed, 4), lowestGasPriceTxHash, common.BigIntToEString(lowestGasPrice, 4)) 375 | b.AddError(msg) 376 | b.ErrorCounter.BundleHasLowerFeeThanLowestNonFbTx += 1 377 | b.BundleIsPayingLessThanLowestTxPercentDiff, _ = diffPercent.Float32() 378 | } 379 | } 380 | // fmt.Println(b.Number, 5, len(b.Errors)) 381 | } 382 | 383 | func (b *BlockCheck) SprintHeader(color bool, markdown bool) (msg string) { 384 | minerAddr, found := AddressLookup.GetAddressDetail(b.Miner) 385 | minerStr := fmt.Sprintf("[%s]()", b.Miner, b.Miner) 386 | if found { 387 | minerStr = fmt.Sprintf("[%s]()", minerAddr.Name, b.Miner) 388 | } 389 | 390 | numTx := len(b.BlockWithTxReceipts.Block.Transactions()) 391 | numFbTx := len(b.FlashbotsApiBlock.Transactions) 392 | numBundles := len(b.Bundles) 393 | 394 | if markdown { 395 | msg = fmt.Sprintf("Block [%d]() ([bundle explorer]()), miner: %s - tx: %d, fb-tx: %d, bundles: %d", b.Number, b.Number, b.Number, minerStr, numTx, numFbTx, numBundles) 396 | } else { 397 | msg = fmt.Sprintf("Block %d, miner %s - tx: %d, fb-tx: %d, bundles: %d", b.Number, minerStr, numTx, numFbTx, numBundles) 398 | } 399 | return msg 400 | } 401 | 402 | func (b *BlockCheck) Sprint(color bool, markdown bool, includeBundles bool) (msg string) { 403 | msg = b.SprintHeader(color, markdown) 404 | msg += "\n" 405 | 406 | // Print errors 407 | for _, err := range b.Errors { 408 | err = "- error: " + err 409 | if color { 410 | msg += fmt.Sprintf(utils.WarningColor, err) 411 | } else { 412 | msg += err 413 | } 414 | } 415 | 416 | if !includeBundles { 417 | return msg 418 | } 419 | 420 | if markdown { 421 | msg += "```" 422 | } 423 | 424 | // Print bundles 425 | for _, bundle := range b.Bundles { 426 | // Build string for percent(gasprice difference to previous bundle) 427 | percentPart := "" 428 | if bundle.PercentPriceDiff.Cmp(big.NewFloat(0)) == -1 { 429 | percentPart = fmt.Sprintf("(%6s%s)", bundle.PercentPriceDiff.Text('f', 2), "%") 430 | } else if bundle.PercentPriceDiff.Cmp(big.NewFloat(0)) == 1 { 431 | percentPart = fmt.Sprintf("(+%5s%s)", bundle.PercentPriceDiff.Text('f', 2), "%") 432 | } 433 | 434 | msg += fmt.Sprintf("- bundle %d: tx: %d, gasUsed: %7d \t coinbase_transfer: %13v, total_miner_reward: %13v \t coinbase/gasused: %13v, reward/gasused: %13v %v", bundle.Index, len(bundle.Transactions), bundle.TotalGasUsed, common.BigIntToEString(bundle.TotalCoinbaseTransfer, 4), common.BigIntToEString(bundle.TotalMinerReward, 4), common.BigIntToEString(bundle.CoinbaseDivGasUsed, 4), common.BigIntToEString(bundle.RewardDivGasUsed, 4), percentPart) 435 | if bundle.IsOutOfOrder || bundle.IsPayingLessThanLowestTx { 436 | msg += " <--" 437 | } 438 | msg += "\n" 439 | } 440 | 441 | if markdown { 442 | msg += "```" 443 | } 444 | 445 | return msg 446 | } 447 | 448 | func (b *BlockCheck) checkBlockForFailedTx() (failedTransactions []FailedTx) { 449 | b.FailedTx = make(map[string]*FailedTx) 450 | 451 | // 1. iterate over all Flashbots transactions and check if any has failed 452 | for _, fbTx := range b.FlashbotsTransactions { 453 | receipt := b.BlockWithTxReceipts.TxReceipts[ethcommon.HexToHash(fbTx.Hash)] 454 | if receipt == nil { 455 | continue 456 | } 457 | 458 | if receipt.Status == 0 { // failed Flashbots TX 459 | b.FailedTx[fbTx.Hash] = &FailedTx{ 460 | Hash: fbTx.Hash, 461 | IsFlashbots: true, 462 | From: fbTx.EoaAddress, 463 | To: fbTx.ToAddress, 464 | Block: uint64(fbTx.BlockNumber), 465 | } 466 | 467 | msg := fmt.Sprintf("failed %s tx [%s]() in bundle %d (from [%s]())\n", fbTx.BundleType, fbTx.Hash, fbTx.Hash, fbTx.BundleIndex, fbTx.EoaAddress, fbTx.EoaAddress) 468 | b.ErrorCounter.FailedFlashbotsTx += 1 469 | b.AddError(msg) 470 | b.HasFailedFlashbotsTx = true 471 | if fbTx.BundleType == api.BundleTypeFlashbots { // alert only for type=flashbots 472 | b.TriggerAlertOnFailedTx = true 473 | } 474 | } 475 | } 476 | 477 | // 2. iterate over all failed 0-gas transactions in the EthBlock 478 | for _, tx := range b.EthBlock.Transactions() { 479 | receipt := b.BlockWithTxReceipts.TxReceipts[tx.Hash()] 480 | if receipt == nil { 481 | continue 482 | } 483 | 484 | if utils.IsBigIntZero(tx.GasPrice()) && len(tx.Data()) > 0 { 485 | if receipt.Status == 0 { // failed tx 486 | if _, exists := b.FailedTx[tx.Hash().String()]; exists { 487 | // Already known (Flashbots TX) 488 | continue 489 | } 490 | 491 | from, _ := utils.GetTxSender(tx) 492 | to := "" 493 | if tx.To() != nil { 494 | to = tx.To().String() 495 | } 496 | b.FailedTx[tx.Hash().String()] = &FailedTx{ 497 | Hash: tx.Hash().String(), 498 | IsFlashbots: false, 499 | From: from.String(), 500 | To: to, 501 | Block: uint64(b.Number), 502 | } 503 | 504 | msg := fmt.Sprintf("failed 0-gas tx [%s]() from [%s]()\n", tx.Hash(), tx.Hash(), from, from) 505 | b.AddError(msg) 506 | b.ErrorCounter.Failed0GasTx += 1 507 | b.HasFailed0GasTx = true 508 | b.TriggerAlertOnFailedTx = true 509 | } 510 | } 511 | } 512 | 513 | return failedTransactions 514 | } 515 | 516 | func CacheFlashbotsBlocks(startBlock int64, endBlock int64) error { 517 | numBlocks := endBlock - startBlock 518 | limit1 := int64(10_000) 519 | if numBlocks < 10_000 { 520 | limit1 = numBlocks 521 | } 522 | 523 | // API call to flashbots 524 | opts := api.GetBlocksOptions{ 525 | Before: endBlock + 1, 526 | Limit: limit1, 527 | } 528 | 529 | flashbotsResponse, err := api.GetBlocks(&opts) 530 | if err != nil { 531 | return err 532 | } 533 | 534 | // Return an error if API doesn't have the block yet 535 | if flashbotsResponse.LatestBlockNumber < endBlock { 536 | return ErrFlashbotsApiDoesntHaveThatBlockYet 537 | } 538 | 539 | // Cache now 540 | lowestBlock := endBlock 541 | for _, block := range flashbotsResponse.Blocks { 542 | FlashbotsBlockCache[block.BlockNumber] = block 543 | if block.BlockNumber < lowestBlock { 544 | lowestBlock = block.BlockNumber 545 | } 546 | } 547 | 548 | // If necessary, fetch more 549 | for { 550 | // fmt.Println("check2. lowestCurrent:", lowestBlock, "start", startBlock) 551 | if lowestBlock <= startBlock { 552 | return nil 553 | } 554 | 555 | numBlocks = lowestBlock - startBlock 556 | limit1 := int64(10_000) 557 | if numBlocks < 10_000 { 558 | limit1 = numBlocks 559 | } 560 | 561 | opts := api.GetBlocksOptions{ 562 | Before: lowestBlock, 563 | Limit: limit1, 564 | } 565 | 566 | flashbotsResponse, err = api.GetBlocks(&opts) 567 | if err != nil { 568 | return err 569 | } 570 | 571 | // fmt.Println("cached before", len(FlashbotsBlockCache)) 572 | for _, block := range flashbotsResponse.Blocks { 573 | FlashbotsBlockCache[block.BlockNumber] = block 574 | if block.BlockNumber < lowestBlock { 575 | lowestBlock = block.BlockNumber 576 | } 577 | } 578 | // fmt.Println("cached after", len(FlashbotsBlockCache)) 579 | } 580 | } 581 | -------------------------------------------------------------------------------- /blockcheck/errorsummary.go: -------------------------------------------------------------------------------- 1 | package blockcheck 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "time" 7 | ) 8 | 9 | type ErrorSummary struct { 10 | TimeStarted time.Time 11 | MinerErrors map[string]*MinerErrors 12 | } 13 | 14 | func NewErrorSummary() ErrorSummary { 15 | return ErrorSummary{ 16 | TimeStarted: time.Now(), 17 | MinerErrors: make(map[string]*MinerErrors), 18 | } 19 | } 20 | 21 | func (es *ErrorSummary) String() (ret string) { 22 | // Get list of keys by number of errorBlocks 23 | keys := make([]string, 0, len(es.MinerErrors)) 24 | for k := range es.MinerErrors { 25 | keys = append(keys, k) 26 | } 27 | sort.Slice(keys, func(i, j int) bool { 28 | return len(es.MinerErrors[keys[i]].Blocks) > len(es.MinerErrors[keys[j]].Blocks) 29 | }) 30 | 31 | for _, key := range keys { 32 | minerErrors := es.MinerErrors[key] 33 | minerId := key 34 | if minerErrors.MinerName != "" { 35 | minerId += fmt.Sprintf(" (%s)", minerErrors.MinerName) 36 | } 37 | ret += fmt.Sprintf("%-66s errorBlocks=%d \t failed0gas=%d \t failedFbTx=%d \t bundlePaysMore=%d \t bundleTooLowFee=%d \t has0fee=%d \t hasNegativeFee=%d\n", minerId, len(minerErrors.Blocks), minerErrors.ErrorCounts.Failed0GasTx, minerErrors.ErrorCounts.FailedFlashbotsTx, minerErrors.ErrorCounts.BundlePaysMoreThanPrevBundle, minerErrors.ErrorCounts.BundleHasLowerFeeThanLowestNonFbTx, minerErrors.ErrorCounts.BundleHas0Fee, minerErrors.ErrorCounts.BundleHasNegativeFee) 38 | } 39 | return ret 40 | } 41 | 42 | func (es *ErrorSummary) AddErrorCounts(MinerHash string, MinerName string, block int64, errors ErrorCounts) { 43 | _, found := es.MinerErrors[MinerHash] 44 | if !found { 45 | es.MinerErrors[MinerHash] = &MinerErrors{ 46 | MinerHash: MinerHash, 47 | MinerName: MinerName, 48 | Blocks: make(map[int64]bool), 49 | } 50 | } 51 | 52 | es.MinerErrors[MinerHash].AddErrorCounts(block, errors) 53 | if es.TimeStarted == time.Unix(0, 0) { 54 | es.TimeStarted = time.Now() 55 | } 56 | } 57 | 58 | func (es *ErrorSummary) AddCheckErrors(check *BlockCheck) { 59 | es.AddErrorCounts(check.Miner, check.MinerName, check.Number, check.ErrorCounter) 60 | } 61 | 62 | func (es *ErrorSummary) Reset() { 63 | es.TimeStarted = time.Now() 64 | es.MinerErrors = make(map[string]*MinerErrors) 65 | } 66 | -------------------------------------------------------------------------------- /blockcheck/failedtx.go: -------------------------------------------------------------------------------- 1 | // Representation of a failed Flashbots or other 0-gas transaction (used in webserver) 2 | package blockcheck 3 | 4 | // FailedTx contains information about a failed 0-gas or Flashbots tx 5 | type FailedTx struct { 6 | Hash string 7 | IsFlashbots bool 8 | From string 9 | To string 10 | Block uint64 11 | } 12 | -------------------------------------------------------------------------------- /blockcheck/minererrors.go: -------------------------------------------------------------------------------- 1 | package blockcheck 2 | 3 | type MinerErrors struct { 4 | MinerHash string 5 | MinerName string 6 | 7 | Blocks map[int64]bool // To avoid counting errors / blocks twice 8 | ErrorCounts ErrorCounts 9 | } 10 | 11 | func NewMinerErrorCounter() MinerErrors { 12 | return MinerErrors{ 13 | Blocks: make(map[int64]bool), 14 | } 15 | } 16 | 17 | func (ec *MinerErrors) AddErrorCounts(block int64, counts ErrorCounts) { 18 | ec.ErrorCounts.Add(counts) 19 | ec.Blocks[block] = true 20 | } 21 | -------------------------------------------------------------------------------- /blockcheck/statsservice.go: -------------------------------------------------------------------------------- 1 | package blockcheck 2 | 3 | // import ( 4 | // "time" 5 | 6 | // "github.com/go-redis/redis/v8" 7 | // ) 8 | 9 | // type StatsService struct { 10 | // rdb *redis.Client 11 | // MinerErrorsToday map[string]*MinerErrorCounter // key is miner hash as hex string 12 | // // MinerErrorsThisWeek map[string]*MinerErrorCounter // key is miner hash as hex string 13 | // LastUpdate time.Time 14 | // } 15 | 16 | // func NewMinerStatsService() StatsService { 17 | // rdb := redis.NewClient(&redis.Options{ 18 | // Addr: "localhost:6379", 19 | // }) 20 | 21 | // service := StatsService{ 22 | // rdb: rdb, 23 | // MinerErrorsToday: make(map[string]*MinerErrorCounter), 24 | // } 25 | 26 | // // Load error counts from redis 27 | 28 | // return service 29 | // } 30 | 31 | // func (s *StatsService) AddErrors(minerHash string, minerName string, block int64, errors ErrorCounts) { 32 | // _, found := s.MinerErrorsToday[minerHash] 33 | // if !found { 34 | // s.MinerErrorsToday[minerHash] = &MinerErrorCounter{ 35 | // MinerHash: minerHash, 36 | // MinerName: minerName, 37 | // Blocks: make(map[int64]bool), 38 | // } 39 | // } 40 | 41 | // s.MinerErrorsToday[minerHash].AddErrorCounts(block, errors) 42 | // } 43 | -------------------------------------------------------------------------------- /cmd/block-watch/README.md: -------------------------------------------------------------------------------- 1 | Example blocks: 2 | 3 | ```bash 4 | # Too low gas fee: 5 | go run cmd/block-watch/*.go -block 12693354 6 | 7 | # Bundle out of order: 8 | go run cmd/block-watch/*.go -block 12699873 9 | 10 | # Failed Flashbots tx: 11 | go run cmd/block-watch/*.go -block 12705543 12 | 13 | # Failed 0-gas-and-data (non-fb) tx: 14 | go run cmd/block-watch/*.go -block 12605331 15 | ``` 16 | 17 | 18 | ## TODO 19 | 20 | * ErrorCount struct method to add counts of another ErrorCount struct to self 21 | * discord.go should just accept a blockcheck struct and create the right message there 22 | -------------------------------------------------------------------------------- /cmd/block-watch/discord.go: -------------------------------------------------------------------------------- 1 | // Discord webhook helpers 2 | // https://discord.com/developers/docs/resources/webhook#execute-webhook 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "errors" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "os" 13 | "strings" 14 | ) 15 | 16 | type DiscordWebhookPayload struct { 17 | Content string `json:"content"` 18 | } 19 | 20 | var discordUrl string = os.Getenv("DISCORD_WEBHOOK") 21 | 22 | // SendToDiscord splits one message into multiple if necessary (max size is 2k characters) 23 | func SendToDiscord(msg string) error { 24 | if msg == "" { 25 | return nil 26 | } 27 | 28 | for { 29 | if len(msg) < 2000 { 30 | return _SendToDiscord(msg) 31 | } 32 | 33 | // Extract 2k of message and send those 34 | smallMsg := "" 35 | if strings.Contains(msg, "```") { 36 | smallMsg = msg[0:1994] + "...```" 37 | msg = "```..." + msg[1994:] 38 | } else { 39 | smallMsg = msg[0:1997] + "..." 40 | msg = "..." + msg[1997:] 41 | } 42 | 43 | err := _SendToDiscord(smallMsg) 44 | if err != nil { 45 | return err 46 | } 47 | } 48 | } 49 | 50 | // _SendToDiscord sends to discord without any error checks 51 | func _SendToDiscord(msg string) error { 52 | if len(discordUrl) == 0 { 53 | return errors.New("no DISCORD_WEBHOOK env variable found") 54 | } 55 | 56 | discordPayload := DiscordWebhookPayload{Content: msg} 57 | payloadBytes, err := json.Marshal(discordPayload) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | res, err := http.Post(discordUrl, "application/json", bytes.NewBuffer(payloadBytes)) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | defer res.Body.Close() 68 | log.Println("Discord response status:", res.Status) 69 | 70 | if res.StatusCode >= 300 { 71 | bodyBytes, _ := ioutil.ReadAll(res.Body) 72 | bodyString := string(bodyBytes) 73 | log.Println(bodyString) 74 | } 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /cmd/block-watch/main.go: -------------------------------------------------------------------------------- 1 | // Watch blocks and report issues (to terminal and to Discord) 2 | // 3 | // Issues: 4 | // 1. Failed Flashbots (or other 0-gas) transaction 5 | // 2. Bundle out of order by effective-gasprice 6 | // 3. Bundle effective-gasprice is lower than lowest non-fb tx gasprice 7 | package main 8 | 9 | import ( 10 | "context" 11 | "flag" 12 | "fmt" 13 | "log" 14 | "os" 15 | "time" 16 | 17 | "github.com/ethereum/go-ethereum/core/types" 18 | "github.com/ethereum/go-ethereum/ethclient" 19 | "github.com/metachris/flashbots/api" 20 | "github.com/metachris/flashbots/blockcheck" 21 | "github.com/metachris/go-ethutils/blockswithtx" 22 | "github.com/metachris/go-ethutils/utils" 23 | "github.com/pkg/errors" 24 | ) 25 | 26 | var silent bool 27 | var sendErrorsToDiscord bool 28 | 29 | // Backlog of new blocks that are not yet present in the mev-blocks API (it has ~5 blocks delay) 30 | var BlockBacklog map[int64]*blockswithtx.BlockWithTxReceipts = make(map[int64]*blockswithtx.BlockWithTxReceipts) 31 | 32 | var dailyErrorSummary blockcheck.ErrorSummary = blockcheck.NewErrorSummary() 33 | var weeklyErrorSummary blockcheck.ErrorSummary = blockcheck.NewErrorSummary() 34 | 35 | func main() { 36 | log.SetOutput(os.Stdout) 37 | 38 | ethUri := flag.String("eth", os.Getenv("ETH_NODE"), "Ethereum node URI") 39 | // recentBundleOrdersPtr := flag.Bool("recentBundleOrder", false, "check recent bundle orders blocks") 40 | blockHeightPtr := flag.Int64("block", 0, "specific block to check") 41 | watchPtr := flag.Bool("watch", false, "watch and process new blocks") 42 | silentPtr := flag.Bool("silent", false, "don't print info about every block") 43 | discordPtr := flag.Bool("discord", false, "send errors to Discord") 44 | flag.Parse() 45 | 46 | silent = *silentPtr 47 | 48 | if *discordPtr { 49 | if len(os.Getenv("DISCORD_WEBHOOK")) == 0 { 50 | log.Fatal("No DISCORD_WEBHOOK environment variable found!") 51 | } 52 | sendErrorsToDiscord = true 53 | } 54 | 55 | // Connect to the geth node and start the BlockCheckService 56 | if *ethUri == "" { 57 | log.Fatal("Pass a valid eth node with -eth argument or ETH_NODE env var.") 58 | } 59 | 60 | fmt.Printf("Connecting to %s ...", *ethUri) 61 | client, err := ethclient.Dial(*ethUri) 62 | utils.Perror(err) 63 | fmt.Printf(" ok\n") 64 | 65 | if *blockHeightPtr != 0 { 66 | // get block with receipts 67 | block, err := blockswithtx.GetBlockWithTxReceipts(client, *blockHeightPtr) 68 | utils.Perror(err) 69 | 70 | // check the block 71 | check, err := blockcheck.CheckBlock(block, false) 72 | if err != nil { 73 | fmt.Println("Check at height error:", err) 74 | } 75 | msg := check.Sprint(true, false, true) 76 | print(msg) 77 | } 78 | 79 | if *watchPtr { 80 | log.Println("Start watching...") 81 | watch(client) 82 | } 83 | } 84 | 85 | func watch(client *ethclient.Client) { 86 | headers := make(chan *types.Header) 87 | sub, err := client.SubscribeNewHead(context.Background(), headers) 88 | utils.Perror(err) 89 | 90 | var errorCountSerious int 91 | var errorCountNonSerious int 92 | 93 | for { 94 | select { 95 | case err := <-sub.Err(): 96 | log.Fatal(err) 97 | case header := <-headers: 98 | // New block header received. Download block with tx-receipts 99 | b, err := blockswithtx.GetBlockWithTxReceipts(client, header.Number.Int64()) 100 | if err != nil { 101 | err = errors.Wrap(err, "error in GetBlockWithTxReceipts") 102 | log.Printf("%+v\n", err) 103 | continue 104 | } 105 | 106 | if !silent { 107 | fmt.Println("Queueing new block", b.Block.Number()) 108 | } 109 | 110 | // Add to backlog, because it can only be processed when the Flashbots API has caught up 111 | BlockBacklog[header.Number.Int64()] = b 112 | 113 | // Query flashbots API to get latest block it has processed 114 | opts := api.GetBlocksOptions{BlockNumber: header.Number.Int64()} 115 | flashbotsResponse, err := api.GetBlocks(&opts) 116 | if err != nil { 117 | log.Println("Flashbots API error:", err) 118 | continue 119 | } 120 | 121 | // Go through block-backlog, and process those within Flashbots API range 122 | for height, blockFromBacklog := range BlockBacklog { 123 | if height <= flashbotsResponse.LatestBlockNumber { 124 | if !silent { 125 | utils.PrintBlock(blockFromBacklog.Block) 126 | } 127 | 128 | check, err := blockcheck.CheckBlock(blockFromBacklog, false) 129 | if err != nil { 130 | log.Println("CheckBlock from backlog error:", err, "block:", blockFromBacklog.Block.Number()) 131 | break 132 | } 133 | 134 | // no checking error, can process and remove from backlog 135 | delete(BlockBacklog, blockFromBacklog.Block.Number().Int64()) 136 | 137 | // Handle errors in the bundle (print, Discord, etc.) 138 | if check.HasErrors() { 139 | if check.HasSeriousErrors() { // only serious errors are printed and sent to Discord 140 | errorCountSerious += 1 141 | msg := check.Sprint(true, false, true) 142 | fmt.Println(msg) 143 | 144 | // if sendErrorsToDiscord { 145 | // if len(check.Errors) == 1 && check.HasBundleWith0EffectiveGasPrice { 146 | // // Short message if only 1 error and that is a 0-effective-gas-price 147 | // msg := check.SprintHeader(false, true) 148 | // msg += " - Error: " + check.Errors[0] 149 | // SendToDiscord(msg) 150 | // } else { 151 | // SendToDiscord(check.Sprint(false, true)) 152 | // } 153 | // } 154 | fmt.Println("") 155 | } else if check.HasLessSeriousErrors() { // less serious errors are only counted 156 | errorCountNonSerious += 1 157 | } 158 | 159 | // Send failed TX to Discord 160 | // if sendErrorsToDiscord && check.TriggerAlertOnFailedTx { 161 | // SendToDiscord(check.Sprint(false, true, false)) 162 | // } 163 | 164 | // Count errors 165 | if check.HasSeriousErrors() || check.HasLessSeriousErrors() { // update and print miner error count on serious and less-serious errors 166 | log.Printf("stats - 50p_errors: %d, 25p_errors: %d\n", errorCountSerious, errorCountNonSerious) 167 | weeklyErrorSummary.AddCheckErrors(check) 168 | dailyErrorSummary.AddCheckErrors(check) 169 | fmt.Println(dailyErrorSummary.String()) 170 | } 171 | } 172 | 173 | // IS IT TIME TO RESET DAILY & WEEKLY ERRORS? 174 | now := time.Now() 175 | 176 | // Daily summary at 3pm ET 177 | dailySummaryTriggerHourUtc := 19 // 3pm ET 178 | // log.Println(now.UTC().Hour(), dailySummaryTriggerHourUtc, time.Since(dailyErrorSummary.TimeStarted).Hours()) 179 | if now.UTC().Hour() == dailySummaryTriggerHourUtc && time.Since(dailyErrorSummary.TimeStarted).Hours() >= 2 { 180 | log.Println("trigger daily summary") 181 | if sendErrorsToDiscord { 182 | msg := dailyErrorSummary.String() 183 | if msg != "" { 184 | fmt.Println(msg) 185 | SendToDiscord("Daily miner summary: ```" + msg + "```") 186 | } 187 | } 188 | 189 | // reset daily summery 190 | dailyErrorSummary.Reset() 191 | } 192 | 193 | // Weekly summary on Friday at 10am ET 194 | weeklySummaryTriggerHourUtc := 14 // 10am ET 195 | if now.UTC().Weekday() == time.Friday && now.UTC().Hour() == weeklySummaryTriggerHourUtc && time.Since(weeklyErrorSummary.TimeStarted).Hours() >= 2 { 196 | log.Println("trigger weekly summary") 197 | if sendErrorsToDiscord { 198 | msg := weeklyErrorSummary.String() 199 | if msg != "" { 200 | fmt.Println(msg) 201 | SendToDiscord("Weekly miner summary: ```" + msg + "```") 202 | } 203 | } 204 | 205 | // reset weekly summery 206 | weeklyErrorSummary.Reset() 207 | } 208 | 209 | // // -------- Send daily summary to Discord --------- 210 | // if sendErrorsToDiscord { 211 | // // Check if it's time to send to Discord: first block after 3pm ET (7pm UTC) 212 | // // triggerHourUtc := 19 213 | 214 | // // dateLastSent := lastSummarySentToDiscord.Format("01-02-2006") 215 | // // dateToday := now.Format("01-02-2006") 216 | 217 | // // For testing, send at specific interval 218 | // if time.Since(dailyErrorSummary.TimeStarted).Hours() >= 3 { 219 | // // if dateToday != dateLastSent && now.UTC().Hour() == triggerHourUtc { 220 | // log.Println("Sending summary to Discord:") 221 | // msg := dailyErrorSummary.String() 222 | // if msg != "" { 223 | // fmt.Println(msg) 224 | // SendToDiscord("```" + msg + "```") 225 | // } 226 | 227 | // // Reset errors 228 | // dailyErrorSummary.Reset() 229 | // log.Println("Done, errors are reset.") 230 | // } 231 | // } 232 | 233 | time.Sleep(1 * time.Second) 234 | } 235 | } 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /cmd/blocksim/README.md: -------------------------------------------------------------------------------- 1 | Get miner rewards for a full block (via number or hash) by simulating it with eth_callBundle at an mev-geth instance. 2 | Note: The result already excludes the 2 ETH block reward and any burnt gas fees, it's the actual miner earnings from the transactions. 3 | 4 | Example arguments: 5 | 6 | $ go run cmd/blocksim/main.go -mevgeth http://xxx.xxx.xxx.xxx:8545 -number 13100622 7 | $ go run cmd/blocksim/main.go -mevgeth http://xxx.xxx.xxx.xxx:8545 -hash 0x662f81506bd1d1f7cbefa308261ba94ee63438998cdf085c95081448aaf4cc81 8 | 9 | Example output: 10 | 11 | Connected to http://xxx.xxx.xxx.xxx:8545 12 | Block 13100622 0x662f81506bd1d1f7cbefa308261ba94ee63438998cdf085c95081448aaf4cc81 2021-08-26 11:14:55 +0000 UTC tx=99 gas=13854382 uncles=0 13 | Simulation result: 14 | - CoinbaseDiff: 67391709273784431 0.0674 ETH 15 | - GasFees: 67391709273784431 0.0674 ETH 16 | - EthSentToCoinbase: 0 0.0000 ETH 17 | 18 | Transactions: 19 | 1 0x5b9f8480250b56e6e1a954c2db75551c104751133f48540c76afb9f290d34b79 cbD=2.5441, gasFee=2.5441, ethSentToCb=0.0000 20 | 2 0x50e408ea25ed3b0fd8c016bef289e18a8f5d308e1377adfbb3cd88aa5313e30f cbD=2.0123, gasFee=2.0123, ethSentToCb=0.0000 21 | 3 0x6ad722ca388de995b87f1a18da8f66afd9b163bcf5f8e584c1e0f1462b22b220 cbD=1.4072, gasFee=1.4072, ethSentToCb=0.0000 22 | ... 23 | -------------------------------------------------------------------------------- /cmd/blocksim/main.go: -------------------------------------------------------------------------------- 1 | // Get miner rewards for a full block (via number or hash) by simulating it with eth_callBundle at an mev-geth instance. 2 | // Note: The result already excludes the 2 ETH block reward and any burnt gas fees, it's the actual miner earnings from the transactions. 3 | // 4 | // Example arguments: 5 | // 6 | // $ go run cmd/blocksim/main.go -mevgeth http://xxx.xxx.xxx.xxx:8545 -number 13100622 7 | // $ go run cmd/blocksim/main.go -mevgeth http://xxx.xxx.xxx.xxx:8545 -hash 0x662f81506bd1d1f7cbefa308261ba94ee63438998cdf085c95081448aaf4cc81 8 | // 9 | // Example output: 10 | // 11 | // Connected to http://xxx.xxx.xxx.xxx:8545 12 | // Block 13100622 0x662f81506bd1d1f7cbefa308261ba94ee63438998cdf085c95081448aaf4cc81 2021-08-26 11:14:55 +0000 UTC tx=99 gas=13854382 uncles=0 13 | // Simulation result: 14 | // - CoinbaseDiff: 67391709273784431 0.0674 ETH 15 | // - GasFees: 67391709273784431 0.0674 ETH 16 | // - EthSentToCoinbase: 0 0.0000 ETH 17 | // 18 | package main 19 | 20 | import ( 21 | "context" 22 | "flag" 23 | "fmt" 24 | "log" 25 | "math/big" 26 | "sort" 27 | "strings" 28 | "time" 29 | 30 | "github.com/ethereum/go-ethereum/common" 31 | "github.com/ethereum/go-ethereum/core/types" 32 | "github.com/ethereum/go-ethereum/crypto" 33 | "github.com/ethereum/go-ethereum/ethclient" 34 | flashbotsrpc "github.com/metachris/flashbots-rpc" 35 | fbcommon "github.com/metachris/flashbots/common" 36 | "github.com/metachris/go-ethutils/addresslookup" 37 | "github.com/metachris/go-ethutils/utils" 38 | ) 39 | 40 | var addressLookup *addresslookup.AddressLookupService 41 | 42 | func main() { 43 | mevGethUriPtr := flag.String("mevgeth", "", "mev-geth node URI") 44 | gethUriPtr := flag.String("geth", "", "geth node URI for tx lookup") 45 | blockHash := flag.String("hash", "", "hash of block to simulate") 46 | blockNumber := flag.Int64("number", -1, "number of block to simulate") 47 | checkTxPtr := flag.Bool("checktx", false, "if non-canonical block, additional transaction checks") 48 | debugPtr := flag.Bool("debug", false, "print debug information") 49 | flag.Parse() 50 | 51 | if *mevGethUriPtr == "" { 52 | log.Fatal("No mev geth URI provided") 53 | } 54 | 55 | if *blockHash == "" && *blockNumber == -1 { 56 | log.Fatal("Either block number or hash is needed") 57 | } 58 | 59 | mevGethClient, err := ethclient.Dial(*mevGethUriPtr) 60 | utils.Perror(err) 61 | fmt.Println("Connected to", *mevGethUriPtr) 62 | gethClient := mevGethClient 63 | 64 | if *gethUriPtr != "" && *gethUriPtr != *mevGethUriPtr { 65 | gethClient, err = ethclient.Dial(*gethUriPtr) 66 | utils.Perror(err) 67 | fmt.Println("Connected to", *gethUriPtr) 68 | } 69 | 70 | addressLookup = addresslookup.NewAddressLookupService(gethClient) 71 | err = addressLookup.AddAllAddresses() 72 | if err != nil { 73 | fmt.Println("addresslookup error:", err) 74 | } 75 | 76 | fmt.Println("Downloading block...") 77 | var block *types.Block 78 | var canonicalBlock *types.Block 79 | isCanonicalBlock := true 80 | 81 | if *blockHash != "" { 82 | hash := common.HexToHash(*blockHash) 83 | block, err = mevGethClient.BlockByHash(context.Background(), hash) 84 | utils.Perror(err) 85 | 86 | // Get block by number, to check if the block is from canonical chain or was reorg'ed 87 | canonicalBlock, err = mevGethClient.BlockByNumber(context.Background(), block.Number()) 88 | utils.Perror(err) 89 | if block.Hash() != canonicalBlock.Hash() { 90 | isCanonicalBlock = false 91 | } 92 | 93 | } else { 94 | block, err = mevGethClient.BlockByNumber(context.Background(), big.NewInt(*blockNumber)) 95 | utils.Perror(err) 96 | } 97 | 98 | fmt.Println("") 99 | printBlock(block) 100 | if !isCanonicalBlock { 101 | fmt.Print("- Block is not in canonical chain. Was replaced by:\n ") 102 | printBlock(canonicalBlock) 103 | } 104 | 105 | if len(block.Transactions()) == 0 { 106 | fmt.Println("No transactions in this block") 107 | return 108 | } 109 | 110 | rpc := flashbotsrpc.NewFlashbotsRPC(*mevGethUriPtr) 111 | rpc.Debug = *debugPtr 112 | 113 | fmt.Println("\nSimulating block...") 114 | privateKey, _ := crypto.GenerateKey() 115 | result, err := rpc.FlashbotsSimulateBlock(privateKey, block, 0) 116 | utils.Perror(err) 117 | fmt.Println("Simulation result:") 118 | fmt.Printf("- CoinbaseDiff: %22s %10s ETH\n", result.CoinbaseDiff, weiStrToEthStr(result.CoinbaseDiff, 4)) 119 | fmt.Printf("- GasFees: %22s %10s ETH\n", result.GasFees, weiStrToEthStr(result.GasFees, 4)) 120 | fmt.Printf("- EthSentToCoinbase: %22s %10s ETH\n", result.EthSentToCoinbase, weiStrToEthStr(result.EthSentToCoinbase, 4)) 121 | 122 | blockCbDiffWei := big.NewFloat(0) 123 | blockCbDiffWei, _ = blockCbDiffWei.SetString(result.CoinbaseDiff) 124 | 125 | // sort transactions by coinbasediff 126 | sort.Slice(result.Results, func(i, j int) bool { 127 | a := fbcommon.StrToBigInt(result.Results[i].CoinbaseDiff) 128 | b := fbcommon.StrToBigInt(result.Results[j].CoinbaseDiff) 129 | return a.Cmp(b) == 1 130 | }) 131 | 132 | numTxNeededFor80PercentValue := 0 133 | currentValue := big.NewFloat(0) 134 | 135 | // If an address is receiving at least 2 tx, load the address info 136 | _addressUsed := make(map[string]bool) 137 | for _, entry := range result.Results { 138 | if _addressUsed[entry.ToAddress] { 139 | addressLookup.GetAddressDetail(entry.ToAddress) 140 | } 141 | _addressUsed[entry.ToAddress] = true 142 | } 143 | 144 | fmt.Println("\nTransactions:") 145 | txInclusionBlocks := make(map[uint64]int) 146 | for i, entry := range result.Results { 147 | _incl := "" 148 | if *checkTxPtr && !isCanonicalBlock { 149 | // Where was this tx included? 150 | r, err := gethClient.TransactionReceipt(context.Background(), common.HexToHash(entry.TxHash)) 151 | if err != nil { 152 | _incl = fmt.Sprintf("included-in: - (%s)", err) 153 | txInclusionBlocks[0] += 1 154 | } else { 155 | _incl = fmt.Sprintf("included-in: %s", r.BlockNumber) 156 | txInclusionBlocks[r.BlockNumber.Uint64()] += 1 157 | } 158 | } 159 | 160 | _to := entry.ToAddress 161 | if detail, found := addressLookup.Cache[strings.ToLower(entry.ToAddress)]; found && len(detail.Name) > 0 { 162 | _to = fmt.Sprintf("%s (%s)", _to, detail.Name) 163 | } 164 | fmt.Printf("%4d %s \t cbD=%8s, gasFee=%8s, ethSentToCb=%8s \t to=%-64s %s\n", i+1, entry.TxHash, weiStrToEthStr(entry.CoinbaseDiff, 4), weiStrToEthStr(entry.GasFees, 4), weiStrToEthStr(entry.EthSentToCoinbase, 4), _to, _incl) 165 | 166 | cbDiffWei := new(big.Float) 167 | cbDiffWei, _ = cbDiffWei.SetString(entry.CoinbaseDiff) 168 | 169 | currentValue = new(big.Float).Add(currentValue, cbDiffWei) 170 | 171 | percentValueReached := new(big.Float).Quo(currentValue, blockCbDiffWei) 172 | // fmt.Println(percentValueReached.Text('f', 4)) 173 | if numTxNeededFor80PercentValue == 0 && percentValueReached.Cmp(big.NewFloat(0.8)) > -1 { 174 | numTxNeededFor80PercentValue = i 175 | } 176 | } 177 | 178 | fmt.Printf("\n%d/%d tx needed for 80%% of miner value.\n", numTxNeededFor80PercentValue, len(result.Results)) 179 | 180 | if *checkTxPtr && !isCanonicalBlock { 181 | fmt.Println("\nTransactions included in these blocks:") 182 | for blockNum, count := range txInclusionBlocks { 183 | _blockNum := fmt.Sprintf("%d", blockNum) 184 | if blockNum == 0 { 185 | _blockNum = "missing" 186 | } 187 | 188 | if blockNum == block.NumberU64() { 189 | _blockNum += " (sibling)" 190 | } 191 | 192 | fmt.Printf("- %-8s: %3d\n", _blockNum, count) 193 | } 194 | } 195 | } 196 | 197 | func printBlock(block *types.Block) { 198 | t := time.Unix(int64(block.Header().Time), 0).UTC() 199 | miner := block.Coinbase().Hex() 200 | if details, found := addressLookup.GetAddressDetail(block.Coinbase().Hex()); found { 201 | miner += " (" + details.Name + ")" 202 | } 203 | fmt.Printf("Block %d %s \t %s \t tx=%d, uncles=%d, miner: %s\n", block.NumberU64(), block.Hash(), t, len(block.Transactions()), len(block.Uncles()), miner) 204 | } 205 | 206 | func weiStrToEthStr(weiStr string, decimals int) string { 207 | i := new(big.Int) 208 | i.SetString(weiStr, 10) 209 | return utils.WeiBigIntToEthString(i, decimals) 210 | } 211 | -------------------------------------------------------------------------------- /cmd/examples/blocks-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/metachris/flashbots/api" 8 | ) 9 | 10 | func main() { 11 | // Blocks API: default 12 | block, err := api.GetBlocks(nil) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | fmt.Println("latest block:", block.LatestBlockNumber) 18 | fmt.Println(len(block.Blocks), "blocks") 19 | fmt.Println("bundle_type:", block.Blocks[0].Transactions[0].BundleType) 20 | fmt.Println("is_flashbots:", block.Blocks[0].Transactions[0].BundleType == api.BundleTypeFlashbots) 21 | fmt.Println("is_rogue:", block.Blocks[0].Transactions[0].BundleType == api.BundleTypeRogue) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/experiments/unclecheck2/main.go: -------------------------------------------------------------------------------- 1 | // Scan a certain time range for uncle blocks, and collect statistics about miners 2 | package main 3 | 4 | import ( 5 | "context" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "math/big" 10 | "os" 11 | "sort" 12 | "sync" 13 | "time" 14 | 15 | "github.com/ethereum/go-ethereum/common" 16 | "github.com/ethereum/go-ethereum/core/types" 17 | "github.com/ethereum/go-ethereum/ethclient" 18 | fbcommon "github.com/metachris/flashbots/common" 19 | "github.com/metachris/go-ethutils/addresslookup" 20 | "github.com/metachris/go-ethutils/utils" 21 | ) 22 | 23 | type MinerStat struct { 24 | Coinbase common.Address 25 | NumBlocks int64 26 | NumUncled int64 // how many blocks by the miner have been uncled (not part of mainchain) 27 | NumUnclings int64 // how many blocks by the miner became mainchain where an alternative exists 28 | NumUnclingsPlus1 int64 // how many blocks did this miner mine that replaced another block, where the miner also mined the following block 29 | 30 | // Calculated afterwards 31 | NumUnclingsPlus1PercentOfBlocks float64 32 | } 33 | 34 | var minerStats map[common.Address]*MinerStat = make(map[common.Address]*MinerStat) 35 | var uncles map[common.Hash]*types.Block = make(map[common.Hash]*types.Block) 36 | var blocksTotal int 37 | 38 | var client *ethclient.Client 39 | var ethNodeUri *string 40 | 41 | var AddressLookup *addresslookup.AddressLookupService 42 | 43 | func main() { 44 | var err error 45 | log.SetOutput(os.Stdout) 46 | 47 | AddressLookup = addresslookup.NewAddressLookupService(nil) 48 | err = AddressLookup.AddAllAddresses() 49 | utils.Perror(err) 50 | 51 | ethNodeUri = flag.String("eth", os.Getenv("xx"), "geth node URI") 52 | startDate := flag.String("start", "", "time offset") 53 | endDate := flag.String("end", "", "time offset") 54 | // endDate := flag.String("end", "", "block number or time offset or date (yyyy-mm-dd) (optional)") 55 | flag.Parse() 56 | 57 | if *ethNodeUri == "" { 58 | log.Fatal("Missing eth node uri") 59 | } 60 | 61 | if *startDate == "" { 62 | log.Fatal("Missing start") 63 | } 64 | 65 | if *endDate == "" { 66 | log.Fatal("Missing end") 67 | } 68 | 69 | fmt.Printf("Connecting to %s ... ", *ethNodeUri) 70 | client, err = ethclient.Dial(*ethNodeUri) 71 | utils.Perror(err) 72 | fmt.Printf("ok\n") 73 | 74 | // Find start block 75 | startTime, err := utils.DateToTime(*startDate, 0, 0, 0) 76 | utils.Perror(err) 77 | startBlockHeader, err := utils.GetFirstBlockHeaderAtOrAfterTime(client, startTime) 78 | utils.Perror(err) 79 | tb1 := time.Unix(int64(startBlockHeader.Time), 0).UTC() 80 | fmt.Println("first block:", startBlockHeader.Number, tb1) 81 | 82 | // Find end block 83 | endTime, err := utils.DateToTime(*endDate, 0, 0, 0) 84 | utils.Perror(err) 85 | 86 | latestBlockHeader, err := utils.GetFirstBlockHeaderAtOrAfterTime(client, endTime) 87 | utils.Perror(err) 88 | tb2 := time.Unix(int64(latestBlockHeader.Time), 0).UTC() 89 | fmt.Println("last block: ", latestBlockHeader.Number, tb2) 90 | 91 | // fmt.Println("blocks", startBlock, "...", endBlock) 92 | timeStartBlockProcessing := time.Now() 93 | FindUncles(startBlockHeader.Number, latestBlockHeader.Number) 94 | 95 | // All done. Stop timer and print 96 | timeNeededBlockProcessing := time.Since(timeStartBlockProcessing) 97 | 98 | fmt.Printf("All done in %.3fs\n", timeNeededBlockProcessing.Seconds()) 99 | } 100 | 101 | func FindUncles(startBlockNumber *big.Int, endBlockNumber *big.Int) { 102 | if startBlockNumber == nil || endBlockNumber == nil { 103 | log.Fatal("error: FindUncles blockNumber is nil") 104 | } 105 | 106 | blockChan := make(chan *types.Block, 100) 107 | 108 | // Start block processor 109 | var analyzeLock sync.Mutex 110 | go func() { 111 | analyzeLock.Lock() 112 | defer analyzeLock.Unlock() // we unlock when done 113 | 114 | for block := range blockChan { 115 | fbcommon.PrintBlock(block) 116 | blocksTotal += 1 117 | 118 | if _, found := minerStats[block.Coinbase()]; !found { 119 | minerStats[block.Coinbase()] = &MinerStat{Coinbase: block.Coinbase()} 120 | } 121 | 122 | minerStats[block.Coinbase()].NumBlocks += 1 123 | 124 | // Download the uncles for processing later 125 | for _, uncleHeader := range block.Uncles() { 126 | fmt.Printf("Downloading uncle %s...\n", uncleHeader.Hash()) 127 | uncleBlock, err := client.BlockByHash(context.Background(), uncleHeader.Hash()) 128 | if err != nil { 129 | fmt.Println("- error:", err) 130 | continue 131 | } 132 | uncles[uncleHeader.Hash()] = uncleBlock 133 | } 134 | } 135 | }() 136 | 137 | // Start getting blocks 138 | fbcommon.GetBlocks(blockChan, client, startBlockNumber.Int64(), endBlockNumber.Int64(), 15) 139 | 140 | // Wait until all blocks have been processed 141 | close(blockChan) 142 | analyzeLock.Lock() 143 | 144 | // Process the uncles: collect the mainchain block and the next one, and add to minerStats 145 | i := 0 146 | for _, uncleBlock := range uncles { 147 | if uncleBlock == nil { 148 | fmt.Println("err - block is nil") 149 | continue 150 | } 151 | 152 | if _, found := minerStats[uncleBlock.Coinbase()]; !found { 153 | minerStats[uncleBlock.Coinbase()] = &MinerStat{Coinbase: uncleBlock.Coinbase()} 154 | } 155 | 156 | // For each uncle, add stats 157 | minerStats[uncleBlock.Coinbase()].NumBlocks += 1 158 | minerStats[uncleBlock.Coinbase()].NumUncled += 1 159 | 160 | // get sibling block (mainchain block at height of uncleBlock) 161 | mainChainBlock, err := client.BlockByNumber(context.Background(), uncleBlock.Number()) 162 | utils.Perror(err) 163 | if _, found := minerStats[mainChainBlock.Coinbase()]; !found { 164 | minerStats[mainChainBlock.Coinbase()] = &MinerStat{Coinbase: mainChainBlock.Coinbase()} 165 | } 166 | minerStats[mainChainBlock.Coinbase()].NumUnclings += 1 167 | 168 | // get next block 169 | nextHeight := new(big.Int).Add(uncleBlock.Number(), common.Big1) 170 | mainChainBlockChild1, err := client.BlockByNumber(context.Background(), nextHeight) 171 | utils.Perror(err) 172 | 173 | if mainChainBlock.Coinbase() == mainChainBlockChild1.Coinbase() { 174 | fmt.Printf("uncling+1 at %d by miner: %s (block %d / %d)\n", mainChainBlock.NumberU64(), mainChainBlock.Coinbase(), i, len(uncles)) 175 | minerStats[mainChainBlock.Coinbase()].NumUnclingsPlus1 += 1 176 | } 177 | 178 | i += 1 179 | } 180 | 181 | // Compute percentage of unclings+1 compared to mined blocks by each miner 182 | for _, minerStat := range minerStats { 183 | if minerStat.NumBlocks == 0 { 184 | fmt.Println("x minerstat with 0 blocks:", minerStat.Coinbase, minerStat.NumBlocks, minerStat.NumUncled, minerStat.NumUnclingsPlus1) 185 | } 186 | minerStat.NumUnclingsPlus1PercentOfBlocks = float64(minerStat.NumUnclingsPlus1) / float64(minerStat.NumBlocks) 187 | } 188 | 189 | // Sort 190 | keys := make([]common.Address, 0, len(minerStats)) 191 | for key := range minerStats { 192 | keys = append(keys, key) 193 | } 194 | sort.Slice(keys, func(i, j int) bool { 195 | return minerStats[keys[i]].NumUnclingsPlus1PercentOfBlocks > minerStats[keys[j]].NumUnclingsPlus1PercentOfBlocks 196 | }) 197 | 198 | // Print sorted results 199 | for _, key := range keys { 200 | stat := minerStats[key] 201 | fmt.Printf("%s \t numBlocks=%4d, numUnclings=%4d, numUnclings+1=%4d \t perc=%.6f\n", key, stat.NumBlocks, stat.NumUnclings, stat.NumUnclingsPlus1, stat.NumUnclingsPlus1PercentOfBlocks) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /cmd/experiments/unclecounts/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | "os" 10 | "sort" 11 | "sync" 12 | "time" 13 | 14 | "github.com/ethereum/go-ethereum/common" 15 | "github.com/ethereum/go-ethereum/core/types" 16 | "github.com/ethereum/go-ethereum/ethclient" 17 | fbcommon "github.com/metachris/flashbots/common" 18 | "github.com/metachris/go-ethutils/addresslookup" 19 | "github.com/metachris/go-ethutils/utils" 20 | ) 21 | 22 | var client *ethclient.Client 23 | var mevGethUri string 24 | 25 | var AddressLookup *addresslookup.AddressLookupService 26 | var minerUncles map[common.Address]uint64 = make(map[common.Address]uint64) // number of uncles per miner 27 | var minersWhoHadMainSibling map[common.Address]uint64 = make(map[common.Address]uint64) // number of blocks that have an uncles, per miner 28 | var minerBlockTotal map[common.Address]uint64 = make(map[common.Address]uint64) // number of uncles per miner 29 | var unclesTotal int 30 | var blocksTotal int 31 | 32 | func main() { 33 | var err error 34 | log.SetOutput(os.Stdout) 35 | 36 | AddressLookup = addresslookup.NewAddressLookupService(nil) 37 | err = AddressLookup.AddAllAddresses() 38 | utils.Perror(err) 39 | 40 | mevGethUri = *flag.String("eth", os.Getenv("MEVGETH_NODE"), "mev-geth node URI") 41 | startDate := flag.String("start", "", "time offset") 42 | endDate := flag.String("end", "", "time offset") 43 | // endDate := flag.String("end", "", "block number or time offset or date (yyyy-mm-dd) (optional)") 44 | flag.Parse() 45 | 46 | if mevGethUri == "" { 47 | log.Fatal("Missing eth node uri") 48 | } 49 | 50 | if *startDate == "" { 51 | log.Fatal("Missing start") 52 | } 53 | 54 | if *endDate == "" { 55 | log.Fatal("Missing end") 56 | } 57 | 58 | fmt.Printf("Connecting to %s ... ", mevGethUri) 59 | client, err = ethclient.Dial(mevGethUri) 60 | utils.Perror(err) 61 | fmt.Printf("ok\n") 62 | 63 | // Find start block 64 | startTime, err := utils.DateToTime(*startDate, 0, 0, 0) 65 | utils.Perror(err) 66 | // startTimeOffsetSec, err := fbcommon.TimeStringToSec(*startDate) 67 | // utils.Perror(err) 68 | // now := time.Now() 69 | // startTime := now.Add(time.Duration(startTimeOffsetSec) * time.Second * -1) 70 | startBlockHeader, err := utils.GetFirstBlockHeaderAtOrAfterTime(client, startTime) 71 | utils.Perror(err) 72 | tb1 := time.Unix(int64(startBlockHeader.Time), 0).UTC() 73 | fmt.Println("first block:", startBlockHeader.Number, tb1) 74 | 75 | // Find end block 76 | endTime, err := utils.DateToTime(*endDate, 0, 0, 0) 77 | utils.Perror(err) 78 | 79 | // latestBlockHeader, err := client.HeaderByNumber(context.Background(), nil) 80 | latestBlockHeader, err := utils.GetFirstBlockHeaderAtOrAfterTime(client, endTime) 81 | utils.Perror(err) 82 | tb2 := time.Unix(int64(latestBlockHeader.Time), 0).UTC() 83 | fmt.Println("last block: ", latestBlockHeader.Number, tb2) 84 | 85 | // fmt.Println("blocks", startBlock, "...", endBlock) 86 | timeStartBlockProcessing := time.Now() 87 | FindUncles(startBlockHeader.Number, latestBlockHeader.Number) 88 | 89 | // All done. Stop timer and print 90 | timeNeededBlockProcessing := time.Since(timeStartBlockProcessing) 91 | // PrintResult() 92 | PrintResultUncler() 93 | 94 | fmt.Printf("All done in %.3fs\n", timeNeededBlockProcessing.Seconds()) 95 | } 96 | 97 | func FindUncles(startBlockNumber *big.Int, endBlockNumber *big.Int) { 98 | if startBlockNumber == nil || endBlockNumber == nil { 99 | log.Fatal("error: FindUncles blockNumber is nil") 100 | } 101 | 102 | blockChan := make(chan *types.Block, 100) 103 | 104 | // Start block processor 105 | var analyzeLock sync.Mutex 106 | go func() { 107 | analyzeLock.Lock() 108 | defer analyzeLock.Unlock() // we unlock when done 109 | 110 | for block := range blockChan { 111 | blocksTotal += 1 112 | fbcommon.PrintBlock(block) 113 | minerBlockTotal[block.Coinbase()] += 1 114 | if len(block.Uncles()) > 0 { 115 | // get ancestor of the uncle, the block who replaced the uncle in the chain 116 | parentBlockNumber := new(big.Int).Sub(block.Number(), common.Big1) 117 | parentBlock, err := client.BlockByNumber(context.Background(), parentBlockNumber) 118 | if err != nil { 119 | fmt.Println("- error: ", err) 120 | } else { 121 | minersWhoHadMainSibling[parentBlock.Coinbase()] += 1 122 | } 123 | 124 | for _, uncleHeader := range block.Uncles() { 125 | unclesTotal += 1 126 | minerUncles[uncleHeader.Coinbase] += 1 127 | } 128 | } 129 | } 130 | }() 131 | 132 | fbcommon.GetBlocks(blockChan, client, startBlockNumber.Int64(), endBlockNumber.Int64(), 15) 133 | 134 | close(blockChan) 135 | analyzeLock.Lock() // wait until all blocks have been processed 136 | } 137 | 138 | func PrintResult() { 139 | // sort miners by number of uncles 140 | keys := make([]common.Address, 0, len(minerUncles)) 141 | for key := range minerUncles { 142 | keys = append(keys, key) 143 | } 144 | sort.Slice(keys, func(i, j int) bool { return minerBlockTotal[keys[i]] > minerBlockTotal[keys[j]] }) 145 | 146 | for _, key := range keys { 147 | minerStr := key.Hex() 148 | minerAddr, found := AddressLookup.GetAddressDetail(key.Hex()) 149 | if found { 150 | minerStr += fmt.Sprintf(" %s", minerAddr.Name) 151 | } 152 | 153 | numUncles := minerUncles[key] 154 | totalBlocks := minerBlockTotal[key] 155 | p := float64(numUncles) / float64(totalBlocks) * 100 156 | fmt.Printf("%-60s %3d / %5d = %6.2f %%\n", minerStr, numUncles, totalBlocks, p) 157 | } 158 | 159 | unclePercentage := float64(unclesTotal) / float64(blocksTotal) * 100 160 | fmt.Printf("%d uncles / %d valid blocks = %.2f %%\n", unclesTotal, blocksTotal, unclePercentage) 161 | } 162 | 163 | func PrintResultUncler() { 164 | fmt.Println("Summery of unclers") 165 | // sort miners by number of unclings 166 | keys := make([]common.Address, 0, len(minersWhoHadMainSibling)) 167 | for key := range minersWhoHadMainSibling { 168 | keys = append(keys, key) 169 | } 170 | sort.Slice(keys, func(i, j int) bool { return minerBlockTotal[keys[i]] > minerBlockTotal[keys[j]] }) 171 | 172 | for _, key := range keys { 173 | minerStr := key.Hex() 174 | minerAddr, found := AddressLookup.GetAddressDetail(key.Hex()) 175 | if found { 176 | minerStr += fmt.Sprintf(" %s", minerAddr.Name) 177 | } 178 | 179 | numTotalBlocks := minerBlockTotal[key] 180 | numUnclings := minersWhoHadMainSibling[key] 181 | numUncles := minerUncles[key] 182 | 183 | unclingsPercent := float64(numUnclings) / float64(numTotalBlocks) * 100 184 | unclePercent := float64(numUncles) / float64(numTotalBlocks) * 100 185 | fmt.Printf("%-60s includedBlocks: %5d \t blocksWithSiblings: %4d (%6.2f %%) \t unclesProduced: %5d (%6.2f %%)\n", minerStr, numTotalBlocks, numUnclings, unclingsPercent, numUncles, unclePercent) 186 | } 187 | 188 | unclePercentage := float64(unclesTotal) / float64(blocksTotal) * 100 189 | fmt.Printf("%d uncles / %d valid blocks = %.2f %%\n", unclesTotal, blocksTotal, unclePercentage) 190 | } 191 | -------------------------------------------------------------------------------- /cmd/history-check/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metachris/flashbots/0589310929203e4e8f49a7dfd4a53831d42cb4cf/cmd/history-check/README.md -------------------------------------------------------------------------------- /cmd/history-check/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "sync" 9 | "time" 10 | 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | "github.com/metachris/flashbots/blockcheck" 13 | "github.com/metachris/go-ethutils/blockswithtx" 14 | "github.com/metachris/go-ethutils/utils" 15 | ) 16 | 17 | var errorSummary blockcheck.ErrorSummary = blockcheck.NewErrorSummary() 18 | 19 | func main() { 20 | log.SetOutput(os.Stdout) 21 | 22 | ethUri := flag.String("eth", os.Getenv("ETH_NODE"), "Ethereum node URI") 23 | startDate := flag.String("start", "", "date (yyyy-mm-dd)") 24 | endDate := flag.String("end", "", "date (yyyy-mm-dd)") 25 | flag.Parse() 26 | 27 | if *startDate == "" || *endDate == "" { 28 | log.Fatal("Missing date") 29 | } 30 | 31 | if *ethUri == "" { 32 | log.Fatal("Missing eth node uri") 33 | } 34 | 35 | fmt.Printf("Connecting to %s ... ", *ethUri) 36 | client, err := ethclient.Dial(*ethUri) 37 | utils.Perror(err) 38 | fmt.Printf("ok\n") 39 | 40 | startTime, err := utils.DateToTime(*startDate, 0, 0, 0) 41 | utils.Perror(err) 42 | startBlockHeader, err := utils.GetFirstBlockHeaderAtOrAfterTime(client, startTime) 43 | utils.Perror(err) 44 | startBlock := startBlockHeader.Number.Int64() 45 | 46 | endTime, err := utils.DateToTime(*endDate, 0, 0, 0) 47 | // endTime, err := utils.DateToTime(*startDate, 5, 10, 0) 48 | utils.Perror(err) 49 | endBlockHeader, err := utils.GetFirstBlockHeaderAtOrAfterTime(client, endTime) 50 | utils.Perror(err) 51 | endBlock := endBlockHeader.Number.Int64() 52 | 53 | fmt.Println("blocks", startBlock, "...", endBlock) 54 | 55 | timestampMainStart := time.Now() // for measuring execution time 56 | 57 | // Prefetch Flashbots blocks 58 | fmt.Print("Caching flashbots blocks... ") 59 | blockcheck.CacheFlashbotsBlocks(startBlock, endBlock) 60 | fmt.Print("done\n") 61 | 62 | // Start fetching blocks 63 | blockChan := make(chan *blockswithtx.BlockWithTxReceipts, 100) // channel for resulting BlockWithTxReceipt 64 | 65 | // Start block processor 66 | var numBlocksProcessed int 67 | var numTxProcessed int 68 | var analyzeLock sync.Mutex 69 | go func() { 70 | analyzeLock.Lock() 71 | defer analyzeLock.Unlock() // we unlock when done 72 | 73 | for block := range blockChan { 74 | numBlocksProcessed += 1 75 | numTxProcessed += len(block.Block.Transactions()) 76 | processBlockWithReceipts(block, client) 77 | } 78 | }() 79 | 80 | // Start fetching and processing blocks 81 | blockswithtx.GetBlocksWithTxReceipts(client, blockChan, startBlock, endBlock, 15) 82 | 83 | // Wait for processing to finish 84 | fmt.Println("Waiting for Analysis workers...") 85 | close(blockChan) 86 | analyzeLock.Lock() // wait until all blocks have been processed 87 | 88 | fmt.Println(errorSummary.String()) 89 | 90 | timeNeeded := time.Since(timestampMainStart) 91 | fmt.Printf("Analysis of %s blocks, %s transactions finished in %.2fs\n", utils.NumberToHumanReadableString(numBlocksProcessed, 0), utils.NumberToHumanReadableString(numTxProcessed, 0), timeNeeded.Seconds()) 92 | } 93 | 94 | func processBlockWithReceipts(block *blockswithtx.BlockWithTxReceipts, client *ethclient.Client) { 95 | utils.PrintBlock(block.Block) 96 | check, err := blockcheck.CheckBlock(block, true) 97 | utils.Perror(err) 98 | 99 | if check.HasSeriousErrors() || check.HasLessSeriousErrors() { // update and print miner error count on serious and less-serious errors 100 | errorSummary.AddCheckErrors(check) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /common/bundle.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/metachris/flashbots/api" 7 | ) 8 | 9 | type Bundle struct { 10 | Index int64 11 | Transactions []api.FlashbotsTransaction 12 | TotalMinerReward *big.Int 13 | TotalCoinbaseTransfer *big.Int 14 | TotalGasUsed *big.Int 15 | 16 | CoinbaseDivGasUsed *big.Int 17 | RewardDivGasUsed *big.Int 18 | 19 | PercentPriceDiff *big.Float // on order error, % difference to previous bundle 20 | 21 | IsOutOfOrder bool 22 | IsPayingLessThanLowestTx bool 23 | Is0EffectiveGasPrice bool 24 | IsNegativeEffectiveGasPrice bool 25 | } 26 | 27 | func NewBundle() *Bundle { 28 | return &Bundle{ 29 | TotalMinerReward: new(big.Int), 30 | TotalCoinbaseTransfer: new(big.Int), 31 | TotalGasUsed: new(big.Int), 32 | CoinbaseDivGasUsed: new(big.Int), 33 | RewardDivGasUsed: new(big.Int), 34 | PercentPriceDiff: new(big.Float), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/minerearnings.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "math/big" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/core/types" 9 | "github.com/ethereum/go-ethereum/ethclient" 10 | ) 11 | 12 | type EarningsService struct { 13 | client *ethclient.Client 14 | MinerEarningsByBlockHash map[common.Hash]*big.Int 15 | } 16 | 17 | func NewEarningsService(client *ethclient.Client) *EarningsService { 18 | return &EarningsService{ 19 | client: client, 20 | MinerEarningsByBlockHash: make(map[common.Hash]*big.Int), 21 | } 22 | } 23 | 24 | func (es *EarningsService) GetBlockCoinbaseEarningsWithoutCache(block *types.Block) (*big.Int, error) { 25 | balanceAfterBlock, err := es.client.BalanceAt(context.Background(), block.Coinbase(), block.Number()) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | balanceBeforeBlock, err := es.client.BalanceAt(context.Background(), block.Coinbase(), new(big.Int).Sub(block.Number(), common.Big1)) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | earnings := new(big.Int).Sub(balanceAfterBlock, balanceBeforeBlock) 36 | 37 | // Iterate over all transactions - add sent value back into earnings, remove received value 38 | for _, tx := range block.Transactions() { 39 | from, fromErr := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx) 40 | to := tx.To() 41 | txIsFromCoinbase := fromErr == nil && from == block.Coinbase() 42 | txIsToCoinbase := to != nil && *to == block.Coinbase() 43 | 44 | if txIsFromCoinbase { 45 | earnings = new(big.Int).Add(earnings, tx.Value()) 46 | } 47 | 48 | if txIsToCoinbase { 49 | earnings = new(big.Int).Sub(earnings, tx.Value()) 50 | } 51 | } 52 | 53 | return earnings, nil 54 | } 55 | 56 | func (es *EarningsService) GetBlockCoinbaseEarnings(block *types.Block) (*big.Int, error) { 57 | var err error 58 | 59 | earnings, found := es.MinerEarningsByBlockHash[block.Hash()] 60 | if found { 61 | return earnings, nil 62 | } 63 | 64 | earnings, err = es.GetBlockCoinbaseEarningsWithoutCache(block) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | es.MinerEarningsByBlockHash[block.Hash()] = earnings 70 | return earnings, nil 71 | } 72 | -------------------------------------------------------------------------------- /common/utils.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "log" 9 | "math/big" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "github.com/ethereum/go-ethereum/core/types" 17 | "github.com/ethereum/go-ethereum/ethclient" 18 | ) 19 | 20 | func StrToBigInt(s string) *big.Int { 21 | i := new(big.Int) 22 | i.SetString(s, 10) 23 | return i 24 | } 25 | 26 | func BigFloatToEString(f *big.Float, prec int) string { 27 | s1 := f.Text('f', 0) 28 | if len(s1) >= 16 { 29 | f2 := new(big.Float).Quo(f, big.NewFloat(1e18)) 30 | s := f2.Text('f', prec) 31 | return s + "e+18" 32 | } else if len(s1) >= 9 { 33 | f2 := new(big.Float).Quo(f, big.NewFloat(1e9)) 34 | s := f2.Text('f', prec) 35 | return s + "e+09" 36 | } 37 | return f.Text('f', prec) 38 | } 39 | 40 | func BigIntToEString(i *big.Int, prec int) string { 41 | f := new(big.Float) 42 | f.SetInt(i) 43 | s1 := f.Text('f', 0) 44 | if len(s1) < 9 { 45 | return i.String() 46 | } 47 | return BigFloatToEString(f, prec) 48 | } 49 | 50 | func TimeStringToSec(s string) (timespanSec int, err error) { 51 | isNegativeNumber := strings.HasPrefix(s, "-") 52 | if isNegativeNumber { 53 | s = s[1:] 54 | } 55 | 56 | var sec int = 0 57 | switch { 58 | case strings.HasSuffix(s, "s"): 59 | sec, err = strconv.Atoi(strings.TrimSuffix(s, "s")) 60 | case strings.HasSuffix(s, "m"): 61 | sec, err = strconv.Atoi(strings.TrimSuffix(s, "m")) 62 | sec *= 60 63 | case strings.HasSuffix(s, "h"): 64 | sec, err = strconv.Atoi(strings.TrimSuffix(s, "h")) 65 | sec *= 60 * 60 66 | case strings.HasSuffix(s, "d"): 67 | sec, err = strconv.Atoi(strings.TrimSuffix(s, "d")) 68 | sec *= 60 * 60 * 24 69 | default: 70 | err = errors.New("couldn't detect time format") 71 | } 72 | if isNegativeNumber { 73 | sec *= -1 // make negative 74 | } 75 | 76 | return sec, err 77 | } 78 | 79 | func TxToRlp(tx *types.Transaction) string { 80 | var buff bytes.Buffer 81 | tx.EncodeRLP(&buff) 82 | return fmt.Sprintf("%x", buff.Bytes()) 83 | } 84 | 85 | func BlockToRlp(block *types.Block) string { 86 | var buff bytes.Buffer 87 | block.EncodeRLP(&buff) 88 | return fmt.Sprintf("%x", buff.Bytes()) 89 | } 90 | 91 | func EnvStr(key string, defaultvalue string) string { 92 | res := os.Getenv(key) 93 | if res != "" { 94 | return res 95 | } 96 | return defaultvalue 97 | } 98 | 99 | func GetBlocks(blockChan chan<- *types.Block, client *ethclient.Client, startBlock int64, endBlock int64, concurrency int) { 100 | var blockWorkerWg sync.WaitGroup 101 | blockHeightChan := make(chan int64, 100) // blockHeight to fetch with receipts 102 | 103 | // Start eth client thread pool 104 | for w := 1; w <= concurrency; w++ { 105 | blockWorkerWg.Add(1) 106 | 107 | // Worker gets a block height from blockHeightChan, downloads it, and puts it in the blockChan 108 | go func() { 109 | defer blockWorkerWg.Done() 110 | for blockHeight := range blockHeightChan { 111 | // fmt.Println(blockHeight) 112 | block, err := client.BlockByNumber(context.Background(), big.NewInt(blockHeight)) 113 | if err != nil { 114 | log.Println("Error getting block:", blockHeight, err) 115 | continue 116 | } 117 | blockChan <- block 118 | } 119 | }() 120 | } 121 | 122 | // Push blocks into channel, for workers to pick up 123 | for currentBlockNumber := startBlock; currentBlockNumber <= endBlock; currentBlockNumber++ { 124 | blockHeightChan <- currentBlockNumber 125 | } 126 | 127 | // Close worker channel and wait for workers to finish 128 | close(blockHeightChan) 129 | blockWorkerWg.Wait() 130 | } 131 | 132 | func PrintBlock(block *types.Block) { 133 | t := time.Unix(int64(block.Header().Time), 0).UTC() 134 | unclesStr := "" 135 | if len(block.Uncles()) > 0 { 136 | unclesStr = fmt.Sprintf("uncles=%d", len(block.Uncles())) 137 | } 138 | fmt.Printf("Block %d %s \t miner: %s \t %s \t tx=%-4d \t gas=%d \t %s\n", block.NumberU64(), block.Hash(), block.Coinbase(), t, len(block.Transactions()), block.GasUsed(), unclesStr) 139 | } 140 | 141 | var ColorGreen = "\033[1;32m%s\033[0m" 142 | -------------------------------------------------------------------------------- /common/utils_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | ) 7 | 8 | func TestBigFloatToEString(t *testing.T) { 9 | f19 := big.NewFloat(2255212009613187301) 10 | f18 := big.NewFloat(225521200961318730) 11 | f17 := big.NewFloat(22552120096131873) 12 | f16 := big.NewFloat(2255212009613187) 13 | f10 := big.NewFloat(2255212009610) 14 | f9 := big.NewFloat(225521200961) 15 | f8 := big.NewFloat(225521200961) 16 | f3 := big.NewFloat(225) 17 | 18 | s := BigFloatToEString(f19, 3) 19 | if s != "2.255e+18" { 20 | t.Error("Unexpected result string from BigFloatToEString:", s) 21 | } 22 | 23 | s = BigFloatToEString(f18, 3) 24 | if s != "0.226e+18" { 25 | t.Error("Unexpected result string from BigFloatToEString:", s) 26 | } 27 | 28 | s = BigFloatToEString(f17, 3) 29 | if s != "0.023e+18" { 30 | t.Error("Unexpected result string from BigFloatToEString:", s) 31 | } 32 | 33 | s = BigFloatToEString(f16, 3) 34 | if s != "0.002e+18" { 35 | t.Error("Unexpected result string from BigFloatToEString:", s) 36 | } 37 | 38 | s = BigFloatToEString(f10, 3) 39 | if s != "2255.212e+09" { 40 | t.Error("Unexpected result string from BigFloatToEString:", s) 41 | } 42 | 43 | s = BigFloatToEString(f9, 3) 44 | if s != "225.521e+09" { 45 | t.Error("Unexpected result string from BigFloatToEString:", s) 46 | } 47 | 48 | s = BigFloatToEString(f8, 3) 49 | if s != "225.521e+09" { 50 | t.Error("Unexpected result string from BigFloatToEString:", s) 51 | } 52 | 53 | s = BigFloatToEString(f3, 3) 54 | if s != "225.000" { 55 | t.Error("Unexpected result string from BigFloatToEString:", s) 56 | } 57 | } 58 | 59 | func TestBigIntToEString(t *testing.T) { 60 | i19 := big.NewInt(2255212009613187301) 61 | s := BigIntToEString(i19, 3) 62 | if s != "2.255e+18" { 63 | t.Error("Unexpected result string from BigFloatToEString:", s) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /flashbotsutils/flashbotsutils.go: -------------------------------------------------------------------------------- 1 | package flashbotsutils 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/ethereum/go-ethereum/core/types" 7 | "github.com/metachris/flashbots/api" 8 | ) 9 | 10 | var ( 11 | ErrFlashbotsApiDoesntHaveThatBlockYet = errors.New("flashbots API latest height < requested block height") 12 | ) 13 | 14 | // Cache for last Flashbots API call (avoids calling multiple times per block) 15 | type FlashbotsApiReqRes struct { 16 | RequestBlock int64 17 | Response api.GetBlocksResponse 18 | } 19 | 20 | var flashbotsApiResponseCache FlashbotsApiReqRes 21 | 22 | // IsFlashbotsTx is a utility for confirming if a specific transactions is actually a Flashbots one 23 | func IsFlashbotsTx(block *types.Block, tx *types.Transaction) (isFlashbotsTx bool, response api.GetBlocksResponse, err error) { 24 | if flashbotsApiResponseCache.RequestBlock == block.Number().Int64() { 25 | isFlashbotsTx = flashbotsApiResponseCache.Response.HasTx(tx.Hash().String()) 26 | return isFlashbotsTx, flashbotsApiResponseCache.Response, nil 27 | } 28 | 29 | opts := api.GetBlocksOptions{BlockNumber: block.Number().Int64()} 30 | flashbotsResponse, err := api.GetBlocks(&opts) 31 | if err != nil { 32 | return isFlashbotsTx, flashbotsResponse, err 33 | } 34 | 35 | if flashbotsResponse.LatestBlockNumber < block.Number().Int64() { // block is not yet processed by Flashbots 36 | return isFlashbotsTx, flashbotsResponse, ErrFlashbotsApiDoesntHaveThatBlockYet 37 | } 38 | 39 | flashbotsApiResponseCache.RequestBlock = block.Number().Int64() 40 | flashbotsApiResponseCache.Response = flashbotsResponse 41 | 42 | flashbotsTx := flashbotsResponse.GetTxMap() 43 | _, exists := flashbotsTx[tx.Hash().String()] 44 | return exists, flashbotsResponse, nil 45 | } 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/metachris/flashbots 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/btcsuite/btcd v0.22.0-beta // indirect 7 | github.com/ethereum/go-ethereum v1.10.7 8 | github.com/metachris/flashbots-rpc v0.1.2 9 | github.com/metachris/go-ethutils v0.4.7 10 | github.com/pkg/errors v0.9.1 11 | golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e // indirect 12 | golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= 5 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 6 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= 11 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 12 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 13 | cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= 14 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 15 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 16 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 17 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 18 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 19 | collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= 20 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 21 | github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= 22 | github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= 23 | github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= 24 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 25 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 26 | github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= 27 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 28 | github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= 29 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 30 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 31 | github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= 32 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 33 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 37 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 38 | github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= 39 | github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 40 | github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= 41 | github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= 42 | github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= 43 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 44 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 45 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 46 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 47 | github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= 48 | github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= 49 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 50 | github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= 51 | github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= 52 | github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= 53 | github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= 54 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= 55 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= 56 | github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= 57 | github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= 58 | github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= 59 | github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= 60 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 61 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 62 | github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= 63 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= 64 | github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= 65 | github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= 66 | github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= 67 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 68 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 69 | github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= 70 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 71 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 72 | github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= 73 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 74 | github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 75 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 76 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 77 | github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= 78 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 79 | github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= 80 | github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= 81 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 82 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 83 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 84 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 85 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 86 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 87 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 88 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 89 | github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= 90 | github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= 91 | github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= 92 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 93 | github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= 94 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 95 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 96 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 97 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 98 | github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0= 99 | github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= 100 | github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= 101 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 102 | github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= 103 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 104 | github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 105 | github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 106 | github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= 107 | github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= 108 | github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= 109 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 110 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 111 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 112 | github.com/ethereum/go-ethereum v1.10.3/go.mod h1:99onQmSd1GRGOziyGldI41YQb7EESX3Q4H41IfJgIQQ= 113 | github.com/ethereum/go-ethereum v1.10.7 h1:oLcBoBwjRYVsYRXAYdm1BodfLmXSvOBUB1wQi7ghnHc= 114 | github.com/ethereum/go-ethereum v1.10.7/go.mod h1:cZVr8i0xeKOaPdPR+XFxrFyt9dtkOHoK2CjOoZREXaE= 115 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 116 | github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= 117 | github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= 118 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 119 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 120 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 121 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 122 | github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= 123 | github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= 124 | github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= 125 | github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= 126 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 127 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 128 | github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= 129 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 130 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 131 | github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= 132 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 133 | github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= 134 | github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= 135 | github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= 136 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 137 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 138 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 139 | github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 140 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 141 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 142 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 143 | github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= 144 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 145 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 146 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 147 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 148 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 149 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 150 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 151 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 152 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 153 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 154 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 155 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 156 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 157 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 158 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 159 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= 160 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 161 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 162 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 163 | github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 164 | github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= 165 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 166 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 167 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 168 | github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 169 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 170 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 171 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 172 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 173 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 174 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 175 | github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 176 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 177 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 178 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 179 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 180 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 181 | github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= 182 | github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 183 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 184 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 185 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 186 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 187 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 188 | github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= 189 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 190 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 191 | github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= 192 | github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 193 | github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= 194 | github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= 195 | github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= 196 | github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= 197 | github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= 198 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 199 | github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= 200 | github.com/huin/goupnp v1.0.2 h1:RfGLP+h3mvisuWEyybxNq5Eft3NWhHLPeUN72kpKZoI= 201 | github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= 202 | github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= 203 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 204 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 205 | github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= 206 | github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= 207 | github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= 208 | github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= 209 | github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= 210 | github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= 211 | github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= 212 | github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= 213 | github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= 214 | github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= 215 | github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= 216 | github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= 217 | github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= 218 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 219 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 220 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 221 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 222 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 223 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 224 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 225 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 226 | github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= 227 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 228 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 229 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 230 | github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= 231 | github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= 232 | github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= 233 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 234 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 235 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 236 | github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 237 | github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 238 | github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= 239 | github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 240 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 241 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= 242 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 243 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 244 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 245 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 246 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 247 | github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= 248 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 249 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 250 | github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= 251 | github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 252 | github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= 253 | github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= 254 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 255 | github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4= 256 | github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 257 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 258 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 259 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 260 | github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 261 | github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= 262 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 263 | github.com/metachris/eth-go-bindings v0.5.0 h1:DpPAdHJLVAwV/NLDDD0SS2MZUGzP2qCRDTL4KP8qYjk= 264 | github.com/metachris/eth-go-bindings v0.5.0/go.mod h1:vd9ktO3U8qNf1vhRPInWJCbqjzf9IZQ6TslR179r8c0= 265 | github.com/metachris/flashbots-rpc v0.1.2 h1:08+jWRk/BcBjYFRNB90RQpW1Wt9nCEQ2w1E5Xzy1SAw= 266 | github.com/metachris/flashbots-rpc v0.1.2/go.mod h1:HYls2JCvQBFya3bnna9Vk9Y2uEUPBEqfsaoShG7w+6Q= 267 | github.com/metachris/go-ethutils v0.4.7 h1:KcbCyG4Wgd004QIUg55t3Fz2hVB0vgcYGOI3qSjrwR4= 268 | github.com/metachris/go-ethutils v0.4.7/go.mod h1:HUZaIue4fgyDt2T7IkY+8lDkoTBxFTsBdr9w7a0ZCaM= 269 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 270 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 271 | github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= 272 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 273 | github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= 274 | github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= 275 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 276 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 277 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 278 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 279 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 280 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 281 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 282 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 283 | github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= 284 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 285 | github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 286 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 287 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 288 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= 289 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 290 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 291 | github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 292 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 293 | github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= 294 | github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= 295 | github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= 296 | github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 297 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 298 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 299 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 300 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 301 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 302 | github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= 303 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 304 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 305 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 306 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 307 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 308 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 309 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 310 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 311 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 312 | github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= 313 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 314 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 315 | github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= 316 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 317 | github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= 318 | github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= 319 | github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= 320 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 321 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 322 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 323 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 324 | github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= 325 | github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= 326 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 327 | github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= 328 | github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 329 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 330 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 331 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 332 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 333 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 334 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 335 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 336 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 337 | github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= 338 | github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= 339 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 340 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 341 | github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 342 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 343 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 344 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 345 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 346 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 347 | github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs= 348 | github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= 349 | github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU= 350 | github.com/tidwall/gjson v1.8.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= 351 | github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= 352 | github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 353 | github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8= 354 | github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 355 | github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= 356 | github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= 357 | github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= 358 | github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= 359 | github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= 360 | github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= 361 | github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= 362 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= 363 | github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= 364 | github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= 365 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 366 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 367 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 368 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 369 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 370 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 371 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 372 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 373 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 374 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 375 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 376 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 377 | golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 378 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 379 | golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 380 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 381 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 382 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 383 | golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ= 384 | golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 385 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 386 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 387 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 388 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 389 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 390 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 391 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 392 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 393 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 394 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 395 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 396 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 397 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 398 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 399 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 400 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 401 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 402 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 403 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 404 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 405 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 406 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 407 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 408 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 409 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 410 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 411 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 412 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 413 | golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 414 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 415 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 416 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 417 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 418 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 419 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 420 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 421 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 422 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 423 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 424 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 425 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 426 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 427 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 428 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 429 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 430 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 431 | golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 432 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 433 | golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 434 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= 435 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 436 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 437 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 438 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 439 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 440 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 441 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 442 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 443 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 444 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 445 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 446 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 447 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 448 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 449 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 450 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 451 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 452 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 453 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 454 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 455 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 456 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 457 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 458 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 459 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 460 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 461 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 462 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 463 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 464 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 465 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 466 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 467 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 468 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 469 | golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 470 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 471 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 472 | golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 473 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 474 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 475 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 476 | golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 477 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 478 | golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 479 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 480 | golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= 481 | golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 482 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 483 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 484 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 485 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 486 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 487 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 488 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 489 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 490 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 491 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 492 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= 493 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 494 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 495 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 496 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 497 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 498 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 499 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 500 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 501 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 502 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 503 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 504 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 505 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 506 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 507 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 508 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 509 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 510 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 511 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 512 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 513 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 514 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 515 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 516 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 517 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 518 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 519 | golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 520 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 521 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 522 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 523 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 524 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 525 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 526 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 527 | gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 528 | gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= 529 | gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 530 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 531 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 532 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 533 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 534 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 535 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 536 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 537 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 538 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 539 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 540 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 541 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 542 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 543 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 544 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 545 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 546 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 547 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 548 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 549 | google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 550 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 551 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 552 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 553 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 554 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 555 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 556 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 557 | google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 558 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 559 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 560 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 561 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 562 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 563 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 564 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 565 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 566 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 567 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 568 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 569 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 570 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 571 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 572 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 573 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 574 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 575 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= 576 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 577 | gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= 578 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 579 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 580 | gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= 581 | gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= 582 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 583 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 584 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 585 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 586 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 587 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 588 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 589 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 590 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 591 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 592 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 593 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 594 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 595 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 596 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 597 | honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= 598 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 599 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 600 | --------------------------------------------------------------------------------