Created
June 17, 2020 02:50
-
-
Save rjl493456442/a1de5eba2b16e68b6884822da8d7a0a7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2020 The go-ethereum Authors | |
// This file is part of the go-ethereum library. | |
// | |
// The go-ethereum library is free software: you can redistribute it and/or modify | |
// it under the terms of the GNU Lesser General Public License as published by | |
// the Free Software Foundation, either version 3 of the License, or | |
// (at your option) any later version. | |
// | |
// The go-ethereum library is distributed in the hope that it will be useful, | |
// but WITHOUT ANY WARRANTY; without even the implied warranty of | |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
// GNU Lesser General Public License for more details. | |
// | |
// You should have received a copy of the GNU Lesser General Public License | |
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | |
// +build none | |
// This file contains a txpool stress test. The workload is inserting countless local | |
// transactions to ensure that "all local transactions won't be dropped and will be | |
// eventually broadcasted in a reasonable time window". | |
package main | |
import ( | |
"bytes" | |
"crypto/ecdsa" | |
"io/ioutil" | |
"math/big" | |
"math/rand" | |
"os" | |
"sync" | |
"sync/atomic" | |
"time" | |
"github.com/ethereum/go-ethereum/accounts/keystore" | |
"github.com/ethereum/go-ethereum/common" | |
"github.com/ethereum/go-ethereum/common/fdlimit" | |
"github.com/ethereum/go-ethereum/core" | |
"github.com/ethereum/go-ethereum/core/types" | |
"github.com/ethereum/go-ethereum/crypto" | |
"github.com/ethereum/go-ethereum/eth" | |
"github.com/ethereum/go-ethereum/eth/downloader" | |
"github.com/ethereum/go-ethereum/log" | |
"github.com/ethereum/go-ethereum/metrics" | |
"github.com/ethereum/go-ethereum/miner" | |
"github.com/ethereum/go-ethereum/node" | |
"github.com/ethereum/go-ethereum/p2p" | |
"github.com/ethereum/go-ethereum/p2p/enode" | |
"github.com/ethereum/go-ethereum/params" | |
) | |
func main() { | |
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) | |
fdlimit.Raise(2048) | |
// Generate a batch of accounts to seal and fund with | |
faucets := make([]*ecdsa.PrivateKey, 128) | |
for i := 0; i < len(faucets); i++ { | |
faucets[i], _ = crypto.GenerateKey() | |
} | |
// Create a batch of sealers, so that we can create more | |
// tiny reorgs for cornercase testing. | |
sealers := make([]*ecdsa.PrivateKey, 1) | |
for i := 0; i < len(sealers); i++ { | |
sealers[i], _ = crypto.GenerateKey() | |
} | |
// Create a Clique network based off of the Rinkeby config | |
genesis := makeGenesis(faucets, sealers) | |
var ( | |
nodes []*node.Node | |
enodes []*enode.Node | |
) | |
for _, sealer := range sealers { | |
// Start the node and wait until it's up | |
node, err := makeSealer(genesis) | |
if err != nil { | |
panic(err) | |
} | |
defer node.Close() | |
for node.Server().NodeInfo().Ports.Listener == 0 { | |
time.Sleep(250 * time.Millisecond) | |
} | |
// Connect the node to al the previous ones | |
for _, n := range enodes { | |
node.Server().AddPeer(n) | |
} | |
// Start tracking the node and it's enode | |
nodes = append(nodes, node) | |
enodes = append(enodes, node.Server().Self()) | |
// Inject the signer key and start sealing with it | |
store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) | |
signer, err := store.ImportECDSA(sealer, "") | |
if err != nil { | |
panic(err) | |
} | |
if err := store.Unlock(signer, ""); err != nil { | |
panic(err) | |
} | |
} | |
// Iterate over all the nodes and start signing with them | |
time.Sleep(3 * time.Second) | |
for _, node := range nodes { | |
var ethereum *eth.Ethereum | |
if err := node.Service(ðereum); err != nil { | |
panic(err) | |
} | |
if err := ethereum.StartMining(1); err != nil { | |
panic(err) | |
} | |
} | |
time.Sleep(3 * time.Second) | |
// Start injecting transactions from the faucet like crazy | |
type txOp struct { | |
tx *types.Transaction | |
index int | |
elapsed time.Duration | |
} | |
var ( | |
nonces = make([]uint64, len(faucets)) | |
opts = make(chan *txOp) | |
close = make(chan struct{}) | |
) | |
// Local transactions checker. Since we are inserting in local | |
// mode, ALL transactions should eventually be mined. Try to | |
// detect silent droppers. | |
go func() { | |
metrics.Enabled = true | |
var ( | |
eths []*eth.Ethereum | |
txopts = make(map[common.Hash]*txOp) | |
metric = metrics.NewRegisteredHistogram("insertion", nil, metrics.NewExpDecaySample(1028, 0.015)) | |
) | |
for _, n := range nodes { | |
var ethereum *eth.Ethereum | |
if err := n.Service(ðereum); err != nil { | |
panic(err) | |
} | |
eths = append(eths, ethereum) | |
} | |
var headCh = make(chan core.ChainHeadEvent) | |
sub := eths[0].BlockChain().SubscribeChainHeadEvent(headCh) | |
defer sub.Unsubscribe() | |
ticker := time.NewTicker(time.Second * 5) | |
defer ticker.Stop() | |
for { | |
select { | |
case op := <-opts: | |
txopts[op.tx.Hash()] = op | |
metric.Update(int64(op.elapsed)) | |
case <-headCh: | |
// Stop the world, ensure the local transactions are either mined or staying in the txpool. | |
//for hash, op := range txopts { | |
// if lookup := eths[op.index].BlockChain().GetTransactionLookup(hash); lookup != nil { | |
// delete(txopts, hash) | |
// continue | |
// } | |
// if !eths[op.index].TxPool().Has(hash) { | |
// delete(txopts, hash) | |
// log.Error("Transaction lost", "hash", hash) | |
// } | |
//} | |
case <-ticker.C: | |
var ctx []interface{} | |
for index, eth := range eths { | |
pending, queue := eth.TxPool().Stats() | |
ctx = append(ctx, "node", index, "pending", pending, "queue", queue) | |
} | |
ms := metric.Snapshot() | |
ps := ms.Percentiles([]float64{0.5, 0.75, 0.95}) | |
ctx = append(ctx, "count", ms.Count(), "max", ms.Max(), "mean", ms.Mean(), "min", ms.Min(), "stddev", ms.StdDev(), | |
"p50", ps[0], "p75", ps[1], "p95", ps[2]) | |
log.Info("Inspect txpool", ctx...) | |
case <-close: | |
return | |
} | |
} | |
}() | |
var thread = 8 | |
var wg sync.WaitGroup | |
for i := 0; i < thread; i++ { | |
wg.Add(1) | |
go func() { | |
defer wg.Done() | |
for { | |
var ( | |
ethereum *eth.Ethereum | |
index = rand.Intn(len(faucets)) | |
nodeIndex = index % len(nodes) | |
) | |
// Fetch the accessor for the relevant signer | |
if err := nodes[nodeIndex].Service(ðereum); err != nil { | |
panic(err) | |
} | |
// Create a self transaction and inject into the pool. GasPrice | |
// is not set, we rely on the gpo for price recommendation. | |
nonce := atomic.AddUint64(&nonces[index], 1) - 1 | |
tx, err := types.SignTx(types.NewTransaction(nonce, crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, nil, nil), types.HomesteadSigner{}, faucets[index]) | |
if err != nil { | |
panic(err) | |
} | |
start := time.Now() | |
if err := ethereum.TxPool().AddLocal(tx); err != nil { | |
panic(err) | |
} | |
opts <- &txOp{tx, nodeIndex, time.Since(start)} | |
} | |
}() | |
} | |
wg.Wait() | |
} | |
// makeGenesis creates a custom Clique genesis block based on some pre-defined | |
// signer and faucet accounts. | |
func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core.Genesis { | |
// Create a Clique network based off of the Rinkeby config | |
genesis := core.DefaultRinkebyGenesisBlock() | |
genesis.GasLimit = 10000000 | |
genesis.Config.ChainID = big.NewInt(18) | |
genesis.Config.Clique.Period = 15 | |
genesis.Config.EIP150Hash = common.Hash{} | |
genesis.Alloc = core.GenesisAlloc{} | |
for _, faucet := range faucets { | |
genesis.Alloc[crypto.PubkeyToAddress(faucet.PublicKey)] = core.GenesisAccount{ | |
Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil), | |
} | |
} | |
// Sort the signers and embed into the extra-data section | |
signers := make([]common.Address, len(sealers)) | |
for i, sealer := range sealers { | |
signers[i] = crypto.PubkeyToAddress(sealer.PublicKey) | |
} | |
for i := 0; i < len(signers); i++ { | |
for j := i + 1; j < len(signers); j++ { | |
if bytes.Compare(signers[i][:], signers[j][:]) > 0 { | |
signers[i], signers[j] = signers[j], signers[i] | |
} | |
} | |
} | |
genesis.ExtraData = make([]byte, 32+len(signers)*common.AddressLength+65) | |
for i, signer := range signers { | |
copy(genesis.ExtraData[32+i*common.AddressLength:], signer[:]) | |
} | |
// Return the genesis block for initialization | |
return genesis | |
} | |
func makeSealer(genesis *core.Genesis) (*node.Node, error) { | |
// Define the basic configurations for the Ethereum node | |
datadir, _ := ioutil.TempDir("", "") | |
config := &node.Config{ | |
Name: "geth", | |
Version: params.Version, | |
DataDir: datadir, | |
P2P: p2p.Config{ | |
ListenAddr: "0.0.0.0:0", | |
NoDiscovery: true, | |
MaxPeers: 25, | |
}, | |
NoUSB: true, | |
} | |
// Start the node and configure a full Ethereum node on it | |
stack, err := node.New(config) | |
if err != nil { | |
return nil, err | |
} | |
txpoolConfig := core.TxPoolConfig{ | |
Journal: "transactions.rlp", | |
Rejournal: time.Hour, | |
PriceLimit: 1, | |
PriceBump: 10, | |
AccountSlots: 4, | |
GlobalSlots: 1024, | |
AccountQueue: 16, | |
GlobalQueue: 256, | |
Lifetime: 3 * time.Hour, | |
} | |
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { | |
return eth.New(ctx, ð.Config{ | |
Genesis: genesis, | |
NetworkId: genesis.Config.ChainID.Uint64(), | |
SyncMode: downloader.FullSync, | |
DatabaseCache: 256, | |
DatabaseHandles: 256, | |
TxPool: txpoolConfig, | |
GPO: eth.DefaultConfig.GPO, | |
Miner: miner.Config{ | |
GasFloor: genesis.GasLimit * 9 / 10, | |
GasCeil: genesis.GasLimit * 11 / 10, | |
GasPrice: big.NewInt(1), | |
Recommit: 3 * time.Second, | |
}, | |
}) | |
}); err != nil { | |
return nil, err | |
} | |
// Start the node and return if successful | |
return stack, stack.Start() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment