WIP: Update for testing with Merge fixturenet.
This commit is contained in:
parent
8ab29bb2b2
commit
8373b23243
10
Makefile
10
Makefile
@ -1,4 +1,12 @@
|
|||||||
## Build docker image
|
## Build docker image
|
||||||
.PHONY: docker-build
|
.PHONY: docker-build
|
||||||
docker-build:
|
docker-build:
|
||||||
docker build -t vulcanize/tx_spammer -f Dockerfile .
|
docker build -t vulcanize/tx_spammer -f Dockerfile .
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
GO111MODULE=on go build -o tx_spammer .
|
||||||
|
|
||||||
|
.PHONY: contract
|
||||||
|
contract:
|
||||||
|
cd sol && solc --abi --bin -o build --overwrite Test.sol
|
||||||
|
@ -1,36 +1,30 @@
|
|||||||
[eth]
|
[eth]
|
||||||
keyDirPath = "" # path to the directory with all of the key pairs to use - env: $ETH_KEY_DIR_PATH
|
keyDirPath = "./keys/" # path to the directory with all of the key pairs to use - env: $ETH_KEY_DIR_PATH
|
||||||
httpPath = "" # http url for the node we wish to send all our transactions to - env: $ETH_HTTP_PATH
|
httpPath = "http://localhost:8545" # http url for the node we wish to send all our transactions to - env: $ETH_HTTP_PATH
|
||||||
chainID = 421 # chain id - env: $ETH_CHAIN_ID
|
|
||||||
type = "L2" # tx type (EIP1559, Standard, or L2) - env: $ETH_TX_TYPE
|
|
||||||
|
|
||||||
[deployment]
|
[deployment]
|
||||||
number = 1 # number of contracts we will deploy for each key at keyPath - env: $ETH_DEPLOYMENT_NUMBER
|
number = 1 # number of contracts we will deploy for each key at keyPath - env: $ETH_DEPLOYMENT_NUMBER
|
||||||
hexData = "" # hex data for the contracts we will deploy - env: $ETH_DEPLOYMENT_HEX_DATA
|
hexData = "608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506103cc806100606000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80632802348a1461004657806343d726d614610062578063b90d3d0c1461006c575b600080fd5b610060600480360381019061005b919061025c565b61009c565b005b61006a6100e4565b005b6100866004803603810190610081919061029c565b6101ab565b60405161009391906102d8565b60405180910390f35b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610172576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161016990610376565b60405180910390fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b60016020528060005260406000206000915090505481565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101f3826101c8565b9050919050565b610203816101e8565b811461020e57600080fd5b50565b600081359050610220816101fa565b92915050565b6000819050919050565b61023981610226565b811461024457600080fd5b50565b60008135905061025681610230565b92915050565b60008060408385031215610273576102726101c3565b5b600061028185828601610211565b925050602061029285828601610247565b9150509250929050565b6000602082840312156102b2576102b16101c3565b5b60006102c084828501610211565b91505092915050565b6102d281610226565b82525050565b60006020820190506102ed60008301846102c9565b92915050565b600082825260208201905092915050565b7f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f60008201527f6e2e000000000000000000000000000000000000000000000000000000000000602082015250565b60006103606022836102f3565b915061036b82610304565b604082019050919050565b6000602082019050818103600083015261038f81610353565b905091905056fea26469706673582212201d7a5e3145cb5b1067ff7c8bb867ec5228d0645ac9eea2ad26735c9b2ad33cd764736f6c63430008110033" # hex data for the contracts we will deploy - env: $ETH_DEPLOYMENT_HEX_DATA
|
||||||
gasLimit = 0 # gasLimit to use for the deployment txs - env: $ETH_DEPLOYMENT_GAS_LIMIT
|
gasLimit = 100000 # gasLimit to use for the deployment txs - env: $ETH_DEPLOYMENT_GAS_LIMIT
|
||||||
gasPrice = "0" # gasPrice to use for the deployment txs - env: $ETH_DEPLOYMENT_GAS_PRICE
|
gasTipCap = "1000000000" # gasTipCap to use for the deployment txs - env: $ETH_DEPLOYMENT_GAS_TIP_CAP
|
||||||
|
gasFeeCap = "1000000007" # gasFeeCap to use for the deployment txs - env: $ETH_DEPLOYMENT_GAS_FEE_CAP
|
||||||
[optimism]
|
|
||||||
l1Sender = "" # l1 sender address hex to use for all txs - env: $ETH_OPTIMISM_L1_SENDER
|
|
||||||
l1RollupTxId = 0 # rollup tx id to use for all txs - env: $ETH_OPTIMISM_ROLLUP_TX_ID
|
|
||||||
sigHashType = 0 # sig hash type to use for all txs - env: $ETH_OPTIMISM_SIG_HASH_TYPE
|
|
||||||
queueOrigin = 0 # queue origin id to use for all txs - env: $ETH_OPTIMISM_QUEUE_ORIGIN
|
|
||||||
|
|
||||||
[contractSpammer]
|
[contractSpammer]
|
||||||
frequency = 30 # how often to send a transaction (in seconds) - env: $ETH_CALL_FREQ
|
frequency = 10 # how often to send a transaction (in milliseconds) - env: $ETH_CALL_FREQ
|
||||||
totalNumber = 10000 # total number of transactions to send (per sender) - env: $ETH_CALL_TOTAL_NUMBER
|
totalNumber = 1000 # total number of transactions to send (per sender) - env: $ETH_CALL_TOTAL_NUMBER
|
||||||
abiPath = "" # path to the abi file for the contract we are calling - env: $ETH_CALL_ABI_PATH
|
abiPath = "sol/build/Test.abi" # path to the abi file for the contract we are calling - env: $ETH_CALL_ABI_PATH
|
||||||
# NOTE: we expect to be calling a method such as Put(address addr, uint256 val) where the first argument is an
|
# NOTE: we expect to be calling a method such as Put(address addr, uint256 val) where the first argument is an
|
||||||
# integer than we can increment to store values at new locations in the contract trie (to grow it) and
|
# integer than we can increment to store values at new locations in the contract trie (to grow it) and
|
||||||
# the second argument is an integer value that we store at these positions
|
# the second argument is an integer value that we store at these positions
|
||||||
methodName = "Put" # the method name we are calling - env: $ETH_CALL_METHOD_NAME
|
methodName = "Put" # the method name we are calling - env: $ETH_CALL_METHOD_NAME
|
||||||
storageValue = 1337 # the value we store at each position - env: $ETH_CALL_STORAGE_VALUE
|
gasLimit = 21000 # gasLimit to use for the eth call txs - env: $ETH_CALL_GAS_LIMIT
|
||||||
gasLimit = 0 # gasLimit to use for the eth call txs - env: $ETH_CALL_GAS_LIMIT
|
gasTipCap = "1000000000" # gasTipCap to use for the eth call txs - env: $ETH_CALL_GAS_TIP_CAP
|
||||||
gasPrice = "0" # gasPrice to use for the eth call txs - env: $ETH_CALL_GAS_PRICE
|
gasFeeCap = "1000000007" # gasFeeCap to use for the eth call txs - env: $ETH_CALL_GAS_FEE_CAP
|
||||||
|
|
||||||
[sendSpammer]
|
[sendSpammer]
|
||||||
frequency = 30 # how often to send a transaction (in seconds) - env: $ETH_SEND_FREQ
|
frequency = 100 # how often to send a transaction (in milliseconds) - env: $ETH_SEND_FREQ
|
||||||
totalNumber = 10000 # total number of transactions to send (per sender) - env: $ETH_SEND_TOTAL_NUMBER
|
totalNumber = 1000 # total number of transactions to send (per sender) - env: $ETH_SEND_TOTAL_NUMBER
|
||||||
amount = "1" # amount of wei (1x10^-18 ETH) to send in each tx (be mindful of the genesis allocations) - env: $ETH_SEND_AMOUNT
|
amount = "10000" # amount of wei (1x10^-18 ETH) to send in each tx (be mindful of the genesis allocations) - env: $ETH_SEND_AMOUNT
|
||||||
gasLimit = 0 # gasLimit to use for the eth transfer txs - env: $ETH_SEND_GAS_LIMIT
|
gasLimit = 21000 # gasLimit to use for the eth transfer txs - env: $ETH_SEND_GAS_LIMIT
|
||||||
gasPrice = "0" # gasPrice to use for the eth transfer txs - env: $ETH_SEND_GAS_PRICE
|
gasTipCap = "1000000000" # gasTipCap to use for the eth transfer txs - env: $ETH_SEND_GAS_TIP_CAP
|
||||||
|
gasFeeCap = "1000000007" # gasFeeCap to use for the eth transfer txs - env: $ETH_SEND_GAS_FEE_CAP
|
||||||
|
53
go.mod
53
go.mod
@ -3,10 +3,55 @@ module github.com/vulcanize/tx_spammer
|
|||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ethereum/go-ethereum v1.9.10
|
github.com/Azure/azure-pipeline-go v0.2.2 // indirect
|
||||||
github.com/sirupsen/logrus v1.7.0
|
github.com/Azure/azure-storage-blob-go v0.7.0 // indirect
|
||||||
github.com/spf13/cobra v1.1.0
|
github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||||
|
github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 // indirect
|
||||||
|
github.com/aws/aws-sdk-go v1.25.48 // indirect
|
||||||
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c // indirect
|
||||||
|
github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 // indirect
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect
|
||||||
|
github.com/coreos/bbolt v1.3.2 // indirect
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||||
|
github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa // indirect
|
||||||
|
github.com/ethereum/go-ethereum v1.10.25
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
|
github.com/go-stack/stack v1.8.1 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
|
||||||
|
github.com/jmoiron/sqlx v1.2.0 // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.1.0 // indirect
|
||||||
|
github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 // indirect
|
||||||
|
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff // indirect
|
||||||
|
github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect
|
||||||
|
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.0
|
||||||
|
github.com/soheilhy/cmux v0.1.4 // indirect
|
||||||
|
github.com/spf13/afero v1.9.2 // indirect
|
||||||
|
github.com/spf13/cobra v1.6.0
|
||||||
|
github.com/spf13/viper v1.13.0
|
||||||
|
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect
|
||||||
|
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.5.0 // indirect
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
|
||||||
|
github.com/urfave/cli v1.22.1 // indirect
|
||||||
|
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 // indirect
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
|
go.etcd.io/bbolt v1.3.2 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect
|
||||||
|
golang.org/x/sys v0.1.0 // indirect
|
||||||
|
golang.org/x/text v0.4.0 // indirect
|
||||||
|
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9 // indirect
|
||||||
|
gopkg.in/resty.v1 v1.12.0 // indirect
|
||||||
|
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||||
|
gopkg.in/urfave/cli.v1 v1.20.0 // indirect
|
||||||
|
gotest.tools v2.2.0+incompatible // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/ethereum/go-ethereum v1.9.10 => github.com/vulcanize/go-ethereum v1.9.10-optimism-0.0.2
|
replace github.com/ethereum/go-ethereum v1.9.10 => github.com/vulcanize/go-ethereum v1.9.10-optimism-0.0.2
|
||||||
|
1
keys/0.key
Normal file
1
keys/0.key
Normal file
@ -0,0 +1 @@
|
|||||||
|
888814df89c4358d7ddb3fa4b0213e7331239a80e1f013eaa7b2deca2a41a218
|
1
keys/1.key
Normal file
1
keys/1.key
Normal file
@ -0,0 +1 @@
|
|||||||
|
570b909da9669b2f35a0b1ac70b8358516d55ae1b5b3710e95e9a94395090597
|
1
keys/2.key
Normal file
1
keys/2.key
Normal file
@ -0,0 +1 @@
|
|||||||
|
111b7500bdce494d6f4bcfe8c2a0dde2ef92f751d9070fac6475dbd6d8021b3f
|
1
keys/3.key
Normal file
1
keys/3.key
Normal file
@ -0,0 +1 @@
|
|||||||
|
fb1e9af328c283ca3e2486e7c24d13582b7912057d8b9542ff41503c85bc05c0
|
1
keys/4.key
Normal file
1
keys/4.key
Normal file
@ -0,0 +1 @@
|
|||||||
|
be4aa664815ea3bc3d63118649a733f6c96b243744310806ecb6d96359ab62cf
|
1
keys/5.key
Normal file
1
keys/5.key
Normal file
@ -0,0 +1 @@
|
|||||||
|
6177345b77c4069ac4d553f8b43cf68a799ca4bb63eac93d6cf796d63694ebf0
|
@ -18,8 +18,10 @@ package auto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -28,7 +30,6 @@ import (
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
@ -48,21 +49,17 @@ func init() {
|
|||||||
// Config holds all the parameters for the auto tx spammer
|
// Config holds all the parameters for the auto tx spammer
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// HTTP client for sending transactions
|
// HTTP client for sending transactions
|
||||||
Client *rpc.Client
|
RpcClient *rpc.Client
|
||||||
|
EthClient *ethclient.Client
|
||||||
|
ChainID *big.Int
|
||||||
|
|
||||||
// Key pairs for the accounts we will use to deploy contracts and send txs
|
// Key pairs for the accounts we will use to deploy contracts and send txs
|
||||||
SenderKeys []*ecdsa.PrivateKey
|
SenderKeys []*ecdsa.PrivateKey
|
||||||
SenderAddrs []common.Address
|
SenderAddrs []common.Address
|
||||||
|
|
||||||
// Type of the txs we are working with
|
|
||||||
Type shared.TxType
|
|
||||||
|
|
||||||
// Tx signer for the chain we are working with
|
// Tx signer for the chain we are working with
|
||||||
Signer types.Signer
|
Signer types.Signer
|
||||||
|
|
||||||
// Configuration for Optimism L2
|
|
||||||
OptimismConfig *OptimismConfig
|
|
||||||
|
|
||||||
// Configuration for the initial contract deployment
|
// Configuration for the initial contract deployment
|
||||||
DeploymentConfig *DeploymentConfig
|
DeploymentConfig *DeploymentConfig
|
||||||
|
|
||||||
@ -76,41 +73,40 @@ type Config struct {
|
|||||||
EIP1559Config *EIP1559Config
|
EIP1559Config *EIP1559Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptimismConfig holds the tx paramaters specific to Optimism L2
|
|
||||||
type OptimismConfig struct {
|
|
||||||
L1SenderAddr *common.Address
|
|
||||||
L1RollupTxId *hexutil.Uint64
|
|
||||||
SigHashType types.SignatureHashType
|
|
||||||
QueueOrigin types.QueueOrigin
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeploymentConfig holds the parameters for the contract deployment contracts
|
// DeploymentConfig holds the parameters for the contract deployment contracts
|
||||||
type DeploymentConfig struct {
|
type DeploymentConfig struct {
|
||||||
GasLimit uint64
|
ChainID *big.Int
|
||||||
GasPrice *big.Int
|
GasLimit uint64
|
||||||
Data []byte
|
GasFeeCap *big.Int
|
||||||
|
GasTipCap *big.Int
|
||||||
|
Data []byte
|
||||||
|
|
||||||
Number uint64
|
Number uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallConfig holds the parameters for the contract calling txs
|
// CallConfig holds the parameters for the contract calling txs
|
||||||
type CallConfig struct {
|
type CallConfig struct {
|
||||||
GasLimit uint64
|
ChainID *big.Int
|
||||||
GasPrice *big.Int
|
GasLimit uint64
|
||||||
|
GasFeeCap *big.Int
|
||||||
|
GasTipCap *big.Int
|
||||||
|
Amount *big.Int
|
||||||
|
|
||||||
MethodName string
|
MethodName string
|
||||||
ABI abi.ABI
|
ABI abi.ABI
|
||||||
StorageValue uint64
|
ContractAddrs []common.Address
|
||||||
StorageAddrs []common.Address
|
|
||||||
|
|
||||||
Frequency time.Duration
|
Frequency time.Duration
|
||||||
|
TotalNumber int
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendConfig holds the parameters for the eth transfer txs
|
// SendConfig holds the parameters for the eth transfer txs
|
||||||
type SendConfig struct {
|
type SendConfig struct {
|
||||||
GasLimit uint64
|
ChainID *big.Int
|
||||||
GasPrice *big.Int
|
GasLimit uint64
|
||||||
Amount *big.Int
|
GasFeeCap *big.Int
|
||||||
|
GasTipCap *big.Int
|
||||||
|
Amount *big.Int
|
||||||
|
|
||||||
DestinationAddresses []common.Address
|
DestinationAddresses []common.Address
|
||||||
Frequency time.Duration
|
Frequency time.Duration
|
||||||
@ -133,6 +129,11 @@ func NewConfig() (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ethClient, err := ethclient.Dial(httpPathStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Load keys
|
// Load keys
|
||||||
keyDirPath := viper.GetString(ethKeyDirPath)
|
keyDirPath := viper.GetString(ethKeyDirPath)
|
||||||
if keyDirPath == "" {
|
if keyDirPath == "" {
|
||||||
@ -157,102 +158,66 @@ func NewConfig() (*Config, error) {
|
|||||||
senderAddrs = append(senderAddrs, crypto.PubkeyToAddress(key.PublicKey))
|
senderAddrs = append(senderAddrs, crypto.PubkeyToAddress(key.PublicKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load tx type
|
// Detect chain ID.
|
||||||
txType, err := shared.TxTypeFromString(viper.GetString(ethType))
|
chainID, err := ethClient.ChainID(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load signer
|
// Load signer
|
||||||
chainID := viper.GetUint64(ethChainID)
|
signer, err := shared.TxSigner(chainID)
|
||||||
signer, err := shared.TxSigner(txType, chainID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load optimism config
|
|
||||||
var optimismConfig *OptimismConfig
|
|
||||||
if txType == shared.OptimismL1ToL2 || txType == shared.OptimismL2 {
|
|
||||||
optimismConfig = NewOptimismConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load deployment config
|
// Load deployment config
|
||||||
deploymentConfig, err := NewDeploymentConfig()
|
deploymentConfig, err := NewDeploymentConfig(chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load call config
|
// Load call config
|
||||||
callConfig, err := NewCallConfig()
|
callConfig, err := NewCallConfig(chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load send config
|
// Load send config
|
||||||
sendConfig, err := NewSendConfig()
|
sendConfig, err := NewSendConfig(chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assemble and return
|
// Assemble and return
|
||||||
return &Config{
|
return &Config{
|
||||||
Client: rpcClient,
|
RpcClient: rpcClient,
|
||||||
|
EthClient: ethClient,
|
||||||
SenderKeys: keys,
|
SenderKeys: keys,
|
||||||
SenderAddrs: senderAddrs,
|
SenderAddrs: senderAddrs,
|
||||||
Type: txType,
|
|
||||||
Signer: signer,
|
Signer: signer,
|
||||||
OptimismConfig: optimismConfig,
|
|
||||||
DeploymentConfig: deploymentConfig,
|
DeploymentConfig: deploymentConfig,
|
||||||
CallConfig: callConfig,
|
CallConfig: callConfig,
|
||||||
SendConfig: sendConfig,
|
SendConfig: sendConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOptimismConfig constructs and returns a new OptimismConfig
|
|
||||||
func NewOptimismConfig() *OptimismConfig {
|
|
||||||
l1SenderStr := viper.GetString(ethOptimismL1Sender)
|
|
||||||
var l1Sender *common.Address
|
|
||||||
if l1SenderStr != "" {
|
|
||||||
sender := common.HexToAddress(l1SenderStr)
|
|
||||||
l1Sender = &sender
|
|
||||||
}
|
|
||||||
l1RollupTxId := viper.GetUint64(ethOptimismRollupTxID)
|
|
||||||
l1rtid := (hexutil.Uint64)(l1RollupTxId)
|
|
||||||
sigHashType := viper.GetUint(ethOptimismSigHashType)
|
|
||||||
queueOrigin := viper.GetInt64(ethOptimismQueueOrigin)
|
|
||||||
return &OptimismConfig{
|
|
||||||
L1SenderAddr: l1Sender,
|
|
||||||
L1RollupTxId: &l1rtid,
|
|
||||||
SigHashType: (types.SignatureHashType)(uint8(sigHashType)),
|
|
||||||
QueueOrigin: (types.QueueOrigin)(queueOrigin),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDeploymentConfig constructs and returns a new DeploymentConfig
|
// NewDeploymentConfig constructs and returns a new DeploymentConfig
|
||||||
func NewDeploymentConfig() (*DeploymentConfig, error) {
|
func NewDeploymentConfig(chainID *big.Int) (*DeploymentConfig, error) {
|
||||||
hexData := viper.GetString(ethDeploymentData)
|
hexData := viper.GetString(ethDeploymentData)
|
||||||
data := common.Hex2Bytes(hexData)
|
data := common.Hex2Bytes(hexData)
|
||||||
gasPriceStr := viper.GetString(ethDeploymentGasPrice)
|
|
||||||
gasPrice, ok := new(big.Int).SetString(gasPriceStr, 10)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unable to convert gasPrice string (%s) into big.Int", gasPriceStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DeploymentConfig{
|
return &DeploymentConfig{
|
||||||
Number: viper.GetUint64(ethDeploymentNumber),
|
ChainID: chainID,
|
||||||
Data: data,
|
Number: viper.GetUint64(ethDeploymentNumber),
|
||||||
GasPrice: gasPrice,
|
Data: data,
|
||||||
GasLimit: viper.GetUint64(ethDeploymentGasLimit),
|
GasLimit: viper.GetUint64(ethDeploymentGasLimit),
|
||||||
|
GasFeeCap: big.NewInt(viper.GetInt64(ethDeploymentGasFeeCap)),
|
||||||
|
GasTipCap: big.NewInt(viper.GetInt64(ethDeploymentGasTipCap)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCallConfig constructs and returns a new CallConfig
|
// NewCallConfig constructs and returns a new CallConfig
|
||||||
func NewCallConfig() (*CallConfig, error) {
|
func NewCallConfig(chainID *big.Int) (*CallConfig, error) {
|
||||||
gasPriceStr := viper.GetString(ethCallGasPrice)
|
|
||||||
gasPrice, ok := new(big.Int).SetString(gasPriceStr, 10)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unable to convert gasPrice string (%s) into big.Int", gasPriceStr)
|
|
||||||
}
|
|
||||||
abiPath := viper.GetString(ethCallABIPath)
|
abiPath := viper.GetString(ethCallABIPath)
|
||||||
if abiPath == "" {
|
if abiPath == "" {
|
||||||
return nil, fmt.Errorf("missing contractSpammer.abiPath")
|
return nil, fmt.Errorf("missing contractSpammer.abiPath")
|
||||||
@ -270,44 +235,37 @@ func NewCallConfig() (*CallConfig, error) {
|
|||||||
if !exist {
|
if !exist {
|
||||||
return nil, fmt.Errorf("method '%s' not found in provided abi", methodName)
|
return nil, fmt.Errorf("method '%s' not found in provided abi", methodName)
|
||||||
}
|
}
|
||||||
number := viper.GetUint64(ethCallTotalNumber)
|
|
||||||
addrs := make([]common.Address, number)
|
|
||||||
for i := uint64(0); i < number; i++ {
|
|
||||||
addrs[i] = crypto.CreateAddress(storageAddressSeed, i)
|
|
||||||
}
|
|
||||||
return &CallConfig{
|
return &CallConfig{
|
||||||
GasPrice: gasPrice,
|
ChainID: chainID,
|
||||||
GasLimit: viper.GetUint64(ethCallGasLimit),
|
GasLimit: viper.GetUint64(ethCallGasLimit),
|
||||||
MethodName: methodName,
|
GasFeeCap: big.NewInt(viper.GetInt64(ethCallGasFeeCap)),
|
||||||
ABI: parsedABI,
|
GasTipCap: big.NewInt(viper.GetInt64(ethCallGasTipCap)),
|
||||||
StorageValue: viper.GetUint64(ethCallStorageValue),
|
MethodName: methodName,
|
||||||
Frequency: viper.GetDuration(ethCallFrequency),
|
ABI: parsedABI,
|
||||||
StorageAddrs: addrs,
|
Frequency: viper.GetDuration(ethCallFrequency) * time.Millisecond,
|
||||||
|
TotalNumber: viper.GetInt(ethCallTotalNumber),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSendConfig constructs and returns a new SendConfig
|
// NewSendConfig constructs and returns a new SendConfig
|
||||||
func NewSendConfig() (*SendConfig, error) {
|
func NewSendConfig(chainID *big.Int) (*SendConfig, error) {
|
||||||
amountStr := viper.GetString(ethSendAmount)
|
amountStr := viper.GetString(ethSendAmount)
|
||||||
amount, ok := new(big.Int).SetString(amountStr, 10)
|
amount, ok := new(big.Int).SetString(amountStr, 10)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unable to convert amount string (%s) into big.Int", amountStr)
|
return nil, fmt.Errorf("unable to convert amount string (%s) into big.Int", amountStr)
|
||||||
}
|
}
|
||||||
gasPriceStr := viper.GetString(ethSendGasPrice)
|
|
||||||
gasPrice, ok := new(big.Int).SetString(gasPriceStr, 10)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unable to convert gasPrice string (%s) into big.Int", gasPriceStr)
|
|
||||||
}
|
|
||||||
number := viper.GetUint64(ethSendTotalNumber)
|
number := viper.GetUint64(ethSendTotalNumber)
|
||||||
addrs := make([]common.Address, number)
|
addrs := make([]common.Address, number)
|
||||||
for i := uint64(0); i < number; i++ {
|
for i := uint64(0); i < number; i++ {
|
||||||
addrs[i] = crypto.CreateAddress(receiverAddressSeed, i)
|
addrs[i] = crypto.CreateAddress(receiverAddressSeed, i)
|
||||||
}
|
}
|
||||||
return &SendConfig{
|
return &SendConfig{
|
||||||
|
ChainID: chainID,
|
||||||
DestinationAddresses: addrs,
|
DestinationAddresses: addrs,
|
||||||
Frequency: viper.GetDuration(ethSendFrequency),
|
Frequency: viper.GetDuration(ethSendFrequency) * time.Millisecond,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
GasPrice: gasPrice,
|
|
||||||
GasLimit: viper.GetUint64(ethSendGasLimit),
|
GasLimit: viper.GetUint64(ethSendGasLimit),
|
||||||
|
GasFeeCap: big.NewInt(viper.GetInt64(ethSendGasFeeCap)),
|
||||||
|
GasTipCap: big.NewInt(viper.GetInt64(ethSendGasTipCap)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package auto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -32,7 +33,6 @@ const (
|
|||||||
// ContractDeployer is responsible for deploying contracts
|
// ContractDeployer is responsible for deploying contracts
|
||||||
type ContractDeployer struct {
|
type ContractDeployer struct {
|
||||||
client *rpc.Client
|
client *rpc.Client
|
||||||
ty shared.TxType
|
|
||||||
txGenerator *TxGenerator
|
txGenerator *TxGenerator
|
||||||
senderKeys []*ecdsa.PrivateKey
|
senderKeys []*ecdsa.PrivateKey
|
||||||
senderAddrs []common.Address
|
senderAddrs []common.Address
|
||||||
@ -42,8 +42,7 @@ type ContractDeployer struct {
|
|||||||
// NewContractDeployer returns a new ContractDeployer
|
// NewContractDeployer returns a new ContractDeployer
|
||||||
func NewContractDeployer(config *Config, gen *TxGenerator) *ContractDeployer {
|
func NewContractDeployer(config *Config, gen *TxGenerator) *ContractDeployer {
|
||||||
return &ContractDeployer{
|
return &ContractDeployer{
|
||||||
client: config.Client,
|
client: config.RpcClient,
|
||||||
ty: config.Type,
|
|
||||||
txGenerator: gen,
|
txGenerator: gen,
|
||||||
config: config.DeploymentConfig,
|
config: config.DeploymentConfig,
|
||||||
senderKeys: config.SenderKeys,
|
senderKeys: config.SenderKeys,
|
||||||
@ -59,11 +58,14 @@ func (cp *ContractDeployer) Deploy() ([]common.Address, error) {
|
|||||||
for i := uint64(0); i < cp.config.Number; i++ {
|
for i := uint64(0); i < cp.config.Number; i++ {
|
||||||
<-ticker.C
|
<-ticker.C
|
||||||
for i, key := range cp.senderKeys {
|
for i, key := range cp.senderKeys {
|
||||||
txBytes, contractAddr, err := cp.txGenerator.GenerateTx(cp.ty, &GenParams{
|
logrus.Infof("Generating contract deployment for %s.", cp.senderAddrs[i].Hex())
|
||||||
|
txBytes, contractAddr, err := cp.txGenerator.GenerateTx(&GenParams{
|
||||||
|
ChainID: cp.config.ChainID,
|
||||||
Sender: cp.senderAddrs[i],
|
Sender: cp.senderAddrs[i],
|
||||||
SenderKey: key,
|
SenderKey: key,
|
||||||
GasLimit: cp.config.GasLimit,
|
GasLimit: cp.config.GasLimit,
|
||||||
GasPrice: cp.config.GasPrice,
|
GasFeeCap: cp.config.GasFeeCap,
|
||||||
|
GasTipCap: cp.config.GasTipCap,
|
||||||
Data: cp.config.Data,
|
Data: cp.config.Data,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -22,91 +22,77 @@ const (
|
|||||||
// env variables
|
// env variables
|
||||||
ETH_KEY_DIR_PATH = "ETH_KEY_DIR_PATH"
|
ETH_KEY_DIR_PATH = "ETH_KEY_DIR_PATH"
|
||||||
ETH_HTTP_PATH = "ETH_HTTP_PATH"
|
ETH_HTTP_PATH = "ETH_HTTP_PATH"
|
||||||
ETH_CHAIN_ID = "ETH_CHAIN_ID"
|
|
||||||
ETH_TX_TYPE = "ETH_TX_TYPE"
|
|
||||||
|
|
||||||
ETH_DEPLOYMENT_NUMBER = "ETH_DEPLOYMENT_NUMBER"
|
ETH_DEPLOYMENT_NUMBER = "ETH_DEPLOYMENT_NUMBER"
|
||||||
ETH_DEPLOYMENT_HEX_DATA = "ETH_DEPLOYMENT_HEX_DATA"
|
ETH_DEPLOYMENT_HEX_DATA = "ETH_DEPLOYMENT_HEX_DATA"
|
||||||
ETH_DEPLOYMENT_GAS_LIMIT = "ETH_DEPLOYMENT_GAS_LIMIT"
|
ETH_DEPLOYMENT_GAS_LIMIT = "ETH_DEPLOYMENT_GAS_LIMIT"
|
||||||
ETH_DEPLOYMENT_GAS_PRICE = "ETH_DEPLOYMENT_GAS_PRICE"
|
ETH_DEPLOYMENT_GAS_FEE_CAP = "ETH_DEPLOYMENT_GAS_FEE_CAP"
|
||||||
|
ETH_DEPLOYMENT_GAS_TIP_CAP = "ETH_DEPLOYMENT_GAS_TIP_CAP"
|
||||||
|
|
||||||
ETH_OPTIMISM_L1_SENDER = "ETH_OPTIMISM_L1_SENDER"
|
ETH_CALL_FREQ = "ETH_CALL_FREQ"
|
||||||
ETH_OPTIMISM_ROLLUP_TX_ID = "ETH_OPTIMISM_ROLLUP_TX_ID"
|
ETH_CALL_TOTAL_NUMBER = "ETH_CALL_TOTAL_NUMBER"
|
||||||
ETH_OPTIMISM_SIG_HASH_TYPE = "ETH_OPTIMISM_SIG_HASH_TYPE"
|
ETH_CALL_ABI_PATH = "ETH_CALL_ABI_PATH"
|
||||||
ETH_OPTIMISM_QUEUE_ORIGIN = "ETH_OPTIMISM_QUEUE_ORIGIN"
|
ETH_CALL_METHOD_NAME = "ETH_CALL_METHOD_NAME"
|
||||||
|
ETH_CALL_GAS_LIMIT = "ETH_CALL_GAS_LIMIT"
|
||||||
ETH_CALL_FREQ = "ETH_CALL_FREQ"
|
ETH_CALL_GAS_FEE_CAP = "ETH_CALL_GAS_FEE_CAP"
|
||||||
ETH_CALL_TOTAL_NUMBER = "ETH_CALL_TOTAL_NUMBER"
|
ETH_CALL_GAS_TIP_CAP = "ETH_CALL_GAS_TIP_CAP"
|
||||||
ETH_CALL_ABI_PATH = "ETH_CALL_ABI_PATH"
|
|
||||||
ETH_CALL_METHOD_NAME = "ETH_CALL_METHOD_NAME"
|
|
||||||
ETH_CALL_STORAGE_VALUE = "ETH_CALL_STORAGE_VALUE"
|
|
||||||
ETH_CALL_GAS_LIMIT = "ETH_CALL_GAS_LIMIT"
|
|
||||||
ETH_CALL_GAS_PRICE = "ETH_CALL_GAS_PRICE"
|
|
||||||
|
|
||||||
ETH_SEND_FREQ = "ETH_SEND_FREQ"
|
ETH_SEND_FREQ = "ETH_SEND_FREQ"
|
||||||
ETH_SEND_TOTAL_NUMBER = "ETH_SEND_TOTAL_NUMBER"
|
ETH_SEND_TOTAL_NUMBER = "ETH_SEND_TOTAL_NUMBER"
|
||||||
ETH_SEND_AMOUNT = "ETH_SEND_AMOUNT"
|
ETH_SEND_AMOUNT = "ETH_SEND_AMOUNT"
|
||||||
ETH_SEND_GAS_LIMIT = "ETH_SEND_GAS_LIMIT"
|
ETH_SEND_GAS_LIMIT = "ETH_SEND_GAS_LIMIT"
|
||||||
ETH_SEND_GAS_PRICE = "ETH_SEND_GAS_PRICE"
|
ETH_SEND_GAS_FEE_CAP = "ETH_SEND_GAS_FEE_CAP"
|
||||||
|
ETH_SEND_GAS_TIP_CAP = "ETH_SEND_GAS_TIP_CAP"
|
||||||
|
|
||||||
// toml bindings
|
// toml bindings
|
||||||
ethKeyDirPath = "eth.keyDirPath"
|
ethKeyDirPath = "eth.keyDirPath"
|
||||||
ethHttpPath = "eth.httpPath"
|
ethHttpPath = "eth.httpPath"
|
||||||
ethChainID = "eth.chainID"
|
|
||||||
ethType = "eth.type"
|
|
||||||
|
|
||||||
ethDeploymentNumber = "deployment.number"
|
ethDeploymentNumber = "deployment.number"
|
||||||
ethDeploymentData = "deployment.hexData"
|
ethDeploymentData = "deployment.hexData"
|
||||||
ethDeploymentGasPrice = "deployment.gasPrice"
|
ethDeploymentGasLimit = "deployment.gasLimit"
|
||||||
ethDeploymentGasLimit = "deployment.gasLimit"
|
ethDeploymentGasFeeCap = "deployment.gasFeeCap"
|
||||||
|
ethDeploymentGasTipCap = "deployment.gasTipCap"
|
||||||
|
|
||||||
ethOptimismL1Sender = "optimism.l1Sender"
|
ethCallFrequency = "contractSpammer.frequency"
|
||||||
ethOptimismRollupTxID = "optimism.l1RollupTxId"
|
ethCallTotalNumber = "contractSpammer.totalNumber"
|
||||||
ethOptimismSigHashType = "optimism.sigHashType"
|
ethCallABIPath = "contractSpammer.abiPath"
|
||||||
ethOptimismQueueOrigin = "optimism.queueOrigin"
|
ethCallMethodName = "contractSpammer.methodName"
|
||||||
|
ethCallGasLimit = "contractSpammer.gasLimit"
|
||||||
ethCallFrequency = "contractSpammer.frequency"
|
ethCallGasFeeCap = "contractSpammer.gasFeeCap"
|
||||||
ethCallTotalNumber = "contractSpammer.totalNumber"
|
ethCallGasTipCap = "contractSpammer.gasTipCap"
|
||||||
ethCallABIPath = "contractSpammer.abiPath"
|
|
||||||
ethCallMethodName = "contractSpammer.methodName"
|
|
||||||
ethCallStorageValue = "contractSpammer.storageValue"
|
|
||||||
ethCallGasLimit = "contractSpammer.gasLimit"
|
|
||||||
ethCallGasPrice = "contractSpammer.gasPrice"
|
|
||||||
|
|
||||||
ethSendFrequency = "sendSpammer.frequency"
|
ethSendFrequency = "sendSpammer.frequency"
|
||||||
ethSendTotalNumber = "sendSpammer.totalNumber"
|
ethSendTotalNumber = "sendSpammer.totalNumber"
|
||||||
ethSendAmount = "sendSpammer.amount"
|
ethSendAmount = "sendSpammer.amount"
|
||||||
ethSendGasLimit = "sendSpammer.gasLimit"
|
ethSendGasLimit = "sendSpammer.gasLimit"
|
||||||
ethSendGasPrice = "sendSpammer.gasPrice"
|
ethSendGasFeeCap = "sendSpammer.gasFeeCap"
|
||||||
|
ethSendGasTipCap = "sendSpammer.gasTipCap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func bindEnv() {
|
func bindEnv() {
|
||||||
viper.BindEnv(ethKeyDirPath, ETH_KEY_DIR_PATH)
|
viper.BindEnv(ethKeyDirPath, ETH_KEY_DIR_PATH)
|
||||||
viper.BindEnv(ethHttpPath, ETH_HTTP_PATH)
|
viper.BindEnv(ethHttpPath, ETH_HTTP_PATH)
|
||||||
viper.BindEnv(ethType, ETH_TX_TYPE)
|
|
||||||
viper.BindEnv(ethChainID, ETH_CHAIN_ID)
|
|
||||||
|
|
||||||
viper.BindEnv(ethOptimismL1Sender, ETH_OPTIMISM_L1_SENDER)
|
|
||||||
viper.BindEnv(ethOptimismQueueOrigin, ETH_OPTIMISM_QUEUE_ORIGIN)
|
|
||||||
viper.BindEnv(ethOptimismRollupTxID, ETH_OPTIMISM_ROLLUP_TX_ID)
|
|
||||||
viper.BindEnv(ethOptimismSigHashType, ETH_OPTIMISM_SIG_HASH_TYPE)
|
|
||||||
|
|
||||||
viper.BindEnv(ethDeploymentNumber, ETH_DEPLOYMENT_NUMBER)
|
viper.BindEnv(ethDeploymentNumber, ETH_DEPLOYMENT_NUMBER)
|
||||||
viper.BindEnv(ethDeploymentData, ETH_DEPLOYMENT_HEX_DATA)
|
viper.BindEnv(ethDeploymentData, ETH_DEPLOYMENT_HEX_DATA)
|
||||||
viper.BindEnv(ethDeploymentGasLimit, ETH_DEPLOYMENT_GAS_LIMIT)
|
viper.BindEnv(ethDeploymentGasLimit, ETH_DEPLOYMENT_GAS_LIMIT)
|
||||||
viper.BindEnv(ethDeploymentGasPrice, ETH_DEPLOYMENT_GAS_PRICE)
|
viper.BindEnv(ethDeploymentGasFeeCap, ETH_DEPLOYMENT_GAS_FEE_CAP)
|
||||||
|
viper.BindEnv(ethDeploymentGasTipCap, ETH_DEPLOYMENT_GAS_TIP_CAP)
|
||||||
|
|
||||||
viper.BindEnv(ethCallABIPath, ETH_CALL_ABI_PATH)
|
viper.BindEnv(ethCallABIPath, ETH_CALL_ABI_PATH)
|
||||||
viper.BindEnv(ethCallFrequency, ETH_CALL_FREQ)
|
viper.BindEnv(ethCallFrequency, ETH_CALL_FREQ)
|
||||||
viper.BindEnv(ethCallGasLimit, ETH_CALL_GAS_LIMIT)
|
viper.BindEnv(ethCallGasLimit, ETH_CALL_GAS_LIMIT)
|
||||||
viper.BindEnv(ethCallGasPrice, ETH_CALL_GAS_PRICE)
|
viper.BindEnv(ethCallGasFeeCap, ETH_CALL_GAS_FEE_CAP)
|
||||||
|
viper.BindEnv(ethCallGasTipCap, ETH_CALL_GAS_TIP_CAP)
|
||||||
viper.BindEnv(ethCallMethodName, ETH_CALL_METHOD_NAME)
|
viper.BindEnv(ethCallMethodName, ETH_CALL_METHOD_NAME)
|
||||||
viper.BindEnv(ethCallStorageValue, ETH_CALL_STORAGE_VALUE)
|
|
||||||
viper.BindEnv(ethCallTotalNumber, ETH_CALL_TOTAL_NUMBER)
|
viper.BindEnv(ethCallTotalNumber, ETH_CALL_TOTAL_NUMBER)
|
||||||
|
|
||||||
viper.BindEnv(ethSendFrequency, ETH_SEND_FREQ)
|
viper.BindEnv(ethSendFrequency, ETH_SEND_FREQ)
|
||||||
viper.BindEnv(ethSendTotalNumber, ETH_SEND_TOTAL_NUMBER)
|
viper.BindEnv(ethSendTotalNumber, ETH_SEND_TOTAL_NUMBER)
|
||||||
viper.BindEnv(ethSendAmount, ETH_SEND_AMOUNT)
|
viper.BindEnv(ethSendAmount, ETH_SEND_AMOUNT)
|
||||||
viper.BindEnv(ethSendGasLimit, ETH_SEND_GAS_LIMIT)
|
viper.BindEnv(ethSendGasLimit, ETH_SEND_GAS_LIMIT)
|
||||||
viper.BindEnv(ethSendGasPrice, ETH_SEND_GAS_PRICE)
|
viper.BindEnv(ethSendGasFeeCap, ETH_SEND_GAS_FEE_CAP)
|
||||||
|
viper.BindEnv(ethSendGasTipCap, ETH_SEND_GAS_TIP_CAP)
|
||||||
|
viper.BindEnv(ethSendGasLimit, ETH_CALL_GAS_LIMIT)
|
||||||
}
|
}
|
||||||
|
@ -30,25 +30,27 @@ type EthSender struct {
|
|||||||
// NewEthSender returns a new EthSender
|
// NewEthSender returns a new EthSender
|
||||||
func NewEthSender(config *Config) *EthSender {
|
func NewEthSender(config *Config) *EthSender {
|
||||||
return &EthSender{
|
return &EthSender{
|
||||||
client: config.Client,
|
client: config.RpcClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send awaits txs off the provided work queue and sends them
|
// Send awaits txs off the provided work queue and sends them
|
||||||
func (s *EthSender) Send(quitChan <-chan bool, txRlpChan <-chan []byte) (<-chan bool, <-chan error) {
|
func (s *EthSender) Send(quitChan <-chan bool, txChan <-chan []byte) (<-chan bool, <-chan error) {
|
||||||
// err channel returned to calling context
|
// err channel returned to calling context
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
doneChan := make(chan bool)
|
doneChan := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(doneChan)
|
defer close(doneChan)
|
||||||
|
counter := 0
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case tx := <-txRlpChan:
|
case tx := <-txChan:
|
||||||
|
counter += 1
|
||||||
if err := shared.SendRawTransaction(s.client, tx); err != nil {
|
if err := shared.SendRawTransaction(s.client, tx); err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
}
|
}
|
||||||
case <-quitChan:
|
case <-quitChan:
|
||||||
logrus.Info("quitting Send loop")
|
logrus.Infof("quitting Send loop (sent %d)", counter)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,31 +50,56 @@ func (s *Spammer) Loop(quitChan <-chan bool) (<-chan bool, error) {
|
|||||||
genQuit := make(chan bool)
|
genQuit := make(chan bool)
|
||||||
senderQuit := make(chan bool)
|
senderQuit := make(chan bool)
|
||||||
doneChan := make(chan bool)
|
doneChan := make(chan bool)
|
||||||
genDoneChan, txRlpChan, genErrChan := s.TxGenerator.GenerateTxs(genQuit, contractAddrs)
|
|
||||||
sendDoneChan, sendErrChan := s.Sender.Send(senderQuit, txRlpChan)
|
s.config.CallConfig.ContractAddrs = contractAddrs
|
||||||
|
genDoneChan, txChan, genErrChan := s.TxGenerator.GenerateTxs(genQuit)
|
||||||
|
sendDoneChan, sendErrChan := s.Sender.Send(senderQuit, txChan)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(doneChan)
|
defer close(doneChan)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case err := <-genErrChan:
|
case err := <-genErrChan:
|
||||||
logrus.Errorf("tx generation error: %v", err)
|
logrus.Errorf("tx generation error: %v", err)
|
||||||
close(genQuit)
|
recoverClose(genQuit)
|
||||||
<-genDoneChan
|
<-genDoneChan
|
||||||
close(senderQuit)
|
recoverClose(senderQuit)
|
||||||
case err := <-sendErrChan:
|
case err := <-sendErrChan:
|
||||||
logrus.Errorf("tx sending error: %v", err)
|
logrus.Errorf("tx sending error: %v", err)
|
||||||
close(genQuit)
|
recoverClose(genQuit)
|
||||||
<-genDoneChan
|
<-genDoneChan
|
||||||
close(senderQuit)
|
recoverClose(senderQuit)
|
||||||
case <-quitChan:
|
case <-quitChan:
|
||||||
logrus.Error("shutting down tx spammer")
|
logrus.Info("shutting down tx spammer")
|
||||||
close(genQuit)
|
recoverClose(genQuit)
|
||||||
<-genDoneChan
|
<-genDoneChan
|
||||||
close(senderQuit)
|
recoverClose(senderQuit)
|
||||||
case <-sendDoneChan:
|
case <-sendDoneChan:
|
||||||
return
|
return
|
||||||
|
case <-genDoneChan:
|
||||||
|
recoverClose(senderQuit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return doneChan, nil
|
return doneChan, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func recoverSend(ch chan bool, value bool) {
|
||||||
|
defer func() {
|
||||||
|
if recover() != nil {
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ch <- value
|
||||||
|
}
|
||||||
|
|
||||||
|
func recoverClose(ch chan bool) (justClosed bool) {
|
||||||
|
defer func() {
|
||||||
|
if recover() != nil {
|
||||||
|
justClosed = false
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
close(ch)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -17,17 +17,17 @@
|
|||||||
package auto
|
package auto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"errors"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"fmt"
|
log "github.com/sirupsen/logrus"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/vulcanize/tx_spammer/pkg/shared"
|
"github.com/vulcanize/tx_spammer/pkg/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,15 +36,24 @@ type TxGenerator struct {
|
|||||||
config *Config
|
config *Config
|
||||||
// keep track of account nonces locally so we aren't spamming to determine the nonce
|
// keep track of account nonces locally so we aren't spamming to determine the nonce
|
||||||
// this assumes these accounts are not sending txs outside this process
|
// this assumes these accounts are not sending txs outside this process
|
||||||
nonces map[common.Address]*uint64
|
nonces map[common.Address]uint64
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gen *TxGenerator) claimNonce(addr common.Address) uint64 {
|
||||||
|
gen.lock.Lock()
|
||||||
|
ret := gen.nonces[addr]
|
||||||
|
gen.nonces[addr] += 1
|
||||||
|
gen.lock.Unlock()
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTxGenerator creates a new tx generator
|
// NewTxGenerator creates a new tx generator
|
||||||
func NewTxGenerator(config *Config) *TxGenerator {
|
func NewTxGenerator(config *Config) *TxGenerator {
|
||||||
nonces := make(map[common.Address]*uint64)
|
nonces := make(map[common.Address]uint64)
|
||||||
for _, addr := range config.SenderAddrs {
|
for _, addr := range config.SenderAddrs {
|
||||||
startingNonce := uint64(0)
|
nonce, _ := config.EthClient.PendingNonceAt(context.Background(), addr)
|
||||||
nonces[addr] = &startingNonce
|
nonces[addr] = nonce
|
||||||
}
|
}
|
||||||
return &TxGenerator{
|
return &TxGenerator{
|
||||||
nonces: nonces,
|
nonces: nonces,
|
||||||
@ -56,25 +65,28 @@ func NewTxGenerator(config *Config) *TxGenerator {
|
|||||||
type GenParams struct {
|
type GenParams struct {
|
||||||
Sender common.Address
|
Sender common.Address
|
||||||
SenderKey *ecdsa.PrivateKey
|
SenderKey *ecdsa.PrivateKey
|
||||||
|
|
||||||
|
ChainID *big.Int
|
||||||
|
GasTipCap *big.Int
|
||||||
|
GasFeeCap *big.Int
|
||||||
|
GasLimit uint64
|
||||||
To *common.Address
|
To *common.Address
|
||||||
Amount *big.Int
|
Amount *big.Int
|
||||||
GasLimit uint64
|
|
||||||
GasPrice *big.Int
|
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gen *TxGenerator) GenerateTxs(quitChan <-chan bool, contractAddrs []common.Address) (<-chan bool, <-chan []byte, <-chan error) {
|
func (gen *TxGenerator) GenerateTxs(quitChan <-chan bool) (<-chan bool, <-chan []byte, <-chan error) {
|
||||||
txRlpChan := make(chan []byte)
|
txChan := make(chan []byte)
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
wg := new(sync.WaitGroup)
|
wg := new(sync.WaitGroup)
|
||||||
for i, sender := range gen.config.SenderKeys {
|
for i, sender := range gen.config.SenderKeys {
|
||||||
if len(gen.config.SendConfig.DestinationAddresses) > 0 {
|
if len(gen.config.SendConfig.DestinationAddresses) > 0 {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go gen.genSends(wg, gen.config.Type, txRlpChan, errChan, quitChan, sender, gen.config.SenderAddrs[i], gen.config.SendConfig)
|
go gen.genSends(wg, txChan, errChan, quitChan, sender, gen.config.SenderAddrs[i], gen.config.SendConfig)
|
||||||
}
|
}
|
||||||
if len(gen.config.CallConfig.StorageAddrs) > 0 {
|
if gen.config.CallConfig.TotalNumber > 0 {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go gen.genCalls(wg, gen.config.Type, txRlpChan, errChan, quitChan, sender, gen.config.SenderAddrs[i], gen.config.CallConfig)
|
go gen.genCalls(wg, txChan, errChan, quitChan, sender, gen.config.SenderAddrs[i], gen.config.CallConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
doneChan := make(chan bool)
|
doneChan := make(chan bool)
|
||||||
@ -82,108 +94,115 @@ func (gen *TxGenerator) GenerateTxs(quitChan <-chan bool, contractAddrs []common
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(doneChan)
|
close(doneChan)
|
||||||
}()
|
}()
|
||||||
return doneChan, txRlpChan, errChan
|
return doneChan, txChan, errChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gen *TxGenerator) genSends(wg *sync.WaitGroup, ty shared.TxType, txRlpChan chan<- []byte, errChan chan<- error, quitChan <-chan bool, senderKey *ecdsa.PrivateKey, senderAddr common.Address, sendConfig *SendConfig) {
|
func (gen *TxGenerator) genSends(wg *sync.WaitGroup, txChan chan<- []byte, errChan chan<- error, quitChan <-chan bool, senderKey *ecdsa.PrivateKey, senderAddr common.Address, sendConfig *SendConfig) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
ticker := time.NewTicker(sendConfig.Frequency)
|
ticker := time.NewTicker(sendConfig.Frequency)
|
||||||
for _, dst := range sendConfig.DestinationAddresses {
|
for _, dst := range sendConfig.DestinationAddresses {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
txRlp, _, err := gen.GenerateTx(ty, &GenParams{
|
log.Infof("Generating send from %s to %s.", senderAddr.Hex(), dst.Hex())
|
||||||
|
rawTx, _, err := gen.GenerateTx(&GenParams{
|
||||||
|
ChainID: sendConfig.ChainID,
|
||||||
|
To: &dst,
|
||||||
Sender: senderAddr,
|
Sender: senderAddr,
|
||||||
SenderKey: senderKey,
|
SenderKey: senderKey,
|
||||||
GasLimit: sendConfig.GasLimit,
|
GasLimit: sendConfig.GasLimit,
|
||||||
GasPrice: sendConfig.GasPrice,
|
GasFeeCap: sendConfig.GasFeeCap,
|
||||||
|
GasTipCap: sendConfig.GasTipCap,
|
||||||
Amount: sendConfig.Amount,
|
Amount: sendConfig.Amount,
|
||||||
To: &dst,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
txRlpChan <- txRlp
|
txChan <- rawTx
|
||||||
case <-quitChan:
|
case <-quitChan:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Info("Done generating sends for ", senderAddr.Hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gen *TxGenerator) genCalls(wg *sync.WaitGroup, ty shared.TxType, txRlpChan chan<- []byte, errChan chan<- error, quitChan <-chan bool, senderKey *ecdsa.PrivateKey, senderAddr common.Address, callConfig *CallConfig) {
|
func (gen *TxGenerator) genCalls(wg *sync.WaitGroup, txChan chan<- []byte, errChan chan<- error, quitChan <-chan bool, senderKey *ecdsa.PrivateKey, senderAddr common.Address, callConfig *CallConfig) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
ticker := time.NewTicker(callConfig.Frequency)
|
ticker := time.NewTicker(callConfig.Frequency)
|
||||||
for _, addr := range callConfig.StorageAddrs {
|
for i := 0; i < callConfig.TotalNumber; i++ {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
data, err := callConfig.ABI.Pack(callConfig.MethodName, addr, callConfig.StorageValue)
|
contractAddr := callConfig.ContractAddrs[rand.Intn(len(callConfig.ContractAddrs))]
|
||||||
|
log.Infof("Generating call from %s to %s.", senderAddr.Hex(), contractAddr.Hex())
|
||||||
|
data, err := callConfig.ABI.Pack(callConfig.MethodName, contractAddr, big.NewInt(time.Now().UnixNano()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
txRlp, _, err := gen.GenerateTx(ty, &GenParams{
|
rawTx, _, err := gen.GenerateTx(&GenParams{
|
||||||
Sender: senderAddr,
|
Sender: senderAddr,
|
||||||
SenderKey: senderKey,
|
SenderKey: senderKey,
|
||||||
GasLimit: callConfig.GasLimit,
|
GasLimit: callConfig.GasLimit,
|
||||||
GasPrice: callConfig.GasPrice,
|
GasFeeCap: callConfig.GasFeeCap,
|
||||||
|
GasTipCap: callConfig.GasTipCap,
|
||||||
Data: data,
|
Data: data,
|
||||||
|
To: &contractAddr,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
txRlpChan <- txRlp
|
txChan <- rawTx
|
||||||
case <-quitChan:
|
case <-quitChan:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Info("Done generating calls for ", senderAddr.Hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateTx generates tx from the provided params
|
// GenerateTx generates tx from the provided params
|
||||||
func (gen TxGenerator) GenerateTx(ty shared.TxType, params *GenParams) ([]byte, common.Address, error) {
|
func (gen *TxGenerator) GenerateTx(params *GenParams) ([]byte, common.Address, error) {
|
||||||
switch ty {
|
nonce := gen.claimNonce(params.Sender)
|
||||||
case shared.OptimismL2:
|
|
||||||
return gen.genL2(params, gen.config.OptimismConfig)
|
|
||||||
case shared.Standard:
|
|
||||||
return gen.gen(params)
|
|
||||||
case shared.EIP1559:
|
|
||||||
return gen.gen1559(params, gen.config.EIP1559Config)
|
|
||||||
default:
|
|
||||||
return nil, common.Address{}, fmt.Errorf("unsupported tx type: %s", ty.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gen TxGenerator) genL2(params *GenParams, op *OptimismConfig) ([]byte, common.Address, error) {
|
|
||||||
nonce := atomic.AddUint64(gen.nonces[params.Sender], 1)
|
|
||||||
tx := new(types.Transaction)
|
tx := new(types.Transaction)
|
||||||
var contractAddr common.Address
|
var contractAddr common.Address
|
||||||
var err error
|
var err error
|
||||||
if params.To == nil {
|
if params.To == nil {
|
||||||
tx = types.NewContractCreation(nonce, params.Amount, params.GasLimit, params.GasPrice, params.Data, op.L1SenderAddr, op.L1RollupTxId, op.QueueOrigin)
|
tx = types.NewTx(
|
||||||
contractAddr, err = shared.WriteContractAddr(shared.DefaultDeploymentAddrLogPathPrefix, params.Sender, nonce)
|
&types.DynamicFeeTx{
|
||||||
|
ChainID: params.ChainID,
|
||||||
|
Nonce: nonce,
|
||||||
|
Gas: params.GasLimit,
|
||||||
|
GasTipCap: params.GasTipCap,
|
||||||
|
GasFeeCap: params.GasFeeCap,
|
||||||
|
To: nil,
|
||||||
|
Value: params.Amount,
|
||||||
|
Data: params.Data,
|
||||||
|
})
|
||||||
|
contractAddr, err = shared.WriteContractAddr("", params.Sender, nonce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.Address{}, err
|
return nil, common.Address{}, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tx = types.NewTransaction(nonce, *params.To, params.Amount, params.GasLimit, params.GasPrice, params.Data, op.L1SenderAddr, op.L1RollupTxId, op.QueueOrigin, op.SigHashType)
|
tx = types.NewTx(
|
||||||
|
&types.DynamicFeeTx{
|
||||||
|
ChainID: params.ChainID,
|
||||||
|
Nonce: nonce,
|
||||||
|
GasTipCap: params.GasTipCap,
|
||||||
|
GasFeeCap: params.GasFeeCap,
|
||||||
|
Gas: params.GasLimit,
|
||||||
|
To: params.To,
|
||||||
|
Value: params.Amount,
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
signedTx, err := types.SignTx(tx, gen.config.Signer, params.SenderKey)
|
signedTx, err := types.SignTx(tx, gen.config.Signer, params.SenderKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.Address{}, err
|
return nil, common.Address{}, err
|
||||||
}
|
}
|
||||||
txRlp, err := rlp.EncodeToBytes(signedTx)
|
data, err := signedTx.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.Address{}, err
|
return nil, common.Address{}, err
|
||||||
}
|
}
|
||||||
return txRlp, contractAddr, err
|
log.Debugf("Generated TX %s with bytes %s", signedTx.Hash().Hex(), hexutil.Encode(data))
|
||||||
}
|
return data, contractAddr, err
|
||||||
|
|
||||||
func (gen TxGenerator) gen(params *GenParams) ([]byte, common.Address, error) {
|
|
||||||
// TODO: support standard geth
|
|
||||||
return nil, common.Address{}, errors.New("L1 support not yet available")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gen TxGenerator) gen1559(params *GenParams, eip1559Config *EIP1559Config) ([]byte, common.Address, error) {
|
|
||||||
// TODO: support EIP1559
|
|
||||||
return nil, common.Address{}, errors.New("1559 support not yet available")
|
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vulcanize/tx_spammer/pkg/shared"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -42,29 +38,16 @@ type TxParams struct {
|
|||||||
// HTTP Client for this tx type
|
// HTTP Client for this tx type
|
||||||
Client *rpc.Client
|
Client *rpc.Client
|
||||||
|
|
||||||
// Type of the tx
|
// DynamicFeeTx properties - Start
|
||||||
Type shared.TxType
|
ChainID *big.Int
|
||||||
|
Nonce uint64
|
||||||
// Chain ID
|
GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas
|
||||||
ChainID uint64
|
GasFeeCap *big.Int // a.k.a. maxFeePerGas
|
||||||
|
Gas uint64
|
||||||
// Universal tx fields
|
To *common.Address // nil means contract creation
|
||||||
To *common.Address
|
Value *big.Int
|
||||||
GasLimit uint64
|
Data []byte
|
||||||
GasPrice *big.Int // nil if eip1559
|
// DynamicFeeTx properties - End
|
||||||
Amount *big.Int
|
|
||||||
Data []byte
|
|
||||||
Sender common.Address
|
|
||||||
|
|
||||||
// Optimism-specific metadata fields
|
|
||||||
L1SenderAddr *common.Address
|
|
||||||
L1RollupTxId *hexutil.Uint64
|
|
||||||
SigHashType types.SignatureHashType
|
|
||||||
QueueOrigin types.QueueOrigin
|
|
||||||
|
|
||||||
// EIP1559-specific fields
|
|
||||||
GasPremium *big.Int
|
|
||||||
FeeCap *big.Int
|
|
||||||
|
|
||||||
// Sender key, if left the senderKeyPath is empty we generate a new key
|
// Sender key, if left the senderKeyPath is empty we generate a new key
|
||||||
SenderKey *ecdsa.PrivateKey
|
SenderKey *ecdsa.PrivateKey
|
||||||
@ -72,6 +55,7 @@ type TxParams struct {
|
|||||||
ContractAddrWritePath string
|
ContractAddrWritePath string
|
||||||
|
|
||||||
// Sending params
|
// Sending params
|
||||||
|
Sender common.Address
|
||||||
// How often we send a tx of this type
|
// How often we send a tx of this type
|
||||||
Frequency time.Duration
|
Frequency time.Duration
|
||||||
// Total number of txs of this type to send
|
// Total number of txs of this type to send
|
||||||
@ -80,7 +64,7 @@ type TxParams struct {
|
|||||||
Delay time.Duration
|
Delay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns a new tx spammer config
|
// NewTxParams NewConfig returns a new tx spammer config
|
||||||
func NewTxParams() ([]TxParams, error) {
|
func NewTxParams() ([]TxParams, error) {
|
||||||
bindEnv()
|
bindEnv()
|
||||||
addrLogPath := viper.GetString("eth.addrLogPath")
|
addrLogPath := viper.GetString("eth.addrLogPath")
|
||||||
@ -100,16 +84,6 @@ func NewTxParams() ([]TxParams, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get tx type and chain id
|
|
||||||
txTypeStr := viper.GetString(txName + typeSuffix)
|
|
||||||
if txTypeStr == "" {
|
|
||||||
return nil, fmt.Errorf("need tx type for tx %s", txName)
|
|
||||||
}
|
|
||||||
txType, err := shared.TxTypeFromString(txTypeStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get basic fields
|
// Get basic fields
|
||||||
toStr := viper.GetString(txName + toSuffix)
|
toStr := viper.GetString(txName + toSuffix)
|
||||||
var toAddr *common.Address
|
var toAddr *common.Address
|
||||||
@ -124,17 +98,9 @@ func NewTxParams() ([]TxParams, error) {
|
|||||||
return nil, fmt.Errorf("amount (%s) for tx %s is not valid", amountStr, txName)
|
return nil, fmt.Errorf("amount (%s) for tx %s is not valid", amountStr, txName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gasPriceStr := viper.GetString(txName + gasPriceSuffix)
|
|
||||||
var gasPrice *big.Int
|
|
||||||
if gasPriceStr != "" {
|
|
||||||
gasPrice = new(big.Int)
|
|
||||||
if _, ok := gasPrice.SetString(gasPriceStr, 10); !ok {
|
|
||||||
return nil, fmt.Errorf("gasPrice (%s) for tx %s is not valid", gasPriceStr, txName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gasLimit := viper.GetUint64(txName + gasLimitSuffix)
|
gasLimit := viper.GetUint64(txName + gasLimitSuffix)
|
||||||
hex := viper.GetString(txName + dataSuffix)
|
hex := viper.GetString(txName + dataSuffix)
|
||||||
data := make([]byte, 0)
|
var data []byte = nil
|
||||||
if hex != "" {
|
if hex != "" {
|
||||||
data = common.Hex2Bytes(hex)
|
data = common.Hex2Bytes(hex)
|
||||||
}
|
}
|
||||||
@ -165,62 +131,41 @@ func NewTxParams() ([]TxParams, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to load Optimism fields
|
|
||||||
l1SenderStr := viper.GetString(txName + l1SenderSuffix)
|
|
||||||
var l1Sender *common.Address
|
|
||||||
if l1SenderStr != "" {
|
|
||||||
sender := common.HexToAddress(l1SenderStr)
|
|
||||||
l1Sender = &sender
|
|
||||||
}
|
|
||||||
l1RollupTxId := viper.GetUint64(txName + l1RollupTxIdSuffix)
|
|
||||||
l1rtid := (hexutil.Uint64)(l1RollupTxId)
|
|
||||||
sigHashType := viper.GetUint(txName + sigHashTypeSuffix)
|
|
||||||
queueOrigin := viper.GetInt64(txName + queueOriginSuffix)
|
|
||||||
|
|
||||||
// If gasPrice was empty, attempt to load EIP1559 fields
|
// If gasPrice was empty, attempt to load EIP1559 fields
|
||||||
var feeCap, gasPremium *big.Int
|
var feeCap, tipCap *big.Int
|
||||||
if gasPrice == nil {
|
feeCapStr := viper.GetString(txName + feeCapSuffix)
|
||||||
feeCapStr := viper.GetString(txName + feeCapSuffix)
|
tipCapStr := viper.GetString(txName + tipCapSuffix)
|
||||||
gasPremiumString := viper.GetString(txName + gasPremiumSuffix)
|
if feeCapStr == "" {
|
||||||
if feeCapStr == "" {
|
return nil, fmt.Errorf("tx %s is missing feeCapStr", txName)
|
||||||
return nil, fmt.Errorf("tx %s is missing feeCapStr", txName)
|
}
|
||||||
}
|
if tipCapStr == "" {
|
||||||
if gasPremiumString == "" {
|
return nil, fmt.Errorf("tx %s is missing tipCapStr", txName)
|
||||||
return nil, fmt.Errorf("tx %s is missing gasPremiumStr", txName)
|
}
|
||||||
}
|
feeCap = new(big.Int)
|
||||||
feeCap = new(big.Int)
|
tipCap = new(big.Int)
|
||||||
gasPremium = new(big.Int)
|
if _, ok := feeCap.SetString(feeCapStr, 10); !ok {
|
||||||
if _, ok := feeCap.SetString(feeCapStr, 10); !ok {
|
return nil, fmt.Errorf("unable to set feeCap to %s for tx %s", feeCapStr, txName)
|
||||||
return nil, fmt.Errorf("unable to set feeCap to %s for tx %s", feeCapStr, txName)
|
}
|
||||||
}
|
if _, ok := tipCap.SetString(tipCapStr, 10); !ok {
|
||||||
if _, ok := gasPremium.SetString(gasPremiumString, 10); !ok {
|
return nil, fmt.Errorf("unable to set tipCap to %s for tx %s", tipCapStr, txName)
|
||||||
return nil, fmt.Errorf("unable to set gasPremium to %s for tx %s", gasPremiumString, txName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
txParams[i] = TxParams{
|
txParams[i] = TxParams{
|
||||||
Name: txName,
|
Name: txName,
|
||||||
Client: rpcClient,
|
Client: rpcClient,
|
||||||
Type: txType,
|
GasTipCap: tipCap,
|
||||||
ChainID: viper.GetUint64(txName + chainIDSuffix),
|
GasFeeCap: feeCap,
|
||||||
|
Gas: gasLimit,
|
||||||
To: toAddr,
|
To: toAddr,
|
||||||
GasLimit: gasLimit,
|
Value: amount,
|
||||||
GasPrice: gasPrice,
|
|
||||||
Amount: amount,
|
|
||||||
Data: data,
|
Data: data,
|
||||||
Sender: sender,
|
Sender: sender,
|
||||||
L1SenderAddr: l1Sender,
|
|
||||||
L1RollupTxId: &l1rtid,
|
|
||||||
SigHashType: (types.SignatureHashType)(uint8(sigHashType)),
|
|
||||||
QueueOrigin: (types.QueueOrigin)(queueOrigin),
|
|
||||||
GasPremium: gasPremium,
|
|
||||||
FeeCap: feeCap,
|
|
||||||
SenderKey: key,
|
SenderKey: key,
|
||||||
StartingNonce: viper.GetUint64(txName + startingNonceSuffix),
|
StartingNonce: viper.GetUint64(txName + startingNonceSuffix),
|
||||||
ContractAddrWritePath: viper.GetString(txName + contractWriteSuffix),
|
ContractAddrWritePath: viper.GetString(txName + contractWriteSuffix),
|
||||||
Frequency: viper.GetDuration(txName + frequencySuffix),
|
Frequency: viper.GetDuration(txName+frequencySuffix) * time.Millisecond,
|
||||||
TotalNumber: viper.GetUint64(txName + totalNumberSuffix),
|
TotalNumber: viper.GetUint64(txName + totalNumberSuffix),
|
||||||
Delay: viper.GetDuration(txName + delaySuffix),
|
Delay: viper.GetDuration(txName+delaySuffix) * time.Millisecond,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return txParams, nil
|
return txParams, nil
|
||||||
|
@ -33,20 +33,15 @@ const (
|
|||||||
toSuffix = ".to"
|
toSuffix = ".to"
|
||||||
amountSuffix = ".amount"
|
amountSuffix = ".amount"
|
||||||
gasLimitSuffix = ".gasLimit"
|
gasLimitSuffix = ".gasLimit"
|
||||||
gasPriceSuffix = ".gasPrice"
|
|
||||||
gasPremiumSuffix = ".gasPremium"
|
|
||||||
feeCapSuffix = ".feeCap"
|
feeCapSuffix = ".feeCap"
|
||||||
|
tipCapSuffix = ".tipCap"
|
||||||
dataSuffix = ".data"
|
dataSuffix = ".data"
|
||||||
senderKeyPathSuffix = ".senderKeyPath"
|
senderKeyPathSuffix = ".senderKeyPath"
|
||||||
writeSenderPathSuffix = ".writeSenderPath"
|
writeSenderPathSuffix = ".writeSenderPath"
|
||||||
l1SenderSuffix = ".l1Sender"
|
|
||||||
l1RollupTxIdSuffix = ".l1RollupTxId"
|
|
||||||
sigHashTypeSuffix = ".sigHashType"
|
|
||||||
frequencySuffix = ".frequency"
|
frequencySuffix = ".frequency"
|
||||||
totalNumberSuffix = ".totalNumber"
|
totalNumberSuffix = ".totalNumber"
|
||||||
delaySuffix = ".delay"
|
delaySuffix = ".delay"
|
||||||
startingNonceSuffix = ".startingNonce"
|
startingNonceSuffix = ".startingNonce"
|
||||||
queueOriginSuffix = ".queueOrigin"
|
|
||||||
chainIDSuffix = ".chainID"
|
chainIDSuffix = ".chainID"
|
||||||
contractWriteSuffix = ".writeDeploymentAddrPath"
|
contractWriteSuffix = ".writeDeploymentAddrPath"
|
||||||
)
|
)
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
package manual
|
package manual
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -46,34 +44,39 @@ func NewTxGenerator(params []TxParams) *TxGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateTx generates tx from the provided params
|
// GenerateTx generates tx from the provided params
|
||||||
func (tg TxGenerator) GenerateTx(params TxParams) ([]byte, error) {
|
func (gen TxGenerator) GenerateTx(params TxParams) ([]byte, error) {
|
||||||
tx := make([]byte, 0)
|
|
||||||
switch params.Type {
|
|
||||||
case shared.OptimismL2:
|
|
||||||
return tg.genL2(params)
|
|
||||||
case shared.Standard:
|
|
||||||
return tg.gen(params)
|
|
||||||
case shared.EIP1559:
|
|
||||||
return tg.gen1559(params)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported tx type: %s", params.Type.String())
|
|
||||||
}
|
|
||||||
return tx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gen TxGenerator) genL2(params TxParams) ([]byte, error) {
|
|
||||||
nonce := atomic.AddUint64(gen.nonces[params.Sender], 1)
|
nonce := atomic.AddUint64(gen.nonces[params.Sender], 1)
|
||||||
tx := new(types.Transaction)
|
tx := new(types.Transaction)
|
||||||
if params.To == nil {
|
if params.To == nil {
|
||||||
tx = types.NewContractCreation(nonce, params.Amount, params.GasLimit, params.GasPrice, params.Data, params.L1SenderAddr, params.L1RollupTxId, params.QueueOrigin)
|
tx = types.NewTx(
|
||||||
|
&types.DynamicFeeTx{
|
||||||
|
ChainID: params.ChainID,
|
||||||
|
Nonce: nonce,
|
||||||
|
Gas: params.Gas,
|
||||||
|
GasTipCap: params.GasTipCap,
|
||||||
|
GasFeeCap: params.GasFeeCap,
|
||||||
|
To: nil,
|
||||||
|
Value: params.Value,
|
||||||
|
Data: params.Data,
|
||||||
|
})
|
||||||
if _, err := shared.WriteContractAddr(params.ContractAddrWritePath, params.Sender, nonce); err != nil {
|
if _, err := shared.WriteContractAddr(params.ContractAddrWritePath, params.Sender, nonce); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
tx = types.NewTransaction(nonce, *params.To, params.Amount, params.GasLimit, params.GasPrice, params.Data, params.L1SenderAddr, params.L1RollupTxId, params.QueueOrigin, params.SigHashType)
|
tx = types.NewTx(
|
||||||
|
&types.DynamicFeeTx{
|
||||||
|
ChainID: params.ChainID,
|
||||||
|
Nonce: nonce,
|
||||||
|
Gas: params.Gas,
|
||||||
|
GasTipCap: params.GasTipCap,
|
||||||
|
GasFeeCap: params.GasFeeCap,
|
||||||
|
To: params.To,
|
||||||
|
Value: params.Value,
|
||||||
|
Data: params.Data,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
signer, err := shared.TxSigner(shared.OptimismL2, params.ChainID)
|
signer, err := shared.TxSigner(params.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -81,19 +84,9 @@ func (gen TxGenerator) genL2(params TxParams) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
txRlp, err := rlp.EncodeToBytes(signedTx)
|
rawTx, err := rlp.EncodeToBytes(signedTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return txRlp, nil
|
return rawTx, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (gen TxGenerator) gen(params TxParams) ([]byte, error) {
|
|
||||||
// TODO: support standard geth
|
|
||||||
return nil, errors.New("L1 support not yet available")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gen TxGenerator) gen1559(params TxParams) ([]byte, error) {
|
|
||||||
// TODO: support EIP1559
|
|
||||||
return nil, errors.New("1559 support not yet available")
|
|
||||||
}
|
}
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
// VulcanizeDB
|
|
||||||
// Copyright © 2020 Vulcanize
|
|
||||||
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// This program 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 Affero General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package shared
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TxType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Unsupported TxType = iota
|
|
||||||
Standard
|
|
||||||
OptimismL2
|
|
||||||
OptimismL1ToL2
|
|
||||||
EIP1559
|
|
||||||
)
|
|
||||||
|
|
||||||
func (tt TxType) String() string {
|
|
||||||
switch tt {
|
|
||||||
case Standard:
|
|
||||||
return "Standard"
|
|
||||||
case OptimismL2:
|
|
||||||
return "L2"
|
|
||||||
case OptimismL1ToL2:
|
|
||||||
return "L1toL2"
|
|
||||||
case EIP1559:
|
|
||||||
return "EIP1559"
|
|
||||||
default:
|
|
||||||
return "Unsupported"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxTypeFromString returns the tx enum type from provided string
|
|
||||||
func TxTypeFromString(str string) (TxType, error) {
|
|
||||||
switch strings.ToLower(str) {
|
|
||||||
case "geth", "standard", "parity":
|
|
||||||
return Standard, nil
|
|
||||||
case "l2":
|
|
||||||
return OptimismL2, nil
|
|
||||||
case "l1", "l2tol1":
|
|
||||||
return OptimismL1ToL2, nil
|
|
||||||
case "eip1559":
|
|
||||||
return EIP1559, nil
|
|
||||||
default:
|
|
||||||
return Unsupported, fmt.Errorf("unsupported tx type: %s", str)
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,7 +18,7 @@ package shared
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"github.com/sirupsen/logrus"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@ -31,20 +31,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ChainConfig returns the appropriate ethereum chain config for the provided chain id
|
// ChainConfig returns the appropriate ethereum chain config for the provided chain id
|
||||||
func TxSigner(kind TxType, chainID uint64) (types.Signer, error) {
|
func TxSigner(chainID *big.Int) (types.Signer, error) {
|
||||||
switch kind {
|
return types.NewLondonSigner(chainID), nil
|
||||||
case Standard, EIP1559:
|
|
||||||
return types.NewEIP155Signer(new(big.Int).SetUint64(chainID)), nil
|
|
||||||
case OptimismL2, OptimismL1ToL2:
|
|
||||||
return types.NewOVMSigner(new(big.Int).SetUint64(chainID)), nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("chain config for chainid %d not available", chainID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendRawTransaction sends a raw, signed tx using the provided client
|
// SendRawTransaction sends a raw, signed tx using the provided client
|
||||||
func SendRawTransaction(rpcClient *rpc.Client, txRlp []byte) error {
|
func SendRawTransaction(rpcClient *rpc.Client, raw []byte) error {
|
||||||
return rpcClient.CallContext(context.Background(), nil, "eth_sendRawTransaction", hexutil.Encode(txRlp))
|
logrus.Debug("eth_sendRawTransaction: ", hexutil.Encode(raw))
|
||||||
|
return rpcClient.CallContext(context.Background(), nil, "eth_sendRawTransaction", hexutil.Encode(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteContractAddr appends a contract addr to an out file
|
// WriteContractAddr appends a contract addr to an out file
|
||||||
|
28
sol/Test.sol
Normal file
28
sol/Test.sol
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
contract Test {
|
||||||
|
address payable owner;
|
||||||
|
|
||||||
|
modifier onlyOwner {
|
||||||
|
require(
|
||||||
|
msg.sender == owner,
|
||||||
|
"Only owner can call this function."
|
||||||
|
);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping(address => uint256) public data;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
owner = payable(msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Put(address addr, uint256 value) public {
|
||||||
|
data[addr] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() public onlyOwner {
|
||||||
|
selfdestruct(owner);
|
||||||
|
}
|
||||||
|
}
|
1
sol/build/Test.abi
Normal file
1
sol/build/Test.abi
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"Put","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"close","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"data","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
|
1
sol/build/Test.bin
Normal file
1
sol/build/Test.bin
Normal file
@ -0,0 +1 @@
|
|||||||
|
608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506103cc806100606000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80632802348a1461004657806343d726d614610062578063b90d3d0c1461006c575b600080fd5b610060600480360381019061005b919061025c565b61009c565b005b61006a6100e4565b005b6100866004803603810190610081919061029c565b6101ab565b60405161009391906102d8565b60405180910390f35b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610172576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161016990610376565b60405180910390fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b60016020528060005260406000206000915090505481565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101f3826101c8565b9050919050565b610203816101e8565b811461020e57600080fd5b50565b600081359050610220816101fa565b92915050565b6000819050919050565b61023981610226565b811461024457600080fd5b50565b60008135905061025681610230565b92915050565b60008060408385031215610273576102726101c3565b5b600061028185828601610211565b925050602061029285828601610247565b9150509250929050565b6000602082840312156102b2576102b16101c3565b5b60006102c084828501610211565b91505092915050565b6102d281610226565b82525050565b60006020820190506102ed60008301846102c9565b92915050565b600082825260208201905092915050565b7f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f60008201527f6e2e000000000000000000000000000000000000000000000000000000000000602082015250565b60006103606022836102f3565b915061036b82610304565b604082019050919050565b6000602082019050818103600083015261038f81610353565b905091905056fea26469706673582212201d7a5e3145cb5b1067ff7c8bb867ec5228d0645ac9eea2ad26735c9b2ad33cd764736f6c63430008110033
|
Loading…
Reference in New Issue
Block a user