forked from cerc-io/plugeth
automated merge
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
## Checkpoint-admin
|
||||
|
||||
Checkpoint-admin is a tool for updating checkpoint oracle status. It provides a series of functions including deploying checkpoint oracle contract, signing for new checkpoints, and updating checkpoints in the checkpoint oracle contract.
|
||||
|
||||
### Checkpoint
|
||||
|
||||
In the LES protocol, there is an important concept called checkpoint. In simple terms, whenever a certain number of blocks are generated on the blockchain, a new checkpoint is generated which contains some important information such as
|
||||
|
||||
* Block hash at checkpoint
|
||||
* Canonical hash trie root at checkpoint
|
||||
* Bloom trie root at checkpoint
|
||||
|
||||
*For a more detailed introduction to checkpoint, please see the LES [spec](https://github.com/ethereum/devp2p/blob/master/caps/les.md).*
|
||||
|
||||
Using this information, light clients can skip all historical block headers when synchronizing data and start synchronization from this checkpoint. Therefore, as long as the light client can obtain some latest and correct checkpoints, the amount of data and time for synchronization will be greatly reduced.
|
||||
|
||||
However, from a security perspective, the most critical step in a synchronization algorithm based on checkpoints is to determine whether the checkpoint used by the light client is correct. Otherwise, all blockchain data synchronized based on this checkpoint may be wrong. For this we provide two different ways to ensure the correctness of the checkpoint used by the light client.
|
||||
|
||||
#### Hardcoded checkpoint
|
||||
|
||||
There are several hardcoded checkpoints in the [source code](https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L38) of the go-ethereum project. These checkpoints are updated by go-ethereum developers when new versions of software are released. Because light client users trust Geth developers to some extent, hardcoded checkpoints in the code can also be considered correct.
|
||||
|
||||
#### Checkpoint oracle
|
||||
|
||||
Hardcoded checkpoints can solve the problem of verifying the correctness of checkpoints (although this is a more centralized solution). But the pain point of this solution is that developers can only update checkpoints when a new version of software is released. In addition, light client users usually do not keep the Geth version they use always up to date. So hardcoded checkpoints used by users are generally stale. Therefore, it still needs to download a large amount of blockchain data during synchronization.
|
||||
|
||||
Checkpoint oracle is a more flexible solution. In simple terms, this is a smart contract that is deployed on the blockchain. The smart contract records several designated trusted signers. Whenever enough trusted signers have issued their signatures for the same checkpoint, it can be considered that the checkpoint has been authenticated by the signers. Checkpoints authenticated by trusted signers can be considered correct.
|
||||
|
||||
So this way, even without updating the software version, as long as the trusted signers regularly update the checkpoint in oracle on time, the light client can always use the latest and verified checkpoint for data synchronization.
|
||||
|
||||
### Usage
|
||||
|
||||
Checkpoint-admin is a command line tool designed for checkpoint oracle. Users can easily deploy contracts and update checkpoints through this tool.
|
||||
|
||||
#### Install
|
||||
|
||||
```shell
|
||||
go get github.com/ethereum/go-ethereum/cmd/checkpoint-admin
|
||||
```
|
||||
|
||||
#### Deploy
|
||||
|
||||
Deploy checkpoint oracle contract. `--signers` indicates the specified trusted signer, and `--threshold` indicates the minimum number of signatures required by trusted signers to update a checkpoint.
|
||||
|
||||
```shell
|
||||
checkpoint-admin deploy --rpc <NODE_RPC_ENDPOINT> --clef <CLEF_ENDPOINT> --signer <SIGNER_TO_SIGN_TX> --signers <TRUSTED_SIGNER_LIST> --threshold 1
|
||||
```
|
||||
|
||||
It is worth noting that checkpoint-admin only supports clef as a signer for transactions and plain text(checkpoint). For more clef usage, please see the clef [tutorial](https://geth.ethereum.org/docs/tools/clef/tutorial) .
|
||||
|
||||
#### Sign
|
||||
|
||||
Checkpoint-admin provides two different modes of signing. You can automatically obtain the current stable checkpoint and sign it interactively, and you can also use the information provided by the command line flags to sign checkpoint offline.
|
||||
|
||||
**Interactive mode**
|
||||
|
||||
```shell
|
||||
checkpoint-admin sign --clef <CLEF_ENDPOINT> --signer <SIGNER_TO_SIGN_CHECKPOINT> --rpc <NODE_RPC_ENDPOINT>
|
||||
```
|
||||
|
||||
*It is worth noting that the connected Geth node can be a fullnode or a light client. If it is fullnode, you must enable the LES protocol. E.G. add `--light.serv 50` to the startup command line flags*.
|
||||
|
||||
**Offline mode**
|
||||
|
||||
```shell
|
||||
checkpoint-admin sign --clef <CLEF_ENDPOINT> --signer <SIGNER_TO_SIGN_CHECKPOINT> --index <CHECKPOINT_INDEX> --hash <CHECKPOINT_HASH> --oracle <CHECKPOINT_ORACLE_ADDRESS>
|
||||
```
|
||||
|
||||
*CHECKPOINT_HASH is obtained based on this [calculation method](https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L251).*
|
||||
|
||||
#### Publish
|
||||
|
||||
Collect enough signatures from different trusted signers for the same checkpoint and submit them to oracle to update the "authenticated" checkpoint in the contract.
|
||||
|
||||
```shell
|
||||
checkpoint-admin publish --clef <CLEF_ENDPOINT> --rpc <NODE_RPC_ENDPOINT> --signer <SIGNER_TO_SIGN_TX> --index <CHECKPOINT_INDEX> --signatures <CHECKPOINT_SIGNATURE_LIST>
|
||||
```
|
||||
|
||||
#### Status query
|
||||
|
||||
Check the latest status of checkpoint oracle.
|
||||
|
||||
```shell
|
||||
checkpoint-admin status --rpc <NODE_RPC_ENDPOINT>
|
||||
```
|
||||
|
||||
### Enable checkpoint oracle in your private network
|
||||
|
||||
Currently, only the Ethereum mainnet and the default supported test networks (rinkeby, goerli) activate this feature. If you want to activate this feature in your private network, you can overwrite the relevant checkpoint oracle settings through the configuration file after deploying the oracle contract.
|
||||
|
||||
* Get your node configuration file `geth dumpconfig OTHER_COMMAND_LINE_OPTIONS > config.toml`
|
||||
* Edit the configuration file and add the following information
|
||||
|
||||
```toml
|
||||
[Eth.CheckpointOracle]
|
||||
Address = CHECKPOINT_ORACLE_ADDRESS
|
||||
Signers = [TRUSTED_SIGNER_1, ..., TRUSTED_SIGNER_N]
|
||||
Threshold = THRESHOLD
|
||||
```
|
||||
|
||||
* Start geth with the modified configuration file
|
||||
|
||||
*In the private network, all fullnodes and light clients need to be started using the same checkpoint oracle settings.*
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/accounts/external"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// newClient creates a client with specified remote URL.
|
||||
func newClient(ctx *cli.Context) *ethclient.Client {
|
||||
client, err := ethclient.Dial(ctx.String(nodeURLFlag.Name))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to connect to Ethereum node: %v", err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// newRPCClient creates a rpc client with specified node URL.
|
||||
func newRPCClient(url string) *rpc.Client {
|
||||
client, err := rpc.Dial(url)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to connect to Ethereum node: %v", err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// getContractAddr retrieves the register contract address through
|
||||
// rpc request.
|
||||
func getContractAddr(client *rpc.Client) common.Address {
|
||||
var addr string
|
||||
if err := client.Call(&addr, "les_getCheckpointContractAddress"); err != nil {
|
||||
utils.Fatalf("Failed to fetch checkpoint oracle address: %v", err)
|
||||
}
|
||||
return common.HexToAddress(addr)
|
||||
}
|
||||
|
||||
// getCheckpoint retrieves the specified checkpoint or the latest one
|
||||
// through rpc request.
|
||||
func getCheckpoint(ctx *cli.Context, client *rpc.Client) *params.TrustedCheckpoint {
|
||||
var checkpoint *params.TrustedCheckpoint
|
||||
|
||||
if ctx.IsSet(indexFlag.Name) {
|
||||
var result [3]string
|
||||
index := uint64(ctx.Int64(indexFlag.Name))
|
||||
if err := client.Call(&result, "les_getCheckpoint", index); err != nil {
|
||||
utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err)
|
||||
}
|
||||
checkpoint = ¶ms.TrustedCheckpoint{
|
||||
SectionIndex: index,
|
||||
SectionHead: common.HexToHash(result[0]),
|
||||
CHTRoot: common.HexToHash(result[1]),
|
||||
BloomRoot: common.HexToHash(result[2]),
|
||||
}
|
||||
} else {
|
||||
var result [4]string
|
||||
err := client.Call(&result, "les_latestCheckpoint")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err)
|
||||
}
|
||||
index, err := strconv.ParseUint(result[0], 0, 64)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to parse checkpoint index %v", err)
|
||||
}
|
||||
checkpoint = ¶ms.TrustedCheckpoint{
|
||||
SectionIndex: index,
|
||||
SectionHead: common.HexToHash(result[1]),
|
||||
CHTRoot: common.HexToHash(result[2]),
|
||||
BloomRoot: common.HexToHash(result[3]),
|
||||
}
|
||||
}
|
||||
return checkpoint
|
||||
}
|
||||
|
||||
// newContract creates a registrar contract instance with specified
|
||||
// contract address or the default contracts for mainnet or testnet.
|
||||
func newContract(client *rpc.Client) (common.Address, *checkpointoracle.CheckpointOracle) {
|
||||
addr := getContractAddr(client)
|
||||
if addr == (common.Address{}) {
|
||||
utils.Fatalf("No specified registrar contract address")
|
||||
}
|
||||
contract, err := checkpointoracle.NewCheckpointOracle(addr, ethclient.NewClient(client))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to setup registrar contract %s: %v", addr, err)
|
||||
}
|
||||
return addr, contract
|
||||
}
|
||||
|
||||
// newClefSigner sets up a clef backend and returns a clef transaction signer.
|
||||
func newClefSigner(ctx *cli.Context) *bind.TransactOpts {
|
||||
clef, err := external.NewExternalSigner(ctx.String(clefURLFlag.Name))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to create clef signer %v", err)
|
||||
}
|
||||
return bind.NewClefTransactor(clef, accounts.Account{Address: common.HexToAddress(ctx.String(signerFlag.Name))})
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
|
||||
"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var commandDeploy = &cli.Command{
|
||||
Name: "deploy",
|
||||
Usage: "Deploy a new checkpoint oracle contract",
|
||||
Flags: []cli.Flag{
|
||||
nodeURLFlag,
|
||||
clefURLFlag,
|
||||
signerFlag,
|
||||
signersFlag,
|
||||
thresholdFlag,
|
||||
},
|
||||
Action: deploy,
|
||||
}
|
||||
|
||||
var commandSign = &cli.Command{
|
||||
Name: "sign",
|
||||
Usage: "Sign the checkpoint with the specified key",
|
||||
Flags: []cli.Flag{
|
||||
nodeURLFlag,
|
||||
clefURLFlag,
|
||||
signerFlag,
|
||||
indexFlag,
|
||||
hashFlag,
|
||||
oracleFlag,
|
||||
},
|
||||
Action: sign,
|
||||
}
|
||||
|
||||
var commandPublish = &cli.Command{
|
||||
Name: "publish",
|
||||
Usage: "Publish a checkpoint into the oracle",
|
||||
Flags: []cli.Flag{
|
||||
nodeURLFlag,
|
||||
clefURLFlag,
|
||||
signerFlag,
|
||||
indexFlag,
|
||||
signaturesFlag,
|
||||
},
|
||||
Action: publish,
|
||||
}
|
||||
|
||||
// deploy deploys the checkpoint registrar contract.
|
||||
//
|
||||
// Note the network where the contract is deployed depends on
|
||||
// the network where the connected node is located.
|
||||
func deploy(ctx *cli.Context) error {
|
||||
// Gather all the addresses that should be permitted to sign
|
||||
var addrs []common.Address
|
||||
for _, account := range strings.Split(ctx.String(signersFlag.Name), ",") {
|
||||
if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
|
||||
utils.Fatalf("Invalid account in --signers: '%s'", trimmed)
|
||||
}
|
||||
addrs = append(addrs, common.HexToAddress(account))
|
||||
}
|
||||
// Retrieve and validate the signing threshold
|
||||
needed := ctx.Int(thresholdFlag.Name)
|
||||
if needed == 0 || needed > len(addrs) {
|
||||
utils.Fatalf("Invalid signature threshold %d", needed)
|
||||
}
|
||||
// Print a summary to ensure the user understands what they're signing
|
||||
fmt.Printf("Deploying new checkpoint oracle:\n\n")
|
||||
for i, addr := range addrs {
|
||||
fmt.Printf("Admin %d => %s\n", i+1, addr.Hex())
|
||||
}
|
||||
fmt.Printf("\nSignatures needed to publish: %d\n", needed)
|
||||
|
||||
// setup clef signer, create an abigen transactor and an RPC client
|
||||
transactor, client := newClefSigner(ctx), newClient(ctx)
|
||||
|
||||
// Deploy the checkpoint oracle
|
||||
fmt.Println("Sending deploy request to Clef...")
|
||||
oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)),
|
||||
big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed)))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to deploy checkpoint oracle %v", err)
|
||||
}
|
||||
log.Info("Deployed checkpoint oracle", "address", oracle, "tx", tx.Hash().Hex())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sign creates the signature for specific checkpoint
|
||||
// with local key. Only contract admins have the permission to
|
||||
// sign checkpoint.
|
||||
func sign(ctx *cli.Context) error {
|
||||
var (
|
||||
offline bool // The indicator whether we sign checkpoint by offline.
|
||||
chash common.Hash
|
||||
cindex uint64
|
||||
address common.Address
|
||||
|
||||
node *rpc.Client
|
||||
oracle *checkpointoracle.CheckpointOracle
|
||||
)
|
||||
if !ctx.IsSet(nodeURLFlag.Name) {
|
||||
// Offline mode signing
|
||||
offline = true
|
||||
if !ctx.IsSet(hashFlag.Name) {
|
||||
utils.Fatalf("Please specify the checkpoint hash (--hash) to sign in offline mode")
|
||||
}
|
||||
chash = common.HexToHash(ctx.String(hashFlag.Name))
|
||||
|
||||
if !ctx.IsSet(indexFlag.Name) {
|
||||
utils.Fatalf("Please specify checkpoint index (--index) to sign in offline mode")
|
||||
}
|
||||
cindex = ctx.Uint64(indexFlag.Name)
|
||||
|
||||
if !ctx.IsSet(oracleFlag.Name) {
|
||||
utils.Fatalf("Please specify oracle address (--oracle) to sign in offline mode")
|
||||
}
|
||||
address = common.HexToAddress(ctx.String(oracleFlag.Name))
|
||||
} else {
|
||||
// Interactive mode signing, retrieve the data from the remote node
|
||||
node = newRPCClient(ctx.String(nodeURLFlag.Name))
|
||||
|
||||
checkpoint := getCheckpoint(ctx, node)
|
||||
chash, cindex, address = checkpoint.Hash(), checkpoint.SectionIndex, getContractAddr(node)
|
||||
|
||||
// Check the validity of checkpoint
|
||||
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancelFn()
|
||||
|
||||
head, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
num := head.Number.Uint64()
|
||||
if num < ((cindex+1)*params.CheckpointFrequency + params.CheckpointProcessConfirmations) {
|
||||
utils.Fatalf("Invalid future checkpoint")
|
||||
}
|
||||
_, oracle = newContract(node)
|
||||
latest, _, h, err := oracle.Contract().GetLatestCheckpoint(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cindex < latest {
|
||||
utils.Fatalf("Checkpoint is too old")
|
||||
}
|
||||
if cindex == latest && (latest != 0 || h.Uint64() != 0) {
|
||||
utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest, cindex)
|
||||
}
|
||||
}
|
||||
var (
|
||||
signature string
|
||||
signer string
|
||||
)
|
||||
// isAdmin checks whether the specified signer is admin.
|
||||
isAdmin := func(addr common.Address) error {
|
||||
signers, err := oracle.Contract().GetAllAdmin(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range signers {
|
||||
if s == addr {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("signer %v is not the admin", addr.Hex())
|
||||
}
|
||||
// Print to the user the data thy are about to sign
|
||||
fmt.Printf("Oracle => %s\n", address.Hex())
|
||||
fmt.Printf("Index %4d => %s\n", cindex, chash.Hex())
|
||||
|
||||
// Sign checkpoint in clef mode.
|
||||
signer = ctx.String(signerFlag.Name)
|
||||
|
||||
if !offline {
|
||||
if err := isAdmin(common.HexToAddress(signer)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
clef := newRPCClient(ctx.String(clefURLFlag.Name))
|
||||
p := make(map[string]string)
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, cindex)
|
||||
p["address"] = address.Hex()
|
||||
p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
|
||||
|
||||
fmt.Println("Sending signing request to Clef...")
|
||||
if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil {
|
||||
utils.Fatalf("Failed to sign checkpoint, err %v", err)
|
||||
}
|
||||
fmt.Printf("Signer => %s\n", signer)
|
||||
fmt.Printf("Signature => %s\n", signature)
|
||||
return nil
|
||||
}
|
||||
|
||||
// sighash calculates the hash of the data to sign for the checkpoint oracle.
|
||||
func sighash(index uint64, oracle common.Address, hash common.Hash) []byte {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, index)
|
||||
|
||||
data := append([]byte{0x19, 0x00}, append(oracle[:], append(buf, hash[:]...)...)...)
|
||||
return crypto.Keccak256(data)
|
||||
}
|
||||
|
||||
// ecrecover calculates the sender address from a sighash and signature combo.
|
||||
func ecrecover(sighash []byte, sig []byte) common.Address {
|
||||
sig[64] -= 27
|
||||
defer func() { sig[64] += 27 }()
|
||||
|
||||
signer, err := crypto.SigToPub(sighash, sig)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to recover sender from signature %x: %v", sig, err)
|
||||
}
|
||||
return crypto.PubkeyToAddress(*signer)
|
||||
}
|
||||
|
||||
// publish registers the specified checkpoint which generated by connected node
|
||||
// with a authorised private key.
|
||||
func publish(ctx *cli.Context) error {
|
||||
// Print the checkpoint oracle's current status to make sure we're interacting
|
||||
// with the correct network and contract.
|
||||
status(ctx)
|
||||
|
||||
// Gather the signatures from the CLI
|
||||
var sigs [][]byte
|
||||
for _, sig := range strings.Split(ctx.String(signaturesFlag.Name), ",") {
|
||||
trimmed := strings.TrimPrefix(strings.TrimSpace(sig), "0x")
|
||||
if len(trimmed) != 130 {
|
||||
utils.Fatalf("Invalid signature in --signature: '%s'", trimmed)
|
||||
} else {
|
||||
sigs = append(sigs, common.Hex2Bytes(trimmed))
|
||||
}
|
||||
}
|
||||
// Retrieve the checkpoint we want to sign to sort the signatures
|
||||
var (
|
||||
client = newRPCClient(ctx.String(nodeURLFlag.Name))
|
||||
addr, oracle = newContract(client)
|
||||
checkpoint = getCheckpoint(ctx, client)
|
||||
sighash = sighash(checkpoint.SectionIndex, addr, checkpoint.Hash())
|
||||
)
|
||||
for i := 0; i < len(sigs); i++ {
|
||||
for j := i + 1; j < len(sigs); j++ {
|
||||
signerA := ecrecover(sighash, sigs[i])
|
||||
signerB := ecrecover(sighash, sigs[j])
|
||||
if bytes.Compare(signerA.Bytes(), signerB.Bytes()) > 0 {
|
||||
sigs[i], sigs[j] = sigs[j], sigs[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Retrieve recent header info to protect replay attack
|
||||
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancelFn()
|
||||
|
||||
head, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
num := head.Number.Uint64()
|
||||
recent, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, big.NewInt(int64(num-128)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Print a summary of the operation that's going to be performed
|
||||
fmt.Printf("Publishing %d => %s:\n\n", checkpoint.SectionIndex, checkpoint.Hash().Hex())
|
||||
for i, sig := range sigs {
|
||||
fmt.Printf("Signer %d => %s\n", i+1, ecrecover(sighash, sig).Hex())
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Printf("Sentry number => %d\nSentry hash => %s\n", recent.Number, recent.Hash().Hex())
|
||||
|
||||
// Publish the checkpoint into the oracle
|
||||
fmt.Println("Sending publish request to Clef...")
|
||||
tx, err := oracle.RegisterCheckpoint(newClefSigner(ctx), checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
|
||||
if err != nil {
|
||||
utils.Fatalf("Register contract failed %v", err)
|
||||
}
|
||||
log.Info("Successfully registered checkpoint", "tx", tx.Hash().Hex())
|
||||
return nil
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// checkpoint-admin is a utility that can be used to query checkpoint information
|
||||
// and register stable checkpoints into an oracle contract.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var app = flags.NewApp("ethereum checkpoint helper tool")
|
||||
|
||||
func init() {
|
||||
app.Commands = []*cli.Command{
|
||||
commandStatus,
|
||||
commandDeploy,
|
||||
commandSign,
|
||||
commandPublish,
|
||||
}
|
||||
app.Flags = []cli.Flag{
|
||||
oracleFlag,
|
||||
nodeURLFlag,
|
||||
}
|
||||
}
|
||||
|
||||
// Commonly used command line flags.
|
||||
var (
|
||||
indexFlag = &cli.Int64Flag{
|
||||
Name: "index",
|
||||
Usage: "Checkpoint index (query latest from remote node if not specified)",
|
||||
}
|
||||
hashFlag = &cli.StringFlag{
|
||||
Name: "hash",
|
||||
Usage: "Checkpoint hash (query latest from remote node if not specified)",
|
||||
}
|
||||
oracleFlag = &cli.StringFlag{
|
||||
Name: "oracle",
|
||||
Usage: "Checkpoint oracle address (query from remote node if not specified)",
|
||||
}
|
||||
thresholdFlag = &cli.Int64Flag{
|
||||
Name: "threshold",
|
||||
Usage: "Minimal number of signatures required to approve a checkpoint",
|
||||
}
|
||||
nodeURLFlag = &cli.StringFlag{
|
||||
Name: "rpc",
|
||||
Value: "http://localhost:8545",
|
||||
Usage: "The rpc endpoint of a local or remote geth node",
|
||||
}
|
||||
clefURLFlag = &cli.StringFlag{
|
||||
Name: "clef",
|
||||
Value: "http://localhost:8550",
|
||||
Usage: "The rpc endpoint of clef",
|
||||
}
|
||||
signerFlag = &cli.StringFlag{
|
||||
Name: "signer",
|
||||
Usage: "Signer address for clef signing",
|
||||
}
|
||||
signersFlag = &cli.StringFlag{
|
||||
Name: "signers",
|
||||
Usage: "Comma separated accounts of trusted checkpoint signers",
|
||||
}
|
||||
signaturesFlag = &cli.StringFlag{
|
||||
Name: "signatures",
|
||||
Usage: "Comma separated checkpoint signatures to submit",
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||
fdlimit.Raise(2048)
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var commandStatus = &cli.Command{
|
||||
Name: "status",
|
||||
Usage: "Fetches the signers and checkpoint status of the oracle contract",
|
||||
Flags: []cli.Flag{
|
||||
nodeURLFlag,
|
||||
},
|
||||
Action: status,
|
||||
}
|
||||
|
||||
// status fetches the admin list of specified registrar contract.
|
||||
func status(ctx *cli.Context) error {
|
||||
// Create a wrapper around the checkpoint oracle contract
|
||||
addr, oracle := newContract(newRPCClient(ctx.String(nodeURLFlag.Name)))
|
||||
fmt.Printf("Oracle => %s\n", addr.Hex())
|
||||
fmt.Println()
|
||||
|
||||
// Retrieve the list of authorized signers (admins)
|
||||
admins, err := oracle.Contract().GetAllAdmin(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, admin := range admins {
|
||||
fmt.Printf("Admin %d => %s\n", i+1, admin.Hex())
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Retrieve the latest checkpoint
|
||||
index, checkpoint, height, err := oracle.Contract().GetLatestCheckpoint(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Checkpoint (published at #%d) %d => %s\n", height, index, common.Hash(checkpoint).Hex())
|
||||
|
||||
return nil
|
||||
}
|
||||
+4
-4
@@ -326,7 +326,7 @@ func initializeSecrets(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
if num != len(masterSeed) {
|
||||
return fmt.Errorf("failed to read enough random")
|
||||
return errors.New("failed to read enough random")
|
||||
}
|
||||
n, p := keystore.StandardScryptN, keystore.StandardScryptP
|
||||
if c.Bool(utils.LightKDFFlag.Name) {
|
||||
@@ -482,7 +482,7 @@ func initialize(c *cli.Context) error {
|
||||
}
|
||||
} else if !c.Bool(acceptFlag.Name) {
|
||||
if !confirm(legalWarning) {
|
||||
return fmt.Errorf("aborted by user")
|
||||
return errors.New("aborted by user")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
@@ -590,7 +590,7 @@ func accountImport(c *cli.Context) error {
|
||||
Address %v
|
||||
Keystore file: %v
|
||||
|
||||
The key is now encrypted; losing the password will result in permanently losing
|
||||
The key is now encrypted; losing the password will result in permanently losing
|
||||
access to the key and all associated funds!
|
||||
|
||||
Make sure to backup keystore and passwords in a safe location.`,
|
||||
@@ -844,7 +844,7 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) {
|
||||
}
|
||||
masterSeed, err := decryptSeed(cipherKey, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt the master seed of clef")
|
||||
return nil, errors.New("failed to decrypt the master seed of clef")
|
||||
}
|
||||
if len(masterSeed) < 256 {
|
||||
return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
@@ -177,7 +178,7 @@ func discv4Resolve(ctx *cli.Context) error {
|
||||
|
||||
func discv4ResolveJSON(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return fmt.Errorf("need nodes file as argument")
|
||||
return errors.New("need nodes file as argument")
|
||||
}
|
||||
nodesFile := ctx.Args().Get(0)
|
||||
inputSet := make(nodeSet)
|
||||
@@ -207,7 +208,7 @@ func discv4ResolveJSON(ctx *cli.Context) error {
|
||||
|
||||
func discv4Crawl(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return fmt.Errorf("need nodes file as argument")
|
||||
return errors.New("need nodes file as argument")
|
||||
}
|
||||
nodesFile := ctx.Args().First()
|
||||
var inputSet nodeSet
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -98,7 +99,7 @@ func discv5Resolve(ctx *cli.Context) error {
|
||||
|
||||
func discv5Crawl(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return fmt.Errorf("need nodes file as argument")
|
||||
return errors.New("need nodes file as argument")
|
||||
}
|
||||
nodesFile := ctx.Args().First()
|
||||
var inputSet nodeSet
|
||||
|
||||
@@ -18,6 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -48,7 +49,7 @@ type cloudflareClient struct {
|
||||
func newCloudflareClient(ctx *cli.Context) *cloudflareClient {
|
||||
token := ctx.String(cloudflareTokenFlag.Name)
|
||||
if token == "" {
|
||||
exit(fmt.Errorf("need cloudflare API token to proceed"))
|
||||
exit(errors.New("need cloudflare API token to proceed"))
|
||||
}
|
||||
api, err := cloudflare.NewWithAPIToken(token)
|
||||
if err != nil {
|
||||
|
||||
@@ -81,7 +81,7 @@ func newRoute53Client(ctx *cli.Context) *route53Client {
|
||||
akey := ctx.String(route53AccessKeyFlag.Name)
|
||||
asec := ctx.String(route53AccessSecretFlag.Name)
|
||||
if akey == "" || asec == "" {
|
||||
exit(fmt.Errorf("need Route53 Access Key ID and secret to proceed"))
|
||||
exit(errors.New("need Route53 Access Key ID and secret to proceed"))
|
||||
}
|
||||
creds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(akey, asec, ""))
|
||||
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithCredentialsProvider(creds))
|
||||
|
||||
@@ -19,6 +19,7 @@ package main
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -147,7 +148,7 @@ func dnsSync(ctx *cli.Context) error {
|
||||
|
||||
func dnsSign(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 2 {
|
||||
return fmt.Errorf("need tree definition directory and key file as arguments")
|
||||
return errors.New("need tree definition directory and key file as arguments")
|
||||
}
|
||||
var (
|
||||
defdir = ctx.Args().Get(0)
|
||||
@@ -201,7 +202,7 @@ func directoryName(dir string) string {
|
||||
// dnsToTXT performs dnsTXTCommand.
|
||||
func dnsToTXT(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return fmt.Errorf("need tree definition directory as argument")
|
||||
return errors.New("need tree definition directory as argument")
|
||||
}
|
||||
output := ctx.Args().Get(1)
|
||||
if output == "" {
|
||||
@@ -218,7 +219,7 @@ func dnsToTXT(ctx *cli.Context) error {
|
||||
// dnsToCloudflare performs dnsCloudflareCommand.
|
||||
func dnsToCloudflare(ctx *cli.Context) error {
|
||||
if ctx.NArg() != 1 {
|
||||
return fmt.Errorf("need tree definition directory as argument")
|
||||
return errors.New("need tree definition directory as argument")
|
||||
}
|
||||
domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0))
|
||||
if err != nil {
|
||||
@@ -231,7 +232,7 @@ func dnsToCloudflare(ctx *cli.Context) error {
|
||||
// dnsToRoute53 performs dnsRoute53Command.
|
||||
func dnsToRoute53(ctx *cli.Context) error {
|
||||
if ctx.NArg() != 1 {
|
||||
return fmt.Errorf("need tree definition directory as argument")
|
||||
return errors.New("need tree definition directory as argument")
|
||||
}
|
||||
domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0))
|
||||
if err != nil {
|
||||
@@ -244,7 +245,7 @@ func dnsToRoute53(ctx *cli.Context) error {
|
||||
// dnsNukeRoute53 performs dnsRoute53NukeCommand.
|
||||
func dnsNukeRoute53(ctx *cli.Context) error {
|
||||
if ctx.NArg() != 1 {
|
||||
return fmt.Errorf("need domain name as argument")
|
||||
return errors.New("need domain name as argument")
|
||||
}
|
||||
client := newRoute53Client(ctx)
|
||||
return client.deleteDomain(ctx.Args().First())
|
||||
@@ -363,10 +364,10 @@ func loadTreeDefinitionForExport(dir string) (domain string, t *dnsdisc.Tree, er
|
||||
// tree's signature if valid.
|
||||
func ensureValidTreeSignature(t *dnsdisc.Tree, pubkey *ecdsa.PublicKey, sig string) error {
|
||||
if sig == "" {
|
||||
return fmt.Errorf("missing signature, run 'devp2p dns sign' first")
|
||||
return errors.New("missing signature, run 'devp2p dns sign' first")
|
||||
}
|
||||
if err := t.SetSignature(pubkey, sig); err != nil {
|
||||
return fmt.Errorf("invalid signature on tree, run 'devp2p dns sign' to update it")
|
||||
return errors.New("invalid signature on tree, run 'devp2p dns sign' to update it")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -48,7 +49,7 @@ func enrdump(ctx *cli.Context) error {
|
||||
var source string
|
||||
if file := ctx.String(fileFlag.Name); file != "" {
|
||||
if ctx.NArg() != 0 {
|
||||
return fmt.Errorf("can't dump record from command-line argument in -file mode")
|
||||
return errors.New("can't dump record from command-line argument in -file mode")
|
||||
}
|
||||
var b []byte
|
||||
var err error
|
||||
@@ -64,7 +65,7 @@ func enrdump(ctx *cli.Context) error {
|
||||
} else if ctx.NArg() == 1 {
|
||||
source = ctx.Args().First()
|
||||
} else {
|
||||
return fmt.Errorf("need record as argument")
|
||||
return errors.New("need record as argument")
|
||||
}
|
||||
|
||||
r, err := parseRecord(source)
|
||||
|
||||
@@ -19,6 +19,7 @@ package ethtest
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
@@ -98,7 +99,7 @@ func (c *Chain) Head() *types.Block {
|
||||
|
||||
func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) {
|
||||
if req.Amount < 1 {
|
||||
return nil, fmt.Errorf("no block headers requested")
|
||||
return nil, errors.New("no block headers requested")
|
||||
}
|
||||
|
||||
headers := make([]*types.Header, req.Amount)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package ethtest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
@@ -185,7 +186,7 @@ loop:
|
||||
}
|
||||
// make sure eth protocol version is set for negotiation
|
||||
if c.negotiatedProtoVersion == 0 {
|
||||
return nil, fmt.Errorf("eth protocol version must be set in Conn")
|
||||
return nil, errors.New("eth protocol version must be set in Conn")
|
||||
}
|
||||
if status == nil {
|
||||
// default status message
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"terminalTotalDifficultyPassed": true,
|
||||
"ethash": {}
|
||||
},
|
||||
"nonce": "0xdeadbeefdeadbeef",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package ethtest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
@@ -39,7 +40,7 @@ func (s *Suite) sendSuccessfulTxs(t *utesting.T) error {
|
||||
}
|
||||
for i, tx := range tests {
|
||||
if tx == nil {
|
||||
return fmt.Errorf("could not find tx to send")
|
||||
return errors.New("could not find tx to send")
|
||||
}
|
||||
t.Logf("Testing tx propagation %d: sending tx %v %v %v\n", i, tx.Hash().String(), tx.GasPrice(), tx.Gas())
|
||||
// get previous tx if exists for reference in case of old tx propagation
|
||||
@@ -348,7 +349,7 @@ func generateTxs(s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Tr
|
||||
|
||||
nextTx := getNextTxFromChain(s)
|
||||
if nextTx == nil {
|
||||
return nil, nil, fmt.Errorf("failed to get the next transaction")
|
||||
return nil, nil, errors.New("failed to get the next transaction")
|
||||
}
|
||||
gas := nextTx.Gas()
|
||||
|
||||
@@ -357,7 +358,7 @@ func generateTxs(s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Tr
|
||||
for i := 0; i < numTxs; i++ {
|
||||
tx := generateTx(s.chain.chainConfig, nonce, gas)
|
||||
if tx == nil {
|
||||
return nil, nil, fmt.Errorf("failed to get the next transaction")
|
||||
return nil, nil, errors.New("failed to get the next transaction")
|
||||
}
|
||||
txHashMap[tx.Hash()] = tx.Hash()
|
||||
txs[i] = tx
|
||||
|
||||
@@ -18,6 +18,7 @@ package ethtest
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -286,5 +287,5 @@ func (c *Conn) ReadSnap(id uint64) (Message, error) {
|
||||
}
|
||||
return snpMsg.(Message), nil
|
||||
}
|
||||
return nil, fmt.Errorf("request timed out")
|
||||
return nil, errors.New("request timed out")
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package v4test
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
@@ -119,7 +120,7 @@ func (te *testenv) checkPingPong(pingHash []byte) error {
|
||||
// and a PING. The two packets do not have to be in any particular order.
|
||||
func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error {
|
||||
if reply == nil {
|
||||
return fmt.Errorf("expected PONG reply, got nil")
|
||||
return errors.New("expected PONG reply, got nil")
|
||||
}
|
||||
if reply.Kind() != v4wire.PongPacket {
|
||||
return fmt.Errorf("expected PONG reply, got %v %v", reply.Name(), reply)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
@@ -86,7 +87,7 @@ var (
|
||||
|
||||
func genkey(ctx *cli.Context) error {
|
||||
if ctx.NArg() != 1 {
|
||||
return fmt.Errorf("need key file as argument")
|
||||
return errors.New("need key file as argument")
|
||||
}
|
||||
file := ctx.Args().Get(0)
|
||||
|
||||
@@ -126,7 +127,7 @@ func keyToRecord(ctx *cli.Context) error {
|
||||
|
||||
func makeRecord(ctx *cli.Context) (*enode.Node, error) {
|
||||
if ctx.NArg() != 1 {
|
||||
return nil, fmt.Errorf("need key file as argument")
|
||||
return nil, errors.New("need key file as argument")
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -59,7 +59,7 @@ var (
|
||||
|
||||
func nodesetInfo(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return fmt.Errorf("need nodes file as argument")
|
||||
return errors.New("need nodes file as argument")
|
||||
}
|
||||
|
||||
ns := loadNodesJSON(ctx.Args().First())
|
||||
@@ -98,7 +98,7 @@ func showAttributeCounts(ns nodeSet) {
|
||||
|
||||
func nodesetFilter(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return fmt.Errorf("need nodes file as argument")
|
||||
return errors.New("need nodes file as argument")
|
||||
}
|
||||
// Parse -limit.
|
||||
limit, err := parseFilterLimit(ctx.Args().Tail())
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
@@ -91,7 +92,7 @@ func rlpxPing(ctx *cli.Context) error {
|
||||
case 1:
|
||||
var msg []p2p.DiscReason
|
||||
if rlp.DecodeBytes(data, &msg); len(msg) == 0 {
|
||||
return fmt.Errorf("invalid disconnect message")
|
||||
return errors.New("invalid disconnect message")
|
||||
}
|
||||
return fmt.Errorf("received disconnect message: %v", msg[0])
|
||||
default:
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@@ -74,11 +73,9 @@ type bbInput struct {
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
|
||||
Clique *cliqueInput `json:"clique,omitempty"`
|
||||
|
||||
Ethash bool `json:"-"`
|
||||
EthashDir string `json:"-"`
|
||||
PowMode ethash.Mode `json:"-"`
|
||||
Txs []*types.Transaction `json:"-"`
|
||||
Ommers []*types.Header `json:"-"`
|
||||
Ethash bool `json:"-"`
|
||||
Txs []*types.Transaction `json:"-"`
|
||||
Ommers []*types.Header `json:"-"`
|
||||
}
|
||||
|
||||
type cliqueInput struct {
|
||||
@@ -162,8 +159,6 @@ func (i *bbInput) ToBlock() *types.Block {
|
||||
// SealBlock seals the given block using the configured engine.
|
||||
func (i *bbInput) SealBlock(block *types.Block) (*types.Block, error) {
|
||||
switch {
|
||||
case i.Ethash:
|
||||
return i.sealEthash(block)
|
||||
case i.Clique != nil:
|
||||
return i.sealClique(block)
|
||||
default:
|
||||
@@ -171,50 +166,23 @@ func (i *bbInput) SealBlock(block *types.Block) (*types.Block, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// sealEthash seals the given block using ethash.
|
||||
func (i *bbInput) sealEthash(block *types.Block) (*types.Block, error) {
|
||||
if i.Header.Nonce != nil {
|
||||
return nil, NewError(ErrorConfig, fmt.Errorf("sealing with ethash will overwrite provided nonce"))
|
||||
}
|
||||
ethashConfig := ethash.Config{
|
||||
PowMode: i.PowMode,
|
||||
DatasetDir: i.EthashDir,
|
||||
CacheDir: i.EthashDir,
|
||||
DatasetsInMem: 1,
|
||||
DatasetsOnDisk: 2,
|
||||
CachesInMem: 2,
|
||||
CachesOnDisk: 3,
|
||||
}
|
||||
engine := ethash.New(ethashConfig, nil, true)
|
||||
defer engine.Close()
|
||||
// Use a buffered chan for results.
|
||||
// If the testmode is used, the sealer will return quickly, and complain
|
||||
// "Sealing result is not read by miner" if it cannot write the result.
|
||||
results := make(chan *types.Block, 1)
|
||||
if err := engine.Seal(nil, block, results, nil); err != nil {
|
||||
panic(fmt.Sprintf("failed to seal block: %v", err))
|
||||
}
|
||||
found := <-results
|
||||
return block.WithSeal(found.Header()), nil
|
||||
}
|
||||
|
||||
// sealClique seals the given block using clique.
|
||||
func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) {
|
||||
// If any clique value overwrites an explicit header value, fail
|
||||
// to avoid silently building a block with unexpected values.
|
||||
if i.Header.Extra != nil {
|
||||
return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique will overwrite provided extra data"))
|
||||
return nil, NewError(ErrorConfig, errors.New("sealing with clique will overwrite provided extra data"))
|
||||
}
|
||||
header := block.Header()
|
||||
if i.Clique.Voted != nil {
|
||||
if i.Header.Coinbase != nil {
|
||||
return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique and voting will overwrite provided coinbase"))
|
||||
return nil, NewError(ErrorConfig, errors.New("sealing with clique and voting will overwrite provided coinbase"))
|
||||
}
|
||||
header.Coinbase = *i.Clique.Voted
|
||||
}
|
||||
if i.Clique.Authorize != nil {
|
||||
if i.Header.Nonce != nil {
|
||||
return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique and voting will overwrite provided nonce"))
|
||||
return nil, NewError(ErrorConfig, errors.New("sealing with clique and voting will overwrite provided nonce"))
|
||||
}
|
||||
if *i.Clique.Authorize {
|
||||
header.Nonce = [8]byte{}
|
||||
@@ -267,28 +235,8 @@ func readInput(ctx *cli.Context) (*bbInput, error) {
|
||||
withdrawalsStr = ctx.String(InputWithdrawalsFlag.Name)
|
||||
txsStr = ctx.String(InputTxsRlpFlag.Name)
|
||||
cliqueStr = ctx.String(SealCliqueFlag.Name)
|
||||
ethashOn = ctx.Bool(SealEthashFlag.Name)
|
||||
ethashDir = ctx.String(SealEthashDirFlag.Name)
|
||||
ethashMode = ctx.String(SealEthashModeFlag.Name)
|
||||
inputData = &bbInput{}
|
||||
)
|
||||
if ethashOn && cliqueStr != "" {
|
||||
return nil, NewError(ErrorConfig, fmt.Errorf("both ethash and clique sealing specified, only one may be chosen"))
|
||||
}
|
||||
if ethashOn {
|
||||
inputData.Ethash = ethashOn
|
||||
inputData.EthashDir = ethashDir
|
||||
switch ethashMode {
|
||||
case "normal":
|
||||
inputData.PowMode = ethash.ModeNormal
|
||||
case "test":
|
||||
inputData.PowMode = ethash.ModeTest
|
||||
case "fake":
|
||||
inputData.PowMode = ethash.ModeFake
|
||||
default:
|
||||
return nil, NewError(ErrorConfig, fmt.Errorf("unknown pow mode: %s, supported modes: test, fake, normal", ethashMode))
|
||||
}
|
||||
}
|
||||
if headerStr == stdinSelector || ommersStr == stdinSelector || txsStr == stdinSelector || cliqueStr == stdinSelector {
|
||||
decoder := json.NewDecoder(os.Stdin)
|
||||
if err := decoder.Decode(inputData); err != nil {
|
||||
|
||||
@@ -125,7 +125,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
}
|
||||
var (
|
||||
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre)
|
||||
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number))
|
||||
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
|
||||
gaspool = new(core.GasPool)
|
||||
blockHash = common.Hash{0x13, 0x37}
|
||||
rejectedTxs []*rejectedTx
|
||||
@@ -293,7 +293,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||
|
||||
func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB {
|
||||
sdb := state.NewDatabaseWithConfig(db, &trie.Config{Preimages: true})
|
||||
statedb, _ := state.New(common.Hash{}, sdb, nil)
|
||||
statedb, _ := state.New(types.EmptyRootHash, sdb, nil)
|
||||
for addr, a := range accounts {
|
||||
statedb.SetCode(addr, a.Code)
|
||||
statedb.SetNonce(addr, a.Nonce)
|
||||
|
||||
@@ -125,19 +125,6 @@ var (
|
||||
Name: "seal.clique",
|
||||
Usage: "Seal block with Clique. `stdin` or file name of where to find the Clique sealing data.",
|
||||
}
|
||||
SealEthashFlag = &cli.BoolFlag{
|
||||
Name: "seal.ethash",
|
||||
Usage: "Seal block with ethash.",
|
||||
}
|
||||
SealEthashDirFlag = &cli.StringFlag{
|
||||
Name: "seal.ethash.dir",
|
||||
Usage: "Path to ethash DAG. If none exists, a new DAG will be generated.",
|
||||
}
|
||||
SealEthashModeFlag = &cli.StringFlag{
|
||||
Name: "seal.ethash.mode",
|
||||
Usage: "Defines the type and amount of PoW verification an ethash engine makes.",
|
||||
Value: "normal",
|
||||
}
|
||||
RewardFlag = &cli.Int64Flag{
|
||||
Name: "state.reward",
|
||||
Usage: "Mining reward. Set to -1 to disable",
|
||||
|
||||
@@ -112,7 +112,7 @@ func Transaction(ctx *cli.Context) error {
|
||||
return NewError(ErrorIO, errors.New("only rlp supported"))
|
||||
}
|
||||
}
|
||||
signer := types.MakeSigner(chainConfig, new(big.Int))
|
||||
signer := types.MakeSigner(chainConfig, new(big.Int), 0)
|
||||
// We now have the transactions in 'body', which is supposed to be an
|
||||
// rlp list of transactions
|
||||
it, err := rlp.NewListIterator([]byte(body))
|
||||
@@ -140,7 +140,7 @@ func Transaction(ctx *cli.Context) error {
|
||||
}
|
||||
// Check intrinsic gas
|
||||
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil,
|
||||
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(0)); err != nil {
|
||||
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0)); err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
continue
|
||||
@@ -172,7 +172,7 @@ func Transaction(ctx *cli.Context) error {
|
||||
r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits")
|
||||
}
|
||||
// Check whether the init code size has been exceeded.
|
||||
if chainConfig.IsShanghai(0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
|
||||
if chainConfig.IsShanghai(new(big.Int), 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
|
||||
r.Error = errors.New("max initcode size exceeded")
|
||||
}
|
||||
results = append(results, r)
|
||||
|
||||
@@ -240,7 +240,7 @@ func Transition(ctx *cli.Context) error {
|
||||
}
|
||||
}
|
||||
// We may have to sign the transactions.
|
||||
signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number)))
|
||||
signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
|
||||
|
||||
if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil {
|
||||
return NewError(ErrorJson, fmt.Errorf("failed signing transactions: %v", err))
|
||||
@@ -261,7 +261,7 @@ func Transition(ctx *cli.Context) error {
|
||||
return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section"))
|
||||
}
|
||||
}
|
||||
if chainConfig.IsShanghai(prestate.Env.Number) && prestate.Env.Withdrawals == nil {
|
||||
if chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) && prestate.Env.Withdrawals == nil {
|
||||
return NewError(ErrorConfig, errors.New("Shanghai config but missing 'withdrawals' in env section"))
|
||||
}
|
||||
isMerged := chainConfig.TerminalTotalDifficulty != nil && chainConfig.TerminalTotalDifficulty.BitLen() == 0
|
||||
@@ -389,7 +389,10 @@ type Alloc map[common.Address]core.GenesisAccount
|
||||
|
||||
func (g Alloc) OnRoot(common.Hash) {}
|
||||
|
||||
func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) {
|
||||
func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
|
||||
if addr == nil {
|
||||
return
|
||||
}
|
||||
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 10)
|
||||
var storage map[common.Hash]common.Hash
|
||||
if dumpAccount.Storage != nil {
|
||||
@@ -404,7 +407,7 @@ func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) {
|
||||
Balance: balance,
|
||||
Nonce: dumpAccount.Nonce,
|
||||
}
|
||||
g[addr] = genesisAccount
|
||||
g[*addr] = genesisAccount
|
||||
}
|
||||
|
||||
// saveFile marshals the object to the given file
|
||||
|
||||
@@ -179,9 +179,6 @@ var blockBuilderCommand = &cli.Command{
|
||||
t8ntool.InputWithdrawalsFlag,
|
||||
t8ntool.InputTxsRlpFlag,
|
||||
t8ntool.SealCliqueFlag,
|
||||
t8ntool.SealEthashFlag,
|
||||
t8ntool.SealEthashDirFlag,
|
||||
t8ntool.SealEthashModeFlag,
|
||||
t8ntool.VerbosityFlag,
|
||||
},
|
||||
}
|
||||
|
||||
+2
-1
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/core/vm/runtime"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
@@ -146,7 +147,7 @@ func runCmd(ctx *cli.Context) error {
|
||||
chainConfig = gen.Config
|
||||
} else {
|
||||
sdb := state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: preimages})
|
||||
statedb, _ = state.New(common.Hash{}, sdb, nil)
|
||||
statedb, _ = state.New(types.EmptyRootHash, sdb, nil)
|
||||
genesisConfig = new(core.Genesis)
|
||||
}
|
||||
if ctx.String(SenderFlag.Name) != "" {
|
||||
|
||||
+28
-30
@@ -17,8 +17,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
var stateTestCommand = &cli.Command{
|
||||
Action: stateTestCmd,
|
||||
Name: "statetest",
|
||||
Usage: "executes the given state tests",
|
||||
Usage: "Executes the given state tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).",
|
||||
ArgsUsage: "<file>",
|
||||
}
|
||||
|
||||
@@ -50,9 +50,6 @@ type StatetestResult struct {
|
||||
}
|
||||
|
||||
func stateTestCmd(ctx *cli.Context) error {
|
||||
if len(ctx.Args().First()) == 0 {
|
||||
return errors.New("path-to-test argument required")
|
||||
}
|
||||
// Configure the go-ethereum logger
|
||||
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
||||
glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
|
||||
@@ -65,34 +62,43 @@ func stateTestCmd(ctx *cli.Context) error {
|
||||
DisableStorage: ctx.Bool(DisableStorageFlag.Name),
|
||||
EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
|
||||
}
|
||||
var (
|
||||
tracer vm.EVMLogger
|
||||
debugger *logger.StructLogger
|
||||
)
|
||||
var cfg vm.Config
|
||||
switch {
|
||||
case ctx.Bool(MachineFlag.Name):
|
||||
tracer = logger.NewJSONLogger(config, os.Stderr)
|
||||
cfg.Tracer = logger.NewJSONLogger(config, os.Stderr)
|
||||
|
||||
case ctx.Bool(DebugFlag.Name):
|
||||
debugger = logger.NewStructLogger(config)
|
||||
tracer = debugger
|
||||
|
||||
default:
|
||||
debugger = logger.NewStructLogger(config)
|
||||
cfg.Tracer = logger.NewStructLogger(config)
|
||||
}
|
||||
// Load the test content from the input file
|
||||
src, err := os.ReadFile(ctx.Args().First())
|
||||
if len(ctx.Args().First()) != 0 {
|
||||
return runStateTest(ctx.Args().First(), cfg, ctx.Bool(MachineFlag.Name), ctx.Bool(DumpFlag.Name))
|
||||
}
|
||||
// Read filenames from stdin and execute back-to-back
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
fname := scanner.Text()
|
||||
if len(fname) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := runStateTest(fname, cfg, ctx.Bool(MachineFlag.Name), ctx.Bool(DumpFlag.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// runStateTest loads the state-test given by fname, and executes the test.
|
||||
func runStateTest(fname string, cfg vm.Config, jsonOut, dump bool) error {
|
||||
src, err := os.ReadFile(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var tests map[string]tests.StateTest
|
||||
if err = json.Unmarshal(src, &tests); err != nil {
|
||||
if err := json.Unmarshal(src, &tests); err != nil {
|
||||
return err
|
||||
}
|
||||
// Iterate over all the tests, run them and aggregate the results
|
||||
cfg := vm.Config{
|
||||
Tracer: tracer,
|
||||
}
|
||||
results := make([]StatetestResult, 0, len(tests))
|
||||
for key, test := range tests {
|
||||
for _, st := range test.Subtests() {
|
||||
@@ -103,28 +109,20 @@ func stateTestCmd(ctx *cli.Context) error {
|
||||
if s != nil {
|
||||
root := s.IntermediateRoot(false)
|
||||
result.Root = &root
|
||||
if ctx.Bool(MachineFlag.Name) {
|
||||
if jsonOut {
|
||||
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
// Test failed, mark as so and dump any state to aid debugging
|
||||
result.Pass, result.Error = false, err.Error()
|
||||
if ctx.Bool(DumpFlag.Name) && s != nil {
|
||||
if dump && s != nil {
|
||||
dump := s.RawDump(nil)
|
||||
result.State = &dump
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, *result)
|
||||
|
||||
// Print any structured logs collected
|
||||
if ctx.Bool(DebugFlag.Name) {
|
||||
if debugger != nil {
|
||||
fmt.Fprintln(os.Stderr, "#### TRACE ####")
|
||||
logger.WriteTrace(os.Stderr, debugger.StructLogs())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out, _ := json.MarshalIndent(results, "", " ")
|
||||
|
||||
+4
-3
@@ -275,7 +275,8 @@ func TestT8n(t *testing.T) {
|
||||
tt.Run("evm-test", args...)
|
||||
// Compare the expected output, if provided
|
||||
if tc.expOut != "" {
|
||||
want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut))
|
||||
file := fmt.Sprintf("%v/%v", tc.base, tc.expOut)
|
||||
want, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Fatalf("test %d: could not read expected output: %v", i, err)
|
||||
}
|
||||
@@ -283,9 +284,9 @@ func TestT8n(t *testing.T) {
|
||||
ok, err := cmpJson(have, want)
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Fatalf("test %d, json parsing failed: %v", i, err)
|
||||
t.Fatalf("test %d, file %v: json parsing failed: %v", i, file, err)
|
||||
case !ok:
|
||||
t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want))
|
||||
t.Fatalf("test %d, file %v: output wrong, have \n%v\nwant\n%v\n", i, file, string(have), string(want))
|
||||
}
|
||||
}
|
||||
tt.WaitExit()
|
||||
|
||||
Vendored
+1
@@ -28,6 +28,7 @@
|
||||
"transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673",
|
||||
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"gasUsed": "0x5208",
|
||||
"effectiveGasPrice": null,
|
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"transactionIndex": "0x0"
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -16,6 +16,7 @@
|
||||
"transactionHash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476",
|
||||
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"gasUsed": "0x84d0",
|
||||
"effectiveGasPrice": null,
|
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"transactionIndex": "0x0"
|
||||
},
|
||||
@@ -29,6 +30,7 @@
|
||||
"transactionHash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a",
|
||||
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"gasUsed": "0x84d0",
|
||||
"effectiveGasPrice": null,
|
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"transactionIndex": "0x1"
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -15,6 +15,7 @@
|
||||
"transactionHash": "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81",
|
||||
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"gasUsed": "0x520b",
|
||||
"effectiveGasPrice": null,
|
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"transactionIndex": "0x0"
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -31,6 +31,7 @@
|
||||
"transactionHash": "0x92ea4a28224d033afb20e0cc2b290d4c7c2d61f6a4800a680e4e19ac962ee941",
|
||||
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"gasUsed": "0xa861",
|
||||
"effectiveGasPrice": null,
|
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"transactionIndex": "0x0"
|
||||
},
|
||||
@@ -43,6 +44,7 @@
|
||||
"transactionHash": "0x16b1d912f1d664f3f60f4e1b5f296f3c82a64a1a253117b4851d18bc03c4f1da",
|
||||
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"gasUsed": "0x5aa5",
|
||||
"effectiveGasPrice": null,
|
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"transactionIndex": "0x1"
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -27,6 +27,7 @@
|
||||
"transactionHash": "0x92ea4a28224d033afb20e0cc2b290d4c7c2d61f6a4800a680e4e19ac962ee941",
|
||||
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"gasUsed": "0x5208",
|
||||
"effectiveGasPrice": null,
|
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"transactionIndex": "0x0"
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -28,6 +28,7 @@
|
||||
"transactionHash": "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81",
|
||||
"contractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"gasUsed": "0x521f",
|
||||
"effectiveGasPrice": null,
|
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"transactionIndex": "0x0"
|
||||
}
|
||||
|
||||
@@ -893,6 +893,6 @@ func getGenesis(genesisFlag string, goerliFlag bool, rinkebyFlag bool, sepoliaFl
|
||||
case sepoliaFlag:
|
||||
return core.DefaultSepoliaGenesisBlock(), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("no genesis flag provided")
|
||||
return nil, errors.New("no genesis flag provided")
|
||||
}
|
||||
}
|
||||
|
||||
+37
-22
@@ -188,15 +188,34 @@ nodes.
|
||||
}
|
||||
)
|
||||
|
||||
// makeAccountManager creates an account manager with backends
|
||||
func makeAccountManager(ctx *cli.Context) *accounts.Manager {
|
||||
cfg := loadBaseConfig(ctx)
|
||||
am := accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: cfg.Node.InsecureUnlockAllowed})
|
||||
keydir, isEphemeral, err := cfg.Node.GetKeyStoreDir()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to get the keystore directory: %v", err)
|
||||
}
|
||||
if isEphemeral {
|
||||
utils.Fatalf("Can't use ephemeral directory as keystore path")
|
||||
}
|
||||
|
||||
if err := setAccountManagerBackends(&cfg.Node, am, keydir); err != nil {
|
||||
utils.Fatalf("Failed to set account manager backends: %v", err)
|
||||
}
|
||||
return am
|
||||
}
|
||||
|
||||
func accountList(ctx *cli.Context) error {
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
am := makeAccountManager(ctx)
|
||||
var index int
|
||||
for _, wallet := range stack.AccountManager().Wallets() {
|
||||
for _, wallet := range am.Wallets() {
|
||||
for _, account := range wallet.Accounts() {
|
||||
fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL)
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -258,17 +277,13 @@ func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrErr
|
||||
|
||||
// accountCreate creates a new account into the keystore defined by the CLI flags.
|
||||
func accountCreate(ctx *cli.Context) error {
|
||||
cfg := gethConfig{Node: defaultNodeConfig()}
|
||||
// Load config file.
|
||||
if file := ctx.String(configFileFlag.Name); file != "" {
|
||||
if err := loadConfig(file, &cfg); err != nil {
|
||||
utils.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
utils.SetNodeConfig(ctx, &cfg.Node)
|
||||
keydir, err := cfg.Node.KeyDirConfig()
|
||||
cfg := loadBaseConfig(ctx)
|
||||
keydir, isEphemeral, err := cfg.Node.GetKeyStoreDir()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read configuration: %v", err)
|
||||
utils.Fatalf("Failed to get the keystore directory: %v", err)
|
||||
}
|
||||
if isEphemeral {
|
||||
utils.Fatalf("Can't use ephemeral directory as keystore path")
|
||||
}
|
||||
scryptN := keystore.StandardScryptN
|
||||
scryptP := keystore.StandardScryptP
|
||||
@@ -300,8 +315,8 @@ func accountUpdate(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 0 {
|
||||
utils.Fatalf("No accounts specified to update")
|
||||
}
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
backends := stack.AccountManager().Backends(keystore.KeyStoreType)
|
||||
am := makeAccountManager(ctx)
|
||||
backends := am.Backends(keystore.KeyStoreType)
|
||||
if len(backends) == 0 {
|
||||
utils.Fatalf("Keystore is not available")
|
||||
}
|
||||
@@ -327,14 +342,14 @@ func importWallet(ctx *cli.Context) error {
|
||||
utils.Fatalf("Could not read wallet file: %v", err)
|
||||
}
|
||||
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
passphrase := utils.GetPassPhraseWithList("", false, 0, utils.MakePasswordList(ctx))
|
||||
|
||||
backends := stack.AccountManager().Backends(keystore.KeyStoreType)
|
||||
am := makeAccountManager(ctx)
|
||||
backends := am.Backends(keystore.KeyStoreType)
|
||||
if len(backends) == 0 {
|
||||
utils.Fatalf("Keystore is not available")
|
||||
}
|
||||
ks := backends[0].(*keystore.KeyStore)
|
||||
passphrase := utils.GetPassPhraseWithList("", false, 0, utils.MakePasswordList(ctx))
|
||||
|
||||
acct, err := ks.ImportPreSaleKey(keyJSON, passphrase)
|
||||
if err != nil {
|
||||
utils.Fatalf("%v", err)
|
||||
@@ -352,14 +367,14 @@ func accountImport(ctx *cli.Context) error {
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to load the private key: %v", err)
|
||||
}
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
passphrase := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
||||
|
||||
backends := stack.AccountManager().Backends(keystore.KeyStoreType)
|
||||
am := makeAccountManager(ctx)
|
||||
backends := am.Backends(keystore.KeyStoreType)
|
||||
if len(backends) == 0 {
|
||||
utils.Fatalf("Keystore is not available")
|
||||
}
|
||||
ks := backends[0].(*keystore.KeyStore)
|
||||
passphrase := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
||||
|
||||
acct, err := ks.ImportECDSA(key, passphrase)
|
||||
if err != nil {
|
||||
utils.Fatalf("Could not create the account: %v", err)
|
||||
|
||||
@@ -478,7 +478,7 @@ func dump(ctx *cli.Context) error {
|
||||
if conf.OnlyWithAddresses {
|
||||
fmt.Fprintf(os.Stderr, "If you want to include accounts with missing preimages, you need iterative output, since"+
|
||||
" otherwise the accounts will overwrite each other in the resulting mapping.")
|
||||
return fmt.Errorf("incompatible options")
|
||||
return errors.New("incompatible options")
|
||||
}
|
||||
fmt.Println(string(state.Dump(conf)))
|
||||
}
|
||||
|
||||
+17
-12
@@ -26,6 +26,7 @@ import (
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/external"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
||||
@@ -119,8 +120,9 @@ func defaultNodeConfig() node.Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// makeConfigNode loads geth configuration and creates a blank node instance.
|
||||
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||
// loadBaseConfig loads the gethConfig based on the given command line
|
||||
// parameters and config file.
|
||||
func loadBaseConfig(ctx *cli.Context) gethConfig {
|
||||
// Load defaults.
|
||||
cfg := gethConfig{
|
||||
Eth: ethconfig.Defaults,
|
||||
@@ -137,12 +139,18 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||
|
||||
// Apply flags.
|
||||
utils.SetNodeConfig(ctx, &cfg.Node)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// makeConfigNode loads geth configuration and creates a blank node instance.
|
||||
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||
cfg := loadBaseConfig(ctx)
|
||||
stack, err := node.New(&cfg.Node)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to create the protocol stack: %v", err)
|
||||
}
|
||||
// Node doesn't by default populate account manager backends
|
||||
if err := setAccountManagerBackends(stack); err != nil {
|
||||
if err := setAccountManagerBackends(stack.Config(), stack.AccountManager(), stack.KeyStoreDir()); err != nil {
|
||||
utils.Fatalf("Failed to set account manager backends: %v", err)
|
||||
}
|
||||
|
||||
@@ -158,9 +166,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||
// makeFullNode loads geth configuration and creates the Ethereum backend.
|
||||
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
|
||||
stack, cfg := makeConfigNode(ctx)
|
||||
if ctx.IsSet(utils.OverrideShanghai.Name) {
|
||||
v := ctx.Uint64(utils.OverrideShanghai.Name)
|
||||
cfg.Eth.OverrideShanghai = &v
|
||||
if ctx.IsSet(utils.OverrideCancun.Name) {
|
||||
v := ctx.Uint64(utils.OverrideCancun.Name)
|
||||
cfg.Eth.OverrideCancun = &v
|
||||
}
|
||||
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
|
||||
|
||||
@@ -269,10 +277,7 @@ func deprecated(field string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func setAccountManagerBackends(stack *node.Node) error {
|
||||
conf := stack.Config()
|
||||
am := stack.AccountManager()
|
||||
keydir := stack.KeyStoreDir()
|
||||
func setAccountManagerBackends(conf *node.Config, am *accounts.Manager, keydir string) error {
|
||||
scryptN := keystore.StandardScryptN
|
||||
scryptP := keystore.StandardScryptP
|
||||
if conf.UseLightweightKDF {
|
||||
@@ -283,8 +288,8 @@ func setAccountManagerBackends(stack *node.Node) error {
|
||||
// Assemble the supported backends
|
||||
if len(conf.ExternalSigner) > 0 {
|
||||
log.Info("Using external signer", "url", conf.ExternalSigner)
|
||||
if extapi, err := external.NewExternalBackend(conf.ExternalSigner); err == nil {
|
||||
am.AddBackend(extapi)
|
||||
if extBackend, err := external.NewExternalBackend(conf.ExternalSigner); err == nil {
|
||||
am.AddBackend(extBackend)
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("error connecting to external signer: %v", err)
|
||||
|
||||
@@ -116,7 +116,7 @@ func TestAttachWelcome(t *testing.T) {
|
||||
waitForEndpoint(t, endpoint, 3*time.Second)
|
||||
testAttachWelcome(t, geth, endpoint, httpAPIs)
|
||||
})
|
||||
geth.ExpectExit()
|
||||
geth.Kill()
|
||||
}
|
||||
|
||||
func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// Genesis block for nodes which don't care about the DAO fork (i.e. not configured)
|
||||
var daoOldGenesis = `{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {
|
||||
"homesteadBlock" : 0
|
||||
}
|
||||
}`
|
||||
|
||||
// Genesis block for nodes which actively oppose the DAO fork
|
||||
var daoNoForkGenesis = `{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {
|
||||
"homesteadBlock" : 0,
|
||||
"daoForkBlock" : 314,
|
||||
"daoForkSupport" : false
|
||||
}
|
||||
}`
|
||||
|
||||
// Genesis block for nodes which actively support the DAO fork
|
||||
var daoProForkGenesis = `{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {
|
||||
"homesteadBlock" : 0,
|
||||
"daoForkBlock" : 314,
|
||||
"daoForkSupport" : true
|
||||
}
|
||||
}`
|
||||
|
||||
var daoGenesisHash = common.HexToHash("5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0")
|
||||
var daoGenesisForkBlock = big.NewInt(314)
|
||||
|
||||
// TestDAOForkBlockNewChain tests that the DAO hard-fork number and the nodes support/opposition is correctly
|
||||
// set in the database after various initialization procedures and invocations.
|
||||
func TestDAOForkBlockNewChain(t *testing.T) {
|
||||
for i, arg := range []struct {
|
||||
genesis string
|
||||
expectBlock *big.Int
|
||||
expectVote bool
|
||||
}{
|
||||
// Test DAO Default Mainnet
|
||||
{"", params.MainnetChainConfig.DAOForkBlock, true},
|
||||
// test DAO Init Old Privnet
|
||||
{daoOldGenesis, nil, false},
|
||||
// test DAO Default No Fork Privnet
|
||||
{daoNoForkGenesis, daoGenesisForkBlock, false},
|
||||
// test DAO Default Pro Fork Privnet
|
||||
{daoProForkGenesis, daoGenesisForkBlock, true},
|
||||
} {
|
||||
testDAOForkBlockNewChain(t, i, arg.genesis, arg.expectBlock, arg.expectVote)
|
||||
}
|
||||
}
|
||||
|
||||
func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBlock *big.Int, expectVote bool) {
|
||||
// Create a temporary data directory to use and inspect later
|
||||
datadir := t.TempDir()
|
||||
|
||||
// Start a Geth instance with the requested flags set and immediately terminate
|
||||
if genesis != "" {
|
||||
json := filepath.Join(datadir, "genesis.json")
|
||||
if err := os.WriteFile(json, []byte(genesis), 0600); err != nil {
|
||||
t.Fatalf("test %d: failed to write genesis file: %v", test, err)
|
||||
}
|
||||
runGeth(t, "--datadir", datadir, "--networkid", "1337", "init", json).WaitExit()
|
||||
} else {
|
||||
// Force chain initialization
|
||||
args := []string{"--port", "0", "--networkid", "1337", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
|
||||
runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...).WaitExit()
|
||||
}
|
||||
// Retrieve the DAO config flag from the database
|
||||
path := filepath.Join(datadir, "geth", "chaindata")
|
||||
db, err := rawdb.NewLevelDBDatabase(path, 0, 0, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("test %d: failed to open test database: %v", test, err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
genesisHash := common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
||||
if genesis != "" {
|
||||
genesisHash = daoGenesisHash
|
||||
}
|
||||
config := rawdb.ReadChainConfig(db, genesisHash)
|
||||
if config == nil {
|
||||
t.Errorf("test %d: failed to retrieve chain config: %v", test, err)
|
||||
return // we want to return here, the other checks can't make it past this point (nil panic).
|
||||
}
|
||||
// Validate the DAO hard-fork block number against the expected value
|
||||
if config.DAOForkBlock == nil {
|
||||
if expectBlock != nil {
|
||||
t.Errorf("test %d: dao hard-fork block mismatch: have nil, want %v", test, expectBlock)
|
||||
}
|
||||
} else if expectBlock == nil {
|
||||
t.Errorf("test %d: dao hard-fork block mismatch: have %v, want nil", test, config.DAOForkBlock)
|
||||
} else if config.DAOForkBlock.Cmp(expectBlock) != 0 {
|
||||
t.Errorf("test %d: dao hard-fork block mismatch: have %v, want %v", test, config.DAOForkBlock, expectBlock)
|
||||
}
|
||||
if config.DAOForkSupport != expectVote {
|
||||
t.Errorf("test %d: dao hard-fork support mismatch: have %v, want %v", test, config.DAOForkSupport, expectVote)
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,9 @@ var customGenesisTests = []struct {
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {}
|
||||
"config" : {
|
||||
"terminalTotalDifficultyPassed": true
|
||||
}
|
||||
}`,
|
||||
query: "eth.getBlock(0).nonce",
|
||||
result: "0x0000000000001338",
|
||||
@@ -59,9 +61,10 @@ var customGenesisTests = []struct {
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {
|
||||
"homesteadBlock" : 42,
|
||||
"daoForkBlock" : 141,
|
||||
"daoForkSupport" : true
|
||||
"homesteadBlock" : 42,
|
||||
"daoForkBlock" : 141,
|
||||
"daoForkSupport" : true,
|
||||
"terminalTotalDifficultyPassed" : true
|
||||
}
|
||||
}`,
|
||||
query: "eth.getBlock(0).nonce",
|
||||
@@ -111,8 +114,10 @@ func TestCustomBackend(t *testing.T) {
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {}
|
||||
}`
|
||||
"config" : {
|
||||
"terminalTotalDifficultyPassed": true
|
||||
}
|
||||
}`
|
||||
type backendTest struct {
|
||||
initArgs []string
|
||||
initExpect string
|
||||
@@ -146,8 +151,8 @@ func TestCustomBackend(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
for i, tt := range []backendTest{
|
||||
{ // When not specified, it should default to leveldb
|
||||
execArgs: []string{"--db.engine", "leveldb"},
|
||||
{ // When not specified, it should default to pebble
|
||||
execArgs: []string{"--db.engine", "pebble"},
|
||||
execExpect: "0x0000000000001338",
|
||||
},
|
||||
{ // Explicit leveldb
|
||||
|
||||
+3
-19
@@ -67,16 +67,8 @@ var (
|
||||
utils.NoUSBFlag,
|
||||
utils.USBFlag,
|
||||
utils.SmartCardDaemonPathFlag,
|
||||
utils.OverrideShanghai,
|
||||
utils.OverrideCancun,
|
||||
utils.EnablePersonal,
|
||||
utils.EthashCacheDirFlag,
|
||||
utils.EthashCachesInMemoryFlag,
|
||||
utils.EthashCachesOnDiskFlag,
|
||||
utils.EthashCachesLockMmapFlag,
|
||||
utils.EthashDatasetDirFlag,
|
||||
utils.EthashDatasetsInMemoryFlag,
|
||||
utils.EthashDatasetsOnDiskFlag,
|
||||
utils.EthashDatasetsLockMmapFlag,
|
||||
utils.TxPoolLocalsFlag,
|
||||
utils.TxPoolNoLocalsFlag,
|
||||
utils.TxPoolJournalFlag,
|
||||
@@ -118,19 +110,17 @@ var (
|
||||
utils.CachePreimagesFlag,
|
||||
utils.CacheLogSizeFlag,
|
||||
utils.FDLimitFlag,
|
||||
utils.CryptoKZGFlag,
|
||||
utils.ListenPortFlag,
|
||||
utils.DiscoveryPortFlag,
|
||||
utils.MaxPeersFlag,
|
||||
utils.MaxPendingPeersFlag,
|
||||
utils.MiningEnabledFlag,
|
||||
utils.MinerThreadsFlag,
|
||||
utils.MinerNotifyFlag,
|
||||
utils.MinerGasLimitFlag,
|
||||
utils.MinerGasPriceFlag,
|
||||
utils.MinerEtherbaseFlag,
|
||||
utils.MinerExtraDataFlag,
|
||||
utils.MinerRecommitIntervalFlag,
|
||||
utils.MinerNoVerifyFlag,
|
||||
utils.MinerNewPayloadTimeout,
|
||||
utils.NATFlag,
|
||||
utils.NoDiscoverFlag,
|
||||
@@ -145,13 +135,11 @@ var (
|
||||
utils.VMEnableDebugFlag,
|
||||
utils.NetworkIdFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.FakePoWFlag,
|
||||
utils.NoCompactionFlag,
|
||||
utils.GpoBlocksFlag,
|
||||
utils.GpoPercentileFlag,
|
||||
utils.GpoMaxGasPriceFlag,
|
||||
utils.GpoIgnoreGasPriceFlag,
|
||||
utils.MinerNotifyFullFlag,
|
||||
configFileFlag,
|
||||
}, utils.NetworkFlags, utils.DatabasePathFlags)
|
||||
|
||||
@@ -227,8 +215,6 @@ func init() {
|
||||
attachCommand,
|
||||
javascriptCommand,
|
||||
// See misccmd.go:
|
||||
makecacheCommand,
|
||||
makedagCommand,
|
||||
versionCommand,
|
||||
versionCheckCommand,
|
||||
licenseCommand,
|
||||
@@ -455,9 +441,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon
|
||||
// Set the gas price to the limits from the CLI and start mining
|
||||
gasprice := flags.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
|
||||
ethBackend.TxPool().SetGasPrice(gasprice)
|
||||
// start mining
|
||||
threads := ctx.Int(utils.MinerThreadsFlag.Name)
|
||||
if err := ethBackend.StartMining(threads); err != nil {
|
||||
if err := ethBackend.StartMining(); err != nil {
|
||||
utils.Fatalf("Failed to start mining: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/internal/version"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -41,30 +38,6 @@ var (
|
||||
Usage: "Version to check",
|
||||
Value: version.ClientName(clientIdentifier),
|
||||
}
|
||||
makecacheCommand = &cli.Command{
|
||||
Action: makecache,
|
||||
Name: "makecache",
|
||||
Usage: "Generate ethash verification cache (for testing)",
|
||||
ArgsUsage: "<blockNum> <outputDir>",
|
||||
Description: `
|
||||
The makecache command generates an ethash cache in <outputDir>.
|
||||
|
||||
This command exists to support the system testing project.
|
||||
Regular users do not need to execute it.
|
||||
`,
|
||||
}
|
||||
makedagCommand = &cli.Command{
|
||||
Action: makedag,
|
||||
Name: "makedag",
|
||||
Usage: "Generate ethash mining DAG (for testing)",
|
||||
ArgsUsage: "<blockNum> <outputDir>",
|
||||
Description: `
|
||||
The makedag command generates an ethash DAG in <outputDir>.
|
||||
|
||||
This command exists to support the system testing project.
|
||||
Regular users do not need to execute it.
|
||||
`,
|
||||
}
|
||||
versionCommand = &cli.Command{
|
||||
Action: printVersion,
|
||||
Name: "version",
|
||||
@@ -96,36 +69,6 @@ and displays information about any security vulnerabilities that affect the curr
|
||||
}
|
||||
)
|
||||
|
||||
// makecache generates an ethash verification cache into the provided folder.
|
||||
func makecache(ctx *cli.Context) error {
|
||||
args := ctx.Args().Slice()
|
||||
if len(args) != 2 {
|
||||
utils.Fatalf(`Usage: geth makecache <block number> <outputdir>`)
|
||||
}
|
||||
block, err := strconv.ParseUint(args[0], 0, 64)
|
||||
if err != nil {
|
||||
utils.Fatalf("Invalid block number: %v", err)
|
||||
}
|
||||
ethash.MakeCache(block, args[1])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makedag generates an ethash mining DAG into the provided folder.
|
||||
func makedag(ctx *cli.Context) error {
|
||||
args := ctx.Args().Slice()
|
||||
if len(args) != 2 {
|
||||
utils.Fatalf(`Usage: geth makedag <block number> <outputdir>`)
|
||||
}
|
||||
block, err := strconv.ParseUint(args[0], 0, 64)
|
||||
if err != nil {
|
||||
utils.Fatalf("Invalid block number: %v", err)
|
||||
}
|
||||
ethash.MakeDataset(block, args[1])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printVersion(ctx *cli.Context) error {
|
||||
git, _ := version.VCS()
|
||||
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Both balance and nonce are 0")
|
||||
return errors.New("Both balance and nonce are 0")
|
||||
case verkle.Empty:
|
||||
// nothing to do
|
||||
default:
|
||||
|
||||
+13
-4
@@ -118,6 +118,9 @@ func StartNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
|
||||
}
|
||||
|
||||
func monitorFreeDiskSpace(sigc chan os.Signal, path string, freeDiskSpaceCritical uint64) {
|
||||
if path == "" {
|
||||
return
|
||||
}
|
||||
for {
|
||||
freeSpace, err := getFreeDiskSpace(path)
|
||||
if err != nil {
|
||||
@@ -181,7 +184,7 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
||||
for batch := 0; ; batch++ {
|
||||
// Load a batch of RLP blocks.
|
||||
if checkInterrupt() {
|
||||
return fmt.Errorf("interrupted")
|
||||
return errors.New("interrupted")
|
||||
}
|
||||
i := 0
|
||||
for ; i < importBatchSize; i++ {
|
||||
@@ -204,15 +207,21 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
||||
}
|
||||
// Import the batch.
|
||||
if checkInterrupt() {
|
||||
return fmt.Errorf("interrupted")
|
||||
return errors.New("interrupted")
|
||||
}
|
||||
missing := missingBlocks(chain, blocks[:i])
|
||||
if len(missing) == 0 {
|
||||
log.Info("Skipping batch as all blocks present", "batch", batch, "first", blocks[0].Hash(), "last", blocks[i-1].Hash())
|
||||
continue
|
||||
}
|
||||
if _, err := chain.InsertChain(missing); err != nil {
|
||||
return fmt.Errorf("invalid block %d: %v", n, err)
|
||||
if failindex, err := chain.InsertChain(missing); err != nil {
|
||||
var failnumber uint64
|
||||
if failindex > 0 && failindex < len(missing) {
|
||||
failnumber = missing[failindex].NumberU64()
|
||||
} else {
|
||||
failnumber = missing[0].NumberU64()
|
||||
}
|
||||
return fmt.Errorf("invalid block %d: %v", failnumber, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
+24
-121
@@ -39,13 +39,13 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
ethcatalyst "github.com/ethereum/go-ethereum/eth/catalyst"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
@@ -101,8 +101,8 @@ var (
|
||||
}
|
||||
DBEngineFlag = &cli.StringFlag{
|
||||
Name: "db.engine",
|
||||
Usage: "Backing database implementation to use ('leveldb' or 'pebble')",
|
||||
Value: "leveldb",
|
||||
Usage: "Backing database implementation to use ('pebble' or 'leveldb')",
|
||||
Value: node.DefaultConfig.DBEngine,
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
AncientFlag = &flags.DirectoryFlag{
|
||||
@@ -268,9 +268,9 @@ var (
|
||||
Value: 2048,
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideShanghai = &cli.Uint64Flag{
|
||||
Name: "override.shanghai",
|
||||
Usage: "Manually specify the Shanghai fork timestamp, overriding the bundled setting",
|
||||
OverrideCancun = &cli.Uint64Flag{
|
||||
Name: "override.cancun",
|
||||
Usage: "Manually specify the Cancun fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
// Light server and client settings
|
||||
@@ -325,54 +325,6 @@ var (
|
||||
Usage: "Enables serving light clients before syncing",
|
||||
Category: flags.LightCategory,
|
||||
}
|
||||
|
||||
// Ethash settings
|
||||
EthashCacheDirFlag = &flags.DirectoryFlag{
|
||||
Name: "ethash.cachedir",
|
||||
Usage: "Directory to store the ethash verification caches (default = inside the datadir)",
|
||||
Category: flags.EthashCategory,
|
||||
}
|
||||
EthashCachesInMemoryFlag = &cli.IntFlag{
|
||||
Name: "ethash.cachesinmem",
|
||||
Usage: "Number of recent ethash caches to keep in memory (16MB each)",
|
||||
Value: ethconfig.Defaults.Ethash.CachesInMem,
|
||||
Category: flags.EthashCategory,
|
||||
}
|
||||
EthashCachesOnDiskFlag = &cli.IntFlag{
|
||||
Name: "ethash.cachesondisk",
|
||||
Usage: "Number of recent ethash caches to keep on disk (16MB each)",
|
||||
Value: ethconfig.Defaults.Ethash.CachesOnDisk,
|
||||
Category: flags.EthashCategory,
|
||||
}
|
||||
EthashCachesLockMmapFlag = &cli.BoolFlag{
|
||||
Name: "ethash.cacheslockmmap",
|
||||
Usage: "Lock memory maps of recent ethash caches",
|
||||
Category: flags.EthashCategory,
|
||||
}
|
||||
EthashDatasetDirFlag = &flags.DirectoryFlag{
|
||||
Name: "ethash.dagdir",
|
||||
Usage: "Directory to store the ethash mining DAGs",
|
||||
Value: flags.DirectoryString(ethconfig.Defaults.Ethash.DatasetDir),
|
||||
Category: flags.EthashCategory,
|
||||
}
|
||||
EthashDatasetsInMemoryFlag = &cli.IntFlag{
|
||||
Name: "ethash.dagsinmem",
|
||||
Usage: "Number of recent ethash mining DAGs to keep in memory (1+GB each)",
|
||||
Value: ethconfig.Defaults.Ethash.DatasetsInMem,
|
||||
Category: flags.EthashCategory,
|
||||
}
|
||||
EthashDatasetsOnDiskFlag = &cli.IntFlag{
|
||||
Name: "ethash.dagsondisk",
|
||||
Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)",
|
||||
Value: ethconfig.Defaults.Ethash.DatasetsOnDisk,
|
||||
Category: flags.EthashCategory,
|
||||
}
|
||||
EthashDatasetsLockMmapFlag = &cli.BoolFlag{
|
||||
Name: "ethash.dagslockmmap",
|
||||
Usage: "Lock memory maps for recent ethash mining DAGs",
|
||||
Category: flags.EthashCategory,
|
||||
}
|
||||
|
||||
// Transaction pool settings
|
||||
TxPoolLocalsFlag = &cli.StringFlag{
|
||||
Name: "txpool.locals",
|
||||
@@ -503,6 +455,12 @@ var (
|
||||
Usage: "Raise the open file descriptor resource limit (default = system fd limit)",
|
||||
Category: flags.PerfCategory,
|
||||
}
|
||||
CryptoKZGFlag = &cli.StringFlag{
|
||||
Name: "crypto.kzg",
|
||||
Usage: "KZG library implementation to use; gokzg (recommended) or ckzg",
|
||||
Value: "gokzg",
|
||||
Category: flags.PerfCategory,
|
||||
}
|
||||
|
||||
// Miner settings
|
||||
MiningEnabledFlag = &cli.BoolFlag{
|
||||
@@ -510,22 +468,6 @@ var (
|
||||
Usage: "Enable mining",
|
||||
Category: flags.MinerCategory,
|
||||
}
|
||||
MinerThreadsFlag = &cli.IntFlag{
|
||||
Name: "miner.threads",
|
||||
Usage: "Number of CPU threads to use for mining",
|
||||
Value: 0,
|
||||
Category: flags.MinerCategory,
|
||||
}
|
||||
MinerNotifyFlag = &cli.StringFlag{
|
||||
Name: "miner.notify",
|
||||
Usage: "Comma separated HTTP URL list to notify of new work packages",
|
||||
Category: flags.MinerCategory,
|
||||
}
|
||||
MinerNotifyFullFlag = &cli.BoolFlag{
|
||||
Name: "miner.notify.full",
|
||||
Usage: "Notify with pending block headers instead of work packages",
|
||||
Category: flags.MinerCategory,
|
||||
}
|
||||
MinerGasLimitFlag = &cli.Uint64Flag{
|
||||
Name: "miner.gaslimit",
|
||||
Usage: "Target gas ceiling for mined blocks",
|
||||
@@ -554,11 +496,6 @@ var (
|
||||
Value: ethconfig.Defaults.Miner.Recommit,
|
||||
Category: flags.MinerCategory,
|
||||
}
|
||||
MinerNoVerifyFlag = &cli.BoolFlag{
|
||||
Name: "miner.noverify",
|
||||
Usage: "Disable remote sealing verification",
|
||||
Category: flags.MinerCategory,
|
||||
}
|
||||
MinerNewPayloadTimeout = &cli.DurationFlag{
|
||||
Name: "miner.newpayload-timeout",
|
||||
Usage: "Specify the maximum time allowance for creating a new payload",
|
||||
@@ -648,11 +585,6 @@ var (
|
||||
Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)",
|
||||
Category: flags.MetricsCategory,
|
||||
}
|
||||
FakePoWFlag = &cli.BoolFlag{
|
||||
Name: "fakepow",
|
||||
Usage: "Disables proof-of-work verification",
|
||||
Category: flags.LoggingCategory,
|
||||
}
|
||||
NoCompactionFlag = &cli.BoolFlag{
|
||||
Name: "nocompaction",
|
||||
Usage: "Disables db compaction after import",
|
||||
@@ -1604,38 +1536,7 @@ func setTxPool(ctx *cli.Context, cfg *txpool.Config) {
|
||||
}
|
||||
}
|
||||
|
||||
func setEthash(ctx *cli.Context, cfg *ethconfig.Config) {
|
||||
if ctx.IsSet(EthashCacheDirFlag.Name) {
|
||||
cfg.Ethash.CacheDir = ctx.String(EthashCacheDirFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(EthashDatasetDirFlag.Name) {
|
||||
cfg.Ethash.DatasetDir = ctx.String(EthashDatasetDirFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(EthashCachesInMemoryFlag.Name) {
|
||||
cfg.Ethash.CachesInMem = ctx.Int(EthashCachesInMemoryFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(EthashCachesOnDiskFlag.Name) {
|
||||
cfg.Ethash.CachesOnDisk = ctx.Int(EthashCachesOnDiskFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(EthashCachesLockMmapFlag.Name) {
|
||||
cfg.Ethash.CachesLockMmap = ctx.Bool(EthashCachesLockMmapFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(EthashDatasetsInMemoryFlag.Name) {
|
||||
cfg.Ethash.DatasetsInMem = ctx.Int(EthashDatasetsInMemoryFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(EthashDatasetsOnDiskFlag.Name) {
|
||||
cfg.Ethash.DatasetsOnDisk = ctx.Int(EthashDatasetsOnDiskFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(EthashDatasetsLockMmapFlag.Name) {
|
||||
cfg.Ethash.DatasetsLockMmap = ctx.Bool(EthashDatasetsLockMmapFlag.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func setMiner(ctx *cli.Context, cfg *miner.Config) {
|
||||
if ctx.IsSet(MinerNotifyFlag.Name) {
|
||||
cfg.Notify = strings.Split(ctx.String(MinerNotifyFlag.Name), ",")
|
||||
}
|
||||
cfg.NotifyFull = ctx.Bool(MinerNotifyFullFlag.Name)
|
||||
if ctx.IsSet(MinerExtraDataFlag.Name) {
|
||||
cfg.ExtraData = []byte(ctx.String(MinerExtraDataFlag.Name))
|
||||
}
|
||||
@@ -1648,9 +1549,6 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
|
||||
if ctx.IsSet(MinerRecommitIntervalFlag.Name) {
|
||||
cfg.Recommit = ctx.Duration(MinerRecommitIntervalFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(MinerNoVerifyFlag.Name) {
|
||||
cfg.Noverify = ctx.Bool(MinerNoVerifyFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(MinerNewPayloadTimeout.Name) {
|
||||
cfg.NewPayloadTimeout = ctx.Duration(MinerNewPayloadTimeout.Name)
|
||||
}
|
||||
@@ -1741,7 +1639,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||
setEtherbase(ctx, cfg)
|
||||
setGPO(ctx, &cfg.GPO, ctx.String(SyncModeFlag.Name) == "light")
|
||||
setTxPool(ctx, &cfg.TxPool)
|
||||
setEthash(ctx, cfg)
|
||||
setMiner(ctx, &cfg.Miner)
|
||||
setRequiredBlocks(ctx, cfg)
|
||||
setLes(ctx, cfg)
|
||||
@@ -1857,7 +1754,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||
cfg.EthDiscoveryURLs = SplitAndTrim(urls)
|
||||
}
|
||||
}
|
||||
|
||||
// Override any default configs for hard coded networks.
|
||||
switch {
|
||||
case ctx.Bool(MainnetFlag.Name):
|
||||
@@ -1967,6 +1863,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||
SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
|
||||
}
|
||||
}
|
||||
// Set any dangling config values
|
||||
if ctx.String(CryptoKZGFlag.Name) != "gokzg" && ctx.String(CryptoKZGFlag.Name) != "ckzg" {
|
||||
Fatalf("--%s flag must be 'gokzg' or 'ckzg'", CryptoKZGFlag.Name)
|
||||
}
|
||||
log.Info("Initializing the KZG library", "backend", ctx.String(CryptoKZGFlag.Name))
|
||||
if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil {
|
||||
Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if
|
||||
@@ -2226,15 +2130,14 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
||||
gspec = MakeGenesis(ctx)
|
||||
chainDb = MakeChainDatabase(ctx, stack, readonly)
|
||||
)
|
||||
cliqueConfig, err := core.LoadCliqueConfig(chainDb, gspec)
|
||||
config, err := core.LoadChainConfig(chainDb, gspec)
|
||||
if err != nil {
|
||||
Fatalf("%v", err)
|
||||
}
|
||||
ethashConfig := ethconfig.Defaults.Ethash
|
||||
if ctx.Bool(FakePoWFlag.Name) {
|
||||
ethashConfig.PowMode = ethash.ModeFake
|
||||
engine, err := ethconfig.CreateConsensusEngine(config, chainDb)
|
||||
if err != nil {
|
||||
Fatalf("%v", err)
|
||||
}
|
||||
engine := ethconfig.CreateConsensusEngine(stack, ðashConfig, cliqueConfig, nil, false, chainDb)
|
||||
if gcmode := ctx.String(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" {
|
||||
Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user