ok i guess
This commit is contained in:
parent
e34e4a72d1
commit
6567072793
|
@ -0,0 +1,527 @@
|
||||||
|
- [Introduction](#introduction)
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
* [Running locally](#running-locally)
|
||||||
|
* [Running remotely](#running-remotely)
|
||||||
|
* [Healthcheck](#healthcheck)
|
||||||
|
* [Testing](#testing)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
* [Relations between prune options and rpc methods](#relations-between-prune-options-and-rpc-method)
|
||||||
|
* [RPC Implementation Status](#rpc-implementation-status)
|
||||||
|
* [Securing the communication between RPC daemon and Erigon instance via TLS and authentication](#securing-the-communication-between-rpc-daemon-and-erigon-instance-via-tls-and-authentication)
|
||||||
|
* [Ethstats](#ethstats)
|
||||||
|
* [Allowing only specific methods (Allowlist)](#allowing-only-specific-methods--allowlist-)
|
||||||
|
* [Trace transactions progress](#trace-transactions-progress)
|
||||||
|
* [Clients getting timeout, but server load is low](#clients-getting-timeout--but-server-load-is-low)
|
||||||
|
* [Server load too high](#server-load-too-high)
|
||||||
|
* [Faster Batch requests](#faster-batch-requests)
|
||||||
|
- [For Developers](#for-developers)
|
||||||
|
* [Code generation](#code-generation)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Erigon's `rpcdaemon` runs in its own seperate process.
|
||||||
|
|
||||||
|
This brings many benefits including easier development, the ability to run multiple daemons at once, and the ability to
|
||||||
|
run the daemon remotely. It is possible to run the daemon locally as well (read-only) if both processes have access to
|
||||||
|
the data folder.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
The `rpcdaemon` gets built as part of the main `erigon` build process, but you can build it directly with this command:
|
||||||
|
|
||||||
|
```[bash]
|
||||||
|
make rpcdaemon
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running locally
|
||||||
|
|
||||||
|
Run `rpcdaemon` on same computer with Erigon. It's default option because it using Shared Memory access to Erigon's db -
|
||||||
|
it's much faster than TCP access. Provide both `--datadir` and `--private.api.addr` flags:
|
||||||
|
|
||||||
|
```[bash]
|
||||||
|
make erigon
|
||||||
|
./build/bin/erigon --datadir=<your_data_dir> --private.api.addr=localhost:9090
|
||||||
|
make rpcdaemon
|
||||||
|
./build/bin/rpcdaemon --datadir=<your_data_dir> --txpool.api.addr=localhost:9090 --private.api.addr=localhost:9090 --http.api=eth,erigon,web3,net,debug,trace,txpool
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we've also specified which RPC namespaces to enable in the above command by `--http.api` flag.
|
||||||
|
|
||||||
|
### Running remotely
|
||||||
|
|
||||||
|
To start the daemon remotely - just don't set `--datadir` flag:
|
||||||
|
|
||||||
|
```[bash]
|
||||||
|
make erigon
|
||||||
|
./build/bin/erigon --datadir=<your_data_dir> --private.api.addr=0.0.0.0:9090
|
||||||
|
make rpcdaemon
|
||||||
|
./build/bin/rpcdaemon --private.api.addr=<erigon_ip>:9090 --txpool.api.addr=localhost:9090 --http.api=eth,erigon,web3,net,debug,trace,txpool
|
||||||
|
```
|
||||||
|
|
||||||
|
The daemon should respond with something like:
|
||||||
|
|
||||||
|
```[bash]
|
||||||
|
INFO [date-time] HTTP endpoint opened url=localhost:8545...
|
||||||
|
```
|
||||||
|
|
||||||
|
When RPC daemon runs remotely, by default it maintains a state cache, which is updated every time when Erigon imports a
|
||||||
|
new block. When state cache is reasonably warm, it allows such remote RPC daemon to execute queries related to `latest`
|
||||||
|
block (i.e. to current state) with comparable performance to a local RPC daemon
|
||||||
|
(around 2x slower vs 10x slower without state cache). Since there can be multiple such RPC daemons per one Erigon node,
|
||||||
|
it may scale well for some workloads that are heavy on the current state queries.
|
||||||
|
|
||||||
|
### Healthcheck
|
||||||
|
|
||||||
|
There are 2 options for running healtchecks, POST request, or GET request with custom headers. Both options are available
|
||||||
|
at the `/health` endpoint.
|
||||||
|
|
||||||
|
#### POST request
|
||||||
|
|
||||||
|
If the health check is successful it returns 200 OK.
|
||||||
|
|
||||||
|
If the health check fails it returns 500 Internal Server Error.
|
||||||
|
|
||||||
|
Configuration of the health check is sent as POST body of the method.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"min_peer_count": <minimal number of the node peers>,
|
||||||
|
"known_block": <number_of_block_that_node_should_know>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Not adding a check disables that.
|
||||||
|
|
||||||
|
**`min_peer_count`** -- checks for mimimum of healthy node peers. Requires
|
||||||
|
`net` namespace to be listed in `http.api`.
|
||||||
|
|
||||||
|
**`known_block`** -- sets up the block that node has to know about. Requires
|
||||||
|
`eth` namespace to be listed in `http.api`.
|
||||||
|
|
||||||
|
Example request
|
||||||
|
```http POST http://localhost:8545/health --raw '{"min_peer_count": 3, "known_block": "0x1F"}'```
|
||||||
|
Example response
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"check_block": "HEALTHY",
|
||||||
|
"healthcheck_query": "HEALTHY",
|
||||||
|
"min_peer_count": "HEALTHY"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GET with headers
|
||||||
|
|
||||||
|
If the healthcheck is successful it will return a 200 status code.
|
||||||
|
|
||||||
|
If the healthcheck fails for any reason a status 500 will be returned. This is true if one of the criteria requested
|
||||||
|
fails its check.
|
||||||
|
|
||||||
|
You can set any number of values on the `X-ERIGON-HEALTHCHECK` header. Ones that are not included are skipped in the
|
||||||
|
checks.
|
||||||
|
|
||||||
|
Available Options:
|
||||||
|
- `synced` - will check if the node has completed syncing
|
||||||
|
- `min_peer_count<count>` - will check that the node has at least `<count>` many peers
|
||||||
|
- `check_block<block>` - will check that the node is at least ahead of the `<block>` specified
|
||||||
|
- `max_seconds_behind<seconds>` - will check that the node is no more than `<seconds>` behind from its latest block
|
||||||
|
|
||||||
|
Example Request
|
||||||
|
```
|
||||||
|
curl --location --request GET 'http://localhost:8545/health' \
|
||||||
|
--header 'X-ERIGON-HEALTHCHECK: min_peer_count1' \
|
||||||
|
--header 'X-ERIGON-HEALTHCHECK: synced' \
|
||||||
|
--header 'X-ERIGON-HEALTHCHECK: max_seconds_behind600'
|
||||||
|
```
|
||||||
|
|
||||||
|
Example Response
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"check_block":"DISABLED",
|
||||||
|
"max_seconds_behind":"HEALTHY",
|
||||||
|
"min_peer_count":"HEALTHY",
|
||||||
|
"synced":"HEALTHY"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
By default, the `rpcdaemon` serves data from `localhost:8545`. You may send `curl` commands to see if things are
|
||||||
|
working.
|
||||||
|
|
||||||
|
Try `eth_blockNumber` for example. In a third terminal window enter this command:
|
||||||
|
|
||||||
|
```[bash]
|
||||||
|
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id":1}' localhost:8545
|
||||||
|
```
|
||||||
|
|
||||||
|
This should return something along the lines of this (depending on how far your Erigon node has synced):
|
||||||
|
|
||||||
|
```[bash]
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"result":" 0xa5b9ba"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, there
|
||||||
|
are [extensive instructions for using Postman](https://github.com/ledgerwatch/erigon/wiki/Using-Postman-to-Test-TurboGeth-RPC)
|
||||||
|
to test the RPC.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Relations between prune options and RPC methods
|
||||||
|
|
||||||
|
Next options available (by `--prune` flag):
|
||||||
|
|
||||||
|
```
|
||||||
|
* h - prune history (ChangeSets, HistoryIndices - used to access historical state, like eth_getStorageAt, eth_getBalanceAt, debug_traceTransaction, trace_block, trace_transaction, etc.)
|
||||||
|
* r - prune receipts (Receipts, Logs, LogTopicIndex, LogAddressIndex - used by eth_getLogs and similar RPC methods)
|
||||||
|
* t - prune tx lookup (used to get transaction by hash)
|
||||||
|
* c - prune call traces (used by trace_filter method)
|
||||||
|
```
|
||||||
|
|
||||||
|
By default data pruned after 90K blocks, can change it by flags like `--prune.history.after=100_000`
|
||||||
|
|
||||||
|
Some methods, if not found historical data in DB, can fallback to old blocks re-execution - but it require `h`.
|
||||||
|
|
||||||
|
### RPC Implementation Status
|
||||||
|
|
||||||
|
Label "remote" means: `--private.api.addr` flag is required.
|
||||||
|
|
||||||
|
The following table shows the current implementation status of Erigon's RPC daemon.
|
||||||
|
|
||||||
|
| Command | Avail | Notes |
|
||||||
|
| ------------------------------------------ |---------|--------------------------------------|
|
||||||
|
| admin_nodeInfo | Yes | |
|
||||||
|
| admin_peers | Yes | |
|
||||||
|
| | | |
|
||||||
|
| web3_clientVersion | Yes | |
|
||||||
|
| web3_sha3 | Yes | |
|
||||||
|
| | | |
|
||||||
|
| net_listening | HC | (`remote` hard coded returns true) |
|
||||||
|
| net_peerCount | Limited | internal sentries only |
|
||||||
|
| net_version | Yes | `remote`. |
|
||||||
|
| | | |
|
||||||
|
| eth_blockNumber | Yes | |
|
||||||
|
| eth_chainID/eth_chainId | Yes | |
|
||||||
|
| eth_protocolVersion | Yes | |
|
||||||
|
| eth_syncing | Yes | |
|
||||||
|
| eth_gasPrice | Yes | |
|
||||||
|
| eth_maxPriorityFeePerGas | Yes | |
|
||||||
|
| eth_feeHistory | Yes | |
|
||||||
|
| | | |
|
||||||
|
| eth_getBlockByHash | Yes | |
|
||||||
|
| eth_getBlockByNumber | Yes | |
|
||||||
|
| eth_getBlockTransactionCountByHash | Yes | |
|
||||||
|
| eth_getBlockTransactionCountByNumber | Yes | |
|
||||||
|
| eth_getUncleByBlockHashAndIndex | Yes | |
|
||||||
|
| eth_getUncleByBlockNumberAndIndex | Yes | |
|
||||||
|
| eth_getUncleCountByBlockHash | Yes | |
|
||||||
|
| eth_getUncleCountByBlockNumber | Yes | |
|
||||||
|
| | | |
|
||||||
|
| eth_getTransactionByHash | Yes | |
|
||||||
|
| eth_getRawTransactionByHash | Yes | |
|
||||||
|
| eth_getTransactionByBlockHashAndIndex | Yes | |
|
||||||
|
| eth_retRawTransactionByBlockHashAndIndex | Yes | |
|
||||||
|
| eth_getTransactionByBlockNumberAndIndex | Yes | |
|
||||||
|
| eth_retRawTransactionByBlockNumberAndIndex | Yes | |
|
||||||
|
| eth_getTransactionReceipt | Yes | |
|
||||||
|
| eth_getBlockReceipts | Yes | |
|
||||||
|
| | | |
|
||||||
|
| eth_estimateGas | Yes | |
|
||||||
|
| eth_getBalance | Yes | |
|
||||||
|
| eth_getCode | Yes | |
|
||||||
|
| eth_getTransactionCount | Yes | |
|
||||||
|
| eth_getStorageAt | Yes | |
|
||||||
|
| eth_call | Yes | |
|
||||||
|
| eth_callMany | Yes | Erigon Method PR#4567 |
|
||||||
|
| eth_callBundle | Yes | |
|
||||||
|
| eth_createAccessList | Yes | |
|
||||||
|
| | | |
|
||||||
|
| eth_newFilter | Yes | Added by PR#4253 |
|
||||||
|
| eth_newBlockFilter | Yes | |
|
||||||
|
| eth_newPendingTransactionFilter | Yes | |
|
||||||
|
| eth_getFilterChanges | Yes | |
|
||||||
|
| eth_uninstallFilter | Yes | |
|
||||||
|
| eth_getLogs | Yes | |
|
||||||
|
| | | |
|
||||||
|
| eth_accounts | No | deprecated |
|
||||||
|
| eth_sendRawTransaction | Yes | `remote`. |
|
||||||
|
| eth_sendTransaction | - | not yet implemented |
|
||||||
|
| eth_sign | No | deprecated |
|
||||||
|
| eth_signTransaction | - | not yet implemented |
|
||||||
|
| eth_signTypedData | - | ???? |
|
||||||
|
| | | |
|
||||||
|
| eth_getProof | - | not yet implemented |
|
||||||
|
| | | |
|
||||||
|
| eth_mining | Yes | returns true if --mine flag provided |
|
||||||
|
| eth_coinbase | Yes | |
|
||||||
|
| eth_hashrate | Yes | |
|
||||||
|
| eth_submitHashrate | Yes | |
|
||||||
|
| eth_getWork | Yes | |
|
||||||
|
| eth_submitWork | Yes | |
|
||||||
|
| | | |
|
||||||
|
| eth_subscribe | Limited | Websock Only - newHeads, |
|
||||||
|
| | | newPendingTransactions, |
|
||||||
|
| | | newPendingBlock |
|
||||||
|
| eth_unsubscribe | Yes | Websock Only |
|
||||||
|
| | | |
|
||||||
|
| engine_newPayloadV1 | Yes | |
|
||||||
|
| engine_forkchoiceUpdatedV1 | Yes | |
|
||||||
|
| engine_getPayloadV1 | Yes | |
|
||||||
|
| engine_exchangeTransitionConfigurationV1 | Yes | |
|
||||||
|
| | | |
|
||||||
|
| debug_accountRange | Yes | Private Erigon debug module |
|
||||||
|
| debug_accountAt | Yes | Private Erigon debug module |
|
||||||
|
| debug_getModifiedAccountsByNumber | Yes | |
|
||||||
|
| debug_getModifiedAccountsByHash | Yes | |
|
||||||
|
| debug_storageRangeAt | Yes | |
|
||||||
|
| debug_traceBlockByHash | Yes | Streaming (can handle huge results) |
|
||||||
|
| debug_traceBlockByNumber | Yes | Streaming (can handle huge results) |
|
||||||
|
| debug_traceTransaction | Yes | Streaming (can handle huge results) |
|
||||||
|
| debug_traceCall | Yes | Streaming (can handle huge results) |
|
||||||
|
| debug_traceCallMany | Yes | Erigon Method PR#4567. |
|
||||||
|
| | | |
|
||||||
|
| trace_call | Yes | |
|
||||||
|
| trace_callMany | Yes | |
|
||||||
|
| trace_rawTransaction | - | not yet implemented (come help!) |
|
||||||
|
| trace_replayBlockTransactions | yes | stateDiff only (come help!) |
|
||||||
|
| trace_replayTransaction | yes | stateDiff only (come help!) |
|
||||||
|
| trace_block | Yes | |
|
||||||
|
| trace_filter | Yes | no pagination, but streaming |
|
||||||
|
| trace_get | Yes | |
|
||||||
|
| trace_transaction | Yes | |
|
||||||
|
| | | |
|
||||||
|
| txpool_content | Yes | `remote` |
|
||||||
|
| txpool_status | Yes | `remote` |
|
||||||
|
| | | |
|
||||||
|
| eth_getCompilers | No | deprecated |
|
||||||
|
| eth_compileLLL | No | deprecated |
|
||||||
|
| eth_compileSolidity | No | deprecated |
|
||||||
|
| eth_compileSerpent | No | deprecated |
|
||||||
|
| | | |
|
||||||
|
| db_putString | No | deprecated |
|
||||||
|
| db_getString | No | deprecated |
|
||||||
|
| db_putHex | No | deprecated |
|
||||||
|
| db_getHex | No | deprecated |
|
||||||
|
| | | |
|
||||||
|
| erigon_getHeaderByHash | Yes | Erigon only |
|
||||||
|
| erigon_getHeaderByNumber | Yes | Erigon only |
|
||||||
|
| erigon_getLogsByHash | Yes | Erigon only |
|
||||||
|
| erigon_forks | Yes | Erigon only |
|
||||||
|
| erigon_issuance | Yes | Erigon only |
|
||||||
|
| erigon_GetBlockByTimestamp | Yes | Erigon only |
|
||||||
|
| erigon_BlockNumber | Yes | Erigon only |
|
||||||
|
| | | |
|
||||||
|
| bor_getSnapshot | Yes | Bor only |
|
||||||
|
| bor_getAuthor | Yes | Bor only |
|
||||||
|
| bor_getSnapshotAtHash | Yes | Bor only |
|
||||||
|
| bor_getSigners | Yes | Bor only |
|
||||||
|
| bor_getSignersAtHash | Yes | Bor only |
|
||||||
|
| bor_getCurrentProposer | Yes | Bor only |
|
||||||
|
| bor_getCurrentValidators | Yes | Bor only |
|
||||||
|
| bor_getRootHash | Yes | Bor only |
|
||||||
|
|
||||||
|
This table is constantly updated. Please visit again.
|
||||||
|
|
||||||
|
### Securing the communication between RPC daemon and Erigon instance via TLS and authentication
|
||||||
|
|
||||||
|
In some cases, it is useful to run Erigon nodes in a different network (for example, in a Public cloud), but RPC daemon
|
||||||
|
locally. To ensure the integrity of communication and access control to the Erigon node, TLS authentication can be
|
||||||
|
enabled. On the high level, the process consists of these steps (this process needs to be done for any "cluster" of
|
||||||
|
Erigon and RPC daemon nodes that are supposed to work together):
|
||||||
|
|
||||||
|
1. Generate key pair for the Certificate Authority (CA). The private key of CA will be used to authorise new Erigon
|
||||||
|
instances as well as new RPC daemon instances, so that they can mutually authenticate.
|
||||||
|
2. Create CA certificate file that needs to be deployed on any Erigon instance and any RPC daemon. This CA cerf file is
|
||||||
|
used as a "root of trust", whatever is in it, will be trusted by the participants when they authenticate their
|
||||||
|
counterparts.
|
||||||
|
3. For each Erigon instance and each RPC daemon instance, generate a key pair. If you are lazy, you can generate one
|
||||||
|
pair for all Erigon nodes, and one pair for all RPC daemons, and copy these keys around.
|
||||||
|
4. Using the CA private key, create cerificate file for each public key generated on the previous step. This
|
||||||
|
effectively "inducts" these keys into the "cluster of trust".
|
||||||
|
5. On each instance, deploy 3 files - CA certificate, instance key, and certificate signed by CA for this instance key.
|
||||||
|
|
||||||
|
Following is the detailed description of how it can be done using `openssl` suite of tools.
|
||||||
|
|
||||||
|
Generate CA key pair using Elliptic Curve (as opposed to RSA). The generated CA key will be in the file `CA-key.pem`.
|
||||||
|
Access to this file will allow anyone to later include any new instance key pair into the "cluster of trust", so keep it
|
||||||
|
secure.
|
||||||
|
|
||||||
|
```
|
||||||
|
openssl ecparam -name prime256v1 -genkey -noout -out CA-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Create CA self-signed certificate (this command will ask questions, answers aren't important for now). The file created
|
||||||
|
by this command is `CA-cert.pem`
|
||||||
|
|
||||||
|
```
|
||||||
|
openssl req -x509 -new -nodes -key CA-key.pem -sha256 -days 3650 -out CA-cert.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
For Erigon node, generate a key pair:
|
||||||
|
|
||||||
|
```
|
||||||
|
openssl ecparam -name prime256v1 -genkey -noout -out erigon-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, generate one for the RPC daemon:
|
||||||
|
|
||||||
|
```
|
||||||
|
openssl ecparam -name prime256v1 -genkey -noout -out RPC-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Now create certificate signing request for Erigon key pair:
|
||||||
|
|
||||||
|
```
|
||||||
|
openssl req -new -key erigon-key.pem -out erigon.csr
|
||||||
|
```
|
||||||
|
|
||||||
|
And from this request, produce the certificate (signed by CA), proving that this key is now part of the "cluster of
|
||||||
|
trust"
|
||||||
|
|
||||||
|
```
|
||||||
|
openssl x509 -req -in erigon.csr -CA CA-cert.pem -CAkey CA-key.pem -CAcreateserial -out erigon.crt -days 3650 -sha256
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, produce the certificate signing request for RPC daemon key pair:
|
||||||
|
|
||||||
|
```
|
||||||
|
openssl req -new -key RPC-key.pem -out RPC.csr
|
||||||
|
```
|
||||||
|
|
||||||
|
And from this request, produce the certificate (signed by CA), proving that this key is now part of the "cluster of
|
||||||
|
trust"
|
||||||
|
|
||||||
|
```
|
||||||
|
openssl x509 -req -in RPC.csr -CA CA-cert.pem -CAkey CA-key.pem -CAcreateserial -out RPC.crt -days 3650 -sha256
|
||||||
|
```
|
||||||
|
|
||||||
|
When this is all done, these three files need to be placed on the machine where Erigon is running: `CA-cert.pem`
|
||||||
|
, `erigon-key.pem`, `erigon.crt`. And Erigon needs to be run with these extra options:
|
||||||
|
|
||||||
|
```
|
||||||
|
--tls --tls.cacert CA-cert.pem --tls.key erigon-key.pem --tls.cert erigon.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
On the RPC daemon machine, these three files need to be placed: `CA-cert.pem`, `RPC-key.pem`, and `RPC.crt`. And RPC
|
||||||
|
daemon needs to be started with these extra options:
|
||||||
|
|
||||||
|
```
|
||||||
|
--tls.key RPC-key.pem --tls.cacert CA-cert.pem --tls.cert RPC.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
**WARNING** Normally, the "client side" (which in our case is RPC daemon), verifies that the host name of the server
|
||||||
|
matches the "Common Name" attribute of the "server" cerificate. At this stage, this verification is turned off, and it
|
||||||
|
will be turned on again once we have updated the instruction above on how to properly generate cerificates with "Common
|
||||||
|
Name".
|
||||||
|
|
||||||
|
When running Erigon instance in the Google Cloud, for example, you need to specify the **Internal IP** in
|
||||||
|
the `--private.api.addr` option. And, you will need to open the firewall on the port you are using, to that connection
|
||||||
|
to the Erigon instances can be made.
|
||||||
|
|
||||||
|
### Ethstats
|
||||||
|
|
||||||
|
This version of the RPC daemon is compatible with [ethstats-client](https://github.com/goerli/ethstats-client).
|
||||||
|
|
||||||
|
To run ethstats, run the RPC daemon remotely and open some of the APIs.
|
||||||
|
|
||||||
|
`./build/bin/rpcdaemon --private.api.addr=localhost:9090 --http.api=net,eth,web3`
|
||||||
|
|
||||||
|
Then update your `app.json` for ethstats-client like that:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "ethstats",
|
||||||
|
"script": "app.js",
|
||||||
|
"log_date_format": "YYYY-MM-DD HH:mm Z",
|
||||||
|
"merge_logs": false,
|
||||||
|
"watch": false,
|
||||||
|
"max_restarts": 10,
|
||||||
|
"exec_interpreter": "node",
|
||||||
|
"exec_mode": "fork_mode",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "production",
|
||||||
|
"RPC_HOST": "localhost",
|
||||||
|
"RPC_PORT": "8545",
|
||||||
|
"LISTENING_PORT": "30303",
|
||||||
|
"INSTANCE_NAME": "Erigon node",
|
||||||
|
"CONTACT_DETAILS": <your twitter handle>,
|
||||||
|
"WS_SERVER": "wss://ethstats.net/api",
|
||||||
|
"WS_SECRET": <put your secret key here>,
|
||||||
|
"VERBOSITY": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Run ethstats-client through pm2 as usual.
|
||||||
|
|
||||||
|
You will see these warnings in the RPC daemon output, but they are expected
|
||||||
|
|
||||||
|
```
|
||||||
|
WARN [11-05|09:03:47.911] Served conn=127.0.0.1:59753 method=eth_newBlockFilter reqid=5 t="21.194µs" err="the method eth_newBlockFilter does not exist/is not available"
|
||||||
|
WARN [11-05|09:03:47.911] Served conn=127.0.0.1:59754 method=eth_newPendingTransactionFilter reqid=6 t="9.053µs" err="the method eth_newPendingTransactionFilter does not exist/is not available"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Allowing only specific methods (Allowlist)
|
||||||
|
|
||||||
|
In some cases you might want to only allow certain methods in the namespaces and hide others. That is possible
|
||||||
|
with `rpc.accessList` flag.
|
||||||
|
|
||||||
|
1. Create a file, say, `rules.json`
|
||||||
|
|
||||||
|
2. Add the following content
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"allow": [
|
||||||
|
"net_version",
|
||||||
|
"web3_eth_getBlockByHash"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Provide this file to the rpcdaemon using `--rpc.accessList` flag
|
||||||
|
|
||||||
|
```
|
||||||
|
> rpcdaemon --private.api.addr=localhost:9090 --http.api=eth,debug,net,web3 --rpc.accessList=rules.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Now only these two methods are available.
|
||||||
|
|
||||||
|
### Clients getting timeout, but server load is low
|
||||||
|
|
||||||
|
In this case: increase default rate-limit - amount of requests server handle simultaneously - requests over this limit
|
||||||
|
will wait. Increase it - if your 'hot data' is small or have much RAM or see "request timeout" while server load is low.
|
||||||
|
|
||||||
|
```
|
||||||
|
./build/bin/erigon --private.api.addr=localhost:9090 --private.api.ratelimit=1024
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server load too high
|
||||||
|
|
||||||
|
Reduce `--private.api.ratelimit`
|
||||||
|
|
||||||
|
### Read DB directly without Json-RPC/Graphql
|
||||||
|
|
||||||
|
[./../../docs/programmers_guide/db_faq.md](./../../docs/programmers_guide/db_faq.md)
|
||||||
|
|
||||||
|
### Faster Batch requests
|
||||||
|
|
||||||
|
Currently batch requests are spawn multiple goroutines and process all sub-requests in parallel. To limit impact of 1
|
||||||
|
huge batch to other users - added flag `--rpc.batch.concurrency` (default: 2). Increase it to process large batches
|
||||||
|
faster.
|
||||||
|
|
||||||
|
Known Issue: if at least 1 request is "streamable" (has parameter of type *jsoniter.Stream) - then whole batch will
|
||||||
|
processed sequentially (on 1 goroutine).
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
### Code generation
|
||||||
|
|
||||||
|
`go.mod` stores right version of generators, use `make grpc` to install it and generate code (it also installs protoc
|
||||||
|
into ./build/bin folder).
|
|
@ -0,0 +1,678 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/common/dir"
|
||||||
|
libstate "github.com/ledgerwatch/erigon-lib/state"
|
||||||
|
"github.com/ledgerwatch/erigon/eth/ethconfig"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc/rpccfg"
|
||||||
|
|
||||||
|
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/cli/httpcfg"
|
||||||
|
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/health"
|
||||||
|
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/rpcservices"
|
||||||
|
"github.com/wmitsuda/otterscan/erigon_internal/debug"
|
||||||
|
"github.com/wmitsuda/otterscan/erigon_internal/logging"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/direct"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces/grpcutil"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces/remote"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv/kvcache"
|
||||||
|
kv2 "github.com/ledgerwatch/erigon-lib/kv/mdbx"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv/remotedb"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv/remotedbserver"
|
||||||
|
"github.com/ledgerwatch/erigon/cmd/utils"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/common/paths"
|
||||||
|
"github.com/ledgerwatch/erigon/core/rawdb"
|
||||||
|
"github.com/ledgerwatch/erigon/node"
|
||||||
|
"github.com/ledgerwatch/erigon/node/nodecfg"
|
||||||
|
"github.com/ledgerwatch/erigon/node/nodecfg/datadir"
|
||||||
|
"github.com/ledgerwatch/erigon/params"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/services"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/snapshotsync"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/snapshotsync/snap"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sync/semaphore"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
grpcHealth "google.golang.org/grpc/health"
|
||||||
|
"google.golang.org/grpc/health/grpc_health_v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "rpcdaemon",
|
||||||
|
Short: "rpcdaemon is JSON RPC server that connects to Erigon node for remote DB access",
|
||||||
|
}
|
||||||
|
|
||||||
|
func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
|
||||||
|
utils.CobraFlags(rootCmd, debug.Flags, utils.MetricFlags, logging.Flags)
|
||||||
|
|
||||||
|
cfg := &httpcfg.HttpCfg{Enabled: true, StateCache: kvcache.DefaultCoherentConfig}
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.PrivateApiAddr, "private.api.addr", "127.0.0.1:9090", "private api network address, for example: 127.0.0.1:9090")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.DataDir, "datadir", "", "path to Erigon working directory")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.HttpListenAddress, "http.addr", nodecfg.DefaultHTTPHost, "HTTP-RPC server listening interface")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.TLSCertfile, "tls.cert", "", "certificate for client side TLS handshake")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.TLSKeyFile, "tls.key", "", "key file for client side TLS handshake")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.TLSCACert, "tls.cacert", "", "CA certificate for client side TLS handshake")
|
||||||
|
rootCmd.PersistentFlags().IntVar(&cfg.HttpPort, "http.port", nodecfg.DefaultHTTPPort, "HTTP-RPC server listening port")
|
||||||
|
rootCmd.PersistentFlags().StringSliceVar(&cfg.HttpCORSDomain, "http.corsdomain", []string{}, "Comma separated list of domains from which to accept cross origin requests (browser enforced)")
|
||||||
|
rootCmd.PersistentFlags().StringSliceVar(&cfg.HttpVirtualHost, "http.vhosts", nodecfg.DefaultConfig.HTTPVirtualHosts, "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.")
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.HttpCompression, "http.compression", true, "Disable http compression")
|
||||||
|
rootCmd.PersistentFlags().StringSliceVar(&cfg.API, "http.api", []string{"eth", "erigon"}, "API's offered over the HTTP-RPC interface: eth,erigon,web3,net,debug,trace,txpool,db. Supported methods: https://github.com/ledgerwatch/erigon/tree/devel/cmd/rpcdaemon")
|
||||||
|
rootCmd.PersistentFlags().Uint64Var(&cfg.Gascap, "rpc.gascap", 50000000, "Sets a cap on gas that can be used in eth_call/estimateGas")
|
||||||
|
rootCmd.PersistentFlags().Uint64Var(&cfg.MaxTraces, "trace.maxtraces", 200, "Sets a limit on traces that can be returned in trace_filter")
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.WebsocketEnabled, "ws", false, "Enable Websockets")
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.WebsocketCompression, "ws.compression", false, "Enable Websocket compression (RFC 7692)")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.RpcAllowListFilePath, "rpc.accessList", "", "Specify granular (method-by-method) API allowlist")
|
||||||
|
rootCmd.PersistentFlags().UintVar(&cfg.RpcBatchConcurrency, utils.RpcBatchConcurrencyFlag.Name, 2, utils.RpcBatchConcurrencyFlag.Usage)
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.RpcStreamingDisable, utils.RpcStreamingDisableFlag.Name, false, utils.RpcStreamingDisableFlag.Usage)
|
||||||
|
rootCmd.PersistentFlags().IntVar(&cfg.DBReadConcurrency, utils.DBReadConcurrencyFlag.Name, utils.DBReadConcurrencyFlag.Value, utils.DBReadConcurrencyFlag.Usage)
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.TraceCompatibility, "trace.compat", false, "Bug for bug compatibility with OE for trace_ routines")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.TxPoolApiAddr, "txpool.api.addr", "", "txpool api network address, for example: 127.0.0.1:9090 (default: use value of --private.api.addr)")
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.Sync.UseSnapshots, "snapshot", true, utils.SnapshotFlag.Usage)
|
||||||
|
rootCmd.PersistentFlags().IntVar(&cfg.StateCache.KeysLimit, "state.cache", kvcache.DefaultCoherentConfig.KeysLimit, "Amount of keys to store in StateCache (enabled if no --datadir set). Set 0 to disable StateCache. 1_000_000 keys ~ equal to 2Gb RAM (maybe we will add RAM accounting in future versions).")
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.GRPCServerEnabled, "grpc", false, "Enable GRPC server")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfg.GRPCListenAddress, "grpc.addr", nodecfg.DefaultGRPCHost, "GRPC server listening interface")
|
||||||
|
rootCmd.PersistentFlags().IntVar(&cfg.GRPCPort, "grpc.port", nodecfg.DefaultGRPCPort, "GRPC server listening port")
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.GRPCHealthCheckEnabled, "grpc.healthcheck", false, "Enable GRPC health check")
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&cfg.TraceRequests, utils.HTTPTraceFlag.Name, false, "Trace HTTP requests with INFO level")
|
||||||
|
rootCmd.PersistentFlags().DurationVar(&cfg.HTTPTimeouts.ReadTimeout, "http.timeouts.read", rpccfg.DefaultHTTPTimeouts.ReadTimeout, "Maximum duration for reading the entire request, including the body.")
|
||||||
|
rootCmd.PersistentFlags().DurationVar(&cfg.HTTPTimeouts.WriteTimeout, "http.timeouts.write", rpccfg.DefaultHTTPTimeouts.WriteTimeout, "Maximum duration before timing out writes of the response. It is reset whenever a new request's header is read")
|
||||||
|
rootCmd.PersistentFlags().DurationVar(&cfg.HTTPTimeouts.IdleTimeout, "http.timeouts.idle", rpccfg.DefaultHTTPTimeouts.IdleTimeout, "Maximum amount of time to wait for the next request when keep-alives are enabled. If http.timeouts.idle is zero, the value of http.timeouts.read is used")
|
||||||
|
rootCmd.PersistentFlags().DurationVar(&cfg.EvmCallTimeout, "rpc.evmtimeout", rpccfg.DefaultEvmCallTimeout, "Maximum amount of time to wait for the answer from EVM call.")
|
||||||
|
|
||||||
|
if err := rootCmd.MarkPersistentFlagFilename("rpc.accessList", "json"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := rootCmd.MarkPersistentFlagDirname("datadir"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := debug.SetupCobra(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.WithDatadir = cfg.DataDir != ""
|
||||||
|
if cfg.WithDatadir {
|
||||||
|
if cfg.DataDir == "" {
|
||||||
|
cfg.DataDir = paths.DefaultDataDir()
|
||||||
|
}
|
||||||
|
cfg.Dirs = datadir.New(cfg.DataDir)
|
||||||
|
}
|
||||||
|
if cfg.TxPoolApiAddr == "" {
|
||||||
|
cfg.TxPoolApiAddr = cfg.PrivateApiAddr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rootCmd.PersistentPostRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
|
debug.Exit()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.StateCache.MetricsLabel = "rpc"
|
||||||
|
|
||||||
|
return rootCmd, cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateChangesClient interface {
|
||||||
|
StateChanges(ctx context.Context, in *remote.StateChangeRequest, opts ...grpc.CallOption) (remote.KV_StateChangesClient, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func subscribeToStateChangesLoop(ctx context.Context, client StateChangesClient, cache kvcache.Cache) {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if err := subscribeToStateChanges(ctx, client, cache); err != nil {
|
||||||
|
if grpcutil.IsRetryLater(err) || grpcutil.IsEndOfStream(err) {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Warn("[txpool.handleStateChanges]", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func subscribeToStateChanges(ctx context.Context, client StateChangesClient, cache kvcache.Cache) error {
|
||||||
|
streamCtx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
stream, err := client.StateChanges(streamCtx, &remote.StateChangeRequest{WithStorage: true, WithTransactions: false}, grpc.WaitForReady(true))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for req, err := stream.Recv(); ; req, err = stream.Recv() {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.OnNewBlock(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDbCompatibility(ctx context.Context, db kv.RoDB) error {
|
||||||
|
// DB schema version compatibility check
|
||||||
|
var version []byte
|
||||||
|
var compatErr error
|
||||||
|
var compatTx kv.Tx
|
||||||
|
if compatTx, compatErr = db.BeginRo(ctx); compatErr != nil {
|
||||||
|
return fmt.Errorf("open Ro Tx for DB schema compability check: %w", compatErr)
|
||||||
|
}
|
||||||
|
defer compatTx.Rollback()
|
||||||
|
if version, compatErr = compatTx.GetOne(kv.DatabaseInfo, kv.DBSchemaVersionKey); compatErr != nil {
|
||||||
|
return fmt.Errorf("read version for DB schema compability check: %w", compatErr)
|
||||||
|
}
|
||||||
|
if len(version) != 12 {
|
||||||
|
return fmt.Errorf("database does not have major schema version. upgrade and restart Erigon core")
|
||||||
|
}
|
||||||
|
major := binary.BigEndian.Uint32(version)
|
||||||
|
minor := binary.BigEndian.Uint32(version[4:])
|
||||||
|
patch := binary.BigEndian.Uint32(version[8:])
|
||||||
|
var compatible bool
|
||||||
|
dbSchemaVersion := &kv.DBSchemaVersion
|
||||||
|
if major != dbSchemaVersion.Major {
|
||||||
|
compatible = false
|
||||||
|
} else if minor != dbSchemaVersion.Minor {
|
||||||
|
compatible = false
|
||||||
|
} else {
|
||||||
|
compatible = true
|
||||||
|
}
|
||||||
|
if !compatible {
|
||||||
|
return fmt.Errorf("incompatible DB Schema versions: reader %d.%d.%d, database %d.%d.%d",
|
||||||
|
dbSchemaVersion.Major, dbSchemaVersion.Minor, dbSchemaVersion.Patch,
|
||||||
|
major, minor, patch)
|
||||||
|
}
|
||||||
|
log.Info("DB schemas compatible", "reader", fmt.Sprintf("%d.%d.%d", dbSchemaVersion.Major, dbSchemaVersion.Minor, dbSchemaVersion.Patch),
|
||||||
|
"database", fmt.Sprintf("%d.%d.%d", major, minor, patch))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmbeddedServices(ctx context.Context,
|
||||||
|
erigonDB kv.RoDB, stateCacheCfg kvcache.CoherentConfig,
|
||||||
|
blockReader services.FullBlockReader, snapshots *snapshotsync.RoSnapshots, agg *libstate.Aggregator22,
|
||||||
|
ethBackendServer remote.ETHBACKENDServer, txPoolServer txpool.TxpoolServer, miningServer txpool.MiningServer,
|
||||||
|
) (eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient, stateCache kvcache.Cache, ff *rpchelper.Filters, err error) {
|
||||||
|
if stateCacheCfg.KeysLimit > 0 {
|
||||||
|
stateCache = kvcache.NewDummy()
|
||||||
|
// notification about new blocks (state stream) doesn't work now inside erigon - because
|
||||||
|
// erigon does send this stream to privateAPI (erigon with enabled rpc, still have enabled privateAPI).
|
||||||
|
// without this state stream kvcache can't work and only slow-down things
|
||||||
|
//
|
||||||
|
//stateCache = kvcache.New(stateCacheCfg)
|
||||||
|
} else {
|
||||||
|
stateCache = kvcache.NewDummy()
|
||||||
|
}
|
||||||
|
kvRPC := remotedbserver.NewKvServer(ctx, erigonDB, snapshots, agg)
|
||||||
|
stateDiffClient := direct.NewStateDiffClientDirect(kvRPC)
|
||||||
|
subscribeToStateChangesLoop(ctx, stateDiffClient, stateCache)
|
||||||
|
|
||||||
|
directClient := direct.NewEthBackendClientDirect(ethBackendServer)
|
||||||
|
|
||||||
|
eth = rpcservices.NewRemoteBackend(directClient, erigonDB, blockReader)
|
||||||
|
txPool = direct.NewTxPoolClient(txPoolServer)
|
||||||
|
mining = direct.NewMiningClient(miningServer)
|
||||||
|
ff = rpchelper.New(ctx, eth, txPool, mining, func() {})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteServices - use when RPCDaemon run as independent process. Still it can use --datadir flag to enable
|
||||||
|
// `cfg.WithDatadir` (mode when it on 1 machine with Erigon)
|
||||||
|
func RemoteServices(ctx context.Context, cfg httpcfg.HttpCfg, logger log.Logger, rootCancel context.CancelFunc) (
|
||||||
|
db kv.RoDB, borDb kv.RoDB,
|
||||||
|
eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient,
|
||||||
|
stateCache kvcache.Cache, blockReader services.FullBlockReader,
|
||||||
|
ff *rpchelper.Filters, agg *libstate.Aggregator22, err error) {
|
||||||
|
if !cfg.WithDatadir && cfg.PrivateApiAddr == "" {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, fmt.Errorf("either remote db or local db must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not change the order of these checks. Chaindata needs to be checked first, because PrivateApiAddr has default value which is not ""
|
||||||
|
// If PrivateApiAddr is checked first, the Chaindata option will never work
|
||||||
|
if cfg.WithDatadir {
|
||||||
|
dir.MustExist(cfg.Dirs.SnapHistory)
|
||||||
|
var rwKv kv.RwDB
|
||||||
|
log.Trace("Creating chain db", "path", cfg.Dirs.Chaindata)
|
||||||
|
limiter := semaphore.NewWeighted(int64(cfg.DBReadConcurrency))
|
||||||
|
rwKv, err = kv2.NewMDBX(logger).RoTxsLimiter(limiter).Path(cfg.Dirs.Chaindata).Readonly().Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, err
|
||||||
|
}
|
||||||
|
if compatErr := checkDbCompatibility(ctx, rwKv); compatErr != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, compatErr
|
||||||
|
}
|
||||||
|
db = rwKv
|
||||||
|
stateCache = kvcache.NewDummy()
|
||||||
|
blockReader = snapshotsync.NewBlockReader()
|
||||||
|
|
||||||
|
// bor (consensus) specific db
|
||||||
|
var borKv kv.RoDB
|
||||||
|
borDbPath := filepath.Join(cfg.DataDir, "bor")
|
||||||
|
{
|
||||||
|
// ensure db exist
|
||||||
|
tmpDb, err := kv2.NewMDBX(logger).Path(borDbPath).Label(kv.ConsensusDB).Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, err
|
||||||
|
}
|
||||||
|
tmpDb.Close()
|
||||||
|
}
|
||||||
|
log.Trace("Creating consensus db", "path", borDbPath)
|
||||||
|
borKv, err = kv2.NewMDBX(logger).Path(borDbPath).Label(kv.ConsensusDB).Readonly().Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, err
|
||||||
|
}
|
||||||
|
// Skip the compatibility check, until we have a schema in erigon-lib
|
||||||
|
borDb = borKv
|
||||||
|
} else {
|
||||||
|
if cfg.StateCache.KeysLimit > 0 {
|
||||||
|
stateCache = kvcache.NewDummy()
|
||||||
|
//stateCache = kvcache.New(cfg.StateCache)
|
||||||
|
} else {
|
||||||
|
stateCache = kvcache.NewDummy()
|
||||||
|
}
|
||||||
|
log.Info("if you run RPCDaemon on same machine with Erigon add --datadir option")
|
||||||
|
}
|
||||||
|
|
||||||
|
if db != nil {
|
||||||
|
var cc *params.ChainConfig
|
||||||
|
if err := db.View(context.Background(), func(tx kv.Tx) error {
|
||||||
|
genesisBlock, err := rawdb.ReadBlockByNumber(tx, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if genesisBlock == nil {
|
||||||
|
return fmt.Errorf("genesis not found in DB. Likely Erigon was never started on this datadir")
|
||||||
|
}
|
||||||
|
cc, err = rawdb.ReadChainConfig(tx, genesisBlock.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.Snap.Enabled, err = snap.Enabled(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, err
|
||||||
|
}
|
||||||
|
if cc == nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, fmt.Errorf("chain config not found in db. Need start erigon at least once on this db")
|
||||||
|
}
|
||||||
|
cfg.Snap.Enabled = cfg.Snap.Enabled || cfg.Sync.UseSnapshots
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := grpcutil.TLS(cfg.TLSCACert, cfg.TLSCertfile, cfg.TLSKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, fmt.Errorf("open tls cert: %w", err)
|
||||||
|
}
|
||||||
|
conn, err := grpcutil.Connect(creds, cfg.PrivateApiAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, fmt.Errorf("could not connect to execution service privateApi: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kvClient := remote.NewKVClient(conn)
|
||||||
|
remoteKv, err := remotedb.NewRemote(gointerfaces.VersionFromProto(remotedbserver.KvServiceAPIVersion), logger, kvClient).Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, fmt.Errorf("could not connect to remoteKv: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeToStateChangesLoop(ctx, kvClient, stateCache)
|
||||||
|
|
||||||
|
onNewSnapshot := func() {}
|
||||||
|
if cfg.WithDatadir {
|
||||||
|
if cfg.Snap.Enabled {
|
||||||
|
|
||||||
|
allSnapshots := snapshotsync.NewRoSnapshots(cfg.Snap, cfg.Dirs.Snap)
|
||||||
|
// To povide good UX - immediatly can read snapshots after RPCDaemon start, even if Erigon is down
|
||||||
|
// Erigon does store list of snapshots in db: means RPCDaemon can read this list now, but read by `kvClient.Snapshots` after establish grpc connection
|
||||||
|
allSnapshots.OptimisticReopenWithDB(db)
|
||||||
|
allSnapshots.LogStat()
|
||||||
|
|
||||||
|
if agg, err = libstate.NewAggregator22(cfg.Dirs.SnapHistory, cfg.Dirs.Tmp, ethconfig.HistoryV3AggregationStep, db); err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, fmt.Errorf("create aggregator: %w", err)
|
||||||
|
}
|
||||||
|
if err = agg.ReopenFiles(); err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, fmt.Errorf("create aggregator: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.View(context.Background(), func(tx kv.Tx) error {
|
||||||
|
agg.LogStats(tx, func(endTxNumMinimax uint64) uint64 {
|
||||||
|
_, histBlockNumProgress, _ := rawdb.TxNums.FindBlockNum(tx, endTxNumMinimax)
|
||||||
|
return histBlockNumProgress
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
onNewSnapshot = func() {
|
||||||
|
go func() { // don't block events processing by network communication
|
||||||
|
reply, err := kvClient.Snapshots(ctx, &remote.SnapshotsRequest{}, grpc.WaitForReady(true))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("[Snapshots] reopen", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := allSnapshots.ReopenList(reply.BlockFiles, true); err != nil {
|
||||||
|
log.Error("[Snapshots] reopen", "err", err)
|
||||||
|
} else {
|
||||||
|
allSnapshots.LogStat()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = agg.ReopenFiles(); err != nil {
|
||||||
|
log.Error("[Snapshots] reopen", "err", err)
|
||||||
|
} else {
|
||||||
|
db.View(context.Background(), func(tx kv.Tx) error {
|
||||||
|
agg.LogStats(tx, func(endTxNumMinimax uint64) uint64 {
|
||||||
|
_, histBlockNumProgress, _ := rawdb.TxNums.FindBlockNum(tx, endTxNumMinimax)
|
||||||
|
return histBlockNumProgress
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
onNewSnapshot()
|
||||||
|
// TODO: how to don't block startup on remote RPCDaemon?
|
||||||
|
// txNums = exec22.TxNumsFromDB(allSnapshots, db)
|
||||||
|
blockReader = snapshotsync.NewBlockReaderWithSnapshots(allSnapshots)
|
||||||
|
} else {
|
||||||
|
log.Info("Use --snapshots=false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.WithDatadir {
|
||||||
|
blockReader = snapshotsync.NewRemoteBlockReader(remote.NewETHBACKENDClient(conn))
|
||||||
|
}
|
||||||
|
remoteEth := rpcservices.NewRemoteBackend(remote.NewETHBACKENDClient(conn), db, blockReader)
|
||||||
|
blockReader = remoteEth
|
||||||
|
|
||||||
|
txpoolConn := conn
|
||||||
|
if cfg.TxPoolApiAddr != cfg.PrivateApiAddr {
|
||||||
|
txpoolConn, err = grpcutil.Connect(creds, cfg.TxPoolApiAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, ff, nil, fmt.Errorf("could not connect to txpool api: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mining = txpool.NewMiningClient(txpoolConn)
|
||||||
|
miningService := rpcservices.NewMiningService(mining)
|
||||||
|
txPool = txpool.NewTxpoolClient(txpoolConn)
|
||||||
|
txPoolService := rpcservices.NewTxPoolService(txPool)
|
||||||
|
if db == nil {
|
||||||
|
db = remoteKv
|
||||||
|
}
|
||||||
|
eth = remoteEth
|
||||||
|
go func() {
|
||||||
|
if !remoteKv.EnsureVersionCompatibility() {
|
||||||
|
rootCancel()
|
||||||
|
}
|
||||||
|
if !remoteEth.EnsureVersionCompatibility() {
|
||||||
|
rootCancel()
|
||||||
|
}
|
||||||
|
if mining != nil && !miningService.EnsureVersionCompatibility() {
|
||||||
|
rootCancel()
|
||||||
|
}
|
||||||
|
if !txPoolService.EnsureVersionCompatibility() {
|
||||||
|
rootCancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ff = rpchelper.New(ctx, eth, txPool, mining, onNewSnapshot)
|
||||||
|
return db, borDb, eth, txPool, mining, stateCache, blockReader, ff, agg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rpc.API, authAPI []rpc.API) error {
|
||||||
|
if len(authAPI) > 0 {
|
||||||
|
engineInfo, err := startAuthenticatedRpcServer(cfg, authAPI)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go stopAuthenticatedRpcServer(ctx, engineInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Enabled {
|
||||||
|
return startRegularRpcServer(ctx, cfg, rpcAPI)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func startRegularRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rpc.API) error {
|
||||||
|
// register apis and create handler stack
|
||||||
|
httpEndpoint := fmt.Sprintf("%s:%d", cfg.HttpListenAddress, cfg.HttpPort)
|
||||||
|
|
||||||
|
log.Trace("TraceRequests = %t\n", cfg.TraceRequests)
|
||||||
|
srv := rpc.NewServer(cfg.RpcBatchConcurrency, cfg.TraceRequests, cfg.RpcStreamingDisable)
|
||||||
|
|
||||||
|
allowListForRPC, err := parseAllowListForRPC(cfg.RpcAllowListFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srv.SetAllowList(allowListForRPC)
|
||||||
|
|
||||||
|
var defaultAPIList []rpc.API
|
||||||
|
|
||||||
|
for _, api := range rpcAPI {
|
||||||
|
if api.Namespace != "engine" {
|
||||||
|
defaultAPIList = append(defaultAPIList, api)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiFlags []string
|
||||||
|
for _, flag := range cfg.API {
|
||||||
|
if flag != "engine" {
|
||||||
|
apiFlags = append(apiFlags, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := node.RegisterApisFromWhitelist(defaultAPIList, apiFlags, srv, false); err != nil {
|
||||||
|
return fmt.Errorf("could not start register RPC apis: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpHandler := node.NewHTTPHandlerStack(srv, cfg.HttpCORSDomain, cfg.HttpVirtualHost, cfg.HttpCompression)
|
||||||
|
var wsHandler http.Handler
|
||||||
|
if cfg.WebsocketEnabled {
|
||||||
|
wsHandler = srv.WebsocketHandler([]string{"*"}, nil, cfg.WebsocketCompression)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiHandler, err := createHandler(cfg, defaultAPIList, httpHandler, wsHandler, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, _, err := node.StartHTTPEndpoint(httpEndpoint, cfg.HTTPTimeouts, apiHandler)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not start RPC api: %w", err)
|
||||||
|
}
|
||||||
|
info := []interface{}{"url", httpEndpoint, "ws", cfg.WebsocketEnabled,
|
||||||
|
"ws.compression", cfg.WebsocketCompression, "grpc", cfg.GRPCServerEnabled}
|
||||||
|
|
||||||
|
var (
|
||||||
|
healthServer *grpcHealth.Server
|
||||||
|
grpcServer *grpc.Server
|
||||||
|
grpcListener net.Listener
|
||||||
|
grpcEndpoint string
|
||||||
|
)
|
||||||
|
if cfg.GRPCServerEnabled {
|
||||||
|
grpcEndpoint = fmt.Sprintf("%s:%d", cfg.GRPCListenAddress, cfg.GRPCPort)
|
||||||
|
if grpcListener, err = net.Listen("tcp", grpcEndpoint); err != nil {
|
||||||
|
return fmt.Errorf("could not start GRPC listener: %w", err)
|
||||||
|
}
|
||||||
|
grpcServer = grpc.NewServer()
|
||||||
|
if cfg.GRPCHealthCheckEnabled {
|
||||||
|
healthServer = grpcHealth.NewServer()
|
||||||
|
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
|
||||||
|
}
|
||||||
|
go grpcServer.Serve(grpcListener)
|
||||||
|
info = append(info, "grpc.port", cfg.GRPCPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("HTTP endpoint opened", info...)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
srv.Stop()
|
||||||
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
_ = listener.Shutdown(shutdownCtx)
|
||||||
|
log.Info("HTTP endpoint closed", "url", httpEndpoint)
|
||||||
|
|
||||||
|
if cfg.GRPCServerEnabled {
|
||||||
|
if cfg.GRPCHealthCheckEnabled {
|
||||||
|
healthServer.Shutdown()
|
||||||
|
}
|
||||||
|
grpcServer.GracefulStop()
|
||||||
|
_ = grpcListener.Close()
|
||||||
|
log.Info("GRPC endpoint closed", "url", grpcEndpoint)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
<-ctx.Done()
|
||||||
|
log.Info("Exiting...")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type engineInfo struct {
|
||||||
|
Srv *rpc.Server
|
||||||
|
EngineSrv *rpc.Server
|
||||||
|
EngineListener *http.Server
|
||||||
|
EngineHttpEndpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func startAuthenticatedRpcServer(cfg httpcfg.HttpCfg, rpcAPI []rpc.API) (*engineInfo, error) {
|
||||||
|
log.Trace("TraceRequests = %t\n", cfg.TraceRequests)
|
||||||
|
srv := rpc.NewServer(cfg.RpcBatchConcurrency, cfg.TraceRequests, cfg.RpcStreamingDisable)
|
||||||
|
|
||||||
|
engineListener, engineSrv, engineHttpEndpoint, err := createEngineListener(cfg, rpcAPI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not start RPC api for engine: %w", err)
|
||||||
|
}
|
||||||
|
return &engineInfo{Srv: srv, EngineSrv: engineSrv, EngineListener: engineListener, EngineHttpEndpoint: engineHttpEndpoint}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopAuthenticatedRpcServer(ctx context.Context, engineInfo *engineInfo) {
|
||||||
|
defer func() {
|
||||||
|
engineInfo.Srv.Stop()
|
||||||
|
if engineInfo.EngineSrv != nil {
|
||||||
|
engineInfo.EngineSrv.Stop()
|
||||||
|
}
|
||||||
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if engineInfo.EngineListener != nil {
|
||||||
|
_ = engineInfo.EngineListener.Shutdown(shutdownCtx)
|
||||||
|
log.Info("Engine HTTP endpoint close", "url", engineInfo.EngineHttpEndpoint)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
<-ctx.Done()
|
||||||
|
log.Info("Exiting Engine...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWebsocket checks the header of a http request for a websocket upgrade request.
|
||||||
|
func isWebsocket(r *http.Request) bool {
|
||||||
|
return strings.EqualFold(r.Header.Get("Upgrade"), "websocket") &&
|
||||||
|
strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
// obtainJWTSecret loads the jwt-secret, either from the provided config,
|
||||||
|
// or from the default location. If neither of those are present, it generates
|
||||||
|
// a new secret and stores to the default location.
|
||||||
|
func obtainJWTSecret(cfg httpcfg.HttpCfg) ([]byte, error) {
|
||||||
|
// try reading from file
|
||||||
|
log.Info("Reading JWT secret", "path", cfg.JWTSecretPath)
|
||||||
|
// If we run the rpcdaemon and datadir is not specified we just use jwt.hex in current directory.
|
||||||
|
if len(cfg.JWTSecretPath) == 0 {
|
||||||
|
cfg.JWTSecretPath = "jwt.hex"
|
||||||
|
}
|
||||||
|
if data, err := os.ReadFile(cfg.JWTSecretPath); err == nil {
|
||||||
|
jwtSecret := common.FromHex(strings.TrimSpace(string(data)))
|
||||||
|
if len(jwtSecret) == 32 {
|
||||||
|
return jwtSecret, nil
|
||||||
|
}
|
||||||
|
log.Error("Invalid JWT secret", "path", cfg.JWTSecretPath, "length", len(jwtSecret))
|
||||||
|
return nil, errors.New("invalid JWT secret")
|
||||||
|
}
|
||||||
|
// Need to generate one
|
||||||
|
jwtSecret := make([]byte, 32)
|
||||||
|
rand.Read(jwtSecret)
|
||||||
|
|
||||||
|
if err := os.WriteFile(cfg.JWTSecretPath, []byte(hexutil.Encode(jwtSecret)), 0600); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Info("Generated JWT secret", "path", cfg.JWTSecretPath)
|
||||||
|
return jwtSecret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Handler, wsHandler http.Handler, jwtSecret []byte) (http.Handler, error) {
|
||||||
|
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// adding a healthcheck here
|
||||||
|
if health.ProcessHealthcheckIfNeeded(w, r, apiList) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cfg.WebsocketEnabled && wsHandler != nil && isWebsocket(r) {
|
||||||
|
wsHandler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if jwtSecret != nil && !rpc.CheckJwtSecret(w, r, jwtSecret) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpHandler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
return handler, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEngineListener(cfg httpcfg.HttpCfg, engineApi []rpc.API) (*http.Server, *rpc.Server, string, error) {
|
||||||
|
engineHttpEndpoint := fmt.Sprintf("%s:%d", cfg.AuthRpcHTTPListenAddress, cfg.AuthRpcPort)
|
||||||
|
|
||||||
|
engineSrv := rpc.NewServer(cfg.RpcBatchConcurrency, cfg.TraceRequests, true)
|
||||||
|
|
||||||
|
if err := node.RegisterApisFromWhitelist(engineApi, nil, engineSrv, true); err != nil {
|
||||||
|
return nil, nil, "", fmt.Errorf("could not start register RPC engine api: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtSecret, err := obtainJWTSecret(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
wsHandler := engineSrv.WebsocketHandler([]string{"*"}, jwtSecret, cfg.WebsocketCompression)
|
||||||
|
|
||||||
|
engineHttpHandler := node.NewHTTPHandlerStack(engineSrv, nil /* authCors */, cfg.AuthRpcVirtualHost, cfg.HttpCompression)
|
||||||
|
|
||||||
|
engineApiHandler, err := createHandler(cfg, engineApi, engineHttpHandler, wsHandler, jwtSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
engineListener, _, err := node.StartHTTPEndpoint(engineHttpEndpoint, cfg.AuthRpcTimeouts, engineApiHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", fmt.Errorf("could not start RPC api: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
engineInfo := []interface{}{"url", engineHttpEndpoint, "ws", true, "ws.compression", cfg.WebsocketCompression}
|
||||||
|
log.Info("HTTP endpoint opened for Engine API", engineInfo...)
|
||||||
|
|
||||||
|
return engineListener, engineSrv, engineHttpEndpoint, nil
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package httpcfg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv/kvcache"
|
||||||
|
"github.com/ledgerwatch/erigon/eth/ethconfig"
|
||||||
|
"github.com/ledgerwatch/erigon/node/nodecfg/datadir"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc/rpccfg"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HttpCfg struct {
|
||||||
|
Enabled bool
|
||||||
|
PrivateApiAddr string
|
||||||
|
WithDatadir bool // Erigon's database can be read by separated processes on same machine - in read-only mode - with full support of transactions. It will share same "OS PageCache" with Erigon process.
|
||||||
|
DataDir string
|
||||||
|
Dirs datadir.Dirs
|
||||||
|
HttpListenAddress string
|
||||||
|
AuthRpcHTTPListenAddress string
|
||||||
|
TLSCertfile string
|
||||||
|
TLSCACert string
|
||||||
|
TLSKeyFile string
|
||||||
|
HttpPort int
|
||||||
|
AuthRpcPort int
|
||||||
|
HttpCORSDomain []string
|
||||||
|
HttpVirtualHost []string
|
||||||
|
AuthRpcVirtualHost []string
|
||||||
|
HttpCompression bool
|
||||||
|
API []string
|
||||||
|
Gascap uint64
|
||||||
|
MaxTraces uint64
|
||||||
|
WebsocketEnabled bool
|
||||||
|
WebsocketCompression bool
|
||||||
|
RpcAllowListFilePath string
|
||||||
|
RpcBatchConcurrency uint
|
||||||
|
RpcStreamingDisable bool
|
||||||
|
DBReadConcurrency int
|
||||||
|
TraceCompatibility bool // Bug for bug compatibility for trace_ routines with OpenEthereum
|
||||||
|
TxPoolApiAddr string
|
||||||
|
StateCache kvcache.CoherentConfig
|
||||||
|
Snap ethconfig.Snapshot
|
||||||
|
Sync ethconfig.Sync
|
||||||
|
GRPCServerEnabled bool
|
||||||
|
GRPCListenAddress string
|
||||||
|
GRPCPort int
|
||||||
|
GRPCHealthCheckEnabled bool
|
||||||
|
StarknetGRPCAddress string
|
||||||
|
JWTSecretPath string // Engine API Authentication
|
||||||
|
TraceRequests bool // Always trace requests in INFO level
|
||||||
|
HTTPTimeouts rpccfg.HTTPTimeouts
|
||||||
|
AuthRpcTimeouts rpccfg.HTTPTimeouts
|
||||||
|
EvmCallTimeout time.Duration
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type allowListFile struct {
|
||||||
|
Allow rpc.AllowList `json:"allow"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAllowListForRPC(path string) (rpc.AllowList, error) {
|
||||||
|
path = strings.TrimSpace(path)
|
||||||
|
if path == "" { // no file is provided
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
file.Close() //nolint: errcheck
|
||||||
|
}()
|
||||||
|
|
||||||
|
fileContents, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowListFileObj allowListFile
|
||||||
|
|
||||||
|
err = json.Unmarshal(fileContents, &allowListFileObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowListFileObj.Allow, nil
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv/kvcache"
|
||||||
|
libstate "github.com/ledgerwatch/erigon-lib/state"
|
||||||
|
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/commands"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/services"
|
||||||
|
|
||||||
|
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/cli/httpcfg"
|
||||||
|
|
||||||
|
erigonHttpcfg "github.com/ledgerwatch/erigon/cmd/rpcdaemon/cli/httpcfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIList describes the list of available RPC apis
|
||||||
|
func APIList(db kv.RoDB, borDb kv.RoDB, eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient,
|
||||||
|
filters *rpchelper.Filters, stateCache kvcache.Cache,
|
||||||
|
blockReader services.FullBlockReader, agg *libstate.Aggregator22, cfg httpcfg.HttpCfg) (list []rpc.API) {
|
||||||
|
tcfg := erigonHttpcfg.HttpCfg{
|
||||||
|
MaxTraces: cfg.MaxTraces,
|
||||||
|
Gascap: cfg.Gascap,
|
||||||
|
TraceCompatibility: cfg.TraceCompatibility,
|
||||||
|
}
|
||||||
|
baseUtils := NewBaseUtilsApi(filters, stateCache, blockReader, agg, cfg.WithDatadir, cfg.EvmCallTimeout)
|
||||||
|
base := commands.NewBaseApi(filters, stateCache, blockReader, agg, cfg.WithDatadir, cfg.EvmCallTimeout)
|
||||||
|
ethImpl := commands.NewEthAPI(base, db, eth, txPool, mining, cfg.Gascap)
|
||||||
|
erigonImpl := commands.NewErigonAPI(base, db, eth)
|
||||||
|
txpoolImpl := commands.NewTxPoolAPI(base, db, txPool)
|
||||||
|
netImpl := commands.NewNetAPIImpl(eth)
|
||||||
|
debugImpl := commands.NewPrivateDebugAPI(base, db, cfg.Gascap)
|
||||||
|
traceImpl := commands.NewTraceAPI(base, db, &tcfg)
|
||||||
|
web3Impl := commands.NewWeb3APIImpl(eth)
|
||||||
|
dbImpl := commands.NewDBAPIImpl() /* deprecated */
|
||||||
|
adminImpl := commands.NewAdminAPI(eth)
|
||||||
|
parityImpl := commands.NewParityAPIImpl(db)
|
||||||
|
borImpl := commands.NewBorAPI(base, db, borDb) // bor (consensus) specific
|
||||||
|
otsImpl := NewOtterscanAPI(baseUtils, db)
|
||||||
|
|
||||||
|
for _, enabledAPI := range cfg.API {
|
||||||
|
switch enabledAPI {
|
||||||
|
case "eth":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "eth",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.EthAPI(ethImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "debug":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "debug",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.PrivateDebugAPI(debugImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "net":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "net",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.NetAPI(netImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "txpool":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "txpool",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.TxPoolAPI(txpoolImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "web3":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "web3",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.Web3API(web3Impl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "trace":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "trace",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.TraceAPI(traceImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "db": /* Deprecated */
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "db",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.DBAPI(dbImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "erigon":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "erigon",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.ErigonAPI(erigonImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "bor":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "bor",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.BorAPI(borImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "admin":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "admin",
|
||||||
|
Public: false,
|
||||||
|
Service: commands.AdminAPI(adminImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "parity":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "parity",
|
||||||
|
Public: false,
|
||||||
|
Service: commands.ParityAPI(parityImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
case "ots":
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "ots",
|
||||||
|
Public: true,
|
||||||
|
Service: OtterscanAPI(otsImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthAPIList(db kv.RoDB, eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient,
|
||||||
|
filters *rpchelper.Filters, stateCache kvcache.Cache, blockReader services.FullBlockReader,
|
||||||
|
agg *libstate.Aggregator22,
|
||||||
|
cfg httpcfg.HttpCfg) (list []rpc.API) {
|
||||||
|
base := commands.NewBaseApi(filters, stateCache, blockReader, agg, cfg.WithDatadir, cfg.EvmCallTimeout)
|
||||||
|
|
||||||
|
ethImpl := commands.NewEthAPI(base, db, eth, txPool, mining, cfg.Gascap)
|
||||||
|
engineImpl := commands.NewEngineAPI(base, db, eth)
|
||||||
|
|
||||||
|
list = append(list, rpc.API{
|
||||||
|
Namespace: "eth",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.EthAPI(ethImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
}, rpc.API{
|
||||||
|
Namespace: "engine",
|
||||||
|
Public: true,
|
||||||
|
Service: commands.EngineAPI(engineImpl),
|
||||||
|
Version: "1.0",
|
||||||
|
})
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
// NotImplemented is the URI prefix for smartcard wallets.
|
||||||
|
const NotImplemented = "the method is currently not implemented: %s"
|
||||||
|
|
||||||
|
// NotAvailableChainData x
|
||||||
|
const NotAvailableChainData = "the function %s is not available, please use --private.api.addr option instead of --datadir option"
|
||||||
|
|
||||||
|
// NotAvailableDeprecated x
|
||||||
|
const NotAvailableDeprecated = "the method has been deprecated: %s"
|
|
@ -0,0 +1,39 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv/memdb"
|
||||||
|
"github.com/ledgerwatch/erigon/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetChainConfig(t *testing.T) {
|
||||||
|
db := memdb.NewTestDB(t)
|
||||||
|
config, _, err := core.CommitGenesisBlock(db, core.DefaultGenesisBlock())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("setting up genensis block: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, txErr := db.BeginRo(context.Background())
|
||||||
|
if txErr != nil {
|
||||||
|
t.Fatalf("error starting tx: %v", txErr)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
api := &BaseAPI{}
|
||||||
|
config1, err1 := api.chainConfig(tx)
|
||||||
|
if err1 != nil {
|
||||||
|
t.Fatalf("reading chain config: %v", err1)
|
||||||
|
}
|
||||||
|
if config.String() != config1.String() {
|
||||||
|
t.Fatalf("read different config: %s, expected %s", config1.String(), config.String())
|
||||||
|
}
|
||||||
|
config2, err2 := api.chainConfig(tx)
|
||||||
|
if err2 != nil {
|
||||||
|
t.Fatalf("reading chain config: %v", err2)
|
||||||
|
}
|
||||||
|
if config.String() != config2.String() {
|
||||||
|
t.Fatalf("read different config: %s, expected %s", config2.String(), config.String())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,526 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/commands"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/consensus/ethash"
|
||||||
|
"github.com/ledgerwatch/erigon/core"
|
||||||
|
"github.com/ledgerwatch/erigon/core/rawdb"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
"github.com/ledgerwatch/erigon/params"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/transactions"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
|
||||||
|
"github.com/wmitsuda/otterscan/erigon_internal/ethapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API_LEVEL Must be incremented every time new additions are made
|
||||||
|
const API_LEVEL = 8
|
||||||
|
|
||||||
|
type TransactionsWithReceipts struct {
|
||||||
|
Txs []*commands.RPCTransaction `json:"txs"`
|
||||||
|
Receipts []map[string]interface{} `json:"receipts"`
|
||||||
|
FirstPage bool `json:"firstPage"`
|
||||||
|
LastPage bool `json:"lastPage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OtterscanAPI interface {
|
||||||
|
GetApiLevel() uint8
|
||||||
|
GetInternalOperations(ctx context.Context, hash common.Hash) ([]*InternalOperation, error)
|
||||||
|
SearchTransactionsBefore(ctx context.Context, addr common.Address, blockNum uint64, pageSize uint16) (*TransactionsWithReceipts, error)
|
||||||
|
SearchTransactionsAfter(ctx context.Context, addr common.Address, blockNum uint64, pageSize uint16) (*TransactionsWithReceipts, error)
|
||||||
|
GetBlockDetails(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error)
|
||||||
|
GetBlockDetailsByHash(ctx context.Context, hash common.Hash) (map[string]interface{}, error)
|
||||||
|
GetBlockTransactions(ctx context.Context, number rpc.BlockNumber, pageNumber uint8, pageSize uint8) (map[string]interface{}, error)
|
||||||
|
HasCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (bool, error)
|
||||||
|
TraceTransaction(ctx context.Context, hash common.Hash) ([]*TraceEntry, error)
|
||||||
|
GetTransactionError(ctx context.Context, hash common.Hash) (hexutil.Bytes, error)
|
||||||
|
GetTransactionBySenderAndNonce(ctx context.Context, addr common.Address, nonce uint64) (*common.Hash, error)
|
||||||
|
GetContractCreator(ctx context.Context, addr common.Address) (*ContractCreatorData, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OtterscanAPIImpl struct {
|
||||||
|
*BaseAPIUtils
|
||||||
|
|
||||||
|
db kv.RoDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOtterscanAPI(base *BaseAPIUtils, db kv.RoDB) *OtterscanAPIImpl {
|
||||||
|
return &OtterscanAPIImpl{
|
||||||
|
BaseAPIUtils: base,
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) GetApiLevel() uint8 {
|
||||||
|
return API_LEVEL
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: dedup from eth_txs.go#GetTransactionByHash
|
||||||
|
func (api *OtterscanAPIImpl) getTransactionByHash(ctx context.Context, tx kv.Tx, hash common.Hash) (types.Transaction, *types.Block, common.Hash, uint64, uint64, error) {
|
||||||
|
// https://infura.io/docs/ethereum/json-rpc/eth-getTransactionByHash
|
||||||
|
blockNum, ok, err := api.txnLookup(ctx, tx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, common.Hash{}, 0, 0, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, common.Hash{}, 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := api.blockByNumberWithSenders(tx, blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, common.Hash{}, 0, 0, err
|
||||||
|
}
|
||||||
|
if block == nil {
|
||||||
|
return nil, nil, common.Hash{}, 0, 0, nil
|
||||||
|
}
|
||||||
|
blockHash := block.Hash()
|
||||||
|
var txnIndex uint64
|
||||||
|
var txn types.Transaction
|
||||||
|
for i, transaction := range block.Transactions() {
|
||||||
|
if transaction.Hash() == hash {
|
||||||
|
txn = transaction
|
||||||
|
txnIndex = uint64(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add GasPrice for the DynamicFeeTransaction
|
||||||
|
// var baseFee *big.Int
|
||||||
|
// if chainConfig.IsLondon(blockNum) && blockHash != (common.Hash{}) {
|
||||||
|
// baseFee = block.BaseFee()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if no transaction was found then we return nil
|
||||||
|
if txn == nil {
|
||||||
|
return nil, nil, common.Hash{}, 0, 0, nil
|
||||||
|
}
|
||||||
|
return txn, block, blockHash, blockNum, txnIndex, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) runTracer(ctx context.Context, tx kv.Tx, hash common.Hash, tracer vm.Tracer) (*core.ExecutionResult, error) {
|
||||||
|
txn, block, blockHash, _, txIndex, err := api.getTransactionByHash(ctx, tx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if txn == nil {
|
||||||
|
return nil, fmt.Errorf("transaction %#x not found", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
chainConfig, err := api.chainConfig(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeader := func(hash common.Hash, number uint64) *types.Header {
|
||||||
|
return rawdb.ReadHeader(tx, hash, number)
|
||||||
|
}
|
||||||
|
msg, blockCtx, txCtx, ibs, _, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, ethash.NewFaker(), tx, blockHash, txIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var vmConfig vm.Config
|
||||||
|
if tracer == nil {
|
||||||
|
vmConfig = vm.Config{}
|
||||||
|
} else {
|
||||||
|
vmConfig = vm.Config{Debug: true, Tracer: tracer}
|
||||||
|
}
|
||||||
|
vmenv := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vmConfig)
|
||||||
|
|
||||||
|
result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), true, false /* gasBailout */)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("tracing failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) GetInternalOperations(ctx context.Context, hash common.Hash) ([]*InternalOperation, error) {
|
||||||
|
tx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
tracer := NewOperationsTracer(ctx)
|
||||||
|
if _, err := api.runTracer(ctx, tx, hash, tracer); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracer.Results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search transactions that touch a certain address.
|
||||||
|
//
|
||||||
|
// It searches back a certain block (excluding); the results are sorted descending.
|
||||||
|
//
|
||||||
|
// The pageSize indicates how many txs may be returned. If there are less txs than pageSize,
|
||||||
|
// they are just returned. But it may return a little more than pageSize if there are more txs
|
||||||
|
// than the necessary to fill pageSize in the last found block, i.e., let's say you want pageSize == 25,
|
||||||
|
// you already found 24 txs, the next block contains 4 matches, then this function will return 28 txs.
|
||||||
|
func (api *OtterscanAPIImpl) SearchTransactionsBefore(ctx context.Context, addr common.Address, blockNum uint64, pageSize uint16) (*TransactionsWithReceipts, error) {
|
||||||
|
dbtx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer dbtx.Rollback()
|
||||||
|
|
||||||
|
log.Info("got cursor")
|
||||||
|
|
||||||
|
callFromCursor, err := dbtx.Cursor(kv.CallFromIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer callFromCursor.Close()
|
||||||
|
log.Info("call from cur")
|
||||||
|
|
||||||
|
callToCursor, err := dbtx.Cursor(kv.CallToIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer callToCursor.Close()
|
||||||
|
log.Info("cur to call")
|
||||||
|
|
||||||
|
chainConfig, err := api.chainConfig(dbtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirstPage := false
|
||||||
|
if blockNum == 0 {
|
||||||
|
isFirstPage = true
|
||||||
|
} else {
|
||||||
|
// Internal search code considers blockNum [including], so adjust the value
|
||||||
|
blockNum--
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize search cursors at the first shard >= desired block number
|
||||||
|
callFromProvider := NewCallCursorBackwardBlockProvider(callFromCursor, addr, blockNum)
|
||||||
|
callToProvider := NewCallCursorBackwardBlockProvider(callToCursor, addr, blockNum)
|
||||||
|
callFromToProvider := newCallFromToBlockProvider(false, callFromProvider, callToProvider)
|
||||||
|
|
||||||
|
txs := make([]*commands.RPCTransaction, 0, pageSize)
|
||||||
|
receipts := make([]map[string]interface{}, 0, pageSize)
|
||||||
|
|
||||||
|
resultCount := uint16(0)
|
||||||
|
hasMore := true
|
||||||
|
for {
|
||||||
|
if resultCount >= pageSize || !hasMore {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var results []*TransactionsWithReceipts
|
||||||
|
results, hasMore, err = api.traceBlocks(ctx, addr, chainConfig, pageSize, resultCount, callFromToProvider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, r := range results {
|
||||||
|
if r == nil {
|
||||||
|
return nil, errors.New("internal error during search tracing")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(r.Txs) - 1; i >= 0; i-- {
|
||||||
|
txs = append(txs, r.Txs[i])
|
||||||
|
}
|
||||||
|
for i := len(r.Receipts) - 1; i >= 0; i-- {
|
||||||
|
receipts = append(receipts, r.Receipts[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
resultCount += uint16(len(r.Txs))
|
||||||
|
if resultCount >= pageSize {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TransactionsWithReceipts{txs, receipts, isFirstPage, !hasMore}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search transactions that touch a certain address.
|
||||||
|
//
|
||||||
|
// It searches forward a certain block (excluding); the results are sorted descending.
|
||||||
|
//
|
||||||
|
// The pageSize indicates how many txs may be returned. If there are less txs than pageSize,
|
||||||
|
// they are just returned. But it may return a little more than pageSize if there are more txs
|
||||||
|
// than the necessary to fill pageSize in the last found block, i.e., let's say you want pageSize == 25,
|
||||||
|
// you already found 24 txs, the next block contains 4 matches, then this function will return 28 txs.
|
||||||
|
func (api *OtterscanAPIImpl) SearchTransactionsAfter(ctx context.Context, addr common.Address, blockNum uint64, pageSize uint16) (*TransactionsWithReceipts, error) {
|
||||||
|
dbtx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer dbtx.Rollback()
|
||||||
|
|
||||||
|
callFromCursor, err := dbtx.Cursor(kv.CallFromIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer callFromCursor.Close()
|
||||||
|
|
||||||
|
callToCursor, err := dbtx.Cursor(kv.CallToIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer callToCursor.Close()
|
||||||
|
|
||||||
|
chainConfig, err := api.chainConfig(dbtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isLastPage := false
|
||||||
|
if blockNum == 0 {
|
||||||
|
isLastPage = true
|
||||||
|
} else {
|
||||||
|
// Internal search code considers blockNum [including], so adjust the value
|
||||||
|
blockNum++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize search cursors at the first shard >= desired block number
|
||||||
|
callFromProvider := NewCallCursorForwardBlockProvider(callFromCursor, addr, blockNum)
|
||||||
|
callToProvider := NewCallCursorForwardBlockProvider(callToCursor, addr, blockNum)
|
||||||
|
callFromToProvider := newCallFromToBlockProvider(true, callFromProvider, callToProvider)
|
||||||
|
|
||||||
|
txs := make([]*commands.RPCTransaction, 0, pageSize)
|
||||||
|
receipts := make([]map[string]interface{}, 0, pageSize)
|
||||||
|
|
||||||
|
resultCount := uint16(0)
|
||||||
|
hasMore := true
|
||||||
|
for {
|
||||||
|
if resultCount >= pageSize || !hasMore {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []*TransactionsWithReceipts
|
||||||
|
results, hasMore, err = api.traceBlocks(ctx, addr, chainConfig, pageSize, resultCount, callFromToProvider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range results {
|
||||||
|
if r == nil {
|
||||||
|
return nil, errors.New("internal error during search tracing")
|
||||||
|
}
|
||||||
|
|
||||||
|
txs = append(txs, r.Txs...)
|
||||||
|
receipts = append(receipts, r.Receipts...)
|
||||||
|
|
||||||
|
resultCount += uint16(len(r.Txs))
|
||||||
|
if resultCount >= pageSize {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse results
|
||||||
|
lentxs := len(txs)
|
||||||
|
for i := 0; i < lentxs/2; i++ {
|
||||||
|
txs[i], txs[lentxs-1-i] = txs[lentxs-1-i], txs[i]
|
||||||
|
receipts[i], receipts[lentxs-1-i] = receipts[lentxs-1-i], receipts[i]
|
||||||
|
}
|
||||||
|
return &TransactionsWithReceipts{txs, receipts, !hasMore, isLastPage}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) traceBlocks(ctx context.Context, addr common.Address, chainConfig *params.ChainConfig, pageSize, resultCount uint16, callFromToProvider BlockProvider) ([]*TransactionsWithReceipts, bool, error) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Estimate the common case of user address having at most 1 interaction/block and
|
||||||
|
// trace N := remaining page matches as number of blocks to trace concurrently.
|
||||||
|
// TODO: this is not optimimal for big contract addresses; implement some better heuristics.
|
||||||
|
estBlocksToTrace := pageSize - resultCount
|
||||||
|
results := make([]*TransactionsWithReceipts, estBlocksToTrace)
|
||||||
|
totalBlocksTraced := 0
|
||||||
|
hasMore := true
|
||||||
|
|
||||||
|
for i := 0; i < int(estBlocksToTrace); i++ {
|
||||||
|
var nextBlock uint64
|
||||||
|
var err error
|
||||||
|
nextBlock, hasMore, err = callFromToProvider()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
// TODO: nextBlock == 0 seems redundant with hasMore == false
|
||||||
|
if !hasMore && nextBlock == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
totalBlocksTraced++
|
||||||
|
go api.searchTraceBlock(ctx, &wg, addr, chainConfig, i, nextBlock, results)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return results[:totalBlocksTraced], hasMore, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) delegateGetBlockByNumber(tx kv.Tx, b *types.Block, number rpc.BlockNumber, inclTx bool) (map[string]interface{}, error) {
|
||||||
|
td, err := rawdb.ReadTd(tx, b.Hash(), b.NumberU64())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response, err := ethapi.RPCMarshalBlock(b, inclTx, inclTx)
|
||||||
|
if !inclTx {
|
||||||
|
delete(response, "transactions") // workaround for https://github.com/ledgerwatch/erigon/issues/4989#issuecomment-1218415666
|
||||||
|
}
|
||||||
|
response["totalDifficulty"] = (*hexutil.Big)(td)
|
||||||
|
response["transactionCount"] = b.Transactions().Len()
|
||||||
|
|
||||||
|
if err == nil && number == rpc.PendingBlockNumber {
|
||||||
|
// Pending blocks need to nil out a few fields
|
||||||
|
for _, field := range []string{"hash", "nonce", "miner"} {
|
||||||
|
response[field] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly drop unwanted fields
|
||||||
|
response["logsBloom"] = nil
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: temporary workaround due to API breakage from watch_the_burn
|
||||||
|
type internalIssuance struct {
|
||||||
|
BlockReward string `json:"blockReward,omitempty"`
|
||||||
|
UncleReward string `json:"uncleReward,omitempty"`
|
||||||
|
Issuance string `json:"issuance,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) delegateIssuance(tx kv.Tx, block *types.Block, chainConfig *params.ChainConfig) (internalIssuance, error) {
|
||||||
|
if chainConfig.Ethash == nil {
|
||||||
|
// Clique for example has no issuance
|
||||||
|
return internalIssuance{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
minerReward, uncleRewards := ethash.AccumulateRewards(chainConfig, block.Header(), block.Uncles())
|
||||||
|
issuance := minerReward
|
||||||
|
for _, r := range uncleRewards {
|
||||||
|
p := r // avoids warning?
|
||||||
|
issuance.Add(&issuance, &p)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret internalIssuance
|
||||||
|
ret.BlockReward = hexutil.EncodeBig(minerReward.ToBig())
|
||||||
|
ret.Issuance = hexutil.EncodeBig(issuance.ToBig())
|
||||||
|
issuance.Sub(&issuance, &minerReward)
|
||||||
|
ret.UncleReward = hexutil.EncodeBig(issuance.ToBig())
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) delegateBlockFees(ctx context.Context, tx kv.Tx, block *types.Block, senders []common.Address, chainConfig *params.ChainConfig) (uint64, error) {
|
||||||
|
receipts, err := api.getReceipts(ctx, tx, chainConfig, block, senders)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("getReceipts error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fees := uint64(0)
|
||||||
|
for _, receipt := range receipts {
|
||||||
|
txn := block.Transactions()[receipt.TransactionIndex]
|
||||||
|
effectiveGasPrice := uint64(0)
|
||||||
|
if !chainConfig.IsLondon(block.NumberU64()) {
|
||||||
|
effectiveGasPrice = txn.GetPrice().Uint64()
|
||||||
|
} else {
|
||||||
|
baseFee, _ := uint256.FromBig(block.BaseFee())
|
||||||
|
gasPrice := new(big.Int).Add(block.BaseFee(), txn.GetEffectiveGasTip(baseFee).ToBig())
|
||||||
|
effectiveGasPrice = gasPrice.Uint64()
|
||||||
|
}
|
||||||
|
fees += effectiveGasPrice * receipt.GasUsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return fees, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) getBlockWithSenders(ctx context.Context, number rpc.BlockNumber, tx kv.Tx) (*types.Block, []common.Address, error) {
|
||||||
|
if number == rpc.PendingBlockNumber {
|
||||||
|
return api.pendingBlock(), nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, hash, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(number), tx, api.filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, senders, err := api._blockReader.BlockWithSenders(ctx, tx, hash, n)
|
||||||
|
return block, senders, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) GetBlockTransactions(ctx context.Context, number rpc.BlockNumber, pageNumber uint8, pageSize uint8) (map[string]interface{}, error) {
|
||||||
|
tx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
b, senders, err := api.getBlockWithSenders(ctx, number, tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if b == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
chainConfig, err := api.chainConfig(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
getBlockRes, err := api.delegateGetBlockByNumber(tx, b, number, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receipts
|
||||||
|
receipts, err := api.getReceipts(ctx, tx, chainConfig, b, senders)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getReceipts error: %v", err)
|
||||||
|
}
|
||||||
|
result := make([]map[string]interface{}, 0, len(receipts))
|
||||||
|
for _, receipt := range receipts {
|
||||||
|
txn := b.Transactions()[receipt.TransactionIndex]
|
||||||
|
marshalledRcpt := marshalReceipt(receipt, txn, chainConfig, b, txn.Hash(), true)
|
||||||
|
marshalledRcpt["logs"] = nil
|
||||||
|
marshalledRcpt["logsBloom"] = nil
|
||||||
|
result = append(result, marshalledRcpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pruned block attrs
|
||||||
|
prunedBlock := map[string]interface{}{}
|
||||||
|
for _, k := range []string{"timestamp", "miner", "baseFeePerGas"} {
|
||||||
|
prunedBlock[k] = getBlockRes[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crop tx input to 4bytes
|
||||||
|
var txs = getBlockRes["transactions"].([]interface{})
|
||||||
|
for _, rawTx := range txs {
|
||||||
|
rpcTx := rawTx.(*ethapi.RPCTransaction)
|
||||||
|
if len(rpcTx.Input) >= 4 {
|
||||||
|
rpcTx.Input = rpcTx.Input[:4]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crop page
|
||||||
|
pageEnd := b.Transactions().Len() - int(pageNumber)*int(pageSize)
|
||||||
|
pageStart := pageEnd - int(pageSize)
|
||||||
|
if pageEnd < 0 {
|
||||||
|
pageEnd = 0
|
||||||
|
}
|
||||||
|
if pageStart < 0 {
|
||||||
|
pageStart = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{}
|
||||||
|
getBlockRes["transactions"] = getBlockRes["transactions"].([]interface{})[pageStart:pageEnd]
|
||||||
|
response["fullblock"] = getBlockRes
|
||||||
|
response["receipts"] = result[pageStart:pageEnd]
|
||||||
|
return response, nil
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/core/rawdb"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) GetBlockDetails(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
|
||||||
|
tx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
b, senders, err := api.getBlockWithSenders(ctx, number, tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if b == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
chainConfig, err := api.chainConfig(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
getBlockRes, err := api.delegateGetBlockByNumber(tx, b, number, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
getIssuanceRes, err := api.delegateIssuance(tx, b, chainConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
feesRes, err := api.delegateBlockFees(ctx, tx, b, senders, chainConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{}
|
||||||
|
response["block"] = getBlockRes
|
||||||
|
response["issuance"] = getIssuanceRes
|
||||||
|
response["totalFees"] = hexutil.Uint64(feesRes)
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove duplication with GetBlockDetails
|
||||||
|
func (api *OtterscanAPIImpl) GetBlockDetailsByHash(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
|
||||||
|
tx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// b, senders, err := rawdb.ReadBlockByHashWithSenders(tx, hash)
|
||||||
|
blockNumber := rawdb.ReadHeaderNumber(tx, hash)
|
||||||
|
if blockNumber == nil {
|
||||||
|
return nil, fmt.Errorf("couldn't find block number for hash %v", hash.Bytes())
|
||||||
|
}
|
||||||
|
b, senders, err := api._blockReader.BlockWithSenders(ctx, tx, hash, *blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if b == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
chainConfig, err := api.chainConfig(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
getBlockRes, err := api.delegateGetBlockByNumber(tx, b, rpc.BlockNumber(b.Number().Int64()), false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
getIssuanceRes, err := api.delegateIssuance(tx, b, chainConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
feesRes, err := api.delegateBlockFees(ctx, tx, b, senders, chainConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{}
|
||||||
|
response["block"] = getBlockRes
|
||||||
|
response["issuance"] = getIssuanceRes
|
||||||
|
response["totalFees"] = hexutil.Uint64(feesRes)
|
||||||
|
return response, nil
|
||||||
|
}
|
|
@ -0,0 +1,245 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/RoaringBitmap/roaring/roaring64"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/changeset"
|
||||||
|
"github.com/ledgerwatch/erigon/consensus/ethash"
|
||||||
|
"github.com/ledgerwatch/erigon/core"
|
||||||
|
"github.com/ledgerwatch/erigon/core/state"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types/accounts"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
"github.com/ledgerwatch/erigon/params"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/shards"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContractCreatorData struct {
|
||||||
|
Tx common.Hash `json:"hash"`
|
||||||
|
Creator common.Address `json:"creator"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) GetContractCreator(ctx context.Context, addr common.Address) (*ContractCreatorData, error) {
|
||||||
|
tx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
reader := state.NewPlainStateReader(tx)
|
||||||
|
plainStateAcc, err := reader.ReadAccountData(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No state == non existent
|
||||||
|
if plainStateAcc == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EOA?
|
||||||
|
if plainStateAcc.IsEmptyCodeHash() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contract; search for creation tx; navigate forward on AccountsHistory/ChangeSets
|
||||||
|
//
|
||||||
|
// We search shards in forward order on purpose because popular contracts may have
|
||||||
|
// dozens of states changes due to ETH deposits/withdraw after contract creation,
|
||||||
|
// so it is optimal to search from the beginning even if the contract has multiple
|
||||||
|
// incarnations.
|
||||||
|
accHistory, err := tx.Cursor(kv.AccountsHistory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer accHistory.Close()
|
||||||
|
|
||||||
|
accCS, err := tx.CursorDupSort(kv.AccountChangeSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer accCS.Close()
|
||||||
|
|
||||||
|
// Locate shard that contains the block where incarnation changed
|
||||||
|
acs := changeset.Mapper[kv.AccountChangeSet]
|
||||||
|
k, v, err := accHistory.Seek(acs.IndexChunkKey(addr.Bytes(), 0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(k, addr.Bytes()) {
|
||||||
|
log.Error("Couldn't find any shard for account history", "addr", addr)
|
||||||
|
return nil, fmt.Errorf("could't find any shard for account history addr=%v", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc accounts.Account
|
||||||
|
bm := roaring64.NewBitmap()
|
||||||
|
prevShardMaxBl := uint64(0)
|
||||||
|
for {
|
||||||
|
_, err := bm.ReadFrom(bytes.NewReader(v))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shortcut precheck
|
||||||
|
st, err := acs.Find(accCS, bm.Maximum(), addr.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if st == nil {
|
||||||
|
log.Error("Unexpected error, couldn't find changeset", "block", bm.Maximum(), "addr", addr)
|
||||||
|
return nil, fmt.Errorf("unexpected error, couldn't find changeset block=%v addr=%v", bm.Maximum(), addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found the shard where the incarnation change happens; ignore all
|
||||||
|
// next shards
|
||||||
|
if err := acc.DecodeForStorage(st); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if acc.Incarnation >= plainStateAcc.Incarnation {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevShardMaxBl = bm.Maximum()
|
||||||
|
|
||||||
|
k, v, err = accHistory.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No more shards; it means the max bl from previous shard
|
||||||
|
// contains the incarnation change
|
||||||
|
if !bytes.HasPrefix(k, addr.Bytes()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary search block number inside shard; get first block where desired
|
||||||
|
// incarnation appears
|
||||||
|
blocks := bm.ToArray()
|
||||||
|
var searchErr error
|
||||||
|
r := sort.Search(len(blocks), func(i int) bool {
|
||||||
|
bl := blocks[i]
|
||||||
|
st, err := acs.Find(accCS, bl, addr.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
searchErr = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if st == nil {
|
||||||
|
log.Error("Unexpected error, couldn't find changeset", "block", bl, "addr", addr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := acc.DecodeForStorage(st); err != nil {
|
||||||
|
searchErr = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if acc.Incarnation < plainStateAcc.Incarnation {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if searchErr != nil {
|
||||||
|
return nil, searchErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sort.Search function finds the first block where the incarnation has
|
||||||
|
// changed to the desired one, so we get the previous block from the bitmap;
|
||||||
|
// however if the found block is already the first one from the bitmap, it means
|
||||||
|
// the block we want is the max block from the previous shard.
|
||||||
|
blockFound := prevShardMaxBl
|
||||||
|
if r > 0 {
|
||||||
|
blockFound = blocks[r-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace block, find tx and contract creator
|
||||||
|
chainConfig, err := api.chainConfig(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tracer := NewCreateTracer(ctx, addr)
|
||||||
|
if err := api.deployerFinder(tx, ctx, blockFound, chainConfig, tracer); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ContractCreatorData{
|
||||||
|
Tx: tracer.Tx.Hash(),
|
||||||
|
Creator: tracer.Creator,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) deployerFinder(dbtx kv.Tx, ctx context.Context, blockNum uint64, chainConfig *params.ChainConfig, tracer GenericTracer) error {
|
||||||
|
block, err := api.blockByNumberWithSenders(dbtx, blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if block == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := state.NewPlainState(dbtx, blockNum)
|
||||||
|
stateCache := shards.NewStateCache(32, 0 /* no limit */)
|
||||||
|
cachedReader := state.NewCachedReader(reader, stateCache)
|
||||||
|
noop := state.NewNoopWriter()
|
||||||
|
cachedWriter := state.NewCachedWriter(noop, stateCache)
|
||||||
|
|
||||||
|
ibs := state.New(cachedReader)
|
||||||
|
signer := types.MakeSigner(chainConfig, blockNum)
|
||||||
|
|
||||||
|
getHeader := func(hash common.Hash, number uint64) *types.Header {
|
||||||
|
h, e := api._blockReader.Header(ctx, dbtx, hash, number)
|
||||||
|
if e != nil {
|
||||||
|
log.Error("getHeader error", "number", number, "hash", hash, "err", e)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
engine := ethash.NewFaker()
|
||||||
|
|
||||||
|
header := block.Header()
|
||||||
|
rules := chainConfig.Rules(block.NumberU64())
|
||||||
|
// we can filter away anything that does not include 0xf0, 0xf5, or 0x38, aka create, create2 or codesize opcodes
|
||||||
|
// while this will result in false positives, it should reduce the time a lot.
|
||||||
|
// it can be improved in the future with smarter algorithms (ala, looking for
|
||||||
|
deployers := map[common.Address]struct{}{}
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
dat := tx.GetData()
|
||||||
|
for _, v := range dat {
|
||||||
|
if sender, ok := tx.GetSender(); ok {
|
||||||
|
if v == 0xf0 || v == 0xf5 {
|
||||||
|
deployers[sender] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for idx, tx := range block.Transactions() {
|
||||||
|
if sender, ok := tx.GetSender(); ok {
|
||||||
|
if _, ok := deployers[sender]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ibs.Prepare(tx.Hash(), block.Hash(), idx)
|
||||||
|
|
||||||
|
msg, _ := tx.AsMessage(*signer, header.BaseFee, rules)
|
||||||
|
|
||||||
|
BlockContext := core.NewEVMBlockContext(header, core.GetHashFn(header, getHeader), engine, nil)
|
||||||
|
TxContext := core.NewEVMTxContext(msg)
|
||||||
|
|
||||||
|
vmenv := vm.NewEVM(BlockContext, TxContext, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer})
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.GetGas()), true /* refunds */, false /* gasBailout */); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = ibs.FinalizeTx(vmenv.ChainConfig().Rules(block.NumberU64()), cachedWriter)
|
||||||
|
|
||||||
|
if tracer.Found() {
|
||||||
|
tracer.SetTransaction(tx)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper implementation of vm.Tracer; since the interface is big and most
|
||||||
|
// custom tracers implement just a few of the methods, this is a base struct
|
||||||
|
// to avoid lots of empty boilerplate code
|
||||||
|
type DefaultTracer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTracer) CaptureStart(env *vm.EVM, depth int, from common.Address, to common.Address, precompile bool, create bool, calltype vm.CallType, input []byte, gas uint64, value *big.Int, code []byte) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTracer) CaptureEnd(depth int, output []byte, startGas, endGas uint64, d time.Duration, err error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *big.Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTracer) CaptureAccountRead(account common.Address) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTracer) CaptureAccountWrite(account common.Address) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/consensus/ethash"
|
||||||
|
"github.com/ledgerwatch/erigon/core"
|
||||||
|
"github.com/ledgerwatch/erigon/core/state"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
"github.com/ledgerwatch/erigon/params"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/shards"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenericTracer interface {
|
||||||
|
vm.Tracer
|
||||||
|
SetTransaction(tx types.Transaction)
|
||||||
|
Found() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) genericTracer(dbtx kv.Tx, ctx context.Context, blockNum uint64, chainConfig *params.ChainConfig, tracer GenericTracer) error {
|
||||||
|
block, err := api.blockByNumberWithSenders(dbtx, blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("got block with senders")
|
||||||
|
if block == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := state.NewPlainState(dbtx, blockNum)
|
||||||
|
stateCache := shards.NewStateCache(32, 0 /* no limit */)
|
||||||
|
cachedReader := state.NewCachedReader(reader, stateCache)
|
||||||
|
noop := state.NewNoopWriter()
|
||||||
|
cachedWriter := state.NewCachedWriter(noop, stateCache)
|
||||||
|
|
||||||
|
ibs := state.New(cachedReader)
|
||||||
|
signer := types.MakeSigner(chainConfig, blockNum)
|
||||||
|
log.Info("created states")
|
||||||
|
|
||||||
|
getHeader := func(hash common.Hash, number uint64) *types.Header {
|
||||||
|
h, e := api._blockReader.Header(ctx, dbtx, hash, number)
|
||||||
|
if e != nil {
|
||||||
|
log.Error("getHeader error", "number", number, "hash", hash, "err", e)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
engine := ethash.NewFaker()
|
||||||
|
|
||||||
|
header := block.Header()
|
||||||
|
rules := chainConfig.Rules(block.NumberU64())
|
||||||
|
log.Info("got transactions", "amt", len(block.Transactions()))
|
||||||
|
for idx, tx := range block.Transactions() {
|
||||||
|
log.Info("processing txn", "idx", idx)
|
||||||
|
ibs.Prepare(tx.Hash(), block.Hash(), idx)
|
||||||
|
|
||||||
|
msg, _ := tx.AsMessage(*signer, header.BaseFee, rules)
|
||||||
|
|
||||||
|
BlockContext := core.NewEVMBlockContext(header, core.GetHashFn(header, getHeader), engine, nil)
|
||||||
|
TxContext := core.NewEVMTxContext(msg)
|
||||||
|
|
||||||
|
vmenv := vm.NewEVM(BlockContext, TxContext, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer})
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.GetGas()), true /* refunds */, false /* gasBailout */); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = ibs.FinalizeTx(vmenv.ChainConfig().Rules(block.NumberU64()), cachedWriter)
|
||||||
|
|
||||||
|
if tracer.Found() {
|
||||||
|
tracer.SetTransaction(tx)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/adapter"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) HasCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (bool, error) {
|
||||||
|
tx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("hasCode cannot open tx: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
blockNumber, _, _, err := rpchelper.GetBlockNumber(blockNrOrHash, tx, api.filters)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := adapter.NewStateReader(tx, blockNumber)
|
||||||
|
acc, err := reader.ReadAccountData(address)
|
||||||
|
if acc == nil || err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !acc.IsEmptyCodeHash(), nil
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/RoaringBitmap/roaring/roaring64"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Given a ChunkLocator, moves back over the chunks and inside each chunk, moves
|
||||||
|
// backwards over the block numbers.
|
||||||
|
func NewBackwardBlockProvider(chunkLocator ChunkLocator, maxBlock uint64) BlockProvider {
|
||||||
|
// block == 0 means no max
|
||||||
|
if maxBlock == 0 {
|
||||||
|
maxBlock = MaxBlockNum
|
||||||
|
}
|
||||||
|
var iter roaring64.IntIterable64
|
||||||
|
var chunkProvider ChunkProvider
|
||||||
|
isFirst := true
|
||||||
|
finished := false
|
||||||
|
|
||||||
|
return func() (uint64, bool, error) {
|
||||||
|
if finished {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFirst {
|
||||||
|
isFirst = false
|
||||||
|
|
||||||
|
// Try to get first chunk
|
||||||
|
var ok bool
|
||||||
|
var err error
|
||||||
|
chunkProvider, ok, err = chunkLocator(maxBlock)
|
||||||
|
if err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
finished = true
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
if chunkProvider == nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has at least the first chunk; initialize the iterator
|
||||||
|
chunk, ok, err := chunkProvider()
|
||||||
|
if err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
finished = true
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bm := roaring64.NewBitmap()
|
||||||
|
if _, err := bm.ReadFrom(bytes.NewReader(chunk)); err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// It can happen that on the first chunk we'll get a chunk that contains
|
||||||
|
// the last block <= maxBlock in the middle of the chunk/bitmap, so we
|
||||||
|
// remove all blocks after it (since there is no AdvanceIfNeeded() in
|
||||||
|
// IntIterable64)
|
||||||
|
if maxBlock != MaxBlockNum {
|
||||||
|
bm.RemoveRange(maxBlock+1, MaxBlockNum)
|
||||||
|
}
|
||||||
|
iter = bm.ReverseIterator()
|
||||||
|
|
||||||
|
// This means it is the last chunk and the min block is > the last one
|
||||||
|
if !iter.HasNext() {
|
||||||
|
chunk, ok, err := chunkProvider()
|
||||||
|
if err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
finished = true
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bm := roaring64.NewBitmap()
|
||||||
|
if _, err := bm.ReadFrom(bytes.NewReader(chunk)); err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iter = bm.ReverseIterator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextBlock := iter.Next()
|
||||||
|
hasNext := iter.HasNext()
|
||||||
|
if !hasNext {
|
||||||
|
iter = nil
|
||||||
|
|
||||||
|
// Check if there is another chunk to get blocks from
|
||||||
|
chunk, ok, err := chunkProvider()
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
finished = true
|
||||||
|
return nextBlock, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext = true
|
||||||
|
|
||||||
|
bm := roaring64.NewBitmap()
|
||||||
|
if _, err := bm.ReadFrom(bytes.NewReader(chunk)); err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
iter = bm.ReverseIterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextBlock, hasNext, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCallCursorBackwardBlockProvider(cursor kv.Cursor, addr common.Address, maxBlock uint64) BlockProvider {
|
||||||
|
chunkLocator := newCallChunkLocator(cursor, addr, false)
|
||||||
|
return NewBackwardBlockProvider(chunkLocator, maxBlock)
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFromToBackwardBlockProviderWith1Chunk(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewBackwardBlockProvider(chunkLocator, 0)
|
||||||
|
toBlockProvider := NewBackwardBlockProvider(newMockBackwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(true, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1010, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToBackwardBlockProviderWith1ChunkMiddleBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewBackwardBlockProvider(chunkLocator, 1005)
|
||||||
|
toBlockProvider := NewBackwardBlockProvider(newMockBackwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(true, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToBackwardBlockProviderWith1ChunkNotExactBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewBackwardBlockProvider(chunkLocator, 1003)
|
||||||
|
toBlockProvider := NewBackwardBlockProvider(newMockBackwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(true, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToBackwardBlockProviderWith1ChunkLastBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewBackwardBlockProvider(chunkLocator, 1000)
|
||||||
|
toBlockProvider := NewBackwardBlockProvider(newMockBackwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(true, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToBackwardBlockProviderWith1ChunkBlockNotFound(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewBackwardBlockProvider(chunkLocator, 900)
|
||||||
|
toBlockProvider := NewBackwardBlockProvider(newMockBackwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(true, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToBackwardBlockProviderWithNoChunks(t *testing.T) {
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{})
|
||||||
|
fromBlockProvider := NewBackwardBlockProvider(chunkLocator, 0)
|
||||||
|
toBlockProvider := NewBackwardBlockProvider(newMockBackwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(true, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToBackwardBlockProviderWithMultipleChunks(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
fromBlockProvider := NewBackwardBlockProvider(chunkLocator, 0)
|
||||||
|
toBlockProvider := NewBackwardBlockProvider(newMockBackwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(true, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1600, true)
|
||||||
|
checkNext(t, blockProvider, 1501, true)
|
||||||
|
checkNext(t, blockProvider, 1010, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToBackwardBlockProviderWithMultipleChunksBlockBetweenChunks(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
fromBlockProvider := NewBackwardBlockProvider(chunkLocator, 1500)
|
||||||
|
toBlockProvider := NewBackwardBlockProvider(newMockBackwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(true, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1010, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/RoaringBitmap/roaring/roaring64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMockBackwardChunkLocator(chunks [][]byte) ChunkLocator {
|
||||||
|
return func(block uint64) (ChunkProvider, bool, error) {
|
||||||
|
for i, v := range chunks {
|
||||||
|
bm := roaring64.NewBitmap()
|
||||||
|
if _, err := bm.ReadFrom(bytes.NewReader(v)); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if block > bm.Maximum() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMockBackwardChunkProvider(chunks[:i+1]), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found; return the last to simulate the behavior of returning
|
||||||
|
// everything up to the 0xffff... chunk
|
||||||
|
if len(chunks) > 0 {
|
||||||
|
return newMockBackwardChunkProvider(chunks), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockBackwardChunkProvider(chunks [][]byte) ChunkProvider {
|
||||||
|
i := len(chunks) - 1
|
||||||
|
return func() ([]byte, bool, error) {
|
||||||
|
if i < 0 {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk := chunks[i]
|
||||||
|
i--
|
||||||
|
return chunk, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestBackwardBlockProviderWith1Chunk(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewBackwardBlockProvider(chunkLocator, 0)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1010, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackwardBlockProviderWith1ChunkMiddleBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewBackwardBlockProvider(chunkLocator, 1005)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackwardBlockProviderWith1ChunkNotExactBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewBackwardBlockProvider(chunkLocator, 1003)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackwardBlockProviderWith1ChunkLastBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewBackwardBlockProvider(chunkLocator, 1000)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackwardBlockProviderWith1ChunkBlockNotFound(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewBackwardBlockProvider(chunkLocator, 900)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackwardBlockProviderWithNoChunks(t *testing.T) {
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{})
|
||||||
|
blockProvider := NewBackwardBlockProvider(chunkLocator, 0)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackwardBlockProviderWithMultipleChunks(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
blockProvider := NewBackwardBlockProvider(chunkLocator, 0)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1600, true)
|
||||||
|
checkNext(t, blockProvider, 1501, true)
|
||||||
|
checkNext(t, blockProvider, 1010, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackwardBlockProviderWithMultipleChunksBlockBetweenChunks(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
blockProvider := NewBackwardBlockProvider(chunkLocator, 1500)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1010, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1000, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackwardBlockProviderWithMultipleChunksBlockNotFound(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockBackwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
blockProvider := NewBackwardBlockProvider(chunkLocator, 900)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/RoaringBitmap/roaring/roaring64"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Given a ChunkLocator, moves forward over the chunks and inside each chunk, moves
|
||||||
|
// forward over the block numbers.
|
||||||
|
func NewForwardBlockProvider(chunkLocator ChunkLocator, minBlock uint64) BlockProvider {
|
||||||
|
var iter roaring64.IntPeekable64
|
||||||
|
var chunkProvider ChunkProvider
|
||||||
|
isFirst := true
|
||||||
|
finished := false
|
||||||
|
|
||||||
|
return func() (uint64, bool, error) {
|
||||||
|
if finished {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFirst {
|
||||||
|
isFirst = false
|
||||||
|
|
||||||
|
// Try to get first chunk
|
||||||
|
var ok bool
|
||||||
|
var err error
|
||||||
|
chunkProvider, ok, err = chunkLocator(minBlock)
|
||||||
|
if err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
finished = true
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
if chunkProvider == nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has at least the first chunk; initialize the iterator
|
||||||
|
chunk, ok, err := chunkProvider()
|
||||||
|
if err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
finished = true
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bm := roaring64.NewBitmap()
|
||||||
|
if _, err := bm.ReadFrom(bytes.NewReader(chunk)); err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
iter = bm.Iterator()
|
||||||
|
|
||||||
|
// It can happen that on the first chunk we'll get a chunk that contains
|
||||||
|
// the first block >= minBlock in the middle of the chunk/bitmap, so we
|
||||||
|
// skip all previous blocks before it.
|
||||||
|
iter.AdvanceIfNeeded(minBlock)
|
||||||
|
|
||||||
|
// This means it is the last chunk and the min block is > the last one
|
||||||
|
if !iter.HasNext() {
|
||||||
|
finished = true
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextBlock := iter.Next()
|
||||||
|
hasNext := iter.HasNext()
|
||||||
|
if !hasNext {
|
||||||
|
iter = nil
|
||||||
|
|
||||||
|
// Check if there is another chunk to get blocks from
|
||||||
|
chunk, ok, err := chunkProvider()
|
||||||
|
if err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
finished = true
|
||||||
|
return nextBlock, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext = true
|
||||||
|
|
||||||
|
bm := roaring64.NewBitmap()
|
||||||
|
if _, err := bm.ReadFrom(bytes.NewReader(chunk)); err != nil {
|
||||||
|
finished = true
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
iter = bm.Iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextBlock, hasNext, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCallCursorForwardBlockProvider(cursor kv.Cursor, addr common.Address, minBlock uint64) BlockProvider {
|
||||||
|
chunkLocator := newCallChunkLocator(cursor, addr, true)
|
||||||
|
return NewForwardBlockProvider(chunkLocator, minBlock)
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFromToForwardBlockProviderWith1Chunk(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewForwardBlockProvider(chunkLocator, 0)
|
||||||
|
toBlockProvider := NewForwardBlockProvider(newMockForwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(false, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1000, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1010, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToForwardBlockProviderWith1ChunkMiddleBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewForwardBlockProvider(chunkLocator, 1005)
|
||||||
|
toBlockProvider := NewForwardBlockProvider(newMockForwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(false, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1010, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToForwardBlockProviderWith1ChunkNotExactBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewForwardBlockProvider(chunkLocator, 1007)
|
||||||
|
toBlockProvider := NewForwardBlockProvider(newMockForwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(false, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1010, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToForwardBlockProviderWith1ChunkLastBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewForwardBlockProvider(chunkLocator, 1010)
|
||||||
|
toBlockProvider := NewForwardBlockProvider(newMockForwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(false, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1010, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToForwardBlockProviderWith1ChunkBlockNotFound(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
fromBlockProvider := NewForwardBlockProvider(chunkLocator, 1100)
|
||||||
|
toBlockProvider := NewForwardBlockProvider(newMockForwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(false, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToForwardBlockProviderWithNoChunks(t *testing.T) {
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{})
|
||||||
|
fromBlockProvider := NewForwardBlockProvider(chunkLocator, 0)
|
||||||
|
toBlockProvider := NewForwardBlockProvider(newMockForwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(false, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToForwardBlockProviderWithMultipleChunks(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
fromBlockProvider := NewForwardBlockProvider(chunkLocator, 0)
|
||||||
|
toBlockProvider := NewForwardBlockProvider(newMockForwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(false, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1000, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1010, true)
|
||||||
|
checkNext(t, blockProvider, 1501, true)
|
||||||
|
checkNext(t, blockProvider, 1600, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromToForwardBlockProviderWithMultipleChunksBlockBetweenChunks(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
fromBlockProvider := NewForwardBlockProvider(chunkLocator, 1300)
|
||||||
|
toBlockProvider := NewForwardBlockProvider(newMockForwardChunkLocator([][]byte{}), 0)
|
||||||
|
blockProvider := newCallFromToBlockProvider(false, fromBlockProvider, toBlockProvider)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1501, true)
|
||||||
|
checkNext(t, blockProvider, 1600, false)
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/RoaringBitmap/roaring/roaring64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMockForwardChunkLocator(chunks [][]byte) ChunkLocator {
|
||||||
|
return func(block uint64) (ChunkProvider, bool, error) {
|
||||||
|
for i, v := range chunks {
|
||||||
|
bm := roaring64.NewBitmap()
|
||||||
|
if _, err := bm.ReadFrom(bytes.NewReader(v)); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if block > bm.Maximum() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMockForwardChunkProvider(chunks[i:]), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found; return the last to simulate the behavior of returning
|
||||||
|
// the 0xffff... chunk
|
||||||
|
if len(chunks) > 0 {
|
||||||
|
return newMockForwardChunkProvider(chunks[len(chunks)-1:]), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockForwardChunkProvider(chunks [][]byte) ChunkProvider {
|
||||||
|
i := 0
|
||||||
|
return func() ([]byte, bool, error) {
|
||||||
|
if i >= len(chunks) {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk := chunks[i]
|
||||||
|
i++
|
||||||
|
return chunk, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForwardBlockProviderWith1Chunk(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewForwardBlockProvider(chunkLocator, 0)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1000, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1010, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForwardBlockProviderWith1ChunkMiddleBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewForwardBlockProvider(chunkLocator, 1005)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1010, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForwardBlockProviderWith1ChunkNotExactBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewForwardBlockProvider(chunkLocator, 1007)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1010, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForwardBlockProviderWith1ChunkLastBlock(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewForwardBlockProvider(chunkLocator, 1010)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1010, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForwardBlockProviderWith1ChunkBlockNotFound(t *testing.T) {
|
||||||
|
// Mocks 1 chunk
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1})
|
||||||
|
blockProvider := NewForwardBlockProvider(chunkLocator, 1100)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForwardBlockProviderWithNoChunks(t *testing.T) {
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{})
|
||||||
|
blockProvider := NewForwardBlockProvider(chunkLocator, 0)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForwardBlockProviderWithMultipleChunks(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
blockProvider := NewForwardBlockProvider(chunkLocator, 0)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1000, true)
|
||||||
|
checkNext(t, blockProvider, 1005, true)
|
||||||
|
checkNext(t, blockProvider, 1010, true)
|
||||||
|
checkNext(t, blockProvider, 1501, true)
|
||||||
|
checkNext(t, blockProvider, 1600, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForwardBlockProviderWithMultipleChunksBlockBetweenChunks(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
blockProvider := NewForwardBlockProvider(chunkLocator, 1300)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 1501, true)
|
||||||
|
checkNext(t, blockProvider, 1600, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForwardBlockProviderWithMultipleChunksBlockNotFound(t *testing.T) {
|
||||||
|
// Mocks 2 chunks
|
||||||
|
chunk1 := createBitmap(t, []uint64{1000, 1005, 1010})
|
||||||
|
chunk2 := createBitmap(t, []uint64{1501, 1600})
|
||||||
|
|
||||||
|
chunkLocator := newMockForwardChunkLocator([][]byte{chunk1, chunk2})
|
||||||
|
blockProvider := NewForwardBlockProvider(chunkLocator, 1700)
|
||||||
|
|
||||||
|
checkNext(t, blockProvider, 0, false)
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
func newCallFromToBlockProvider(isBackwards bool, callFromProvider, callToProvider BlockProvider) BlockProvider {
|
||||||
|
var nextFrom, nextTo uint64
|
||||||
|
var hasMoreFrom, hasMoreTo bool
|
||||||
|
initialized := false
|
||||||
|
|
||||||
|
return func() (uint64, bool, error) {
|
||||||
|
if !initialized {
|
||||||
|
initialized = true
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if nextFrom, hasMoreFrom, err = callFromProvider(); err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
hasMoreFrom = hasMoreFrom || nextFrom != 0
|
||||||
|
|
||||||
|
if nextTo, hasMoreTo, err = callToProvider(); err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
hasMoreTo = hasMoreTo || nextTo != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasMoreFrom && !hasMoreTo {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockNum uint64
|
||||||
|
if !hasMoreFrom {
|
||||||
|
blockNum = nextTo
|
||||||
|
} else if !hasMoreTo {
|
||||||
|
blockNum = nextFrom
|
||||||
|
} else {
|
||||||
|
blockNum = nextFrom
|
||||||
|
if isBackwards {
|
||||||
|
if nextTo < nextFrom {
|
||||||
|
blockNum = nextTo
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if nextTo > nextFrom {
|
||||||
|
blockNum = nextTo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull next; it may be that from AND to contains the same blockNum
|
||||||
|
if hasMoreFrom && blockNum == nextFrom {
|
||||||
|
var err error
|
||||||
|
if nextFrom, hasMoreFrom, err = callFromProvider(); err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
hasMoreFrom = hasMoreFrom || nextFrom != 0
|
||||||
|
}
|
||||||
|
if hasMoreTo && blockNum == nextTo {
|
||||||
|
var err error
|
||||||
|
if nextTo, hasMoreTo, err = callToProvider(); err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
hasMoreTo = hasMoreTo || nextTo != 0
|
||||||
|
}
|
||||||
|
return blockNum, hasMoreFrom || hasMoreTo, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/RoaringBitmap/roaring/roaring64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createBitmap(t *testing.T, blocks []uint64) []byte {
|
||||||
|
bm := roaring64.NewBitmap()
|
||||||
|
bm.AddMany(blocks)
|
||||||
|
|
||||||
|
chunk, err := bm.ToBytes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNext(t *testing.T, blockProvider BlockProvider, expectedBlock uint64, expectedHasNext bool) {
|
||||||
|
bl, hasNext, err := blockProvider()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if bl != expectedBlock {
|
||||||
|
t.Fatalf("Expected block %d, received %d", expectedBlock, bl)
|
||||||
|
}
|
||||||
|
if expectedHasNext != hasNext {
|
||||||
|
t.Fatalf("Expected hasNext=%t, received=%t; at block=%d", expectedHasNext, hasNext, expectedBlock)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/commands"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/consensus/ethash"
|
||||||
|
"github.com/ledgerwatch/erigon/core"
|
||||||
|
"github.com/ledgerwatch/erigon/core/rawdb"
|
||||||
|
"github.com/ledgerwatch/erigon/core/state"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
"github.com/ledgerwatch/erigon/params"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/shards"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) searchTraceBlock(ctx context.Context, wg *sync.WaitGroup, addr common.Address, chainConfig *params.ChainConfig, idx int, bNum uint64, results []*TransactionsWithReceipts) {
|
||||||
|
wg.Done()
|
||||||
|
// Trace block for Txs
|
||||||
|
newdbtx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Search trace error", "err", err)
|
||||||
|
results[idx] = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer newdbtx.Rollback()
|
||||||
|
_, result, err := api.traceBlock(newdbtx, ctx, bNum, addr, chainConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Search trace error", "err", err)
|
||||||
|
results[idx] = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
results[idx] = result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) traceBlock(dbtx kv.Tx, ctx context.Context, blockNum uint64, searchAddr common.Address, chainConfig *params.ChainConfig) (bool, *TransactionsWithReceipts, error) {
|
||||||
|
rpcTxs := make([]*commands.RPCTransaction, 0)
|
||||||
|
receipts := make([]map[string]interface{}, 0)
|
||||||
|
|
||||||
|
// Retrieve the transaction and assemble its EVM context
|
||||||
|
blockHash, err := rawdb.ReadCanonicalHash(dbtx, blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, senders, err := api._blockReader.BlockWithSenders(ctx, dbtx, blockHash, blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := state.NewPlainState(dbtx, blockNum)
|
||||||
|
stateCache := shards.NewStateCache(32, 0 /* no limit */)
|
||||||
|
cachedReader := state.NewCachedReader(reader, stateCache)
|
||||||
|
noop := state.NewNoopWriter()
|
||||||
|
cachedWriter := state.NewCachedWriter(noop, stateCache)
|
||||||
|
|
||||||
|
ibs := state.New(cachedReader)
|
||||||
|
signer := types.MakeSigner(chainConfig, blockNum)
|
||||||
|
|
||||||
|
getHeader := func(hash common.Hash, number uint64) *types.Header {
|
||||||
|
h, e := api._blockReader.Header(ctx, dbtx, hash, number)
|
||||||
|
if e != nil {
|
||||||
|
log.Error("getHeader error", "number", number, "hash", hash, "err", e)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
engine := ethash.NewFaker()
|
||||||
|
|
||||||
|
blockReceipts := rawdb.ReadReceipts(dbtx, block, senders)
|
||||||
|
header := block.Header()
|
||||||
|
rules := chainConfig.Rules(block.NumberU64())
|
||||||
|
found := false
|
||||||
|
for idx, tx := range block.Transactions() {
|
||||||
|
ibs.Prepare(tx.Hash(), block.Hash(), idx)
|
||||||
|
|
||||||
|
msg, _ := tx.AsMessage(*signer, header.BaseFee, rules)
|
||||||
|
|
||||||
|
tracer := NewTouchTracer(searchAddr)
|
||||||
|
BlockContext := core.NewEVMBlockContext(header, core.GetHashFn(header, getHeader), engine, nil)
|
||||||
|
TxContext := core.NewEVMTxContext(msg)
|
||||||
|
|
||||||
|
vmenv := vm.NewEVM(BlockContext, TxContext, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer})
|
||||||
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.GetGas()), true /* refunds */, false /* gasBailout */); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
_ = ibs.FinalizeTx(vmenv.ChainConfig().Rules(block.NumberU64()), cachedWriter)
|
||||||
|
|
||||||
|
if tracer.Found {
|
||||||
|
rpcTx := newRPCTransaction(tx, block.Hash(), blockNum, uint64(idx), block.BaseFee())
|
||||||
|
mReceipt := marshalReceipt(blockReceipts[idx], tx, chainConfig, block, tx.Hash(), true)
|
||||||
|
mReceipt["timestamp"] = block.Time()
|
||||||
|
rpcTxs = append(rpcTxs, rpcTx)
|
||||||
|
receipts = append(receipts, mReceipt)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found, &TransactionsWithReceipts{rpcTxs, receipts, false, false}, nil
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateTracer struct {
|
||||||
|
DefaultTracer
|
||||||
|
ctx context.Context
|
||||||
|
target common.Address
|
||||||
|
found bool
|
||||||
|
Creator common.Address
|
||||||
|
Tx types.Transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateTracer(ctx context.Context, target common.Address) *CreateTracer {
|
||||||
|
return &CreateTracer{
|
||||||
|
ctx: ctx,
|
||||||
|
target: target,
|
||||||
|
found: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CreateTracer) SetTransaction(tx types.Transaction) {
|
||||||
|
t.Tx = tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CreateTracer) Found() bool {
|
||||||
|
return t.found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CreateTracer) CaptureStart(env *vm.EVM, depth int, from common.Address, to common.Address, precompile bool, create bool, calltype vm.CallType, input []byte, gas uint64, value *big.Int, code []byte) {
|
||||||
|
if t.found {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !create {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if to != t.target {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.found = true
|
||||||
|
t.Creator = from
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OperationType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
OP_TRANSFER OperationType = 0
|
||||||
|
OP_SELF_DESTRUCT OperationType = 1
|
||||||
|
OP_CREATE OperationType = 2
|
||||||
|
OP_CREATE2 OperationType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type InternalOperation struct {
|
||||||
|
Type OperationType `json:"type"`
|
||||||
|
From common.Address `json:"from"`
|
||||||
|
To common.Address `json:"to"`
|
||||||
|
Value *hexutil.Big `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OperationsTracer struct {
|
||||||
|
DefaultTracer
|
||||||
|
ctx context.Context
|
||||||
|
Results []*InternalOperation
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOperationsTracer(ctx context.Context) *OperationsTracer {
|
||||||
|
return &OperationsTracer{
|
||||||
|
ctx: ctx,
|
||||||
|
Results: make([]*InternalOperation, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *OperationsTracer) CaptureStart(env *vm.EVM, depth int, from common.Address, to common.Address, precompile bool, create bool, calltype vm.CallType, input []byte, gas uint64, value *big.Int, code []byte) {
|
||||||
|
if depth == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if calltype == vm.CALLT && value.Uint64() != 0 {
|
||||||
|
t.Results = append(t.Results, &InternalOperation{OP_TRANSFER, from, to, (*hexutil.Big)(value)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if calltype == vm.CREATET {
|
||||||
|
t.Results = append(t.Results, &InternalOperation{OP_CREATE, from, to, (*hexutil.Big)(value)})
|
||||||
|
}
|
||||||
|
if calltype == vm.CREATE2T {
|
||||||
|
t.Results = append(t.Results, &InternalOperation{OP_CREATE2, from, to, (*hexutil.Big)(value)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *OperationsTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *big.Int) {
|
||||||
|
l.Results = append(l.Results, &InternalOperation{OP_SELF_DESTRUCT, from, to, (*hexutil.Big)(value)})
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TouchTracer struct {
|
||||||
|
DefaultTracer
|
||||||
|
searchAddr common.Address
|
||||||
|
Found bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTouchTracer(searchAddr common.Address) *TouchTracer {
|
||||||
|
return &TouchTracer{
|
||||||
|
searchAddr: searchAddr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TouchTracer) CaptureStart(env *vm.EVM, depth int, from common.Address, to common.Address, precompile bool, create bool, calltype vm.CallType, input []byte, gas uint64, value *big.Int, code []byte) {
|
||||||
|
if !t.Found && (bytes.Equal(t.searchAddr.Bytes(), from.Bytes()) || bytes.Equal(t.searchAddr.Bytes(), to.Bytes())) {
|
||||||
|
t.Found = true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) TraceTransaction(ctx context.Context, hash common.Hash) ([]*TraceEntry, error) {
|
||||||
|
tx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
tracer := NewTransactionTracer(ctx)
|
||||||
|
if _, err := api.runTracer(ctx, tx, hash, tracer); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracer.Results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TraceEntry struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Depth int `json:"depth"`
|
||||||
|
From common.Address `json:"from"`
|
||||||
|
To common.Address `json:"to"`
|
||||||
|
Value *hexutil.Big `json:"value"`
|
||||||
|
Input hexutil.Bytes `json:"input"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTracer struct {
|
||||||
|
DefaultTracer
|
||||||
|
ctx context.Context
|
||||||
|
Results []*TraceEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransactionTracer(ctx context.Context) *TransactionTracer {
|
||||||
|
return &TransactionTracer{
|
||||||
|
ctx: ctx,
|
||||||
|
Results: make([]*TraceEntry, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TransactionTracer) CaptureStart(env *vm.EVM, depth int, from common.Address, to common.Address, precompile bool, create bool, callType vm.CallType, input []byte, gas uint64, value *big.Int, code []byte) {
|
||||||
|
if precompile {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inputCopy := make([]byte, len(input))
|
||||||
|
copy(inputCopy, input)
|
||||||
|
_value := new(big.Int)
|
||||||
|
_value.Set(value)
|
||||||
|
if callType == vm.CALLT {
|
||||||
|
t.Results = append(t.Results, &TraceEntry{"CALL", depth, from, to, (*hexutil.Big)(_value), inputCopy})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if callType == vm.STATICCALLT {
|
||||||
|
t.Results = append(t.Results, &TraceEntry{"STATICCALL", depth, from, to, nil, inputCopy})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if callType == vm.DELEGATECALLT {
|
||||||
|
t.Results = append(t.Results, &TraceEntry{"DELEGATECALL", depth, from, to, nil, inputCopy})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if callType == vm.CALLCODET {
|
||||||
|
t.Results = append(t.Results, &TraceEntry{"CALLCODE", depth, from, to, (*hexutil.Big)(_value), inputCopy})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if callType == vm.CREATET {
|
||||||
|
t.Results = append(t.Results, &TraceEntry{"CREATE", depth, from, to, (*hexutil.Big)(value), inputCopy})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if callType == vm.CREATE2T {
|
||||||
|
t.Results = append(t.Results, &TraceEntry{"CREATE2", depth, from, to, (*hexutil.Big)(value), inputCopy})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *TransactionTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *big.Int) {
|
||||||
|
last := l.Results[len(l.Results)-1]
|
||||||
|
l.Results = append(l.Results, &TraceEntry{"SELFDESTRUCT", last.Depth + 1, from, to, (*hexutil.Big)(value), nil})
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/RoaringBitmap/roaring/roaring64"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/changeset"
|
||||||
|
"github.com/ledgerwatch/erigon/core/rawdb"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types/accounts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) GetTransactionBySenderAndNonce(ctx context.Context, addr common.Address, nonce uint64) (*common.Hash, error) {
|
||||||
|
tx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
accHistoryC, err := tx.Cursor(kv.AccountsHistory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer accHistoryC.Close()
|
||||||
|
|
||||||
|
accChangesC, err := tx.CursorDupSort(kv.AccountChangeSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer accChangesC.Close()
|
||||||
|
|
||||||
|
// Locate the chunk where the nonce happens
|
||||||
|
acs := changeset.Mapper[kv.AccountChangeSet]
|
||||||
|
k, v, err := accHistoryC.Seek(acs.IndexChunkKey(addr.Bytes(), 0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmap := roaring64.New()
|
||||||
|
maxBlPrevChunk := uint64(0)
|
||||||
|
var acc accounts.Account
|
||||||
|
|
||||||
|
for {
|
||||||
|
if k == nil || !bytes.HasPrefix(k, addr.Bytes()) {
|
||||||
|
// Check plain state
|
||||||
|
data, err := tx.GetOne(kv.PlainState, addr.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := acc.DecodeForStorage(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nonce changed in plain state, so it means the last block of last chunk
|
||||||
|
// contains the actual nonce change
|
||||||
|
if acc.Nonce > nonce {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found; asked for nonce still not used
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect block changeset
|
||||||
|
if _, err := bitmap.ReadFrom(bytes.NewReader(v)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
maxBl := bitmap.Maximum()
|
||||||
|
data, err := acs.Find(accChangesC, maxBl, addr.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := acc.DecodeForStorage(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desired nonce was found in this chunk
|
||||||
|
if acc.Nonce > nonce {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
maxBlPrevChunk = maxBl
|
||||||
|
k, v, err = accHistoryC.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locate the exact block inside chunk when the nonce changed
|
||||||
|
blocks := bitmap.ToArray()
|
||||||
|
var errSearch error = nil
|
||||||
|
idx := sort.Search(len(blocks), func(i int) bool {
|
||||||
|
if errSearch != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locate the block changeset
|
||||||
|
data, err := acs.Find(accChangesC, blocks[i], addr.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
errSearch = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := acc.DecodeForStorage(data); err != nil {
|
||||||
|
errSearch = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the state contains the nonce BEFORE the block changes, we look for
|
||||||
|
// the block when the nonce changed to be > the desired once, which means the
|
||||||
|
// previous history block contains the actual change; it may contain multiple
|
||||||
|
// nonce changes.
|
||||||
|
return acc.Nonce > nonce
|
||||||
|
})
|
||||||
|
if errSearch != nil {
|
||||||
|
return nil, errSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the changeset contains the state BEFORE the change, we inspect
|
||||||
|
// the block before the one we found; if it is the first block inside the chunk,
|
||||||
|
// we use the last block from prev chunk
|
||||||
|
nonceBlock := maxBlPrevChunk
|
||||||
|
if idx > 0 {
|
||||||
|
nonceBlock = blocks[idx-1]
|
||||||
|
}
|
||||||
|
found, txHash, err := api.findNonce(ctx, tx, addr, nonce, nonceBlock)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &txHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) findNonce(ctx context.Context, tx kv.Tx, addr common.Address, nonce uint64, blockNum uint64) (bool, common.Hash, error) {
|
||||||
|
hash, err := rawdb.ReadCanonicalHash(tx, blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return false, common.Hash{}, err
|
||||||
|
}
|
||||||
|
block, senders, err := api._blockReader.BlockWithSenders(ctx, tx, hash, blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return false, common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txs := block.Transactions()
|
||||||
|
for i, s := range senders {
|
||||||
|
if s != addr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t := txs[i]
|
||||||
|
if t.GetNonce() == nonce {
|
||||||
|
return true, t.Hash(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, common.Hash{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *OtterscanAPIImpl) GetTransactionError(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||||||
|
tx, err := api.db.BeginRo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
result, err := api.runTracer(ctx, tx, hash, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Revert(), nil
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bootstrap a function able to locate a series of byte chunks containing
|
||||||
|
// related block numbers, starting from a specific block number (greater or equal than).
|
||||||
|
type ChunkLocator func(block uint64) (chunkProvider ChunkProvider, ok bool, err error)
|
||||||
|
|
||||||
|
// Allows to iterate over a set of byte chunks.
|
||||||
|
//
|
||||||
|
// If err is not nil, it indicates an error and the other returned values should be
|
||||||
|
// ignored.
|
||||||
|
//
|
||||||
|
// If err is nil and ok is true, the returned chunk should contain the raw chunk data.
|
||||||
|
//
|
||||||
|
// If err is nil and ok is false, it indicates that there is no more data. Subsequent calls
|
||||||
|
// to the same function should return (nil, false, nil).
|
||||||
|
type ChunkProvider func() (chunk []byte, ok bool, err error)
|
||||||
|
|
||||||
|
type BlockProvider func() (nextBlock uint64, hasMore bool, err error)
|
||||||
|
|
||||||
|
// Standard key format for call from/to indexes [address + block]
|
||||||
|
func callIndexKey(addr common.Address, block uint64) []byte {
|
||||||
|
key := make([]byte, common.AddressLength+8)
|
||||||
|
copy(key[:common.AddressLength], addr.Bytes())
|
||||||
|
binary.BigEndian.PutUint64(key[common.AddressLength:], block)
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
const MaxBlockNum = ^uint64(0)
|
||||||
|
|
||||||
|
// This ChunkLocator searches over a cursor with a key format of [common.Address, block uint64],
|
||||||
|
// where block is the first block number contained in the chunk value.
|
||||||
|
//
|
||||||
|
// It positions the cursor on the chunk that contains the first block >= minBlock.
|
||||||
|
func newCallChunkLocator(cursor kv.Cursor, addr common.Address, navigateForward bool) ChunkLocator {
|
||||||
|
return func(minBlock uint64) (ChunkProvider, bool, error) {
|
||||||
|
searchKey := callIndexKey(addr, minBlock)
|
||||||
|
k, _, err := cursor.Seek(searchKey)
|
||||||
|
if k == nil {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newCallChunkProvider(cursor, addr, navigateForward), true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This ChunkProvider is built by NewForwardChunkLocator and advances the cursor forward until
|
||||||
|
// there is no more chunks for the desired addr.
|
||||||
|
func newCallChunkProvider(cursor kv.Cursor, addr common.Address, navigateForward bool) ChunkProvider {
|
||||||
|
first := true
|
||||||
|
var err error
|
||||||
|
// TODO: is this flag really used?
|
||||||
|
eof := false
|
||||||
|
return func() ([]byte, bool, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if eof {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var k, v []byte
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
k, v, err = cursor.Current()
|
||||||
|
} else {
|
||||||
|
if navigateForward {
|
||||||
|
k, v, err = cursor.Next()
|
||||||
|
} else {
|
||||||
|
k, v, err = cursor.Prev()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
eof = true
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(k, addr.Bytes()) {
|
||||||
|
eof = true
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
return v, true, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,295 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
libstate "github.com/ledgerwatch/erigon-lib/state"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv/kvcache"
|
||||||
|
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/commands"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/common/math"
|
||||||
|
"github.com/ledgerwatch/erigon/consensus/misc"
|
||||||
|
"github.com/ledgerwatch/erigon/core/rawdb"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/params"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseAPIUtils struct {
|
||||||
|
*commands.BaseAPI
|
||||||
|
|
||||||
|
stateCache kvcache.Cache // thread-safe
|
||||||
|
blocksLRU *lru.Cache // thread-safe
|
||||||
|
filters *rpchelper.Filters
|
||||||
|
_chainConfig *params.ChainConfig
|
||||||
|
_genesis *types.Block
|
||||||
|
_genesisLock sync.RWMutex
|
||||||
|
|
||||||
|
_historyV3 *bool
|
||||||
|
_historyV3Lock sync.RWMutex
|
||||||
|
|
||||||
|
_blockReader services.FullBlockReader
|
||||||
|
_txnReader services.TxnReader
|
||||||
|
_agg *libstate.Aggregator22
|
||||||
|
|
||||||
|
evmCallTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBaseUtilsApi(f *rpchelper.Filters, stateCache kvcache.Cache, blockReader services.FullBlockReader, agg *libstate.Aggregator22, singleNodeMode bool, evmCallTimeout time.Duration) *BaseAPIUtils {
|
||||||
|
blocksLRUSize := 128 // ~32Mb
|
||||||
|
if !singleNodeMode {
|
||||||
|
blocksLRUSize = 512
|
||||||
|
}
|
||||||
|
blocksLRU, err := lru.New(blocksLRUSize)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BaseAPIUtils{filters: f, stateCache: stateCache, blocksLRU: blocksLRU, _blockReader: blockReader, _txnReader: blockReader, _agg: agg, evmCallTimeout: evmCallTimeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BaseAPIUtils) chainConfig(tx kv.Tx) (*params.ChainConfig, error) {
|
||||||
|
cfg, _, err := api.chainConfigWithGenesis(tx)
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:unused
|
||||||
|
func (api *BaseAPIUtils) genesis(tx kv.Tx) (*types.Block, error) {
|
||||||
|
_, genesis, err := api.chainConfigWithGenesis(tx)
|
||||||
|
return genesis, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BaseAPIUtils) txnLookup(ctx context.Context, tx kv.Tx, txnHash common.Hash) (uint64, bool, error) {
|
||||||
|
return api._txnReader.TxnLookup(ctx, tx, txnHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BaseAPIUtils) blockByNumberWithSenders(tx kv.Tx, number uint64) (*types.Block, error) {
|
||||||
|
hash, hashErr := rawdb.ReadCanonicalHash(tx, number)
|
||||||
|
if hashErr != nil {
|
||||||
|
return nil, hashErr
|
||||||
|
}
|
||||||
|
return api.blockWithSenders(tx, hash, number)
|
||||||
|
}
|
||||||
|
func (api *BaseAPIUtils) blockByHashWithSenders(tx kv.Tx, hash common.Hash) (*types.Block, error) {
|
||||||
|
if api.blocksLRU != nil {
|
||||||
|
if it, ok := api.blocksLRU.Get(hash); ok && it != nil {
|
||||||
|
return it.(*types.Block), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
number := rawdb.ReadHeaderNumber(tx, hash)
|
||||||
|
if number == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.blockWithSenders(tx, hash, *number)
|
||||||
|
}
|
||||||
|
func (api *BaseAPIUtils) blockWithSenders(tx kv.Tx, hash common.Hash, number uint64) (*types.Block, error) {
|
||||||
|
if api.blocksLRU != nil {
|
||||||
|
if it, ok := api.blocksLRU.Get(hash); ok && it != nil {
|
||||||
|
return it.(*types.Block), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block, _, err := api._blockReader.BlockWithSenders(context.Background(), tx, hash, number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if block == nil { // don't save nil's to cache
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// don't save empty blocks to cache, because in Erigon
|
||||||
|
// if block become non-canonical - we remove it's transactions, but block can become canonical in future
|
||||||
|
if block.Transactions().Len() == 0 {
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
if api.blocksLRU != nil {
|
||||||
|
// calc fields before put to cache
|
||||||
|
for _, txn := range block.Transactions() {
|
||||||
|
txn.Hash()
|
||||||
|
}
|
||||||
|
block.Hash()
|
||||||
|
api.blocksLRU.Add(hash, block)
|
||||||
|
}
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BaseAPIUtils) historyV3(tx kv.Tx) bool {
|
||||||
|
api._historyV3Lock.RLock()
|
||||||
|
historyV3 := api._historyV3
|
||||||
|
api._historyV3Lock.RUnlock()
|
||||||
|
|
||||||
|
if historyV3 != nil {
|
||||||
|
return *historyV3
|
||||||
|
}
|
||||||
|
enabled, err := rawdb.HistoryV3.Enabled(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("HisoryV2Enabled: read", "err", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
api._historyV3Lock.Lock()
|
||||||
|
api._historyV3 = &enabled
|
||||||
|
api._historyV3Lock.Unlock()
|
||||||
|
return enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BaseAPIUtils) chainConfigWithGenesis(tx kv.Tx) (*params.ChainConfig, *types.Block, error) {
|
||||||
|
api._genesisLock.RLock()
|
||||||
|
cc, genesisBlock := api._chainConfig, api._genesis
|
||||||
|
api._genesisLock.RUnlock()
|
||||||
|
|
||||||
|
if cc != nil {
|
||||||
|
return cc, genesisBlock, nil
|
||||||
|
}
|
||||||
|
genesisBlock, err := rawdb.ReadBlockByNumber(tx, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
cc, err = rawdb.ReadChainConfig(tx, genesisBlock.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if cc != nil && genesisBlock != nil {
|
||||||
|
api._genesisLock.Lock()
|
||||||
|
api._genesis = genesisBlock
|
||||||
|
api._chainConfig = cc
|
||||||
|
api._genesisLock.Unlock()
|
||||||
|
}
|
||||||
|
return cc, genesisBlock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BaseAPIUtils) pendingBlock() *types.Block {
|
||||||
|
return api.filters.LastPendingBlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BaseAPIUtils) blockByRPCNumber(number rpc.BlockNumber, tx kv.Tx) (*types.Block, error) {
|
||||||
|
n, _, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(number), tx, api.filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := api.blockByNumberWithSenders(tx, n)
|
||||||
|
return block, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BaseAPIUtils) headerByRPCNumber(number rpc.BlockNumber, tx kv.Tx) (*types.Header, error) {
|
||||||
|
n, h, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(number), tx, api.filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api._blockReader.Header(context.Background(), tx, h, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRPCTransaction returns a transaction that will serialize to the RPC
|
||||||
|
// representation, with the given location metadata set (if available).
|
||||||
|
func newRPCTransaction(tx types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int) *commands.RPCTransaction {
|
||||||
|
// Determine the signer. For replay-protected transactions, use the most permissive
|
||||||
|
// signer, because we assume that signers are backwards-compatible with old
|
||||||
|
// transactions. For non-protected transactions, the homestead signer signer is used
|
||||||
|
// because the return value of ChainId is zero for those transactions.
|
||||||
|
var chainId *big.Int
|
||||||
|
result := &commands.RPCTransaction{
|
||||||
|
Type: hexutil.Uint64(tx.Type()),
|
||||||
|
Gas: hexutil.Uint64(tx.GetGas()),
|
||||||
|
Hash: tx.Hash(),
|
||||||
|
Input: hexutil.Bytes(tx.GetData()),
|
||||||
|
Nonce: hexutil.Uint64(tx.GetNonce()),
|
||||||
|
To: tx.GetTo(),
|
||||||
|
Value: (*hexutil.Big)(tx.GetValue().ToBig()),
|
||||||
|
}
|
||||||
|
switch t := tx.(type) {
|
||||||
|
case *types.LegacyTx:
|
||||||
|
chainId = types.DeriveChainId(&t.V).ToBig()
|
||||||
|
result.GasPrice = (*hexutil.Big)(t.GasPrice.ToBig())
|
||||||
|
result.V = (*hexutil.Big)(t.V.ToBig())
|
||||||
|
result.R = (*hexutil.Big)(t.R.ToBig())
|
||||||
|
result.S = (*hexutil.Big)(t.S.ToBig())
|
||||||
|
case *types.AccessListTx:
|
||||||
|
chainId = t.ChainID.ToBig()
|
||||||
|
result.ChainID = (*hexutil.Big)(chainId)
|
||||||
|
result.GasPrice = (*hexutil.Big)(t.GasPrice.ToBig())
|
||||||
|
result.V = (*hexutil.Big)(t.V.ToBig())
|
||||||
|
result.R = (*hexutil.Big)(t.R.ToBig())
|
||||||
|
result.S = (*hexutil.Big)(t.S.ToBig())
|
||||||
|
result.Accesses = &t.AccessList
|
||||||
|
case *types.DynamicFeeTransaction:
|
||||||
|
chainId = t.ChainID.ToBig()
|
||||||
|
result.ChainID = (*hexutil.Big)(chainId)
|
||||||
|
result.Tip = (*hexutil.Big)(t.Tip.ToBig())
|
||||||
|
result.FeeCap = (*hexutil.Big)(t.FeeCap.ToBig())
|
||||||
|
result.V = (*hexutil.Big)(t.V.ToBig())
|
||||||
|
result.R = (*hexutil.Big)(t.R.ToBig())
|
||||||
|
result.S = (*hexutil.Big)(t.S.ToBig())
|
||||||
|
result.Accesses = &t.AccessList
|
||||||
|
baseFee, overflow := uint256.FromBig(baseFee)
|
||||||
|
if baseFee != nil && !overflow && blockHash != (common.Hash{}) {
|
||||||
|
// price = min(tip + baseFee, gasFeeCap)
|
||||||
|
price := math.Min256(new(uint256.Int).Add(tx.GetTip(), baseFee), tx.GetFeeCap())
|
||||||
|
result.GasPrice = (*hexutil.Big)(price.ToBig())
|
||||||
|
} else {
|
||||||
|
result.GasPrice = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signer := types.LatestSignerForChainID(chainId)
|
||||||
|
result.From, _ = tx.Sender(*signer)
|
||||||
|
if blockHash != (common.Hash{}) {
|
||||||
|
result.BlockHash = &blockHash
|
||||||
|
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
|
||||||
|
result.TransactionIndex = (*hexutil.Uint64)(&index)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRPCBorTransaction returns a Bor transaction that will serialize to the RPC
|
||||||
|
// representation, with the given location metadata set (if available).
|
||||||
|
func newRPCBorTransaction(opaqueTx types.Transaction, txHash common.Hash, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int) *commands.RPCTransaction {
|
||||||
|
tx := opaqueTx.(*types.LegacyTx)
|
||||||
|
result := &commands.RPCTransaction{
|
||||||
|
Type: hexutil.Uint64(tx.Type()),
|
||||||
|
ChainID: (*hexutil.Big)(new(big.Int)),
|
||||||
|
GasPrice: (*hexutil.Big)(tx.GasPrice.ToBig()),
|
||||||
|
Gas: hexutil.Uint64(tx.GetGas()),
|
||||||
|
Hash: txHash,
|
||||||
|
Input: hexutil.Bytes(tx.GetData()),
|
||||||
|
Nonce: hexutil.Uint64(tx.GetNonce()),
|
||||||
|
From: common.Address{},
|
||||||
|
To: tx.GetTo(),
|
||||||
|
Value: (*hexutil.Big)(tx.GetValue().ToBig()),
|
||||||
|
}
|
||||||
|
if blockHash != (common.Hash{}) {
|
||||||
|
result.BlockHash = &blockHash
|
||||||
|
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
|
||||||
|
result.TransactionIndex = (*hexutil.Uint64)(&index)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation
|
||||||
|
func newRPCPendingTransaction(tx types.Transaction, current *types.Header, config *params.ChainConfig) *commands.RPCTransaction {
|
||||||
|
var baseFee *big.Int
|
||||||
|
if current != nil {
|
||||||
|
baseFee = misc.CalcBaseFee(config, current)
|
||||||
|
}
|
||||||
|
return newRPCTransaction(tx, common.Hash{}, 0, 0, baseFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index.
|
||||||
|
func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) (hexutil.Bytes, error) {
|
||||||
|
txs := b.Transactions()
|
||||||
|
if index >= uint64(len(txs)) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := txs[index].MarshalBinary(&buf)
|
||||||
|
return buf.Bytes(), err
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/params"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// headerByNumberOrHash - intent to read recent headers only
|
||||||
|
func headerByNumberOrHash(ctx context.Context, tx kv.Tx, blockNrOrHash rpc.BlockNumberOrHash, api *BaseAPIUtils) (*types.Header, error) {
|
||||||
|
blockNum, _, _, err := rpchelper.GetBlockNumber(blockNrOrHash, tx, api.filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
header, err := api._blockReader.HeaderByNumber(ctx, tx, blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// header can be nil
|
||||||
|
return header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// accessListResult returns an optional accesslist
|
||||||
|
// Its the result of the `eth_createAccessList` RPC call.
|
||||||
|
// It contains an error if the transaction itself failed.
|
||||||
|
type accessListResult struct {
|
||||||
|
Accesslist *types.AccessList `json:"accessList"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
GasUsed hexutil.Uint64 `json:"gasUsed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// to address is warm already, so we can save by adding it to the access list
|
||||||
|
// only if we are adding a lot of its storage slots as well
|
||||||
|
func optimizeToInAccessList(accessList *accessListResult, to common.Address) {
|
||||||
|
indexToRemove := -1
|
||||||
|
|
||||||
|
for i := 0; i < len(*accessList.Accesslist); i++ {
|
||||||
|
entry := (*accessList.Accesslist)[i]
|
||||||
|
if entry.Address != to {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://eips.ethereum.org/EIPS/eip-2930#charging-less-for-accesses-in-the-access-list
|
||||||
|
accessListSavingPerSlot := params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929 - params.TxAccessListStorageKeyGas
|
||||||
|
|
||||||
|
numSlots := uint64(len(entry.StorageKeys))
|
||||||
|
if numSlots*accessListSavingPerSlot <= params.TxAccessListAddressGas {
|
||||||
|
indexToRemove = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if indexToRemove >= 0 {
|
||||||
|
*accessList.Accesslist = removeIndex(*accessList.Accesslist, indexToRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeIndex(s types.AccessList, index int) types.AccessList {
|
||||||
|
return append(s[:index], s[index+1:]...)
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/consensus/ethash"
|
||||||
|
"github.com/ledgerwatch/erigon/core"
|
||||||
|
"github.com/ledgerwatch/erigon/core/rawdb"
|
||||||
|
"github.com/ledgerwatch/erigon/core/state"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/core/vm"
|
||||||
|
"github.com/ledgerwatch/erigon/params"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/transactions"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *BaseAPIUtils) getReceipts(ctx context.Context, tx kv.Tx, chainConfig *params.ChainConfig, block *types.Block, senders []common.Address) (types.Receipts, error) {
|
||||||
|
if cached := rawdb.ReadReceipts(tx, block, senders); cached != nil {
|
||||||
|
return cached, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeader := func(hash common.Hash, number uint64) *types.Header {
|
||||||
|
h, e := api._blockReader.Header(ctx, tx, hash, number)
|
||||||
|
if e != nil {
|
||||||
|
log.Error("getHeader error", "number", number, "hash", hash, "err", e)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
_, _, _, ibs, _, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, ethash.NewFaker(), tx, block.Hash(), 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
usedGas := new(uint64)
|
||||||
|
gp := new(core.GasPool).AddGas(block.GasLimit())
|
||||||
|
|
||||||
|
ethashFaker := ethash.NewFaker()
|
||||||
|
noopWriter := state.NewNoopWriter()
|
||||||
|
|
||||||
|
receipts := make(types.Receipts, len(block.Transactions()))
|
||||||
|
|
||||||
|
for i, txn := range block.Transactions() {
|
||||||
|
ibs.Prepare(txn.Hash(), block.Hash(), i)
|
||||||
|
header := block.Header()
|
||||||
|
receipt, _, err := core.ApplyTransaction(chainConfig, core.GetHashFn(header, getHeader), ethashFaker, nil, gp, ibs, noopWriter, header, txn, usedGas, vm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
receipt.BlockHash = block.Hash()
|
||||||
|
receipts[i] = receipt
|
||||||
|
}
|
||||||
|
|
||||||
|
return receipts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalReceipt(receipt *types.Receipt, txn types.Transaction, chainConfig *params.ChainConfig, block *types.Block, txnHash common.Hash, signed bool) map[string]interface{} {
|
||||||
|
var chainId *big.Int
|
||||||
|
switch t := txn.(type) {
|
||||||
|
case *types.LegacyTx:
|
||||||
|
if t.Protected() {
|
||||||
|
chainId = types.DeriveChainId(&t.V).ToBig()
|
||||||
|
}
|
||||||
|
case *types.AccessListTx:
|
||||||
|
chainId = t.ChainID.ToBig()
|
||||||
|
case *types.DynamicFeeTransaction:
|
||||||
|
chainId = t.ChainID.ToBig()
|
||||||
|
}
|
||||||
|
|
||||||
|
var from common.Address
|
||||||
|
if signed {
|
||||||
|
signer := types.LatestSignerForChainID(chainId)
|
||||||
|
from, _ = txn.Sender(*signer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"blockHash": receipt.BlockHash,
|
||||||
|
"blockNumber": hexutil.Uint64(receipt.BlockNumber.Uint64()),
|
||||||
|
"transactionHash": txnHash,
|
||||||
|
"transactionIndex": hexutil.Uint64(receipt.TransactionIndex),
|
||||||
|
"from": from,
|
||||||
|
"to": txn.GetTo(),
|
||||||
|
"type": hexutil.Uint(txn.Type()),
|
||||||
|
"gasUsed": hexutil.Uint64(receipt.GasUsed),
|
||||||
|
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
|
||||||
|
"contractAddress": nil,
|
||||||
|
"logs": receipt.Logs,
|
||||||
|
"logsBloom": types.CreateBloom(types.Receipts{receipt}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !chainConfig.IsLondon(block.NumberU64()) {
|
||||||
|
fields["effectiveGasPrice"] = hexutil.Uint64(txn.GetPrice().Uint64())
|
||||||
|
} else {
|
||||||
|
baseFee, _ := uint256.FromBig(block.BaseFee())
|
||||||
|
gasPrice := new(big.Int).Add(block.BaseFee(), txn.GetEffectiveGasTip(baseFee).ToBig())
|
||||||
|
fields["effectiveGasPrice"] = hexutil.Uint64(gasPrice.Uint64())
|
||||||
|
}
|
||||||
|
// Assign receipt status.
|
||||||
|
fields["status"] = hexutil.Uint64(receipt.Status)
|
||||||
|
if receipt.Logs == nil {
|
||||||
|
fields["logs"] = [][]*types.Log{}
|
||||||
|
}
|
||||||
|
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
|
||||||
|
if receipt.ContractAddress != (common.Address{}) {
|
||||||
|
fields["contractAddress"] = receipt.ContractAddress
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/core/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageRangeResult is the result of a debug_storageRangeAt API call.
|
||||||
|
type StorageRangeResult struct {
|
||||||
|
Storage StorageMap `json:"storage"`
|
||||||
|
NextKey *common.Hash `json:"nextKey"` // nil if Storage includes the last key in the trie.
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageMap a map from storage locations to StorageEntry items
|
||||||
|
type StorageMap map[common.Hash]StorageEntry
|
||||||
|
|
||||||
|
// StorageEntry an entry in storage of the account
|
||||||
|
type StorageEntry struct {
|
||||||
|
Key *common.Hash `json:"key"`
|
||||||
|
Value common.Hash `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func StorageRangeAt(stateReader *state.PlainState, contractAddress common.Address, start []byte, maxResult int) (StorageRangeResult, error) {
|
||||||
|
result := StorageRangeResult{Storage: StorageMap{}}
|
||||||
|
resultCount := 0
|
||||||
|
|
||||||
|
if err := stateReader.ForEachStorage(contractAddress, common.BytesToHash(start), func(key, seckey common.Hash, value uint256.Int) bool {
|
||||||
|
if resultCount < maxResult {
|
||||||
|
result.Storage[seckey] = StorageEntry{Key: &key, Value: value.Bytes32()}
|
||||||
|
} else {
|
||||||
|
result.NextKey = &key
|
||||||
|
}
|
||||||
|
resultCount++
|
||||||
|
return resultCount <= maxResult
|
||||||
|
}, maxResult+1); err != nil {
|
||||||
|
return StorageRangeResult{}, fmt.Errorf("error walking over storage: %w", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkBlockNumber(blockNumber rpc.BlockNumber, api EthAPI) error {
|
||||||
|
if api == nil {
|
||||||
|
return fmt.Errorf("no connection to the Erigon server or `eth` namespace isn't enabled")
|
||||||
|
}
|
||||||
|
data, err := api.GetBlockByNumber(context.TODO(), blockNumber, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(data) == 0 { // block not found
|
||||||
|
return fmt.Errorf("no known block with number %v (%x hex)", blockNumber, blockNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNotEnoughPeers = errors.New("not enough peers")
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkMinPeers(minPeerCount uint, api NetAPI) error {
|
||||||
|
if api == nil {
|
||||||
|
return fmt.Errorf("no connection to the Erigon server or `net` namespace isn't enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
peerCount, err := api.PeerCount(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint64(peerCount) < uint64(minPeerCount) {
|
||||||
|
return fmt.Errorf("%w: %d (minimum %d)", errNotEnoughPeers, peerCount, minPeerCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNotSynced = errors.New("not synced")
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkSynced(ethAPI EthAPI, r *http.Request) error {
|
||||||
|
i, err := ethAPI.Syncing(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Root().Warn("unable to process synced request", "err", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if i == nil || i == false {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errNotSynced
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errTimestampTooOld = errors.New("timestamp too old")
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkTime(
|
||||||
|
r *http.Request,
|
||||||
|
seconds int,
|
||||||
|
ethAPI EthAPI,
|
||||||
|
) error {
|
||||||
|
i, err := ethAPI.GetBlockByNumber(r.Context(), rpc.LatestBlockNumber, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timestamp := 0
|
||||||
|
if ts, ok := i["timestamp"]; ok {
|
||||||
|
if cs, ok := ts.(uint64); ok {
|
||||||
|
timestamp = int(cs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if timestamp > seconds {
|
||||||
|
return fmt.Errorf("%w: got ts: %d, need: %d", errTimestampTooOld, timestamp, seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type requestBody struct {
|
||||||
|
MinPeerCount *uint `json:"min_peer_count"`
|
||||||
|
BlockNumber *rpc.BlockNumber `json:"known_block"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
urlPath = "/health"
|
||||||
|
healthHeader = "X-ERIGON-HEALTHCHECK"
|
||||||
|
synced = "synced"
|
||||||
|
minPeerCount = "min_peer_count"
|
||||||
|
checkBlock = "check_block"
|
||||||
|
maxSecondsBehind = "max_seconds_behind"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCheckDisabled = errors.New("error check disabled")
|
||||||
|
errBadHeaderValue = errors.New("bad header value")
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProcessHealthcheckIfNeeded(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
rpcAPI []rpc.API,
|
||||||
|
) bool {
|
||||||
|
if !strings.EqualFold(r.URL.Path, urlPath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
netAPI, ethAPI := parseAPI(rpcAPI)
|
||||||
|
|
||||||
|
headers := r.Header.Values(healthHeader)
|
||||||
|
if len(headers) != 0 {
|
||||||
|
processFromHeaders(headers, ethAPI, netAPI, w, r)
|
||||||
|
} else {
|
||||||
|
processFromBody(w, r, netAPI, ethAPI)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFromHeaders(headers []string, ethAPI EthAPI, netAPI NetAPI, w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
errCheckSynced = errCheckDisabled
|
||||||
|
errCheckPeer = errCheckDisabled
|
||||||
|
errCheckBlock = errCheckDisabled
|
||||||
|
errCheckSeconds = errCheckDisabled
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, header := range headers {
|
||||||
|
lHeader := strings.ToLower(header)
|
||||||
|
if lHeader == synced {
|
||||||
|
errCheckSynced = checkSynced(ethAPI, r)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(lHeader, minPeerCount) {
|
||||||
|
peers, err := strconv.Atoi(strings.TrimPrefix(lHeader, minPeerCount))
|
||||||
|
if err != nil {
|
||||||
|
errCheckPeer = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
errCheckPeer = checkMinPeers(uint(peers), netAPI)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(lHeader, checkBlock) {
|
||||||
|
block, err := strconv.Atoi(strings.TrimPrefix(lHeader, checkBlock))
|
||||||
|
if err != nil {
|
||||||
|
errCheckBlock = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
errCheckBlock = checkBlockNumber(rpc.BlockNumber(block), ethAPI)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(lHeader, maxSecondsBehind) {
|
||||||
|
seconds, err := strconv.Atoi(strings.TrimPrefix(lHeader, maxSecondsBehind))
|
||||||
|
if err != nil {
|
||||||
|
errCheckSeconds = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if seconds < 0 {
|
||||||
|
errCheckSeconds = errBadHeaderValue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
errCheckSeconds = checkTime(r, int(now)-seconds, ethAPI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reportHealthFromHeaders(errCheckSynced, errCheckPeer, errCheckBlock, errCheckSeconds, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFromBody(w http.ResponseWriter, r *http.Request, netAPI NetAPI, ethAPI EthAPI) {
|
||||||
|
body, errParse := parseHealthCheckBody(r.Body)
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
var errMinPeerCount = errCheckDisabled
|
||||||
|
var errCheckBlock = errCheckDisabled
|
||||||
|
|
||||||
|
if errParse != nil {
|
||||||
|
log.Root().Warn("unable to process healthcheck request", "err", errParse)
|
||||||
|
} else {
|
||||||
|
// 1. net_peerCount
|
||||||
|
if body.MinPeerCount != nil {
|
||||||
|
errMinPeerCount = checkMinPeers(*body.MinPeerCount, netAPI)
|
||||||
|
}
|
||||||
|
// 2. custom query (shouldn't fail)
|
||||||
|
if body.BlockNumber != nil {
|
||||||
|
errCheckBlock = checkBlockNumber(*body.BlockNumber, ethAPI)
|
||||||
|
}
|
||||||
|
// TODO add time from the last sync cycle
|
||||||
|
}
|
||||||
|
|
||||||
|
err := reportHealthFromBody(errParse, errMinPeerCount, errCheckBlock, w)
|
||||||
|
if err != nil {
|
||||||
|
log.Root().Warn("unable to process healthcheck request", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHealthCheckBody(reader io.Reader) (requestBody, error) {
|
||||||
|
var body requestBody
|
||||||
|
|
||||||
|
bodyBytes, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return body, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(bodyBytes, &body)
|
||||||
|
if err != nil {
|
||||||
|
return body, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reportHealthFromBody(errParse, errMinPeerCount, errCheckBlock error, w http.ResponseWriter) error {
|
||||||
|
statusCode := http.StatusOK
|
||||||
|
errors := make(map[string]string)
|
||||||
|
|
||||||
|
if shouldChangeStatusCode(errParse) {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
errors["healthcheck_query"] = errorStringOrOK(errParse)
|
||||||
|
|
||||||
|
if shouldChangeStatusCode(errMinPeerCount) {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
errors["min_peer_count"] = errorStringOrOK(errMinPeerCount)
|
||||||
|
|
||||||
|
if shouldChangeStatusCode(errCheckBlock) {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
errors["check_block"] = errorStringOrOK(errCheckBlock)
|
||||||
|
|
||||||
|
return writeResponse(w, errors, statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reportHealthFromHeaders(errCheckSynced, errCheckPeer, errCheckBlock, errCheckSeconds error, w http.ResponseWriter) error {
|
||||||
|
statusCode := http.StatusOK
|
||||||
|
errs := make(map[string]string)
|
||||||
|
|
||||||
|
if shouldChangeStatusCode(errCheckSynced) {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
errs[synced] = errorStringOrOK(errCheckSynced)
|
||||||
|
|
||||||
|
if shouldChangeStatusCode(errCheckPeer) {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
errs[minPeerCount] = errorStringOrOK(errCheckPeer)
|
||||||
|
|
||||||
|
if shouldChangeStatusCode(errCheckBlock) {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
errs[checkBlock] = errorStringOrOK(errCheckBlock)
|
||||||
|
|
||||||
|
if shouldChangeStatusCode(errCheckSeconds) {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
errs[maxSecondsBehind] = errorStringOrOK(errCheckSeconds)
|
||||||
|
|
||||||
|
return writeResponse(w, errs, statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeResponse(w http.ResponseWriter, errs map[string]string, statusCode int) error {
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
|
||||||
|
bodyJson, err := json.Marshal(errs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(bodyJson)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldChangeStatusCode(err error) bool {
|
||||||
|
return err != nil && !errors.Is(err, errCheckDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorStringOrOK(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return "HEALTHY"
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, errCheckDisabled) {
|
||||||
|
return "DISABLED"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("ERROR: %v", err)
|
||||||
|
}
|
|
@ -0,0 +1,562 @@
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netApiStub struct {
|
||||||
|
response hexutil.Uint
|
||||||
|
error error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *netApiStub) PeerCount(_ context.Context) (hexutil.Uint, error) {
|
||||||
|
return n.response, n.error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ethApiStub struct {
|
||||||
|
blockResult map[string]interface{}
|
||||||
|
blockError error
|
||||||
|
syncingResult interface{}
|
||||||
|
syncingError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ethApiStub) GetBlockByNumber(_ context.Context, _ rpc.BlockNumber, _ bool) (map[string]interface{}, error) {
|
||||||
|
return e.blockResult, e.blockError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ethApiStub) Syncing(_ context.Context) (interface{}, error) {
|
||||||
|
return e.syncingResult, e.syncingError
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessHealthcheckIfNeeded_HeadersTests(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
headers []string
|
||||||
|
netApiResponse hexutil.Uint
|
||||||
|
netApiError error
|
||||||
|
ethApiBlockResult map[string]interface{}
|
||||||
|
ethApiBlockError error
|
||||||
|
ethApiSyncingResult interface{}
|
||||||
|
ethApiSyncingError error
|
||||||
|
expectedStatusCode int
|
||||||
|
expectedBody map[string]string
|
||||||
|
}{
|
||||||
|
// 0 - sync check enabled - syncing
|
||||||
|
{
|
||||||
|
headers: []string{"synced"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: make(map[string]interface{}),
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "HEALTHY",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 1 - sync check enabled - not syncing
|
||||||
|
{
|
||||||
|
headers: []string{"synced"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: make(map[string]interface{}),
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: struct{}{},
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "ERROR: not synced",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 2 - sync check enabled - error checking sync
|
||||||
|
{
|
||||||
|
headers: []string{"synced"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: make(map[string]interface{}),
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: struct{}{},
|
||||||
|
ethApiSyncingError: errors.New("problem checking sync"),
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "ERROR: problem checking sync",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 3 - peer count enabled - good request
|
||||||
|
{
|
||||||
|
headers: []string{"min_peer_count1"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: make(map[string]interface{}),
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "HEALTHY",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 4 - peer count enabled - not enough peers
|
||||||
|
{
|
||||||
|
headers: []string{"min_peer_count10"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: make(map[string]interface{}),
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "ERROR: not enough peers: 1 (minimum 10)",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 5 - peer count enabled - error checking peers
|
||||||
|
{
|
||||||
|
headers: []string{"min_peer_count10"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: errors.New("problem checking peers"),
|
||||||
|
ethApiBlockResult: make(map[string]interface{}),
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "ERROR: problem checking peers",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 6 - peer count enabled - badly formed request
|
||||||
|
{
|
||||||
|
headers: []string{"min_peer_countABC"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: make(map[string]interface{}),
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "ERROR: strconv.Atoi: parsing \"abc\": invalid syntax",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 7 - block check - all ok
|
||||||
|
{
|
||||||
|
headers: []string{"check_block10"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{"test": struct{}{}},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "HEALTHY",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 8 - block check - no block found
|
||||||
|
{
|
||||||
|
headers: []string{"check_block10"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "ERROR: no known block with number 10 (a hex)",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 9 - block check - error checking block
|
||||||
|
{
|
||||||
|
headers: []string{"check_block10"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{},
|
||||||
|
ethApiBlockError: errors.New("problem checking block"),
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "ERROR: problem checking block",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 10 - block check - badly formed request
|
||||||
|
{
|
||||||
|
headers: []string{"check_blockABC"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "ERROR: strconv.Atoi: parsing \"abc\": invalid syntax",
|
||||||
|
maxSecondsBehind: "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 11 - seconds check - all ok
|
||||||
|
{
|
||||||
|
headers: []string{"max_seconds_behind60"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{
|
||||||
|
"timestamp": time.Now().Add(1 * time.Second).Unix(),
|
||||||
|
},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "HEALTHY",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 12 - seconds check - too old
|
||||||
|
{
|
||||||
|
headers: []string{"max_seconds_behind60"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{
|
||||||
|
"timestamp": uint64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
|
},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "ERROR: timestamp too old: got ts:",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 13 - seconds check - less than 0 seconds
|
||||||
|
{
|
||||||
|
headers: []string{"max_seconds_behind-1"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{
|
||||||
|
"timestamp": uint64(time.Now().Add(1 * time.Hour).Unix()),
|
||||||
|
},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "ERROR: bad header value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 14 - seconds check - badly formed request
|
||||||
|
{
|
||||||
|
headers: []string{"max_seconds_behindABC"},
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "DISABLED",
|
||||||
|
minPeerCount: "DISABLED",
|
||||||
|
checkBlock: "DISABLED",
|
||||||
|
maxSecondsBehind: "ERROR: strconv.Atoi: parsing \"abc\": invalid syntax",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 15 - all checks - report ok
|
||||||
|
{
|
||||||
|
headers: []string{"synced", "check_block10", "min_peer_count1", "max_seconds_behind60"},
|
||||||
|
netApiResponse: hexutil.Uint(10),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{
|
||||||
|
"timestamp": time.Now().Add(1 * time.Second).Unix(),
|
||||||
|
},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
ethApiSyncingResult: false,
|
||||||
|
ethApiSyncingError: nil,
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
synced: "HEALTHY",
|
||||||
|
minPeerCount: "HEALTHY",
|
||||||
|
checkBlock: "HEALTHY",
|
||||||
|
maxSecondsBehind: "HEALTHY",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, c := range cases {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/health", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: creating request: %v", idx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, header := range c.headers {
|
||||||
|
r.Header.Add("X-ERIGON-HEALTHCHECK", header)
|
||||||
|
}
|
||||||
|
|
||||||
|
netAPI := rpc.API{
|
||||||
|
Namespace: "",
|
||||||
|
Version: "",
|
||||||
|
Service: &netApiStub{
|
||||||
|
response: c.netApiResponse,
|
||||||
|
error: c.netApiError,
|
||||||
|
},
|
||||||
|
Public: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ethAPI := rpc.API{
|
||||||
|
Namespace: "",
|
||||||
|
Version: "",
|
||||||
|
Service: ðApiStub{
|
||||||
|
blockResult: c.ethApiBlockResult,
|
||||||
|
blockError: c.ethApiBlockError,
|
||||||
|
syncingResult: c.ethApiSyncingResult,
|
||||||
|
syncingError: c.ethApiSyncingError,
|
||||||
|
},
|
||||||
|
Public: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
apis := make([]rpc.API, 2)
|
||||||
|
apis[0] = netAPI
|
||||||
|
apis[1] = ethAPI
|
||||||
|
|
||||||
|
ProcessHealthcheckIfNeeded(w, r, apis)
|
||||||
|
|
||||||
|
result := w.Result()
|
||||||
|
if result.StatusCode != c.expectedStatusCode {
|
||||||
|
t.Errorf("%v: expected status code: %v, but got: %v", idx, c.expectedStatusCode, result.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, err := io.ReadAll(result.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: reading response body: %s", idx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body map[string]string
|
||||||
|
err = json.Unmarshal(bodyBytes, &body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: unmarshalling the response body: %s", idx, err)
|
||||||
|
}
|
||||||
|
result.Body.Close()
|
||||||
|
|
||||||
|
for k, v := range c.expectedBody {
|
||||||
|
val, found := body[k]
|
||||||
|
if !found {
|
||||||
|
t.Errorf("%v: expected the key: %s to be in the response body but it wasn't there", idx, k)
|
||||||
|
}
|
||||||
|
if !strings.Contains(val, v) {
|
||||||
|
t.Errorf("%v: expected the response body key: %s to contain: %s, but it contained: %s", idx, k, v, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessHealthcheckIfNeeded_RequestBody(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
body string
|
||||||
|
netApiResponse hexutil.Uint
|
||||||
|
netApiError error
|
||||||
|
ethApiBlockResult map[string]interface{}
|
||||||
|
ethApiBlockError error
|
||||||
|
expectedStatusCode int
|
||||||
|
expectedBody map[string]string
|
||||||
|
}{
|
||||||
|
// 0 - happy path
|
||||||
|
{
|
||||||
|
body: "{\"min_peer_count\": 1, \"known_block\": 123}",
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{"test": struct{}{}},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
"healthcheck_query": "HEALTHY",
|
||||||
|
"min_peer_count": "HEALTHY",
|
||||||
|
"check_block": "HEALTHY",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 1 - bad request body
|
||||||
|
{
|
||||||
|
body: "{\"min_peer_count\" 1, \"known_block\": 123}",
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{"test": struct{}{}},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
"healthcheck_query": "ERROR:",
|
||||||
|
"min_peer_count": "DISABLED",
|
||||||
|
"check_block": "DISABLED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 2 - min peers - error from api
|
||||||
|
{
|
||||||
|
body: "{\"min_peer_count\": 1, \"known_block\": 123}",
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: errors.New("problem getting peers"),
|
||||||
|
ethApiBlockResult: map[string]interface{}{"test": struct{}{}},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
"healthcheck_query": "HEALTHY",
|
||||||
|
"min_peer_count": "ERROR: problem getting peers",
|
||||||
|
"check_block": "HEALTHY",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 3 - min peers - not enough peers
|
||||||
|
{
|
||||||
|
body: "{\"min_peer_count\": 10, \"known_block\": 123}",
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{"test": struct{}{}},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
"healthcheck_query": "HEALTHY",
|
||||||
|
"min_peer_count": "ERROR: not enough peers",
|
||||||
|
"check_block": "HEALTHY",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 4 - check block - no block
|
||||||
|
{
|
||||||
|
body: "{\"min_peer_count\": 1, \"known_block\": 123}",
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{},
|
||||||
|
ethApiBlockError: nil,
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
"healthcheck_query": "HEALTHY",
|
||||||
|
"min_peer_count": "HEALTHY",
|
||||||
|
"check_block": "ERROR: no known block with number ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 5 - check block - error getting block info
|
||||||
|
{
|
||||||
|
body: "{\"min_peer_count\": 1, \"known_block\": 123}",
|
||||||
|
netApiResponse: hexutil.Uint(1),
|
||||||
|
netApiError: nil,
|
||||||
|
ethApiBlockResult: map[string]interface{}{},
|
||||||
|
ethApiBlockError: errors.New("problem getting block"),
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedBody: map[string]string{
|
||||||
|
"healthcheck_query": "HEALTHY",
|
||||||
|
"min_peer_count": "HEALTHY",
|
||||||
|
"check_block": "ERROR: problem getting block",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, c := range cases {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/health", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: creating request: %v", idx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Body = io.NopCloser(strings.NewReader(c.body))
|
||||||
|
|
||||||
|
netAPI := rpc.API{
|
||||||
|
Namespace: "",
|
||||||
|
Version: "",
|
||||||
|
Service: &netApiStub{
|
||||||
|
response: c.netApiResponse,
|
||||||
|
error: c.netApiError,
|
||||||
|
},
|
||||||
|
Public: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ethAPI := rpc.API{
|
||||||
|
Namespace: "",
|
||||||
|
Version: "",
|
||||||
|
Service: ðApiStub{
|
||||||
|
blockResult: c.ethApiBlockResult,
|
||||||
|
blockError: c.ethApiBlockError,
|
||||||
|
},
|
||||||
|
Public: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
apis := make([]rpc.API, 2)
|
||||||
|
apis[0] = netAPI
|
||||||
|
apis[1] = ethAPI
|
||||||
|
|
||||||
|
ProcessHealthcheckIfNeeded(w, r, apis)
|
||||||
|
|
||||||
|
result := w.Result()
|
||||||
|
if result.StatusCode != c.expectedStatusCode {
|
||||||
|
t.Errorf("%v: expected status code: %v, but got: %v", idx, c.expectedStatusCode, result.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, err := io.ReadAll(result.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: reading response body: %s", idx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body map[string]string
|
||||||
|
err = json.Unmarshal(bodyBytes, &body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: unmarshalling the response body: %s", idx, err)
|
||||||
|
}
|
||||||
|
result.Body.Close()
|
||||||
|
|
||||||
|
for k, v := range c.expectedBody {
|
||||||
|
val, found := body[k]
|
||||||
|
if !found {
|
||||||
|
t.Errorf("%v: expected the key: %s to be in the response body but it wasn't there", idx, k)
|
||||||
|
}
|
||||||
|
if !strings.Contains(val, v) {
|
||||||
|
t.Errorf("%v: expected the response body key: %s to contain: %s, but it contained: %s", idx, k, v, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetAPI interface {
|
||||||
|
PeerCount(_ context.Context) (hexutil.Uint, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EthAPI interface {
|
||||||
|
GetBlockByNumber(_ context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error)
|
||||||
|
Syncing(ctx context.Context) (interface{}, error)
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ledgerwatch/erigon/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseAPI(api []rpc.API) (netAPI NetAPI, ethAPI EthAPI) {
|
||||||
|
for _, rpc := range api {
|
||||||
|
if rpc.Service == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if netCandidate, ok := rpc.Service.(NetAPI); ok {
|
||||||
|
netAPI = netCandidate
|
||||||
|
}
|
||||||
|
|
||||||
|
if ethCandidate, ok := rpc.Service.(EthAPI); ok {
|
||||||
|
ethAPI = ethCandidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return netAPI, ethAPI
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/common"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/wmitsuda/otterscan/cmd/rpcdaemon/cli"
|
||||||
|
mycmds "github.com/wmitsuda/otterscan/cmd/rpcdaemon/commands"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd, cfg := cli.RootCommand()
|
||||||
|
rootCtx, rootCancel := common.RootContext()
|
||||||
|
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
logger := log.New()
|
||||||
|
db, borDb, backend, txPool, mining, stateCache, blockReader, ff, agg, err := cli.RemoteServices(ctx, *cfg, logger, rootCancel)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Could not connect to DB", "err", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if borDb != nil {
|
||||||
|
defer borDb.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
apiList := mycmds.APIList(db, borDb, backend, txPool, mining, ff, stateCache, blockReader, agg, *cfg)
|
||||||
|
if err := cli.StartRpcServer(ctx, *cfg, apiList, nil); err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.ExecuteContext(rootCtx); err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Postman testing
|
||||||
|
|
||||||
|
There are two files here:
|
||||||
|
|
||||||
|
- RPC_Testing.json
|
||||||
|
- Trace_Testing.json
|
||||||
|
|
||||||
|
You can import them into Postman using these
|
||||||
|
instructions: https://github.com/ledgerwatch/erigon/wiki/Using-Postman-to-Test-TurboGeth-RPC
|
||||||
|
|
||||||
|
The first one is used to generate help text and other documentation as well as running a sanity check against a new
|
||||||
|
release. There is basically one test for each of the 81 RPC endpoints.
|
||||||
|
|
||||||
|
The second file contains 31 test cases specifically for the nine trace routines (five tests for five of the routines,
|
||||||
|
three for another, one each for the other three).
|
||||||
|
|
||||||
|
Another collection of related tests can be found
|
||||||
|
here: https://github.com/Great-Hill-Corporation/trueblocks-core/tree/develop/src/other/trace_tests
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,321 @@
|
||||||
|
package rpcdaemontest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces/remote"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/accounts/abi/bind"
|
||||||
|
"github.com/ledgerwatch/erigon/accounts/abi/bind/backends"
|
||||||
|
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/commands/contracts"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/consensus"
|
||||||
|
"github.com/ledgerwatch/erigon/consensus/ethash"
|
||||||
|
"github.com/ledgerwatch/erigon/core"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/crypto"
|
||||||
|
"github.com/ledgerwatch/erigon/ethdb/privateapi"
|
||||||
|
"github.com/ledgerwatch/erigon/params"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/snapshotsync"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/stages"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/test/bufconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateTestKV(t *testing.T) kv.RwDB {
|
||||||
|
s, _, _ := CreateTestSentry(t)
|
||||||
|
return s.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type testAddresses struct {
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
key1 *ecdsa.PrivateKey
|
||||||
|
key2 *ecdsa.PrivateKey
|
||||||
|
address common.Address
|
||||||
|
address1 common.Address
|
||||||
|
address2 common.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestAddresses() testAddresses {
|
||||||
|
var (
|
||||||
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
key1, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||||
|
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||||
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
address1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
|
address2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||||
|
)
|
||||||
|
|
||||||
|
return testAddresses{
|
||||||
|
key: key,
|
||||||
|
key1: key1,
|
||||||
|
key2: key2,
|
||||||
|
address: address,
|
||||||
|
address1: address1,
|
||||||
|
address2: address2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTestSentry(t *testing.T) (*stages.MockSentry, *core.ChainPack, []*core.ChainPack) {
|
||||||
|
addresses := makeTestAddresses()
|
||||||
|
var (
|
||||||
|
key = addresses.key
|
||||||
|
address = addresses.address
|
||||||
|
address1 = addresses.address1
|
||||||
|
address2 = addresses.address2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
gspec = &core.Genesis{
|
||||||
|
Config: params.AllEthashProtocolChanges,
|
||||||
|
Alloc: core.GenesisAlloc{
|
||||||
|
address: {Balance: big.NewInt(9000000000000000000)},
|
||||||
|
address1: {Balance: big.NewInt(200000000000000000)},
|
||||||
|
address2: {Balance: big.NewInt(300000000000000000)},
|
||||||
|
},
|
||||||
|
GasLimit: 10000000,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
m := stages.MockWithGenesis(t, gspec, key, false)
|
||||||
|
|
||||||
|
contractBackend := backends.NewSimulatedBackendWithConfig(gspec.Alloc, gspec.Config, gspec.GasLimit)
|
||||||
|
defer contractBackend.Close()
|
||||||
|
|
||||||
|
// Generate empty chain to have some orphaned blocks for tests
|
||||||
|
orphanedChain, err := core.GenerateChain(m.ChainConfig, m.Genesis, m.Engine, m.DB, 5, func(i int, block *core.BlockGen) {
|
||||||
|
}, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, err := getChainInstance(&addresses, m.ChainConfig, m.Genesis, m.Engine, m.DB, contractBackend)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = m.InsertChain(orphanedChain); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = m.InsertChain(chain); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, chain, []*core.ChainPack{orphanedChain}
|
||||||
|
}
|
||||||
|
|
||||||
|
var chainInstance *core.ChainPack
|
||||||
|
|
||||||
|
func getChainInstance(
|
||||||
|
addresses *testAddresses,
|
||||||
|
config *params.ChainConfig,
|
||||||
|
parent *types.Block,
|
||||||
|
engine consensus.Engine,
|
||||||
|
db kv.RwDB,
|
||||||
|
contractBackend *backends.SimulatedBackend,
|
||||||
|
) (*core.ChainPack, error) {
|
||||||
|
var err error
|
||||||
|
if chainInstance == nil {
|
||||||
|
chainInstance, err = generateChain(addresses, config, parent, engine, db, contractBackend)
|
||||||
|
}
|
||||||
|
return chainInstance.Copy(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateChain(
|
||||||
|
addresses *testAddresses,
|
||||||
|
config *params.ChainConfig,
|
||||||
|
parent *types.Block,
|
||||||
|
engine consensus.Engine,
|
||||||
|
db kv.RwDB,
|
||||||
|
contractBackend *backends.SimulatedBackend,
|
||||||
|
) (*core.ChainPack, error) {
|
||||||
|
var (
|
||||||
|
key = addresses.key
|
||||||
|
key1 = addresses.key1
|
||||||
|
key2 = addresses.key2
|
||||||
|
address = addresses.address
|
||||||
|
address1 = addresses.address1
|
||||||
|
address2 = addresses.address2
|
||||||
|
theAddr = common.Address{1}
|
||||||
|
chainId = big.NewInt(1337)
|
||||||
|
// this code generates a log
|
||||||
|
signer = types.LatestSignerForChainID(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, chainId)
|
||||||
|
transactOpts1, _ := bind.NewKeyedTransactorWithChainID(key1, chainId)
|
||||||
|
transactOpts2, _ := bind.NewKeyedTransactorWithChainID(key2, chainId)
|
||||||
|
var poly *contracts.Poly
|
||||||
|
var tokenContract *contracts.Token
|
||||||
|
|
||||||
|
// We generate the blocks without plain state because it's not supported in core.GenerateChain
|
||||||
|
return core.GenerateChain(config, parent, engine, db, 10, func(i int, block *core.BlockGen) {
|
||||||
|
var (
|
||||||
|
txn types.Transaction
|
||||||
|
txs []types.Transaction
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
txn, err = types.SignTx(types.NewTransaction(0, theAddr, uint256.NewInt(1000000000000000), 21000, new(uint256.Int), nil), *signer, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = contractBackend.SendTransaction(ctx, txn)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
txn, err = types.SignTx(types.NewTransaction(1, theAddr, uint256.NewInt(1000000000000000), 21000, new(uint256.Int), nil), *signer, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = contractBackend.SendTransaction(ctx, txn)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
_, txn, tokenContract, err = contracts.DeployToken(transactOpts, contractBackend, address1)
|
||||||
|
case 3:
|
||||||
|
txn, err = tokenContract.Mint(transactOpts1, address2, big.NewInt(10))
|
||||||
|
case 4:
|
||||||
|
txn, err = tokenContract.Transfer(transactOpts2, address, big.NewInt(3))
|
||||||
|
case 5:
|
||||||
|
// Multiple transactions sending small amounts of ether to various accounts
|
||||||
|
var j uint64
|
||||||
|
var toAddr common.Address
|
||||||
|
nonce := block.TxNonce(address)
|
||||||
|
for j = 1; j <= 32; j++ {
|
||||||
|
binary.BigEndian.PutUint64(toAddr[:], j)
|
||||||
|
txn, err = types.SignTx(types.NewTransaction(nonce, toAddr, uint256.NewInt(1_000_000_000_000_000), 21000, new(uint256.Int), nil), *signer, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = contractBackend.SendTransaction(ctx, txn)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
txs = append(txs, txn)
|
||||||
|
nonce++
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
_, txn, tokenContract, err = contracts.DeployToken(transactOpts, contractBackend, address1)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
txs = append(txs, txn)
|
||||||
|
txn, err = tokenContract.Mint(transactOpts1, address2, big.NewInt(100))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
txs = append(txs, txn)
|
||||||
|
// Multiple transactions sending small amounts of ether to various accounts
|
||||||
|
var j uint64
|
||||||
|
var toAddr common.Address
|
||||||
|
for j = 1; j <= 32; j++ {
|
||||||
|
binary.BigEndian.PutUint64(toAddr[:], j)
|
||||||
|
txn, err = tokenContract.Transfer(transactOpts2, toAddr, big.NewInt(1))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
txs = append(txs, txn)
|
||||||
|
}
|
||||||
|
case 7:
|
||||||
|
var toAddr common.Address
|
||||||
|
nonce := block.TxNonce(address)
|
||||||
|
binary.BigEndian.PutUint64(toAddr[:], 4)
|
||||||
|
txn, err = types.SignTx(types.NewTransaction(nonce, toAddr, uint256.NewInt(1000000000000000), 21000, new(uint256.Int), nil), *signer, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = contractBackend.SendTransaction(ctx, txn)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
txs = append(txs, txn)
|
||||||
|
binary.BigEndian.PutUint64(toAddr[:], 12)
|
||||||
|
txn, err = tokenContract.Transfer(transactOpts2, toAddr, big.NewInt(1))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
txs = append(txs, txn)
|
||||||
|
case 8:
|
||||||
|
_, txn, poly, err = contracts.DeployPoly(transactOpts, contractBackend)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
txs = append(txs, txn)
|
||||||
|
case 9:
|
||||||
|
txn, err = poly.DeployAndDestruct(transactOpts, big.NewInt(0))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
txs = append(txs, txn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if txs == nil && txn != nil {
|
||||||
|
txs = append(txs, txn)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, txn := range txs {
|
||||||
|
block.AddTx(txn)
|
||||||
|
}
|
||||||
|
contractBackend.Commit()
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IsMiningMock struct{}
|
||||||
|
|
||||||
|
func (*IsMiningMock) IsMining() bool { return false }
|
||||||
|
|
||||||
|
func CreateTestGrpcConn(t *testing.T, m *stages.MockSentry) (context.Context, *grpc.ClientConn) { //nolint
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
apis := m.Engine.APIs(nil)
|
||||||
|
if len(apis) < 1 {
|
||||||
|
t.Fatal("couldn't instantiate Engine api")
|
||||||
|
}
|
||||||
|
|
||||||
|
ethashApi := apis[1].Service.(*ethash.API)
|
||||||
|
server := grpc.NewServer()
|
||||||
|
|
||||||
|
remote.RegisterETHBACKENDServer(server, privateapi.NewEthBackendServer(ctx, nil, m.DB, m.Notifications.Events, snapshotsync.NewBlockReader(), nil, nil, nil, false))
|
||||||
|
txpool.RegisterTxpoolServer(server, m.TxPoolGrpcServer)
|
||||||
|
txpool.RegisterMiningServer(server, privateapi.NewMiningServer(ctx, &IsMiningMock{}, ethashApi))
|
||||||
|
listener := bufconn.Listen(1024 * 1024)
|
||||||
|
|
||||||
|
dialer := func() func(context.Context, string) (net.Conn, error) {
|
||||||
|
go func() {
|
||||||
|
if err := server.Serve(listener); err != nil {
|
||||||
|
fmt.Printf("%v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func(context.Context, string) (net.Conn, error) {
|
||||||
|
return listener.Dial()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
cancel()
|
||||||
|
conn.Close()
|
||||||
|
server.Stop()
|
||||||
|
})
|
||||||
|
return ctx, conn
|
||||||
|
}
|
|
@ -0,0 +1,310 @@
|
||||||
|
package rpcservices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces/remote"
|
||||||
|
types2 "github.com/ledgerwatch/erigon-lib/gointerfaces/types"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/kv"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types"
|
||||||
|
"github.com/ledgerwatch/erigon/ethdb/privateapi"
|
||||||
|
"github.com/ledgerwatch/erigon/p2p"
|
||||||
|
"github.com/ledgerwatch/erigon/rlp"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/services"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RemoteBackend struct {
|
||||||
|
remoteEthBackend remote.ETHBACKENDClient
|
||||||
|
log log.Logger
|
||||||
|
version gointerfaces.Version
|
||||||
|
db kv.RoDB
|
||||||
|
blockReader services.FullBlockReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoteBackend(client remote.ETHBACKENDClient, db kv.RoDB, blockReader services.FullBlockReader) *RemoteBackend {
|
||||||
|
return &RemoteBackend{
|
||||||
|
remoteEthBackend: client,
|
||||||
|
version: gointerfaces.VersionFromProto(privateapi.EthBackendAPIVersion),
|
||||||
|
log: log.New("remote_service", "eth_backend"),
|
||||||
|
db: db,
|
||||||
|
blockReader: blockReader,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) EnsureVersionCompatibility() bool {
|
||||||
|
versionReply, err := back.remoteEthBackend.Version(context.Background(), &emptypb.Empty{}, grpc.WaitForReady(true))
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
back.log.Error("getting Version", "err", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !gointerfaces.EnsureVersion(back.version, versionReply) {
|
||||||
|
back.log.Error("incompatible interface versions", "client", back.version.String(),
|
||||||
|
"server", fmt.Sprintf("%d.%d.%d", versionReply.Major, versionReply.Minor, versionReply.Patch))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
back.log.Info("interfaces compatible", "client", back.version.String(),
|
||||||
|
"server", fmt.Sprintf("%d.%d.%d", versionReply.Major, versionReply.Minor, versionReply.Patch))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) Etherbase(ctx context.Context) (common.Address, error) {
|
||||||
|
res, err := back.remoteEthBackend.Etherbase(ctx, &remote.EtherbaseRequest{})
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return common.Address{}, errors.New(s.Message())
|
||||||
|
}
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gointerfaces.ConvertH160toAddress(res.Address), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) NetVersion(ctx context.Context) (uint64, error) {
|
||||||
|
res, err := back.remoteEthBackend.NetVersion(ctx, &remote.NetVersionRequest{})
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return 0, errors.New(s.Message())
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) NetPeerCount(ctx context.Context) (uint64, error) {
|
||||||
|
res, err := back.remoteEthBackend.NetPeerCount(ctx, &remote.NetPeerCountRequest{})
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return 0, errors.New(s.Message())
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) ProtocolVersion(ctx context.Context) (uint64, error) {
|
||||||
|
res, err := back.remoteEthBackend.ProtocolVersion(ctx, &remote.ProtocolVersionRequest{})
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return 0, errors.New(s.Message())
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) ClientVersion(ctx context.Context) (string, error) {
|
||||||
|
res, err := back.remoteEthBackend.ClientVersion(ctx, &remote.ClientVersionRequest{})
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return "", errors.New(s.Message())
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.NodeName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) Subscribe(ctx context.Context, onNewEvent func(*remote.SubscribeReply)) error {
|
||||||
|
subscription, err := back.remoteEthBackend.Subscribe(ctx, &remote.SubscribeRequest{}, grpc.WaitForReady(true))
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return errors.New(s.Message())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
event, err := subscription.Recv()
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
log.Debug("rpcdaemon: the subscription channel was closed")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewEvent(event)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) SubscribeLogs(ctx context.Context, onNewLogs func(reply *remote.SubscribeLogsReply), requestor *atomic.Value) error {
|
||||||
|
subscription, err := back.remoteEthBackend.SubscribeLogs(ctx, grpc.WaitForReady(true))
|
||||||
|
if err != nil {
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return errors.New(s.Message())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
requestor.Store(subscription.Send)
|
||||||
|
for {
|
||||||
|
logs, err := subscription.Recv()
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
log.Info("rpcdaemon: the logs subscription channel was closed")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
onNewLogs(logs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) TxnLookup(ctx context.Context, tx kv.Getter, txnHash common.Hash) (uint64, bool, error) {
|
||||||
|
return back.blockReader.TxnLookup(ctx, tx, txnHash)
|
||||||
|
}
|
||||||
|
func (back *RemoteBackend) BlockWithSenders(ctx context.Context, tx kv.Getter, hash common.Hash, blockHeight uint64) (block *types.Block, senders []common.Address, err error) {
|
||||||
|
return back.blockReader.BlockWithSenders(ctx, tx, hash, blockHeight)
|
||||||
|
}
|
||||||
|
func (back *RemoteBackend) BodyWithTransactions(ctx context.Context, tx kv.Getter, hash common.Hash, blockHeight uint64) (body *types.Body, err error) {
|
||||||
|
return back.blockReader.BodyWithTransactions(ctx, tx, hash, blockHeight)
|
||||||
|
}
|
||||||
|
func (back *RemoteBackend) BodyRlp(ctx context.Context, tx kv.Getter, hash common.Hash, blockHeight uint64) (bodyRlp rlp.RawValue, err error) {
|
||||||
|
return back.blockReader.BodyRlp(ctx, tx, hash, blockHeight)
|
||||||
|
}
|
||||||
|
func (back *RemoteBackend) Body(ctx context.Context, tx kv.Getter, hash common.Hash, blockHeight uint64) (body *types.Body, txAmount uint32, err error) {
|
||||||
|
return back.blockReader.Body(ctx, tx, hash, blockHeight)
|
||||||
|
}
|
||||||
|
func (back *RemoteBackend) Header(ctx context.Context, tx kv.Getter, hash common.Hash, blockHeight uint64) (*types.Header, error) {
|
||||||
|
return back.blockReader.Header(ctx, tx, hash, blockHeight)
|
||||||
|
}
|
||||||
|
func (back *RemoteBackend) HeaderByNumber(ctx context.Context, tx kv.Getter, blockHeight uint64) (*types.Header, error) {
|
||||||
|
return back.blockReader.HeaderByNumber(ctx, tx, blockHeight)
|
||||||
|
}
|
||||||
|
func (back *RemoteBackend) HeaderByHash(ctx context.Context, tx kv.Getter, hash common.Hash) (*types.Header, error) {
|
||||||
|
return back.blockReader.HeaderByHash(ctx, tx, hash)
|
||||||
|
}
|
||||||
|
func (back *RemoteBackend) CanonicalHash(ctx context.Context, tx kv.Getter, blockHeight uint64) (common.Hash, error) {
|
||||||
|
return back.blockReader.CanonicalHash(ctx, tx, blockHeight)
|
||||||
|
}
|
||||||
|
func (back *RemoteBackend) TxnByIdxInBlock(ctx context.Context, tx kv.Getter, blockNum uint64, i int) (types.Transaction, error) {
|
||||||
|
return back.blockReader.TxnByIdxInBlock(ctx, tx, blockNum, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) EngineNewPayloadV1(ctx context.Context, payload *types2.ExecutionPayload) (res *remote.EnginePayloadStatus, err error) {
|
||||||
|
return back.remoteEthBackend.EngineNewPayloadV1(ctx, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) EngineForkchoiceUpdatedV1(ctx context.Context, request *remote.EngineForkChoiceUpdatedRequest) (*remote.EngineForkChoiceUpdatedReply, error) {
|
||||||
|
return back.remoteEthBackend.EngineForkChoiceUpdatedV1(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) EngineGetPayloadV1(ctx context.Context, payloadId uint64) (res *types2.ExecutionPayload, err error) {
|
||||||
|
return back.remoteEthBackend.EngineGetPayloadV1(ctx, &remote.EngineGetPayloadRequest{
|
||||||
|
PayloadId: payloadId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) NodeInfo(ctx context.Context, limit uint32) ([]p2p.NodeInfo, error) {
|
||||||
|
nodes, err := back.remoteEthBackend.NodeInfo(ctx, &remote.NodesInfoRequest{Limit: limit})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("nodes info request error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodes == nil || len(nodes.NodesInfo) == 0 {
|
||||||
|
return nil, errors.New("empty nodesInfo response")
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]p2p.NodeInfo, 0, len(nodes.NodesInfo))
|
||||||
|
for _, node := range nodes.NodesInfo {
|
||||||
|
var rawProtocols map[string]json.RawMessage
|
||||||
|
if err = json.Unmarshal(node.Protocols, &rawProtocols); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot decode protocols metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocols := make(map[string]interface{}, len(rawProtocols))
|
||||||
|
for k, v := range rawProtocols {
|
||||||
|
protocols[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, p2p.NodeInfo{
|
||||||
|
Enode: node.Enode,
|
||||||
|
ID: node.Id,
|
||||||
|
IP: node.Enode,
|
||||||
|
ENR: node.Enr,
|
||||||
|
ListenAddr: node.ListenerAddr,
|
||||||
|
Name: node.Name,
|
||||||
|
Ports: struct {
|
||||||
|
Discovery int `json:"discovery"`
|
||||||
|
Listener int `json:"listener"`
|
||||||
|
}{
|
||||||
|
Discovery: int(node.Ports.Discovery),
|
||||||
|
Listener: int(node.Ports.Listener),
|
||||||
|
},
|
||||||
|
Protocols: protocols,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) Peers(ctx context.Context) ([]*p2p.PeerInfo, error) {
|
||||||
|
rpcPeers, err := back.remoteEthBackend.Peers(ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ETHBACKENDClient.Peers() error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peers := make([]*p2p.PeerInfo, 0, len(rpcPeers.Peers))
|
||||||
|
|
||||||
|
for _, rpcPeer := range rpcPeers.Peers {
|
||||||
|
peer := p2p.PeerInfo{
|
||||||
|
ENR: rpcPeer.Enr,
|
||||||
|
Enode: rpcPeer.Enode,
|
||||||
|
ID: rpcPeer.Id,
|
||||||
|
Name: rpcPeer.Name,
|
||||||
|
Caps: rpcPeer.Caps,
|
||||||
|
Network: struct {
|
||||||
|
LocalAddress string `json:"localAddress"`
|
||||||
|
RemoteAddress string `json:"remoteAddress"`
|
||||||
|
Inbound bool `json:"inbound"`
|
||||||
|
Trusted bool `json:"trusted"`
|
||||||
|
Static bool `json:"static"`
|
||||||
|
}{
|
||||||
|
LocalAddress: rpcPeer.ConnLocalAddr,
|
||||||
|
RemoteAddress: rpcPeer.ConnRemoteAddr,
|
||||||
|
Inbound: rpcPeer.ConnIsInbound,
|
||||||
|
Trusted: rpcPeer.ConnIsTrusted,
|
||||||
|
Static: rpcPeer.ConnIsStatic,
|
||||||
|
},
|
||||||
|
Protocols: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
peers = append(peers, &peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return peers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (back *RemoteBackend) PendingBlock(ctx context.Context) (*types.Block, error) {
|
||||||
|
blockRlp, err := back.remoteEthBackend.PendingBlock(ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ETHBACKENDClient.PendingBlock() error: %w", err)
|
||||||
|
}
|
||||||
|
if blockRlp == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var block types.Block
|
||||||
|
err = rlp.Decode(bytes.NewReader(blockRlp.BlockRlp), &block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding block from %x: %w", blockRlp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &block, nil
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package rpcservices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
|
||||||
|
"github.com/ledgerwatch/erigon/ethdb/privateapi"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MiningService struct {
|
||||||
|
txpool.MiningClient
|
||||||
|
log log.Logger
|
||||||
|
version gointerfaces.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMiningService(client txpool.MiningClient) *MiningService {
|
||||||
|
return &MiningService{
|
||||||
|
MiningClient: client,
|
||||||
|
version: gointerfaces.VersionFromProto(privateapi.MiningAPIVersion),
|
||||||
|
log: log.New("remote_service", "mining"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiningService) EnsureVersionCompatibility() bool {
|
||||||
|
versionReply, err := s.Version(context.Background(), &emptypb.Empty{}, grpc.WaitForReady(true))
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("getting Version", "err", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !gointerfaces.EnsureVersion(s.version, versionReply) {
|
||||||
|
s.log.Error("incompatible interface versions", "client", s.version.String(),
|
||||||
|
"server", fmt.Sprintf("%d.%d.%d", versionReply.Major, versionReply.Minor, versionReply.Patch))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.log.Info("interfaces compatible", "client", s.version.String(),
|
||||||
|
"server", fmt.Sprintf("%d.%d.%d", versionReply.Major, versionReply.Minor, versionReply.Patch))
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package rpcservices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces"
|
||||||
|
"github.com/ledgerwatch/erigon-lib/gointerfaces/grpcutil"
|
||||||
|
txpooproto "github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
|
||||||
|
txpool2 "github.com/ledgerwatch/erigon-lib/txpool"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TxPoolService struct {
|
||||||
|
txpooproto.TxpoolClient
|
||||||
|
log log.Logger
|
||||||
|
version gointerfaces.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTxPoolService(client txpooproto.TxpoolClient) *TxPoolService {
|
||||||
|
return &TxPoolService{
|
||||||
|
TxpoolClient: client,
|
||||||
|
version: gointerfaces.VersionFromProto(txpool2.TxPoolAPIVersion),
|
||||||
|
log: log.New("remote_service", "tx_pool"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TxPoolService) EnsureVersionCompatibility() bool {
|
||||||
|
Start:
|
||||||
|
versionReply, err := s.Version(context.Background(), &emptypb.Empty{}, grpc.WaitForReady(true))
|
||||||
|
if err != nil {
|
||||||
|
if grpcutil.ErrIs(err, txpool2.ErrPoolDisabled) {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
goto Start
|
||||||
|
}
|
||||||
|
s.log.Error("ensure version", "err", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !gointerfaces.EnsureVersion(s.version, versionReply) {
|
||||||
|
s.log.Error("incompatible interface versions", "client", s.version.String(),
|
||||||
|
"server", fmt.Sprintf("%d.%d.%d", versionReply.Major, versionReply.Minor, versionReply.Patch))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.log.Info("interfaces compatible", "client", s.version.String(),
|
||||||
|
"server", fmt.Sprintf("%d.%d.%d", versionReply.Major, versionReply.Minor, versionReply.Patch))
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_syncing",
|
||||||
|
"params": [],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getBalance",
|
||||||
|
"params": [
|
||||||
|
"0xfffa4763f94f7ad191b366a343092a5d1a47ed08",
|
||||||
|
"0xde84"
|
||||||
|
],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "debug_accountRange",
|
||||||
|
"params": [
|
||||||
|
"0x1e8480",
|
||||||
|
"",
|
||||||
|
256,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
# curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash", "params": ["0x1302cc71b89c1482b18a97a6fa2c9c375f4bf7548122363b6e91528440272fde"], "id":1}' localhost:8545
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getTransactionByHash",
|
||||||
|
"params": [
|
||||||
|
"0x1302cc71b89c1482b18a97a6fa2c9c375f4bf7548122363b6e91528440272fde"
|
||||||
|
],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash", "params": ["0x1302cc71b89c1482b18a97a6fa2c9c375f4bf7548122363b6e91528440272fde"], "id":1}' localhost:8545
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getBlockByNumber",
|
||||||
|
"params": [
|
||||||
|
"0x4C4B40",
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
# curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber", "params": ["0x1b4", true], "id":1}' localhost:8545
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_newHeader",
|
||||||
|
"params": [],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getBlockByNumber",
|
||||||
|
"params": [
|
||||||
|
"0xf4240",
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "debug_storageRangeAt",
|
||||||
|
"params": [
|
||||||
|
"0x4ced0bc30041f7f4e11ba9f341b54404770c7695dfdba6bb64b6ffeee2074177",
|
||||||
|
99,
|
||||||
|
"0x33990122638b9132ca29c723bdf037f1a891a70c",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
1024
|
||||||
|
],
|
||||||
|
"id": 537758
|
||||||
|
}
|
||||||
|
|
||||||
|
### > 60
|
||||||
|
|
||||||
|
### >20
|
||||||
|
###{"jsonrpc":"2.0","method":"debug_storageRangeAt","params":["0x6e6ec30ba20b263d1bdf6d87a0b1b037ea595929ac10ad74f6b7e1890fdad744", 19,"0x793ae8c1b1a160bfc07bfb0d04f85eab1a71f4f2","0x0000000000000000000000000000000000000000000000000000000000000000",1024],"id":113911}
|
||||||
|
|
||||||
|
|
||||||
|
### {"jsonrpc":"2.0","mesthod":"debug_storageRangeAt","params":["0xbcb55dcb321899291d10818dd06eaaf939ff87a717ac40850b54c6b56e8936ff", 2,"0xca7c390f8f843a8c3036841fde755e5d0acb97da","0x0000000000000000000000000000000000000000000000000000000000000000",1024],"id":3836}
|
||||||
|
|
||||||
|
###{"jsonrpc":"2.0","method":"debug_storageRangeAt","params":["0xf212a7655339852bf58f7e1d66f82256d22d13ccba3068a9c47a635738698c84", 0,"0xb278e4cb20dfbf97e78f27001f6b15288302f4d7","0x0000000000000000000000000000000000000000000000000000000000000000",1024],"id":8970}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST 192.168.255.138:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getTransactionReceipt",
|
||||||
|
"params": [
|
||||||
|
"0xc05ce241bec59900356ede868d170bc01d743c3cd5ecb129ca99596593022771"
|
||||||
|
],
|
||||||
|
"id": 537758
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
#POST 192.168.255.138:8545
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "erigon_getLogsByHash",
|
||||||
|
"params": [
|
||||||
|
"0x343f85f13356e138152d77287fda5ae0818c514119119ad439f81d69c59fc2f6"
|
||||||
|
],
|
||||||
|
"id": 537758
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
#POST 192.168.255.138:8545
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getLogs",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"address": "0x6090a6e47849629b7245dfa1ca21d94cd15878ef",
|
||||||
|
"fromBlock": "0x3d0000",
|
||||||
|
"toBlock": "0x3d2600",
|
||||||
|
"topics": [
|
||||||
|
null,
|
||||||
|
"0x374f3a049e006f36f6cf91b02a3b0ee16c858af2f75858733eb0e927b5b7126c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": 537758
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
#POST 192.168.255.138:8545
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "eth_getWork",
|
||||||
|
"params": [],
|
||||||
|
"id": 537758
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST localhost:8545
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"method": "eth_estimateGas",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"to": "0x5fda30bb72b8dfe20e48a00dfc108d0915be9bb0",
|
||||||
|
"value": "0x1234"
|
||||||
|
},
|
||||||
|
"latest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
geth
|
||||||
|
parity
|
||||||
|
nethermind
|
||||||
|
turbogeth
|
||||||
|
erigon
|
|
@ -0,0 +1,22 @@
|
||||||
|
s/,\"id\":\"1\"//g
|
||||||
|
s/\"result\":null,/\"result\":\{\},/g
|
||||||
|
s/suicide/selfdestruct/g
|
||||||
|
s/\"gasUsed\":\"0x0\",//g
|
||||||
|
s/,\"value\":\"0x0\"//g
|
||||||
|
|
||||||
|
s/invalid argument 1: json: cannot unmarshal hex string \\\"0x\\\" into Go value of type hexutil.Uint64/Invalid params: Invalid index: cannot parse integer from empty string./
|
||||||
|
s/invalid argument 1: json: cannot unmarshal number into Go value of type \[\]hexutil.Uint64/Invalid params: invalid type: integer `0`, expected a sequence./
|
||||||
|
s/missing value for required argument 1/Invalid params: invalid length 1, expected a tuple of size 2./
|
||||||
|
s/Invalid params: invalid type: string \\\"0x0\\\", expected a sequence./invalid argument 1: json: cannot unmarshal string into Go value of type \[\]hexutil.Uint64/
|
||||||
|
s/Invalid params\: Invalid block number\: number too large to fit in target type./invalid argument 0: hex number > 64 bits/
|
||||||
|
s/the method trace_junk12 does not exist\/is not available/Method not found/
|
||||||
|
|
||||||
|
s/,\"traceAddress\":null/,\"traceAddress\":[]/g
|
||||||
|
s/\"0x0000000000000000000000000000000000000000000000000000000000000000\"/\"0x\"/g
|
||||||
|
s/\"transactionHash\":\"0x\",\"transactionPosition\":0/\"transactionHash\":null,\"transactionPosition\":null/g
|
||||||
|
s/\"result\":null/\"result\":[]/g
|
||||||
|
|
||||||
|
s/\"error\":{\"code\":-32000,\"message\":\"function trace_replayBlockTransactions not implemented\"}/\"result\":\[\]/
|
||||||
|
s/\"error\":{\"code\":-32000,\"message\":\"function trace_replayTransaction not implemented\"}/\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":\[\],\"vmTrace\":null}/
|
||||||
|
s/\"error\":{\"code\":-32602,\"message\":\"invalid argument 0: json: cannot unmarshal array into Go value of type commands.CallParam\"}/\"result\":\[{\"output\":\"0x\",\"stateDiff\":null,\"trace\":\[\],\"vmTrace\":null},{\"output\":\"0x\",\"stateDiff\":null,\"trace\":\[\],\"vmTrace\":null}]/
|
||||||
|
s/\"error\":{\"code\":-32602,\"message\":\"invalid argument 0: hex string has length 82, want 64 for common.Hash\"}/\"error\":{\"code\":-32602,\"data\":\"RlpIncorrectListLen\",\"message\":\"Couldn't parse parameters: Transaction is not valid RLP\"}/
|
|
@ -0,0 +1,76 @@
|
||||||
|
005 trace_get fail ["0x17104ac9d3312d8c136b7f44d4b8b47852618065ebfa534bd2d3b5ef218ca1f3",0]
|
||||||
|
010 trace_get fail ["0x17104ac9d3312d8c136b7f44d4b8b47852618065ebfa534bd2d3b5ef218ca1f3","0x0"]
|
||||||
|
015 trace_get zero ["0x17104ac9d3312d8c136b7f44d4b8b47852618065ebfa534bd2d3b5ef218ca1f3",["0x0"]]
|
||||||
|
020 trace_get one ["0x17104ac9d3312d8c136b7f44d4b8b47852618065ebfa534bd2d3b5ef218ca1f3",["0x1"]]
|
||||||
|
025 trace_get both ["0x17104ac9d3312d8c136b7f44d4b8b47852618065ebfa534bd2d3b5ef218ca1f3",["0x0","0x1"]]
|
||||||
|
030 trace_get fail ["0x17104ac9d3312d8c136b7f44d4b8b47852618065ebfa534bd2d3b5ef218ca1f3"]
|
||||||
|
035 trace_get two ["0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060",["0x2"]]
|
||||||
|
040 trace_get fail ["0x975994512b958b31608f5692a6dbacba359349533dfb4ba0facfb7291fbec48d",["0x"]]
|
||||||
|
|
||||||
|
050 trace_transaction one ["0x17104ac9d3312d8c136b7f44d4b8b47852618065ebfa534bd2d3b5ef218ca1f3"]
|
||||||
|
055 trace_transaction two ["0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"]
|
||||||
|
060 trace_transaction three ["0x6afbe0f0ea3613edd6b84b71260836c03bddce81604f05c81a070cd671d3d765"]
|
||||||
|
065 trace_transaction four ["0x80926bb17ecdd526a2d901835482615eec87c4ca7fc30b96d8c6d6ab17bc721e"]
|
||||||
|
070 trace_transaction five ["0xb8ae0ab093fe1882249187b8f40dbe6e9285b419d096bd8028172d55b47ff3ce"]
|
||||||
|
075 trace_transaction six ["0xc2b831c051582f13dfaff6df648972e7e94aeeed1e85d23bd968a55b59f3cb5b"]
|
||||||
|
080 trace_transaction seven ["0xf9d426284bd20415a53991a004122b3a3a619b295ea98d1d88a5fd3a4125408b"]
|
||||||
|
085 trace_transaction cr_de ["0x343ba476313771d4431018d7d2e935eba2bfe26d5be3e6cb84af6817fd0e4309"]
|
||||||
|
|
||||||
|
105 trace_block 0x23 ["0x2328"]
|
||||||
|
110 trace_block 0x10 ["0x100"]
|
||||||
|
115 trace_block 0x12 ["0x12"]
|
||||||
|
120 trace_block 0x12 ["0x121212"]
|
||||||
|
125 trace_block 0x2e ["0x2ed119"]
|
||||||
|
130 trace_block 0xa1 ["0xa18dcfbc639be11c353420ede9224d772c56eb9ff327eb73771f798cf42d0027"]
|
||||||
|
#135 trace_block 0xa6 ["0xa60f34"]
|
||||||
|
#140 trace_block 0xf4 ["0xf4629"]
|
||||||
|
#145 trace_block slow ["0x895441"]
|
||||||
|
|
||||||
|
150 trace_filter good_1 [{"fromBlock":"0x2328","toBlock":"0x2328"}]
|
||||||
|
155 trace_filter range_1 [{"fromBlock":"0x2dcaa9","toBlock":"0x2dcaaa"}]
|
||||||
|
160 trace_filter block_3 [{"fromBlock":"0x3","toBlock":"0x3"}]
|
||||||
|
165 trace_filter first_tx [{"fromBlock":"0xb443","toBlock":"0xb443"}]
|
||||||
|
170 trace_filter from_doc [{"fromBlock":"0x2ed0c4","toBlock":"0x2ed128","toAddress":["0x8bbb73bcb5d553b5a556358d27625323fd781d37"],"after":1000,"count":100}]
|
||||||
|
175 trace_filter rem_a_o [{"fromBlock":"0x2ed0c4","toBlock":"0x2ed128","toAddress":["0x8bbb73bcb5d553b5a556358d27625323fd781d37"]}]
|
||||||
|
180 trace_filter count_1 [{"fromBlock":"0x2ed0c4","toBlock":"0x2ed128","toAddress":["0x8bbb73bcb5d553b5a556358d27625323fd781d37"],"count":1}]
|
||||||
|
185 trace_filter after_1 [{"fromBlock":"0x2ed0c4","toBlock":"0x2ed128","toAddress":["0x8bbb73bcb5d553b5a556358d27625323fd781d37"],"after":1,"count":4}]
|
||||||
|
190 trace_filter to_0xc02 [{"fromBlock":"0xa344e0","toBlock":"0xa344e0","toAddress":["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"]}]
|
||||||
|
195 trace_filter fr_0xc3c [{"fromBlock":"0xa344e0","toBlock":"0xa344e0","fromAddress":["0xc3ca90684fd7b8c7e4be88c329269fc32111c4bd"]}]
|
||||||
|
200 trace_filter both [{"fromBlock":"0xa344e0","toBlock":"0xa344e0","fromAddress":["0xc3ca90684fd7b8c7e4be88c329269fc32111c4bd"],"toAddress":["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"]}]
|
||||||
|
205 trace_filter fail_2 [{"fromBlock":"0xa606ba","toBlock":"0x2dcaa9"}]
|
||||||
|
210 trace_filter bad_1 [{"fromBlock":"0x2328","toBlock":"0x2327"}]
|
||||||
|
#215 trace_filter slow_2 [{"fromBlock":"0xa606ba","toBlock":"0xa606ba"}]
|
||||||
|
#220 trace_filter 10700000 [{"fromBlock":"0xa344e0","toBlock":"0xa344e0"}]
|
||||||
|
|
||||||
|
250 trace_replayBlockTransactions fail ["0x3", ["stateDiff"]]
|
||||||
|
300 trace_replayTransaction fail ["0x02d4a872e096445e80d05276ee756cefef7f3b376bcec14246469c0cd97dad8f", ["fail"]]
|
||||||
|
320_erigon trace_call fail [{"input":"0x0","nonce":"0x0","from":"0x02fcf30912b6fe2b6452ee19721c6068fe4c7b61","gas":"0xf4240","to":"0x37a9679c41e99db270bda88de8ff50c0cd23f326","gasPrice":"0x4a817c800","value":"0x0"},["fail"],"latest"]
|
||||||
|
340 trace_callMany fail [[[{"from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1","to":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","value":"0x186a0"},["fail"]],[{"from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1","to":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","value":"0x186a0"},["fail"]]],"latest"]
|
||||||
|
360 trace_rawTransaction fail ["0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675",["fail"]]
|
||||||
|
#255 trace_replayBlockTransactions ["0x1",["trace"]]
|
||||||
|
#250 trace_replayBlockTransactions ["0x1"]
|
||||||
|
#265 trace_replayBlockTransactions ["0x100"]
|
||||||
|
#260 trace_replayBlockTransactions ["0x895441",["trace"]]
|
||||||
|
#275 trace_replayBlockTransactions ["0x895441",["vmTrace"]]
|
||||||
|
#270 trace_replayBlockTransactions ["0xCF9BF",["trace"]]
|
||||||
|
#285 trace_replayBlockTransactions ["0xDBBA1",["trace"]]
|
||||||
|
#280 trace_replayBlockTransactions ["0xDBBA1",["vmTrace"]]
|
||||||
|
#285 trace_replayBlockTransactions ["CF9BF",["trace"]]
|
||||||
|
#290 trace_replayTransactions ["CF9BF",["trace"]]
|
||||||
|
#295trace_replayTransactions ["CF9BF",["trace"]]
|
||||||
|
|
||||||
|
305 trace_junk12 no_rpc []
|
||||||
|
|
||||||
|
# custom, experimental stuff
|
||||||
|
405_erigon trace_blockReward rew_0 ["0x0"]
|
||||||
|
410_erigon trace_blockReward rew_1 ["0x1"]
|
||||||
|
415_erigon trace_blockReward rew_2 ["0x2"]
|
||||||
|
420_erigon trace_blockReward rew_3 ["0x3"]
|
||||||
|
425_erigon trace_uncleReward unc_0 ["0x0"]
|
||||||
|
430_erigon trace_uncleReward unc_1 ["0x1"]
|
||||||
|
435_erigon trace_uncleReward unc_2 ["0x2"]
|
||||||
|
440_erigon trace_uncleReward unc_3 ["0x3"]
|
||||||
|
445_erigon trace_issuance iss_0 ["0x0"]
|
||||||
|
450_erigon trace_issuance iss_1 ["0x1"]
|
||||||
|
455_erigon trace_issuance iss_2 ["0x2"]
|
||||||
|
460_erigon trace_issuance iss_3 ["0x3"]
|
|
@ -0,0 +1,69 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
root := "dist"
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "3001"
|
||||||
|
}
|
||||||
|
if os.Getenv("ROOTDIR") != "" {
|
||||||
|
root = os.Getenv("ROOTDIR")
|
||||||
|
}
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
if os.Args[1] != "" {
|
||||||
|
root = os.Args[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filesDir := http.Dir(root)
|
||||||
|
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
r.Use(middleware.Recoverer)
|
||||||
|
r.Use(middleware.SetHeader("Access-Control-Allow-Origin", "*"))
|
||||||
|
|
||||||
|
FileServer(r, "/", filesDir)
|
||||||
|
|
||||||
|
s := http.Server{
|
||||||
|
Addr: ":" + port,
|
||||||
|
Handler: r,
|
||||||
|
}
|
||||||
|
s.SetKeepAlivesEnabled(false)
|
||||||
|
s.ReadHeaderTimeout = 250 * time.Millisecond
|
||||||
|
s.MaxHeaderBytes = 8192
|
||||||
|
|
||||||
|
log.Println("static asset server running on " + port)
|
||||||
|
// Start the server.
|
||||||
|
if err := s.ListenAndServe(); err != nil {
|
||||||
|
log.Fatalf("error in ListenAndServe: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileServer conveniently sets up a http.FileServer handler to serve
|
||||||
|
// static files from a http.FileSystem.
|
||||||
|
func FileServer(r chi.Router, path string, root http.FileSystem) {
|
||||||
|
if strings.ContainsAny(path, "{}*") {
|
||||||
|
panic("FileServer does not permit any URL parameters.")
|
||||||
|
}
|
||||||
|
if path != "/" && path[len(path)-1] != '/' {
|
||||||
|
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
|
||||||
|
path += "/"
|
||||||
|
}
|
||||||
|
path += "*"
|
||||||
|
r.Get(path, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
rctx := chi.RouteContext(r.Context())
|
||||||
|
pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
|
||||||
|
fs := http.StripPrefix(pathPrefix, http.FileServer(root))
|
||||||
|
fs.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package debug interfaces Go runtime debugging facilities.
|
||||||
|
// This package is mostly glue code making these facilities available
|
||||||
|
// through the CLI and RPC subsystem. If you want to use them from Go code,
|
||||||
|
// use package runtime instead.
|
||||||
|
package debug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"runtime/pprof"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon-lib/common"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler is the global debugging handler.
|
||||||
|
var Handler = new(HandlerT)
|
||||||
|
|
||||||
|
// HandlerT implements the debugging API.
|
||||||
|
// Do not create values of this type, use the one
|
||||||
|
// in the Handler variable instead.
|
||||||
|
type HandlerT struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
cpuW io.WriteCloser
|
||||||
|
cpuFile string
|
||||||
|
traceW io.WriteCloser
|
||||||
|
traceFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verbosity sets the log verbosity ceiling. The verbosity of individual packages
|
||||||
|
// and source files can be raised using Vmodule.
|
||||||
|
func (*HandlerT) Verbosity(level int) {
|
||||||
|
//glogger.Verbosity(log.Lvl(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vmodule sets the log verbosity pattern. See package log for details on the
|
||||||
|
// pattern syntax.
|
||||||
|
func (*HandlerT) Vmodule(pattern string) error {
|
||||||
|
//return glogger.Vmodule(pattern)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BacktraceAt sets the log backtrace location. See package log for details on
|
||||||
|
// the pattern syntax.
|
||||||
|
func (*HandlerT) BacktraceAt(location string) error {
|
||||||
|
//return glogger.BacktraceAt(location)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemStats returns detailed runtime memory statistics.
|
||||||
|
func (*HandlerT) MemStats() *runtime.MemStats {
|
||||||
|
s := new(runtime.MemStats)
|
||||||
|
common.ReadMemStats(s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// GcStats returns GC statistics.
|
||||||
|
func (*HandlerT) GcStats() *debug.GCStats {
|
||||||
|
s := new(debug.GCStats)
|
||||||
|
debug.ReadGCStats(s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// CpuProfile turns on CPU profiling for nsec seconds and writes
|
||||||
|
// profile data to file.
|
||||||
|
func (h *HandlerT) CpuProfile(file string, nsec uint) error {
|
||||||
|
if err := h.StartCPUProfile(file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(nsec) * time.Second)
|
||||||
|
_ = h.StopCPUProfile()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartCPUProfile turns on CPU profiling, writing to the given file.
|
||||||
|
func (h *HandlerT) StartCPUProfile(file string) error {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
if h.cpuW != nil {
|
||||||
|
return errors.New("CPU profiling already in progress")
|
||||||
|
}
|
||||||
|
f, err := os.Create(expandHome(file))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.cpuW = f
|
||||||
|
h.cpuFile = file
|
||||||
|
log.Info("CPU profiling started", "dump", h.cpuFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopCPUProfile stops an ongoing CPU profile.
|
||||||
|
func (h *HandlerT) StopCPUProfile() error {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
if h.cpuW == nil {
|
||||||
|
return errors.New("CPU profiling not in progress")
|
||||||
|
}
|
||||||
|
log.Info("Done writing CPU profile", "dump", h.cpuFile)
|
||||||
|
h.cpuW.Close()
|
||||||
|
h.cpuW = nil
|
||||||
|
h.cpuFile = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoTrace turns on tracing for nsec seconds and writes
|
||||||
|
// trace data to file.
|
||||||
|
func (h *HandlerT) GoTrace(file string, nsec uint) error {
|
||||||
|
if err := h.StartGoTrace(file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(nsec) * time.Second)
|
||||||
|
_ = h.StopGoTrace()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to
|
||||||
|
// file. It uses a profile rate of 1 for most accurate information. If a different rate is
|
||||||
|
// desired, set the rate and write the profile manually.
|
||||||
|
func (*HandlerT) BlockProfile(file string, nsec uint) error {
|
||||||
|
runtime.SetBlockProfileRate(1)
|
||||||
|
time.Sleep(time.Duration(nsec) * time.Second)
|
||||||
|
defer runtime.SetBlockProfileRate(0)
|
||||||
|
return writeProfile("block", file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlockProfileRate sets the rate of goroutine block profile data collection.
|
||||||
|
// rate 0 disables block profiling.
|
||||||
|
func (*HandlerT) SetBlockProfileRate(rate int) {
|
||||||
|
runtime.SetBlockProfileRate(rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBlockProfile writes a goroutine blocking profile to the given file.
|
||||||
|
func (*HandlerT) WriteBlockProfile(file string) error {
|
||||||
|
return writeProfile("block", file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutexProfile turns on mutex profiling for nsec seconds and writes profile data to file.
|
||||||
|
// It uses a profile rate of 1 for most accurate information. If a different rate is
|
||||||
|
// desired, set the rate and write the profile manually.
|
||||||
|
func (*HandlerT) MutexProfile(file string, nsec uint) error {
|
||||||
|
runtime.SetMutexProfileFraction(1)
|
||||||
|
time.Sleep(time.Duration(nsec) * time.Second)
|
||||||
|
defer runtime.SetMutexProfileFraction(0)
|
||||||
|
return writeProfile("mutex", file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMutexProfileFraction sets the rate of mutex profiling.
|
||||||
|
func (*HandlerT) SetMutexProfileFraction(rate int) {
|
||||||
|
runtime.SetMutexProfileFraction(rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMutexProfile writes a goroutine blocking profile to the given file.
|
||||||
|
func (*HandlerT) WriteMutexProfile(file string) error {
|
||||||
|
return writeProfile("mutex", file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMemProfile writes an allocation profile to the given file.
|
||||||
|
// Note that the profiling rate cannot be set through the API,
|
||||||
|
// it must be set on the command line.
|
||||||
|
func (*HandlerT) WriteMemProfile(file string) error {
|
||||||
|
return writeProfile("heap", file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stacks returns a printed representation of the stacks of all goroutines.
|
||||||
|
func (*HandlerT) Stacks() string {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
_ = pprof.Lookup("goroutine").WriteTo(buf, 2)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeOSMemory forces a garbage collection.
|
||||||
|
func (*HandlerT) FreeOSMemory() {
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGCPercent sets the garbage collection target percentage. It returns the previous
|
||||||
|
// setting. A negative value disables GC.
|
||||||
|
func (*HandlerT) SetGCPercent(v int) int {
|
||||||
|
return debug.SetGCPercent(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeProfile(name, file string) error {
|
||||||
|
p := pprof.Lookup(name)
|
||||||
|
log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file)
|
||||||
|
f, err := os.Create(expandHome(file))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return p.WriteTo(f, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expands home directory in file paths.
|
||||||
|
// ~someuser/tmp will not be expanded.
|
||||||
|
func expandHome(p string) string {
|
||||||
|
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
if home == "" {
|
||||||
|
if usr, err := user.Current(); err == nil {
|
||||||
|
home = usr.HomeDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if home != "" {
|
||||||
|
p = home + p[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filepath.Clean(p)
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof" //nolint:gosec
|
||||||
|
|
||||||
|
metrics2 "github.com/VictoriaMetrics/metrics"
|
||||||
|
"github.com/ledgerwatch/erigon/common/fdlimit"
|
||||||
|
"github.com/ledgerwatch/erigon/metrics"
|
||||||
|
"github.com/ledgerwatch/erigon/metrics/exp"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
"github.com/wmitsuda/otterscan/erigon_internal/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//nolint
|
||||||
|
vmoduleFlag = cli.StringFlag{
|
||||||
|
Name: "vmodule",
|
||||||
|
Usage: "Per-module verbosity: comma-separated list of <pattern>=<level> (e.g. eth/*=5,p2p=4)",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
metricsAddrFlag = cli.StringFlag{
|
||||||
|
Name: "metrics.addr",
|
||||||
|
}
|
||||||
|
metricsPortFlag = cli.UintFlag{
|
||||||
|
Name: "metrics.port",
|
||||||
|
Value: 6060,
|
||||||
|
}
|
||||||
|
pprofFlag = cli.BoolFlag{
|
||||||
|
Name: "pprof",
|
||||||
|
Usage: "Enable the pprof HTTP server",
|
||||||
|
}
|
||||||
|
pprofPortFlag = cli.IntFlag{
|
||||||
|
Name: "pprof.port",
|
||||||
|
Usage: "pprof HTTP server listening port",
|
||||||
|
Value: 6060,
|
||||||
|
}
|
||||||
|
pprofAddrFlag = cli.StringFlag{
|
||||||
|
Name: "pprof.addr",
|
||||||
|
Usage: "pprof HTTP server listening interface",
|
||||||
|
Value: "127.0.0.1",
|
||||||
|
}
|
||||||
|
cpuprofileFlag = cli.StringFlag{
|
||||||
|
Name: "pprof.cpuprofile",
|
||||||
|
Usage: "Write CPU profile to the given file",
|
||||||
|
}
|
||||||
|
traceFlag = cli.StringFlag{
|
||||||
|
Name: "trace",
|
||||||
|
Usage: "Write execution trace to the given file",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flags holds all command-line flags required for debugging.
|
||||||
|
var Flags = []cli.Flag{
|
||||||
|
pprofFlag, pprofAddrFlag, pprofPortFlag,
|
||||||
|
cpuprofileFlag, traceFlag,
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupCobra(cmd *cobra.Command) error {
|
||||||
|
RaiseFdLimit()
|
||||||
|
flags := cmd.Flags()
|
||||||
|
|
||||||
|
_ = logging.GetLoggerCmd("debug", cmd)
|
||||||
|
|
||||||
|
traceFile, err := flags.GetString(traceFlag.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cpuFile, err := flags.GetString(cpuprofileFlag.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// profiling, tracing
|
||||||
|
if traceFile != "" {
|
||||||
|
if err2 := Handler.StartGoTrace(traceFile); err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cpuFile != "" {
|
||||||
|
if err2 := Handler.StartCPUProfile(cpuFile); err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go ListenSignals(nil)
|
||||||
|
pprof, err := flags.GetBool(pprofFlag.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pprofAddr, err := flags.GetString(pprofAddrFlag.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pprofPort, err := flags.GetInt(pprofPortFlag.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
metricsAddr, err := flags.GetString(metricsAddrFlag.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
metricsPort, err := flags.GetInt(metricsPortFlag.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if metrics.Enabled && metricsAddr != "" {
|
||||||
|
address := fmt.Sprintf("%s:%d", metricsAddr, metricsPort)
|
||||||
|
exp.Setup(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
withMetrics := metrics.Enabled && metricsAddr == ""
|
||||||
|
if pprof {
|
||||||
|
// metrics and pprof server
|
||||||
|
StartPProf(fmt.Sprintf("%s:%d", pprofAddr, pprofPort), withMetrics)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup initializes profiling and logging based on the CLI flags.
|
||||||
|
// It should be called as early as possible in the program.
|
||||||
|
func Setup(ctx *cli.Context) error {
|
||||||
|
RaiseFdLimit()
|
||||||
|
|
||||||
|
_ = logging.GetLoggerCtx("debug", ctx)
|
||||||
|
|
||||||
|
if traceFile := ctx.String(traceFlag.Name); traceFile != "" {
|
||||||
|
if err := Handler.StartGoTrace(traceFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cpuFile := ctx.String(cpuprofileFlag.Name); cpuFile != "" {
|
||||||
|
if err := Handler.StartCPUProfile(cpuFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pprofEnabled := ctx.Bool(pprofFlag.Name)
|
||||||
|
metricsAddr := ctx.String(metricsAddrFlag.Name)
|
||||||
|
|
||||||
|
if metrics.Enabled && (!pprofEnabled || metricsAddr != "") {
|
||||||
|
metricsPort := ctx.Int(metricsPortFlag.Name)
|
||||||
|
address := fmt.Sprintf("%s:%d", metricsAddr, metricsPort)
|
||||||
|
exp.Setup(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pprof server
|
||||||
|
if pprofEnabled {
|
||||||
|
pprofHost := ctx.String(pprofAddrFlag.Name)
|
||||||
|
pprofPort := ctx.Int(pprofPortFlag.Name)
|
||||||
|
address := fmt.Sprintf("%s:%d", pprofHost, pprofPort)
|
||||||
|
// This context value ("metrics.addr") represents the utils.MetricsHTTPFlag.Name.
|
||||||
|
// It cannot be imported because it will cause a cyclical dependency.
|
||||||
|
withMetrics := metrics.Enabled && metricsAddr == ""
|
||||||
|
StartPProf(address, withMetrics)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartPProf(address string, withMetrics bool) {
|
||||||
|
// Hook go-metrics into expvar on any /debug/metrics request, load all vars
|
||||||
|
// from the registry into expvar, and execute regular expvar handler.
|
||||||
|
if withMetrics {
|
||||||
|
http.HandleFunc("/debug/metrics/prometheus", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
metrics2.WritePrometheus(w, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
cpuMsg := fmt.Sprintf("go tool pprof -lines -http=: http://%s/%s", address, "debug/pprof/profile?seconds=20")
|
||||||
|
heapMsg := fmt.Sprintf("go tool pprof -lines -http=: http://%s/%s", address, "debug/pprof/heap")
|
||||||
|
log.Info("Starting pprof server", "cpu", cpuMsg, "heap", heapMsg)
|
||||||
|
go func() {
|
||||||
|
if err := http.ListenAndServe(address, nil); err != nil { // nolint:gosec
|
||||||
|
log.Error("Failure in running pprof server", "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit stops all running profiles, flushing their output to the
|
||||||
|
// respective file.
|
||||||
|
func Exit() {
|
||||||
|
_ = Handler.StopCPUProfile()
|
||||||
|
_ = Handler.StopGoTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RaiseFdLimit raises out the number of allowed file handles per process
|
||||||
|
func RaiseFdLimit() {
|
||||||
|
limit, err := fdlimit.Maximum()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to retrieve file descriptor allowance", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = fdlimit.Raise(uint64(limit)); err != nil {
|
||||||
|
log.Error("Failed to raise file descriptor allowance", "err", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build go1.6
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
import "runtime/debug"
|
||||||
|
|
||||||
|
// LoudPanic panics in a way that gets all goroutine stacks printed on stderr.
|
||||||
|
func LoudPanic(x interface{}) {
|
||||||
|
debug.SetTraceback("all")
|
||||||
|
panic(x)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build !go1.6
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
// LoudPanic panics in a way that gets all goroutine stacks printed on stderr.
|
||||||
|
func LoudPanic(x interface{}) {
|
||||||
|
panic(x)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime/pprof"
|
||||||
|
|
||||||
|
_debug "github.com/ledgerwatch/erigon/common/debug"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListenSignals(stack io.Closer) {
|
||||||
|
sigc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigc, unix.SIGINT, unix.SIGTERM)
|
||||||
|
_debug.GetSigC(&sigc)
|
||||||
|
defer signal.Stop(sigc)
|
||||||
|
|
||||||
|
usr1 := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(usr1, unix.SIGUSR1)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sigc:
|
||||||
|
log.Info("Got interrupt, shutting down...")
|
||||||
|
if stack != nil {
|
||||||
|
go stack.Close()
|
||||||
|
}
|
||||||
|
for i := 10; i > 0; i-- {
|
||||||
|
<-sigc
|
||||||
|
if i > 1 {
|
||||||
|
log.Warn("Already shutting down, interrupt more to panic.", "times", i-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Exit() // ensure trace and CPU profile data is flushed.
|
||||||
|
LoudPanic("boom")
|
||||||
|
case <-usr1:
|
||||||
|
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
|
_debug "github.com/ledgerwatch/erigon/common/debug"
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListenSignals(stack io.Closer) {
|
||||||
|
sigc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigc, os.Interrupt)
|
||||||
|
_debug.GetSigC(&sigc)
|
||||||
|
defer signal.Stop(sigc)
|
||||||
|
|
||||||
|
<-sigc
|
||||||
|
log.Info("Got interrupt, shutting down...")
|
||||||
|
if stack != nil {
|
||||||
|
go stack.Close()
|
||||||
|
}
|
||||||
|
for i := 10; i > 0; i-- {
|
||||||
|
<-sigc
|
||||||
|
if i > 1 {
|
||||||
|
log.Warn("Already shutting down, interrupt more to panic.", "times", i-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Exit() // ensure trace and CPU profile data is flushed.
|
||||||
|
LoudPanic("boom")
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build go1.5
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"runtime/trace"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartGoTrace turns on tracing, writing to the given file.
|
||||||
|
func (h *HandlerT) StartGoTrace(file string) error {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
if h.traceW != nil {
|
||||||
|
return errors.New("trace already in progress")
|
||||||
|
}
|
||||||
|
f, err := os.Create(expandHome(file))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := trace.Start(f); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.traceW = f
|
||||||
|
h.traceFile = file
|
||||||
|
log.Info("Go tracing started", "dump", h.traceFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopTrace stops an ongoing trace.
|
||||||
|
func (h *HandlerT) StopGoTrace() error {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
trace.Stop()
|
||||||
|
if h.traceW == nil {
|
||||||
|
return errors.New("trace not in progress")
|
||||||
|
}
|
||||||
|
log.Info("Done writing Go trace", "dump", h.traceFile)
|
||||||
|
h.traceW.Close()
|
||||||
|
h.traceW = nil
|
||||||
|
h.traceFile = ""
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build !go1.5
|
||||||
|
|
||||||
|
// no-op implementation of tracing methods for Go < 1.5.
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func (*HandlerT) StartGoTrace(string) error {
|
||||||
|
return errors.New("tracing is not supported on Go < 1.5")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*HandlerT) StopGoTrace() error {
|
||||||
|
return errors.New("tracing is not supported on Go < 1.5")
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package ethapi implements the general Ethereum API functions.
|
||||||
|
package ethapi
|
||||||
|
|
||||||
|
/*
|
||||||
|
func GetAPIs(apiBackend Backend) []rpc.API {
|
||||||
|
nonceLock := new(AddrLocker)
|
||||||
|
return []rpc.API{
|
||||||
|
{
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicEthereumAPI(apiBackend),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicBlockChainAPI(apiBackend),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicTransactionPoolAPI(apiBackend, nonceLock),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "txpool",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicTxPoolAPI(apiBackend),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "debug",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicDebugAPI(apiBackend),
|
||||||
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "debug",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPrivateDebugAPI(apiBackend),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
|
@ -0,0 +1,234 @@
|
||||||
|
package ethapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/common/hexutil"
|
||||||
|
"github.com/ledgerwatch/erigon/core/types/accounts"
|
||||||
|
"github.com/ledgerwatch/erigon/turbo/trie"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Result structs for GetProof
|
||||||
|
type AccountResult struct {
|
||||||
|
Address common.Address `json:"address"`
|
||||||
|
AccountProof []string `json:"accountProof"`
|
||||||
|
Balance *hexutil.Big `json:"balance"`
|
||||||
|
CodeHash common.Hash `json:"codeHash"`
|
||||||
|
Nonce hexutil.Uint64 `json:"nonce"`
|
||||||
|
StorageHash common.Hash `json:"storageHash"`
|
||||||
|
StorageProof []StorageResult `json:"storageProof"`
|
||||||
|
}
|
||||||
|
type StorageResult struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value *hexutil.Big `json:"value"`
|
||||||
|
Proof []string `json:"proof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*TODO: to support proofs
|
||||||
|
func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNr rpc.BlockNumber) (*AccountResult, error) {
|
||||||
|
block := uint64(blockNr.Int64()) + 1
|
||||||
|
db := s.b.ChainDb()
|
||||||
|
ts := dbutils.EncodeBlockNumber(block)
|
||||||
|
accountMap := make(map[string]*accounts.Account)
|
||||||
|
if err := changeset.Walk(db, dbutils.AccountChangeSetBucket, ts, 0, func(blockN uint64, a, v []byte) (bool, error) {
|
||||||
|
a, v = common.CopyBytes(a), common.CopyBytes(v)
|
||||||
|
var kHash, err = common.HashData(a)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
k := kHash[:]
|
||||||
|
if _, ok := accountMap[string(k)]; !ok {
|
||||||
|
if len(v) > 0 {
|
||||||
|
var a accounts.Account
|
||||||
|
if innerErr := a.DecodeForStorage(v); innerErr != nil {
|
||||||
|
return false, innerErr
|
||||||
|
}
|
||||||
|
accountMap[string(k)] = &a
|
||||||
|
} else {
|
||||||
|
accountMap[string(k)] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
storageMap := make(map[string][]byte)
|
||||||
|
if err := changeset.Walk(db, dbutils.AccountChangeSetBucket, ts, 0, func(blockN uint64, a, v []byte) (bool, error) {
|
||||||
|
a, v = common.CopyBytes(a), common.CopyBytes(v)
|
||||||
|
var kHash, err = common.HashData(a)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
k := kHash[:]
|
||||||
|
if _, ok := storageMap[string(k)]; !ok {
|
||||||
|
storageMap[string(k)] = v
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var unfurlList = make([]string, len(accountMap)+len(storageMap))
|
||||||
|
unfurl := trie.NewRetainList(0)
|
||||||
|
i := 0
|
||||||
|
for ks, acc := range accountMap {
|
||||||
|
unfurlList[i] = ks
|
||||||
|
i++
|
||||||
|
unfurl.AddKey([]byte(ks))
|
||||||
|
if acc != nil {
|
||||||
|
// Fill the code hashes
|
||||||
|
if acc.Incarnation > 0 && acc.IsEmptyCodeHash() {
|
||||||
|
if codeHash, err1 := db.Get(dbutils.ContractCodeBucket, dbutils.GenerateStoragePrefix([]byte(ks), acc.Incarnation)); err1 == nil {
|
||||||
|
copy(acc.CodeHash[:], codeHash)
|
||||||
|
} else {
|
||||||
|
return nil, err1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ks := range storageMap {
|
||||||
|
unfurlList[i] = ks
|
||||||
|
i++
|
||||||
|
var sk [64]byte
|
||||||
|
copy(sk[:], []byte(ks)[:common.HashLength])
|
||||||
|
copy(sk[common.HashLength:], []byte(ks)[common.HashLength+common.IncarnationLength:])
|
||||||
|
unfurl.AddKey(sk[:])
|
||||||
|
}
|
||||||
|
rl := trie.NewRetainList(0)
|
||||||
|
addrHash, err := common.HashData(address[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rl.AddKey(addrHash[:])
|
||||||
|
unfurl.AddKey(addrHash[:])
|
||||||
|
for _, key := range storageKeys {
|
||||||
|
keyAsHash := common.HexToHash(key)
|
||||||
|
if keyHash, err1 := common.HashData(keyAsHash[:]); err1 == nil {
|
||||||
|
trieKey := append(addrHash[:], keyHash[:]...)
|
||||||
|
rl.AddKey(trieKey)
|
||||||
|
unfurl.AddKey(trieKey)
|
||||||
|
} else {
|
||||||
|
return nil, err1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(unfurlList)
|
||||||
|
loader := trie.NewFlatDBTrieLoader("checkRoots")
|
||||||
|
if err = loader.Reset(unfurl, nil, nil, false); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r := &Receiver{defaultReceiver: trie.NewDefaultReceiver(), unfurlList: unfurlList, accountMap: accountMap, storageMap: storageMap}
|
||||||
|
r.defaultReceiver.Reset(rl, nil, false)
|
||||||
|
loader.SetStreamReceiver(r)
|
||||||
|
_, err = loader.CalcTrieRoot(db.(ethdb.HasTx).Tx().(ethdb.RwTx), []byte{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
hash, err := rawdb.ReadCanonicalHash(db, block-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
header := rawdb.ReadHeader(db, hash, block-1)
|
||||||
|
tr := trie.New(header.Root)
|
||||||
|
if err = tr.HookSubTries(subTries, [][]byte{nil}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
accountProof, err2 := tr.Prove(addrHash[:], 0, false)
|
||||||
|
if err2 != nil {
|
||||||
|
return nil, err2
|
||||||
|
}
|
||||||
|
storageProof := make([]StorageResult, len(storageKeys))
|
||||||
|
for i, key := range storageKeys {
|
||||||
|
keyAsHash := common.HexToHash(key)
|
||||||
|
if keyHash, err1 := common.HashData(keyAsHash[:]); err1 == nil {
|
||||||
|
trieKey := append(addrHash[:], keyHash[:]...)
|
||||||
|
if proof, err3 := tr.Prove(trieKey, 64 , true); err3 == nil {
|
||||||
|
v, _ := tr.Get(trieKey)
|
||||||
|
bv := new(big.Int)
|
||||||
|
bv.SetBytes(v)
|
||||||
|
storageProof[i] = StorageResult{key, (*hexutil.Big)(bv), toHexSlice(proof)}
|
||||||
|
} else {
|
||||||
|
return nil, err3
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, err1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc, found := tr.GetAccount(addrHash[:])
|
||||||
|
if !found {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &AccountResult{
|
||||||
|
Address: address,
|
||||||
|
AccountProof: toHexSlice(accountProof),
|
||||||
|
Balance: (*hexutil.Big)(acc.Balance.ToBig()),
|
||||||
|
CodeHash: acc.CodeHash,
|
||||||
|
Nonce: hexutil.Uint64(acc.Nonce),
|
||||||
|
StorageHash: acc.Root,
|
||||||
|
StorageProof: storageProof,
|
||||||
|
}, nil
|
||||||
|
return &AccountResult{}, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Receiver struct {
|
||||||
|
defaultReceiver *trie.RootHashAggregator
|
||||||
|
accountMap map[string]*accounts.Account
|
||||||
|
storageMap map[string][]byte
|
||||||
|
unfurlList []string
|
||||||
|
currentIdx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Receiver) Root() common.Hash { panic("don't call me") }
|
||||||
|
func (r *Receiver) Receive(
|
||||||
|
itemType trie.StreamItem,
|
||||||
|
accountKey []byte,
|
||||||
|
storageKey []byte,
|
||||||
|
accountValue *accounts.Account,
|
||||||
|
storageValue []byte,
|
||||||
|
hash []byte,
|
||||||
|
hasTree bool,
|
||||||
|
cutoff int,
|
||||||
|
) error {
|
||||||
|
for r.currentIdx < len(r.unfurlList) {
|
||||||
|
ks := r.unfurlList[r.currentIdx]
|
||||||
|
k := []byte(ks)
|
||||||
|
var c int
|
||||||
|
switch itemType {
|
||||||
|
case trie.StorageStreamItem, trie.SHashStreamItem:
|
||||||
|
c = bytes.Compare(k, storageKey)
|
||||||
|
case trie.AccountStreamItem, trie.AHashStreamItem:
|
||||||
|
c = bytes.Compare(k, accountKey)
|
||||||
|
case trie.CutoffStreamItem:
|
||||||
|
c = -1
|
||||||
|
}
|
||||||
|
if c > 0 {
|
||||||
|
return r.defaultReceiver.Receive(itemType, accountKey, storageKey, accountValue, storageValue, hash, hasTree, cutoff)
|
||||||
|
}
|
||||||
|
if len(k) > common.HashLength {
|
||||||
|
v := r.storageMap[ks]
|
||||||
|
if c <= 0 && len(v) > 0 {
|
||||||
|
if err := r.defaultReceiver.Receive(trie.StorageStreamItem, nil, k, nil, v, nil, hasTree, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v := r.accountMap[ks]
|
||||||
|
if c <= 0 && v != nil {
|
||||||
|
if err := r.defaultReceiver.Receive(trie.AccountStreamItem, k, nil, v, nil, nil, hasTree, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.currentIdx++
|
||||||
|
if c == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We ran out of modifications, simply pass through
|
||||||
|
return r.defaultReceiver.Receive(itemType, accountKey, storageKey, accountValue, storageValue, hash, hasTree, cutoff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Receiver) Result() trie.SubTries {
|
||||||
|
return r.defaultReceiver.Result()
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package ethapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/ledgerwatch/erigon/common"
|
||||||
|
"github.com/ledgerwatch/erigon/core/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StateOverrides map[common.Address]Account
|
||||||
|
|
||||||
|
func (overrides *StateOverrides) Override(state *state.IntraBlockState) error {
|
||||||
|
|
||||||
|
for addr, account := range *overrides {
|
||||||
|
// Override account nonce.
|
||||||
|
if account.Nonce != nil {
|
||||||
|
state.SetNonce(addr, uint64(*account.Nonce))
|
||||||
|
}
|
||||||
|
// Override account(contract) code.
|
||||||
|
if account.Code != nil {
|
||||||
|
state.SetCode(addr, *account.Code)
|
||||||
|
}
|
||||||
|
// Override account balance.
|
||||||
|
if account.Balance != nil {
|
||||||
|
balance, overflow := uint256.FromBig((*big.Int)(*account.Balance))
|
||||||
|
if overflow {
|
||||||
|
return fmt.Errorf("account.Balance higher than 2^256-1")
|
||||||
|
}
|
||||||
|
state.SetBalance(addr, balance)
|
||||||
|
}
|
||||||
|
if account.State != nil && account.StateDiff != nil {
|
||||||
|
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
|
||||||
|
}
|
||||||
|
// Replace entire state if caller requires.
|
||||||
|
if account.State != nil {
|
||||||
|
state.SetStorage(addr, *account.State)
|
||||||
|
}
|
||||||
|
// Apply state diff into specified accounts.
|
||||||
|
if account.StateDiff != nil {
|
||||||
|
for key, value := range *account.StateDiff {
|
||||||
|
key := key
|
||||||
|
state.SetState(addr, &key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
LogJsonFlag = cli.BoolFlag{
|
||||||
|
Name: "log.json",
|
||||||
|
Usage: "Format console logs with JSON",
|
||||||
|
}
|
||||||
|
|
||||||
|
LogConsoleJsonFlag = cli.BoolFlag{
|
||||||
|
Name: "log.console.json",
|
||||||
|
Usage: "Format console logs with JSON",
|
||||||
|
}
|
||||||
|
|
||||||
|
LogDirJsonFlag = cli.BoolFlag{
|
||||||
|
Name: "log.dir.json",
|
||||||
|
Usage: "Format file logs with JSON",
|
||||||
|
}
|
||||||
|
|
||||||
|
LogVerbosityFlag = cli.StringFlag{
|
||||||
|
Name: "verbosity",
|
||||||
|
Usage: "Set the log level for console logs",
|
||||||
|
Value: log.LvlInfo.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
LogConsoleVerbosityFlag = cli.StringFlag{
|
||||||
|
Name: "log.console.verbosity",
|
||||||
|
Usage: "Set the log level for console logs",
|
||||||
|
Value: log.LvlInfo.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
LogDirPathFlag = cli.StringFlag{
|
||||||
|
Name: "log.dir.path",
|
||||||
|
Usage: "Path to store user and error logs to disk",
|
||||||
|
}
|
||||||
|
|
||||||
|
LogDirVerbosityFlag = cli.StringFlag{
|
||||||
|
Name: "log.dir.verbosity",
|
||||||
|
Usage: "Set the log verbosity for logs stored to disk",
|
||||||
|
Value: log.LvlDebug.String(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var Flags = []cli.Flag{
|
||||||
|
LogJsonFlag,
|
||||||
|
LogConsoleJsonFlag,
|
||||||
|
LogDirJsonFlag,
|
||||||
|
LogVerbosityFlag,
|
||||||
|
LogConsoleVerbosityFlag,
|
||||||
|
LogDirPathFlag,
|
||||||
|
LogDirVerbosityFlag,
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/ledgerwatch/log/v3"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetLoggerCtx(filePrefix string, ctx *cli.Context) log.Logger {
|
||||||
|
var consoleJson = ctx.Bool(LogJsonFlag.Name) || ctx.Bool(LogConsoleJsonFlag.Name)
|
||||||
|
var dirJson = ctx.Bool(LogDirJsonFlag.Name)
|
||||||
|
|
||||||
|
consoleLevel, lErr := tryGetLogLevel(ctx.String(LogConsoleVerbosityFlag.Name))
|
||||||
|
if lErr != nil {
|
||||||
|
// try verbosity flag
|
||||||
|
consoleLevel, lErr = tryGetLogLevel(ctx.String(LogVerbosityFlag.Name))
|
||||||
|
if lErr != nil {
|
||||||
|
consoleLevel = log.LvlInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dirLevel, dErr := tryGetLogLevel(ctx.String(LogDirVerbosityFlag.Name))
|
||||||
|
if dErr != nil {
|
||||||
|
dirLevel = log.LvlDebug
|
||||||
|
}
|
||||||
|
|
||||||
|
dirPath := ctx.String(LogDirPathFlag.Name)
|
||||||
|
return initSeparatedLogging(filePrefix, dirPath, consoleLevel, dirLevel, consoleJson, dirJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLoggerCmd(filePrefix string, cmd *cobra.Command) log.Logger {
|
||||||
|
|
||||||
|
logJsonVal, ljerr := cmd.Flags().GetBool(LogJsonFlag.Name)
|
||||||
|
if ljerr != nil {
|
||||||
|
logJsonVal = false
|
||||||
|
}
|
||||||
|
|
||||||
|
logConsoleJsonVal, lcjerr := cmd.Flags().GetBool(LogConsoleJsonFlag.Name)
|
||||||
|
if lcjerr != nil {
|
||||||
|
logConsoleJsonVal = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var consoleJson = logJsonVal || logConsoleJsonVal
|
||||||
|
dirJson, djerr := cmd.Flags().GetBool(LogDirJsonFlag.Name)
|
||||||
|
if djerr != nil {
|
||||||
|
dirJson = false
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleLevel, lErr := tryGetLogLevel(cmd.Flags().Lookup(LogConsoleVerbosityFlag.Name).Value.String())
|
||||||
|
if lErr != nil {
|
||||||
|
// try verbosity flag
|
||||||
|
consoleLevel, lErr = tryGetLogLevel(cmd.Flags().Lookup(LogVerbosityFlag.Name).Value.String())
|
||||||
|
if lErr != nil {
|
||||||
|
consoleLevel = log.LvlInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dirLevel, dErr := tryGetLogLevel(cmd.Flags().Lookup(LogDirVerbosityFlag.Name).Value.String())
|
||||||
|
if dErr != nil {
|
||||||
|
dirLevel = log.LvlDebug
|
||||||
|
}
|
||||||
|
|
||||||
|
dirPath := cmd.Flags().Lookup(LogDirPathFlag.Name).Value.String()
|
||||||
|
return initSeparatedLogging(filePrefix, dirPath, consoleLevel, dirLevel, consoleJson, dirJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLogger(filePrefix string) log.Logger {
|
||||||
|
var logConsoleVerbosity = flag.String(LogConsoleVerbosityFlag.Name, "", LogConsoleVerbosityFlag.Usage)
|
||||||
|
var logDirVerbosity = flag.String(LogDirVerbosityFlag.Name, "", LogDirVerbosityFlag.Usage)
|
||||||
|
var logDirPath = flag.String(LogDirPathFlag.Name, "", LogDirPathFlag.Usage)
|
||||||
|
var logVerbosity = flag.String(LogVerbosityFlag.Name, "", LogVerbosityFlag.Usage)
|
||||||
|
var logConsoleJson = flag.Bool(LogConsoleJsonFlag.Name, false, LogConsoleJsonFlag.Usage)
|
||||||
|
var logJson = flag.Bool(LogJsonFlag.Name, false, LogJsonFlag.Usage)
|
||||||
|
var logDirJson = flag.Bool(LogDirJsonFlag.Name, false, LogDirJsonFlag.Usage)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var consoleJson = *logJson || *logConsoleJson
|
||||||
|
var dirJson = logDirJson
|
||||||
|
|
||||||
|
consoleLevel, lErr := tryGetLogLevel(*logConsoleVerbosity)
|
||||||
|
if lErr != nil {
|
||||||
|
// try verbosity flag
|
||||||
|
consoleLevel, lErr = tryGetLogLevel(*logVerbosity)
|
||||||
|
if lErr != nil {
|
||||||
|
consoleLevel = log.LvlInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dirLevel, dErr := tryGetLogLevel(*logDirVerbosity)
|
||||||
|
if dErr != nil {
|
||||||
|
dirLevel = log.LvlDebug
|
||||||
|
}
|
||||||
|
|
||||||
|
return initSeparatedLogging(filePrefix, *logDirPath, consoleLevel, dirLevel, consoleJson, *dirJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSeparatedLogging(
|
||||||
|
filePrefix string,
|
||||||
|
dirPath string,
|
||||||
|
consoleLevel log.Lvl,
|
||||||
|
dirLevel log.Lvl,
|
||||||
|
consoleJson bool,
|
||||||
|
dirJson bool) log.Logger {
|
||||||
|
|
||||||
|
logger := log.Root()
|
||||||
|
|
||||||
|
if consoleJson {
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(consoleLevel, log.StreamHandler(os.Stderr, log.JsonFormat())))
|
||||||
|
} else {
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(consoleLevel, log.StderrHandler))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dirPath) == 0 {
|
||||||
|
logger.Warn("no log dir set, console logging only")
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll(dirPath, 0764)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("failed to create log dir, console logging only")
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
dirFormat := log.LogfmtFormat()
|
||||||
|
if dirJson {
|
||||||
|
dirFormat = log.JsonFormat()
|
||||||
|
}
|
||||||
|
|
||||||
|
userLog, err := log.FileHandler(path.Join(dirPath, filePrefix+"-user.log"), dirFormat, 1<<27) // 128Mb
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("failed to open user log, console logging only")
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
errLog, err := log.FileHandler(path.Join(dirPath, filePrefix+"-error.log"), dirFormat, 1<<27) // 128Mb
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("failed to open error log, console logging only")
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := log.MultiHandler(logger.GetHandler(), log.LvlFilterHandler(dirLevel, userLog), log.LvlFilterHandler(log.LvlError, errLog))
|
||||||
|
log.Root().SetHandler(mux)
|
||||||
|
logger.SetHandler(mux)
|
||||||
|
logger.Info("logging to file system", "log dir", dirPath, "file prefix", filePrefix, "log level", dirLevel, "json", dirJson)
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryGetLogLevel(s string) (log.Lvl, error) {
|
||||||
|
lvl, err := log.LvlFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
l, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return log.Lvl(l), nil
|
||||||
|
}
|
||||||
|
return lvl, nil
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
module github.com/wmitsuda/otterscan
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
replace github.com/tendermint/tendermint => github.com/bnb-chain/tendermint v0.31.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/RoaringBitmap/roaring v1.2.1
|
||||||
|
github.com/VictoriaMetrics/metrics v1.22.2
|
||||||
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
|
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
|
||||||
|
github.com/holiman/uint256 v1.2.1
|
||||||
|
github.com/ledgerwatch/erigon v1.9.7-0.20221024030055-592ad5792127
|
||||||
|
github.com/ledgerwatch/erigon-lib v0.0.0-20221024030034-c1965e9c5ae3
|
||||||
|
github.com/ledgerwatch/log/v3 v3.6.0
|
||||||
|
github.com/spf13/cobra v1.5.0
|
||||||
|
github.com/urfave/cli v1.22.9
|
||||||
|
golang.org/x/sync v0.1.0
|
||||||
|
golang.org/x/sys v0.1.0
|
||||||
|
google.golang.org/grpc v1.50.1
|
||||||
|
google.golang.org/protobuf v1.28.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508 // indirect
|
||||||
|
github.com/VictoriaMetrics/fastcache v1.12.0 // indirect
|
||||||
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
|
||||||
|
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
|
||||||
|
github.com/anacrolix/chansync v0.3.0 // indirect
|
||||||
|
github.com/anacrolix/dht/v2 v2.19.0 // indirect
|
||||||
|
github.com/anacrolix/envpprof v1.2.1 // indirect
|
||||||
|
github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60 // indirect
|
||||||
|
github.com/anacrolix/go-libutp v1.2.0 // indirect
|
||||||
|
github.com/anacrolix/log v0.13.2-0.20220711050817-613cb738ef30 // indirect
|
||||||
|
github.com/anacrolix/missinggo v1.3.0 // indirect
|
||||||
|
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
||||||
|
github.com/anacrolix/missinggo/v2 v2.7.0 // indirect
|
||||||
|
github.com/anacrolix/mmsg v1.0.0 // indirect
|
||||||
|
github.com/anacrolix/multiless v0.3.0 // indirect
|
||||||
|
github.com/anacrolix/stm v0.4.0 // indirect
|
||||||
|
github.com/anacrolix/sync v0.4.0 // indirect
|
||||||
|
github.com/anacrolix/torrent v1.47.0 // indirect
|
||||||
|
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 // indirect
|
||||||
|
github.com/anacrolix/utp v0.1.0 // indirect
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
|
github.com/benbjohnson/immutable v0.3.0 // indirect
|
||||||
|
github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b // indirect
|
||||||
|
github.com/bits-and-blooms/bitset v1.2.2 // indirect
|
||||||
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||||
|
github.com/btcsuite/btcd v0.22.0-beta // indirect
|
||||||
|
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
github.com/crate-crypto/go-ipa v0.0.0-20220916134416-c5abbdbdf644 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/deckarep/golang-set v1.8.0 // indirect
|
||||||
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
|
||||||
|
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
|
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||||
|
github.com/emicklei/dot v1.0.0 // indirect
|
||||||
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c // indirect
|
||||||
|
github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c // indirect
|
||||||
|
github.com/gballet/go-verkle v0.0.0-20220829125900-a702d458d33c // indirect
|
||||||
|
github.com/go-kit/kit v0.10.0 // indirect
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||||
|
github.com/go-stack/stack v1.8.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.9.7 // indirect
|
||||||
|
github.com/gofrs/flock v0.8.1 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.4.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/btree v1.1.2 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||||
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
|
github.com/huin/goupnp v1.0.3 // indirect
|
||||||
|
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20220405231054-a1ae3e4bba26 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/kevinburke/go-bindata v3.21.0+incompatible // indirect
|
||||||
|
github.com/ledgerwatch/erigon-snapshot v1.1.1-0.20221023043405-d157dec75e9a // indirect
|
||||||
|
github.com/ledgerwatch/secp256k1 v1.0.0 // indirect
|
||||||
|
github.com/lispad/go-generics-tools v1.1.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/mschoch/smat v0.2.0 // indirect
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||||
|
github.com/pion/datachannel v1.5.2 // indirect
|
||||||
|
github.com/pion/dtls/v2 v2.1.5 // indirect
|
||||||
|
github.com/pion/ice/v2 v2.2.6 // indirect
|
||||||
|
github.com/pion/interceptor v0.1.11 // indirect
|
||||||
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
|
github.com/pion/mdns v0.0.5 // indirect
|
||||||
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
|
github.com/pion/rtcp v1.2.9 // indirect
|
||||||
|
github.com/pion/rtp v1.7.13 // indirect
|
||||||
|
github.com/pion/sctp v1.8.2 // indirect
|
||||||
|
github.com/pion/sdp/v3 v3.0.5 // indirect
|
||||||
|
github.com/pion/srtp/v2 v2.0.9 // indirect
|
||||||
|
github.com/pion/stun v0.3.5 // indirect
|
||||||
|
github.com/pion/transport v0.13.1 // indirect
|
||||||
|
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||||
|
github.com/pion/udp v0.1.1 // indirect
|
||||||
|
github.com/pion/webrtc/v3 v3.1.42 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl v0.3.21 // indirect
|
||||||
|
github.com/rs/cors v1.8.2 // indirect
|
||||||
|
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/stretchr/testify v1.8.0 // indirect
|
||||||
|
github.com/tendermint/go-amino v0.14.1 // indirect
|
||||||
|
github.com/tendermint/tendermint v0.31.12 // indirect
|
||||||
|
github.com/tidwall/btree v1.3.1 // indirect
|
||||||
|
github.com/torquem-ch/mdbx-go v0.26.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.1.13 // indirect
|
||||||
|
github.com/ugorji/go/codec/codecgen v1.1.13 // indirect
|
||||||
|
github.com/valyala/fastjson v1.6.3 // indirect
|
||||||
|
github.com/valyala/fastrand v1.1.0 // indirect
|
||||||
|
github.com/valyala/histogram v1.2.0 // indirect
|
||||||
|
github.com/xsleonard/go-merkle v1.1.0 // indirect
|
||||||
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.8.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.8.0 // indirect
|
||||||
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
|
golang.org/x/crypto v0.1.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20221018221608-02f3b879a704 // indirect
|
||||||
|
golang.org/x/mod v0.6.0 // indirect
|
||||||
|
golang.org/x/net v0.1.0 // indirect
|
||||||
|
golang.org/x/text v0.4.0 // indirect
|
||||||
|
golang.org/x/time v0.1.0 // indirect
|
||||||
|
golang.org/x/tools v0.2.0 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect
|
||||||
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,889 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw=
|
||||||
|
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
|
||||||
|
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||||
|
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508 h1:fILCBBFnjnrQ0whVJlGhfv1E/QiaFDNtGFBObEVRnYg=
|
||||||
|
crawshaw.io/sqlite v0.3.3-0.20210127221821-98b1f83c5508/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||||
|
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
||||||
|
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
|
||||||
|
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||||
|
github.com/RoaringBitmap/roaring v1.2.1 h1:58/LJlg/81wfEHd5L9qsHduznOIhyv4qb1yWcSvVq9A=
|
||||||
|
github.com/RoaringBitmap/roaring v1.2.1/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||||
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
|
github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE=
|
||||||
|
github.com/VictoriaMetrics/fastcache v1.12.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8=
|
||||||
|
github.com/VictoriaMetrics/metrics v1.22.2 h1:A6LsNidYwkAHetxsvNFaUWjtzu5ltdgNEoS6i7Bn+6I=
|
||||||
|
github.com/VictoriaMetrics/metrics v1.22.2/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc=
|
||||||
|
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||||
|
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||||
|
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||||
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0=
|
||||||
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k=
|
||||||
|
github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk=
|
||||||
|
github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8=
|
||||||
|
github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||||
|
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||||
|
github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U=
|
||||||
|
github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
|
||||||
|
github.com/anacrolix/dht/v2 v2.19.0 h1:A9oMHWRGbLmCyx1JlYzg79bDrur8V60+0ts8ZwEVYt4=
|
||||||
|
github.com/anacrolix/dht/v2 v2.19.0/go.mod h1:0h83KnnAQ2AUYhpQ/CkoZP45K41pjDAlPR9zGHgFjQE=
|
||||||
|
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||||
|
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||||
|
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
||||||
|
github.com/anacrolix/envpprof v1.2.1 h1:25TJe6t/i0AfzzldiGFKCpD+s+dk8lONBcacJZB2rdE=
|
||||||
|
github.com/anacrolix/envpprof v1.2.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
||||||
|
github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60 h1:k4/h2B1gGF+PJGyGHxs8nmHHt1pzWXZWBj6jn4OBlRc=
|
||||||
|
github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
||||||
|
github.com/anacrolix/go-libutp v1.2.0 h1:sjxoB+/ARiKUR7IK/6wLWyADIBqGmu1fm0xo+8Yy7u0=
|
||||||
|
github.com/anacrolix/go-libutp v1.2.0/go.mod h1:RrJ3KcaDcf9Jqp33YL5V/5CBEc6xMc7aJL8wXfuWL50=
|
||||||
|
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||||
|
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||||
|
github.com/anacrolix/log v0.10.0/go.mod h1:s5yBP/j046fm9odtUTbHOfDUq/zh1W8OkPpJtnX0oQI=
|
||||||
|
github.com/anacrolix/log v0.10.1-0.20220123034749-3920702c17f8/go.mod h1:GmnE2c0nvz8pOIPUSC9Rawgefy1sDXqposC2wgtBZE4=
|
||||||
|
github.com/anacrolix/log v0.13.2-0.20220711050817-613cb738ef30 h1:bAgFzUxN1K3U8KwOzqCOhiygOr5NqYO3kNlV9tvp2Rc=
|
||||||
|
github.com/anacrolix/log v0.13.2-0.20220711050817-613cb738ef30/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
|
||||||
|
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
|
||||||
|
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
|
||||||
|
github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
|
||||||
|
github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
|
||||||
|
github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
|
||||||
|
github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y=
|
||||||
|
github.com/anacrolix/missinggo v1.3.0 h1:06HlMsudotL7BAELRZs0yDZ4yVXsHXGi323QBjAVASw=
|
||||||
|
github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H5fwlq/TeqMc=
|
||||||
|
github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy6BqESAJVw=
|
||||||
|
github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ=
|
||||||
|
github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
|
||||||
|
github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
|
||||||
|
github.com/anacrolix/missinggo/v2 v2.5.2/go.mod h1:yNvsLrtZYRYCOI+KRH/JM8TodHjtIE/bjOGhQaLOWIE=
|
||||||
|
github.com/anacrolix/missinggo/v2 v2.7.0 h1:4fzOAAn/VCvfWGviLmh64MPMttrlYew81JdPO7nSHvI=
|
||||||
|
github.com/anacrolix/missinggo/v2 v2.7.0/go.mod h1:2IZIvmRTizALNYFYXsPR7ofXPzJgyBpKZ4kMqMEICkI=
|
||||||
|
github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw=
|
||||||
|
github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg=
|
||||||
|
github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
|
||||||
|
github.com/anacrolix/multiless v0.3.0 h1:5Bu0DZncjE4e06b9r1Ap2tUY4Au0NToBP5RpuEngSis=
|
||||||
|
github.com/anacrolix/multiless v0.3.0/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4=
|
||||||
|
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
|
||||||
|
github.com/anacrolix/stm v0.4.0 h1:tOGvuFwaBjeu1u9X1eIh9TX8OEedEiEQ1se1FjhFnXY=
|
||||||
|
github.com/anacrolix/stm v0.4.0/go.mod h1:GCkwqWoAsP7RfLW+jw+Z0ovrt2OO7wRzcTtFYMYY5t8=
|
||||||
|
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
|
||||||
|
github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
|
||||||
|
github.com/anacrolix/sync v0.4.0 h1:T+MdO/u87ir/ijWsTFsPYw5jVm0SMm4kVpg8t4KF38o=
|
||||||
|
github.com/anacrolix/sync v0.4.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
|
||||||
|
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
|
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
|
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
||||||
|
github.com/anacrolix/torrent v1.47.0 h1:aDUnhQZ8+kfStLICHiXOGGYVFgDENK+kz4q96linyRg=
|
||||||
|
github.com/anacrolix/torrent v1.47.0/go.mod h1:SYPxEUjMwqhDr3kWGzyQLkFMuAb1bgJ57JRMpuD3ZzE=
|
||||||
|
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA=
|
||||||
|
github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96/go.mod h1:Wa6n8cYIdaG35x15aH3Zy6d03f7P728QfdcDeD/IEOs=
|
||||||
|
github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4=
|
||||||
|
github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk=
|
||||||
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||||
|
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||||
|
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||||
|
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
|
||||||
|
github.com/benbjohnson/immutable v0.3.0 h1:TVRhuZx2wG9SZ0LRdqlbs9S5BZ6Y24hJEHTCgWHZEIw=
|
||||||
|
github.com/benbjohnson/immutable v0.3.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
|
||||||
|
github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b h1:5JgaFtHFRnOPReItxvhMDXbvuBkjSWE+9glJyF466yw=
|
||||||
|
github.com/benesch/cgosymbolizer v0.0.0-20190515212042-bec6fe6e597b/go.mod h1:eMD2XUcPsHYbakFEocKrWZp47G0MRJYoC60qFblGjpA=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||||
|
github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk=
|
||||||
|
github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||||
|
github.com/bnb-chain/tendermint v0.31.12 h1:g+blWaXkRw6iHa56lcRfRzPXHgURCWPmgIvaGBSV7Zc=
|
||||||
|
github.com/bnb-chain/tendermint v0.31.12/go.mod h1:j6XU7CArrhQ+9XBMRwdIz63iUxdVwSrZ8f7vP7gcCqg=
|
||||||
|
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
|
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
||||||
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
|
||||||
|
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||||
|
github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo=
|
||||||
|
github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
|
||||||
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||||
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
|
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ=
|
||||||
|
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
|
||||||
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||||
|
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||||
|
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
|
||||||
|
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||||
|
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||||
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||||
|
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||||
|
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b h1:6+ZFm0flnudZzdSE0JxlhR2hKnGPcNB35BjQf4RYQDY=
|
||||||
|
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||||
|
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||||
|
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||||
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/crate-crypto/go-ipa v0.0.0-20220916134416-c5abbdbdf644 h1:1BOsVjUetPH2Lqv71Dh6uKLVj9WKdDr5KY57KZBbsWU=
|
||||||
|
github.com/crate-crypto/go-ipa v0.0.0-20220916134416-c5abbdbdf644/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
|
||||||
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
|
||||||
|
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
|
||||||
|
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
|
||||||
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
|
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ=
|
||||||
|
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||||
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
|
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
||||||
|
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||||
|
github.com/emicklei/dot v1.0.0 h1:yyObALINBOuI1GdCRwVea2IPtGtVgh0NQgJDrE03Tqc=
|
||||||
|
github.com/emicklei/dot v1.0.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
|
||||||
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c h1:CndMRAH4JIwxbW8KYq6Q+cGWcGHz0FjGR3QqcInWcW0=
|
||||||
|
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
|
||||||
|
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||||
|
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||||
|
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||||
|
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||||
|
github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c h1:uYNKzPntb8c6DKvP9EfrBjkLkU7pM4lM+uuHSIa8UtU=
|
||||||
|
github.com/garslo/gogen v0.0.0-20170307003452-d6ebae628c7c/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8=
|
||||||
|
github.com/gballet/go-verkle v0.0.0-20220829125900-a702d458d33c h1:zN3+0FLRcsoX/lVcgGC0EBtNcBvCXJWgMDmTbEz8pnw=
|
||||||
|
github.com/gballet/go-verkle v0.0.0-20220829125900-a702d458d33c/go.mod h1:o/XfIXWi4/GqbQirfRm5uTbXMG5NpqxkxblnbZ+QM9I=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
|
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
|
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
|
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||||
|
github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||||
|
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
|
||||||
|
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||||
|
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||||
|
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
|
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
||||||
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||||
|
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||||
|
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
|
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||||
|
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o=
|
||||||
|
github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||||
|
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||||
|
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||||
|
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||||
|
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
|
||||||
|
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
|
||||||
|
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
|
||||||
|
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20220405231054-a1ae3e4bba26 h1:UT3hQ6+5hwqUT83cKhKlY5I0W/kqsl6lpn3iFb3Gtqs=
|
||||||
|
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20220405231054-a1ae3e4bba26/go.mod h1:DvXTE/K/RtHehxU8/GtDs4vFtfw64jJ3PaCnFri8CRg=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||||
|
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||||
|
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||||
|
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/kevinburke/go-bindata v3.21.0+incompatible h1:baK7hwFJDlAHrOqmE9U3u8tow1Uc5ihN9E/b7djcK2g=
|
||||||
|
github.com/kevinburke/go-bindata v3.21.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM=
|
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758 h1:0D5M2HQSGD3PYPwICLl+/9oulQauOuETfgFvhBDffs0=
|
||||||
|
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
|
||||||
|
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
|
||||||
|
github.com/ledgerwatch/erigon v1.9.7-0.20221024030055-592ad5792127 h1:6CpFRXkf6p3p1hUGNhjy3KaghtLxv/Q9aPFv0G1rXLU=
|
||||||
|
github.com/ledgerwatch/erigon v1.9.7-0.20221024030055-592ad5792127/go.mod h1:2uW5KjsMycyNF0KSiJKxKfdKFwlf91Hk0YDmRODFe3I=
|
||||||
|
github.com/ledgerwatch/erigon-lib v0.0.0-20221024030034-c1965e9c5ae3 h1:8Kf5Yi9F1Dcmr3KrqvIM02SrPEPMzD+f7aIFaaPrxGw=
|
||||||
|
github.com/ledgerwatch/erigon-lib v0.0.0-20221024030034-c1965e9c5ae3/go.mod h1:jv4jmffliy8Us1wXdT6Q1f/nrrmC6Xrr6V70JxZRp48=
|
||||||
|
github.com/ledgerwatch/erigon-snapshot v1.1.1-0.20221023043405-d157dec75e9a h1:5Lw7NR/KWxtAokY11DKaMnrI6Zb04OzX8BQMbF8Lu/w=
|
||||||
|
github.com/ledgerwatch/erigon-snapshot v1.1.1-0.20221023043405-d157dec75e9a/go.mod h1:3AuPxZc85jkehh/HA9h8gabv5MSi3kb/ddtzBsTVJFo=
|
||||||
|
github.com/ledgerwatch/log/v3 v3.6.0 h1:JBUSK1epPyutUrz7KYDTcJtQLEHnehECRpKbM1ugy5M=
|
||||||
|
github.com/ledgerwatch/log/v3 v3.6.0/go.mod h1:L+Sp+ma/h205EdCjviZECjGEvYUYEyXSdiuHNZzg+xQ=
|
||||||
|
github.com/ledgerwatch/secp256k1 v1.0.0 h1:Usvz87YoTG0uePIV8woOof5cQnLXGYa162rFf3YnwaQ=
|
||||||
|
github.com/ledgerwatch/secp256k1 v1.0.0/go.mod h1:SPmqJFciiF/Q0mPt2jVs2dTr/1TZBTIA+kPMmKgBAak=
|
||||||
|
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||||
|
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||||
|
github.com/lispad/go-generics-tools v1.1.0 h1:mbSgcxdFVmpoyso1X/MJHXbSbSL3dD+qhRryyxk+/XY=
|
||||||
|
github.com/lispad/go-generics-tools v1.1.0/go.mod h1:2csd1EJljo/gy5qG4khXol7ivCPptNjG5Uv2X8MgK84=
|
||||||
|
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||||
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||||
|
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||||
|
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||||
|
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||||
|
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||||
|
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||||
|
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
|
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
github.com/nxadm/tail v1.4.9-0.20211216163028-4472660a31a6 h1:iZ5rEHU561k2tdi/atkIsrP5/3AX3BjyhYtC96nJ260=
|
||||||
|
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||||
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
|
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
|
||||||
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
|
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||||
|
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||||
|
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
|
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||||
|
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||||
|
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||||
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||||
|
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||||
|
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||||
|
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
|
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
|
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||||
|
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||||
|
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||||
|
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
||||||
|
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||||
|
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
|
||||||
|
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
|
||||||
|
github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs=
|
||||||
|
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
||||||
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
|
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||||
|
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
||||||
|
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
|
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
|
||||||
|
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
||||||
|
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||||
|
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||||
|
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||||
|
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
|
||||||
|
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||||
|
github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU=
|
||||||
|
github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||||
|
github.com/pion/srtp/v2 v2.0.9 h1:JJq3jClmDFBPX/F5roEb0U19jSU7eUhyDqR/NZ34EKQ=
|
||||||
|
github.com/pion/srtp/v2 v2.0.9/go.mod h1:5TtM9yw6lsH0ppNCehB/EjEUli7VkUgKSPJqWVqbhQ4=
|
||||||
|
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||||
|
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||||
|
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||||
|
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||||
|
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||||
|
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||||
|
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||||
|
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||||
|
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||||
|
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||||
|
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||||
|
github.com/pion/webrtc/v3 v3.1.42 h1:wJEQFIXVanptnQcHOLTuIo4AtGB2+mG2x4OhIhnITOA=
|
||||||
|
github.com/pion/webrtc/v3 v3.1.42/go.mod h1:ffD9DulDrPxyWvDPUIPAOSAWx9GUlOExiJPf7cCcMLA=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||||
|
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||||
|
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
|
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl v0.3.21 h1:vNkC6fC6qMLzCOGbnIHOd5ixUGgTbp3Z4fGnUgULlDA=
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl v0.3.21/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||||
|
github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
|
||||||
|
github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
|
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs=
|
||||||
|
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||||
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||||
|
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||||
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
|
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||||
|
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||||
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
|
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
|
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/tendermint/go-amino v0.14.1 h1:o2WudxNfdLNBwMyl2dqOJxiro5rfrEaU0Ugs6offJMk=
|
||||||
|
github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso=
|
||||||
|
github.com/tidwall/btree v1.3.1 h1:636+tdVDs8Hjcf35Di260W2xCW4KuoXOKyk9QWOvCpA=
|
||||||
|
github.com/tidwall/btree v1.3.1/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
|
||||||
|
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
|
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
|
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
github.com/torquem-ch/mdbx-go v0.26.1 h1:28Rh8UHlhpgJ+mFlTcvNQF66maqTnOcv8sSLD2ydXbE=
|
||||||
|
github.com/torquem-ch/mdbx-go v0.26.1/go.mod h1:T2fsoJDVppxfAPTLd1svUgH1kpPmeXdPESmroSHcL1E=
|
||||||
|
github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc=
|
||||||
|
github.com/ugorji/go/codec v1.1.13 h1:013LbFhocBoIqgHeIHKlV4JWYhqogATYWZhIcH0WHn4=
|
||||||
|
github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU=
|
||||||
|
github.com/ugorji/go/codec/codecgen v1.1.13 h1:rGpZ4Q63VcWA3DMBbIHvg+SQweUkfXBBa/f9X0W+tFg=
|
||||||
|
github.com/ugorji/go/codec/codecgen v1.1.13/go.mod h1:EhCxlc7Crov+HLygD4+hBCitXNrrGKRrRWj+pRsyJGg=
|
||||||
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
|
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
|
||||||
|
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
|
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
|
||||||
|
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
|
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||||
|
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||||
|
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||||
|
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
|
||||||
|
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
github.com/xsleonard/go-merkle v1.1.0 h1:fHe1fuhJjGH22ZzVTAH0jqHLhTGhOq3wQjJN+8P0jQg=
|
||||||
|
github.com/xsleonard/go-merkle v1.1.0/go.mod h1:cW4z+UZ/4f2n9IJgIiyDCdYguchoDyDAPmpuOWGxdGg=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
|
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||||
|
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||||
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
|
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg=
|
||||||
|
go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM=
|
||||||
|
go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY=
|
||||||
|
go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4=
|
||||||
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||||
|
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20221018221608-02f3b879a704 h1:qeTd8Mtg7Z9G839eB0/DhF2vU3ZeXcP6vwAY/IqVRPM=
|
||||||
|
golang.org/x/exp v0.0.0-20221018221608-02f3b879a704/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||||
|
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||||
|
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||||
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
|
||||||
|
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||||
|
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 h1:b9mVrqYfq3P4bCdaLg1qtBnPzUYgglsIdjZkL/fQVOE=
|
||||||
|
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
|
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||||
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
|
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
|
||||||
|
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||||
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 h1:TLkBREm4nIsEcexnCjgQd5GQWaHcqMzwQV0TX9pq8S0=
|
||||||
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g=
|
||||||
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
|
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
|
@ -46,7 +46,10 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
|
"postbuild": "cp -r chains dist && cp -r topic0 dist",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
"dl-chains": "svn checkout https://github.com/ethereum-lists/chains/trunk/_data/chains && rm -rf chains/.svn",
|
||||||
|
"dl-topic0": "git clone https://github.com/wmitsuda/topic0 && rm -rf topic0/.git",
|
||||||
"source-map-explorer": "source-map-explorer build/static/js/*.js",
|
"source-map-explorer": "source-map-explorer build/static/js/*.js",
|
||||||
"assets-start": "docker run --rm -p 3001:80 --name otterscan-assets -d -v$(pwd)/4bytes/signatures:/usr/share/nginx/html/signatures/ -v$(pwd)/trustwallet/blockchains/ethereum/assets:/usr/share/nginx/html/assets/1 -v$(pwd)/topic0/with_parameter_names:/usr/share/nginx/html/topic0/ -v$(pwd)/chains/_data/chains:/usr/share/nginx/html/chains/ -v$(pwd)/nginx/nginx.conf:/etc/nginx/nginx.conf -v$(pwd)/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf nginx:1.21.1-alpine",
|
"assets-start": "docker run --rm -p 3001:80 --name otterscan-assets -d -v$(pwd)/4bytes/signatures:/usr/share/nginx/html/signatures/ -v$(pwd)/trustwallet/blockchains/ethereum/assets:/usr/share/nginx/html/assets/1 -v$(pwd)/topic0/with_parameter_names:/usr/share/nginx/html/topic0/ -v$(pwd)/chains/_data/chains:/usr/share/nginx/html/chains/ -v$(pwd)/nginx/nginx.conf:/etc/nginx/nginx.conf -v$(pwd)/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf nginx:1.21.1-alpine",
|
||||||
"assets-start-with-param-names": "docker run --rm -p 3001:80 --name otterscan-assets -d -v$(pwd)/4bytes/with_parameter_names:/usr/share/nginx/html/signatures/ -v$(pwd)/trustwallet/blockchains/ethereum/assets:/usr/share/nginx/html/assets/1 -v$(pwd)/topic0/with_parameter_names:/usr/share/nginx/html/topic0/ -v$(pwd)/chains/_data/chains:/usr/share/nginx/html/chains/ -v$(pwd)/nginx/nginx.conf:/etc/nginx/nginx.conf -v$(pwd)/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf nginx:1.21.1-alpine",
|
"assets-start-with-param-names": "docker run --rm -p 3001:80 --name otterscan-assets -d -v$(pwd)/4bytes/with_parameter_names:/usr/share/nginx/html/signatures/ -v$(pwd)/trustwallet/blockchains/ethereum/assets:/usr/share/nginx/html/assets/1 -v$(pwd)/topic0/with_parameter_names:/usr/share/nginx/html/topic0/ -v$(pwd)/chains/_data/chains:/usr/share/nginx/html/chains/ -v$(pwd)/nginx/nginx.conf:/etc/nginx/nginx.conf -v$(pwd)/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf nginx:1.21.1-alpine",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
import { HashRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
import WarningHeader from "./WarningHeader";
|
import WarningHeader from "./WarningHeader";
|
||||||
import Home from "./Home";
|
import Home from "./Home";
|
||||||
import Main from "./Main";
|
import Main from "./Main";
|
||||||
|
|
Loading…
Reference in New Issue