cmd: add tool to batch send txs

This commit is contained in:
Miya Chen 2017-06-28 10:03:56 +08:00
parent 45dc130cbe
commit 1822929738
5 changed files with 425 additions and 0 deletions

52
cmd/sendtx/account.go Normal file
View File

@ -0,0 +1,52 @@
package main
import (
"crypto/ecdsa"
"encoding/hex"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
type Account struct {
priKey *ecdsa.PrivateKey
}
func GenerateAccount() (*Account, error) {
key, err := crypto.GenerateKey()
if err != nil {
return nil, err
}
return &Account{
priKey: key,
}, nil
}
// private has no '0x' prefix
func AccountFromECDSA(private string) (*Account, error) {
key, err := crypto.HexToECDSA(private)
if err != nil {
return nil, err
}
return &Account{
priKey: key,
}, nil
}
func (a *Account) PrivateKey() *ecdsa.PrivateKey {
// hex.EncodeToString(crypto.FromECDSA(a.priKey))
return a.priKey
}
func (a *Account) PublicKey() *ecdsa.PublicKey {
// hex.EncodeToString(crypto.FromECDSAPub(&a.priKey.PublicKey))
return &a.priKey.PublicKey
}
func (a *Account) Address() common.Address {
return crypto.PubkeyToAddress(a.priKey.PublicKey)
}
func (a *Account) String() string {
return hex.EncodeToString(crypto.FromECDSA(a.priKey))
}

44
cmd/sendtx/batch.py Normal file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import json
from subprocess import call
from subprocess import Popen
import shlex
import time
import random
ADMIN='c921c91aa4c5f9886bd1e084a848e7e564644d5cc2f265beebb0d51bd251b7e7'
ACCS=100
TX_NUM=100
PERIOD=600
NODES=['http://127.0.0.1:8811', 'http://127.0.0.1:8822', 'http://127.0.0.1:8833', 'http://127.0.0.1:8844']
def file_reader(path):
"""
Read a file
"""
with open(path) as data_file:
data = json.load(data_file)
return data
def main():
cmd = './sendtx prepare --addr {} --admin {} --number {}'.format(NODES[0], ADMIN, ACCS)
call(cmd, shell=True)
accs = file_reader('accs')
# wait for money coming
time.sleep(5)
processes = []
n = 0
for i in range(0,len(accs)):
cmd = './sendtx batch --addr {} --admin {} --count {} --period {}'.format(NODES[n%len(NODES)], accs[i], TX_NUM, PERIOD)
processes.append(Popen(shlex.split(cmd)))
n = n+1
time.sleep(random.randint(0,3)*0.2)
for p in processes:
p.wait()
if __name__ == '__main__':
main()

239
cmd/sendtx/cmd.go Normal file
View File

@ -0,0 +1,239 @@
// Copyright 2017 AMIS Technologies
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"crypto/ecdsa"
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"math/rand"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli"
)
const (
GAS_PRICE = 20000000000
GAS_LIMIT = 22000 // the gas of ether tx should be 21000
// admin
private = "c921c91aa4c5f9886bd1e084a848e7e564644d5cc2f265beebb0d51bd251b7e7"
public = "1f0a3201bfa623be518eb7fb3742385f6f42e2e09a83e417f51f937c19c5388fc99e9472f5ac7e6c8baebcdf96719312d22fd8783d071c2e71bf713993ab2284"
address = "507a198251ed29f421bd6cf667596f750d9b14ec"
)
var (
prepareCommand = cli.Command{
Action: prepare,
Name: "prepare",
Usage: "Prepare accounts to nodes",
ArgsUsage: "<prepare accounts>",
Flags: []cli.Flag{
NodeAddrFlag,
AdminFlag,
AccountsFlag,
},
Description: `To prepare accounts`,
}
batchCommand = cli.Command{
Action: batch,
Name: "batch",
Usage: "Batch send txs",
ArgsUsage: "<batch send>",
Flags: []cli.Flag{
NodeAddrFlag,
AdminFlag,
TxsCountFlag,
SendPeriodFlag,
},
Description: `To batch send txs`,
}
// target
toAddr = common.HexToAddress("0xcfe3b52266134683d6e9ee765d735431e35c7a54")
)
func prepare(ctx *cli.Context) error {
addr := ctx.String(NodeAddrFlag.Name)
if addr == "" {
return cli.NewExitError("Must provide node address", 0)
}
key := ctx.String(AdminFlag.Name)
if key == "" {
return cli.NewExitError("Must provide admin's private key", 0)
}
num := ctx.Int(AccountsFlag.Name)
conn, err := ethclient.Dial(addr)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Failed to connect to ethclient:: %v", err), 1)
}
admin, err := AccountFromECDSA(key)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Failed to read private key: %v", err), 1)
}
nonce, err := conn.NonceAt(context.Background(), admin.Address(), nil)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Failed to get nonce:: %v", err), 1)
}
fmt.Println("nonce:", nonce)
// generate accounts
prepareAccs := []*Account{}
for i := 0; i < num; i++ {
a, _ := GenerateAccount()
prepareAccs = append(prepareAccs, a)
fmt.Println("account", a)
}
amount := big.NewInt(0).Exp(big.NewInt(int64(10)), big.NewInt(int64(20)), nil)
// give money
for _, pa := range prepareAccs {
tx, err := sendEther(admin.PrivateKey(), pa.Address(), amount, nonce)
if err != nil {
fmt.Printf("Failed to gen tx, err:%v\n", err)
continue
}
err = sendTx(conn, tx)
if err != nil {
fmt.Printf("Failed to send tx, err:%v\n", err)
continue
}
nonce++
}
writeAccounts(prepareAccs)
return nil
}
func sendTx(conn *ethclient.Client, tx *types.Transaction) error {
err := conn.SendTransaction(context.Background(), tx)
if err != nil {
return err
}
fmt.Println("tx", tx.Hash().String())
return nil
}
func sendEther(prv *ecdsa.PrivateKey, to common.Address, amount *big.Int, nonce uint64) (*types.Transaction, error) {
signer := types.FrontierSigner{}
tx := types.NewTransaction(nonce, to, amount, big.NewInt(GAS_LIMIT), big.NewInt(GAS_PRICE), []byte{})
// return tx, nil
signedTx, err := types.SignTx(tx, signer, prv)
if err != nil {
return nil, err
}
return signedTx, nil
}
func batch(ctx *cli.Context) error {
addr := ctx.String(NodeAddrFlag.Name)
if addr == "" {
return cli.NewExitError("Must provide node address", 0)
}
key := ctx.String(AdminFlag.Name)
if key == "" {
return cli.NewExitError("Must provide admin's private key", 0)
}
conn, err := ethclient.Dial(addr)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Failed to connect to ethclient:: %v", err), 1)
}
admin, err := AccountFromECDSA(key)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Failed to read private key: %v", err), 1)
}
count := ctx.Int(TxsCountFlag.Name)
secs := ctx.Int(SendPeriodFlag.Name)
nonce, err := conn.NonceAt(context.Background(), admin.Address(), nil)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Failed to get nonce:: %v", err), 1)
}
fmt.Println("nonce:", nonce)
balance, err := conn.BalanceAt(context.Background(), admin.Address(), nil)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Failed to get nonce:: %v", err), 1)
}
fmt.Println("balance:", balance)
// give money
batchSend(conn, admin.PrivateKey(), toAddr, nonce, count, time.Duration(secs)*time.Second)
return nil
}
func batchSend(conn *ethclient.Client, prv *ecdsa.PrivateKey, to common.Address, nonce uint64, count int, period time.Duration) {
round := 0
timer := time.NewTimer(period)
for {
select {
case <-timer.C:
return
default:
fmt.Println("round", round)
for i := 0; i < count; i++ {
tx, err := sendEther(prv, to, common.Big1, nonce)
if err != nil {
fmt.Printf("Failed to gen tx, err:%v\n", err)
continue
}
err = sendTx(conn, tx)
if err != nil {
fmt.Printf("Failed to send tx, err:%v\n", err)
continue
}
nonce++
}
round++
<-time.After(time.Duration(20+rand.Int()%10) * time.Second)
}
}
}
func writeAccounts(accs []*Account) {
keys := []string{}
for _, acc := range accs {
keys = append(keys, acc.String())
}
b, err := json.Marshal(keys)
if err != nil {
fmt.Println("Failed to json Marshal accounts, err:%v", err)
return
}
err = ioutil.WriteFile("accs", b, 0644)
if err != nil {
fmt.Println("Failed to write file, err:%v", err)
return
}
return
}

47
cmd/sendtx/flags.go Normal file
View File

@ -0,0 +1,47 @@
// Copyright 2017 AMIS Technologies
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"github.com/urfave/cli"
)
var (
NodeAddrFlag = cli.StringFlag{
Name: "addr",
Usage: "Address of geth node",
}
AdminFlag = cli.StringFlag{
Name: "admin",
Usage: "Hex string of admin's private key",
}
AccountsFlag = cli.IntFlag{
Name: "number",
Usage: "Number of prepare accounts",
Value: 4,
}
TxsCountFlag = cli.IntFlag{
Name: "count",
Usage: "Number of txs",
Value: 1000,
}
SendPeriodFlag = cli.IntFlag{
Name: "period",
Usage: "Period in seconds to send txs",
Value: 60,
}
)

43
cmd/sendtx/main.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2017 AMIS Technologies
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"os"
"github.com/getamis/istanbul-tools/cmd/utils"
"github.com/urfave/cli"
)
func main() {
app := utils.NewApp()
app.Usage = "the istanbul-tools command line interface"
app.HideVersion = true // we have a command to print the version
app.Copyright = "Copyright 2017 The Amis Authors"
app.Commands = []cli.Command{
prepareCommand,
batchCommand,
}
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}