From d41cb421d755b8f0bca87b7476f26aa4b879b9d9 Mon Sep 17 00:00:00 2001
From: Jimmy Hu <jimmy.hu@dexon.org>
Date: Sat, 4 May 2019 22:18:40 +0800
Subject: zoo: refacter and save keys (#403)

---
 cmd/zoo/client/client.go  | 190 ++++++++++++++++++++++++++++++++++++++++++
 cmd/zoo/main.go           |  12 +++
 cmd/zoo/monkey/feeder.go  |  23 +++---
 cmd/zoo/monkey/gambler.go |  16 ++--
 cmd/zoo/monkey/monkey.go  | 205 ++++++++++------------------------------------
 cmd/zoo/utils/shutdown.go | 114 ++++++++++++++++++++++++++
 6 files changed, 380 insertions(+), 180 deletions(-)
 create mode 100644 cmd/zoo/client/client.go
 create mode 100644 cmd/zoo/utils/shutdown.go

(limited to 'cmd')

diff --git a/cmd/zoo/client/client.go b/cmd/zoo/client/client.go
new file mode 100644
index 000000000..9b65e778b
--- /dev/null
+++ b/cmd/zoo/client/client.go
@@ -0,0 +1,190 @@
+// Copyright 2019 The dexon-consensus Authors
+// This file is part of the dexon-consensus library.
+//
+// The dexon-consensus 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 dexon-consensus 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 dexon-consensus library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+// A simple monkey that sends random transactions into the network.
+
+package client
+
+import (
+	"context"
+	"crypto/ecdsa"
+	"fmt"
+	"math"
+	"math/big"
+	"time"
+
+	dexon "github.com/dexon-foundation/dexon"
+	"github.com/dexon-foundation/dexon/common"
+	"github.com/dexon-foundation/dexon/core/types"
+	"github.com/dexon-foundation/dexon/crypto"
+	"github.com/dexon-foundation/dexon/ethclient"
+)
+
+type Client struct {
+	ethclient.Client
+
+	source    *ecdsa.PrivateKey
+	networkID *big.Int
+}
+
+func New(ep string) (*Client, error) {
+	client, err := ethclient.Dial(ep)
+	if err != nil {
+		return nil, err
+	}
+
+	networkID, err := client.NetworkID(context.Background())
+	if err != nil {
+		return nil, err
+	}
+
+	return &Client{
+		Client:    *client,
+		networkID: networkID,
+	}, nil
+}
+
+type TransferContext struct {
+	Key       *ecdsa.PrivateKey
+	ToAddress common.Address
+	Amount    *big.Int
+	Data      []byte
+	Nonce     uint64
+	Gas       uint64
+}
+
+func (c *Client) PrepareTx(ctx *TransferContext) *types.Transaction {
+	if ctx.Nonce == math.MaxUint64 {
+		var err error
+		address := crypto.PubkeyToAddress(ctx.Key.PublicKey)
+		ctx.Nonce, err = c.PendingNonceAt(context.Background(), address)
+		if err != nil {
+			panic(err)
+		}
+	}
+
+	if ctx.Gas == uint64(0) {
+		var err error
+		ctx.Gas, err = c.EstimateGas(context.Background(), dexon.CallMsg{
+			Data: ctx.Data,
+		})
+		if err != nil {
+			panic(err)
+		}
+	}
+
+	gasPrice, err := c.SuggestGasPrice(context.Background())
+	if err != nil {
+		panic(err)
+	}
+
+	tx := types.NewTransaction(
+		ctx.Nonce,
+		ctx.ToAddress,
+		ctx.Amount,
+		ctx.Gas,
+		gasPrice,
+		ctx.Data)
+
+	signer := types.NewEIP155Signer(c.networkID)
+	tx, err = types.SignTx(tx, signer, ctx.Key)
+	if err != nil {
+		panic(err)
+	}
+
+	return tx
+}
+
+func (c *Client) Transfer(ctx *TransferContext) {
+	tx := c.PrepareTx(ctx)
+
+	err := c.SendTransaction(context.Background(), tx)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func (c *Client) BatchTransfer(ctxs []*TransferContext) {
+	txs := make([]*types.Transaction, len(ctxs))
+	for i, ctx := range ctxs {
+		txs[i] = c.PrepareTx(ctx)
+	}
+
+	err := c.SendTransactions(context.Background(), txs)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func (c *Client) Deploy(
+	key *ecdsa.PrivateKey, code string, ctors []string, amount *big.Int, nonce uint64) common.Address {
+
+	address := crypto.PubkeyToAddress(key.PublicKey)
+	if nonce == math.MaxUint64 {
+		var err error
+		nonce, err = c.PendingNonceAt(context.Background(), address)
+		if err != nil {
+			panic(err)
+		}
+	}
+
+	var input string
+	for _, ctor := range ctors {
+		input += fmt.Sprintf("%064s", ctor)
+	}
+	data := common.Hex2Bytes(code + input)
+
+	gas, err := c.EstimateGas(context.Background(), dexon.CallMsg{
+		From: address,
+		Data: data,
+	})
+	if err != nil {
+		panic(err)
+	}
+
+	tx := types.NewContractCreation(
+		nonce,
+		amount,
+		gas,
+		big.NewInt(1e9),
+		data)
+
+	signer := types.NewEIP155Signer(c.networkID)
+	tx, err = types.SignTx(tx, signer, key)
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Println("Sending TX", "fullhash", tx.Hash().String())
+
+	err = c.SendTransaction(context.Background(), tx)
+	if err != nil {
+		panic(err)
+	}
+
+	for {
+		time.Sleep(500 * time.Millisecond)
+		recp, err := c.TransactionReceipt(context.Background(), tx.Hash())
+		if err != nil {
+			if err == dexon.NotFound {
+				continue
+			}
+			panic(err)
+		}
+		return recp.ContractAddress
+	}
+}
diff --git a/cmd/zoo/main.go b/cmd/zoo/main.go
index 195d5737c..cd33d4cee 100644
--- a/cmd/zoo/main.go
+++ b/cmd/zoo/main.go
@@ -4,6 +4,7 @@ import (
 	"flag"
 
 	"github.com/dexon-foundation/dexon/cmd/zoo/monkey"
+	"github.com/dexon-foundation/dexon/cmd/zoo/utils"
 )
 
 var key = flag.String("key", "", "private key path")
@@ -14,10 +15,21 @@ var batch = flag.Bool("batch", false, "monkeys will send transaction in batch")
 var sleep = flag.Int("sleep", 500, "time in millisecond that monkeys sleep between each transaction")
 var feeder = flag.Bool("feeder", false, "make this monkey a feeder")
 var timeout = flag.Int("timeout", 0, "execution time limit after start")
+var shutdown = flag.String("shutdown", "", "shutdown the previously opened zoo")
 
 func main() {
 	flag.Parse()
 
+	if *shutdown != "" {
+		utils.Shutdown(&utils.ShutdownConfig{
+			Key:      *key,
+			Endpoint: *endpoint,
+			File:     *shutdown,
+			Batch:    *batch,
+		})
+		return
+	}
+
 	monkey.Init(&monkey.MonkeyConfig{
 		Key:      *key,
 		Endpoint: *endpoint,
diff --git a/cmd/zoo/monkey/feeder.go b/cmd/zoo/monkey/feeder.go
index 7a3db3a3a..99df83b98 100644
--- a/cmd/zoo/monkey/feeder.go
+++ b/cmd/zoo/monkey/feeder.go
@@ -27,6 +27,7 @@ import (
 	"time"
 
 	"github.com/dexon-foundation/dexon/accounts/abi"
+	"github.com/dexon-foundation/dexon/cmd/zoo/client"
 	"github.com/dexon-foundation/dexon/common"
 	"github.com/dexon-foundation/dexon/crypto"
 )
@@ -44,12 +45,12 @@ func init() {
 func (m *Monkey) DistributeBanana(contract common.Address) {
 	fmt.Println("Distributing Banana to random accounts ...")
 	address := crypto.PubkeyToAddress(m.source.PublicKey)
-	nonce, err := m.client.PendingNonceAt(context.Background(), address)
+	nonce, err := m.PendingNonceAt(context.Background(), address)
 	if err != nil {
 		panic(err)
 	}
 
-	ctxs := make([]*transferContext, len(m.keys))
+	ctxs := make([]*client.TransferContext, len(m.keys))
 	amount := new(big.Int)
 	amount.SetString("10000000000000000", 10)
 	for i, key := range m.keys {
@@ -58,22 +59,22 @@ func (m *Monkey) DistributeBanana(contract common.Address) {
 		if err != nil {
 			panic(err)
 		}
-		ctxs[i] = &transferContext{
+		ctxs[i] = &client.TransferContext{
 			Key:       m.source,
 			ToAddress: contract,
 			Data:      input,
 			Nonce:     nonce,
 			Gas:       100000,
 		}
-		nonce += 1
+		nonce++
 	}
-	m.batchTransfer(ctxs)
+	m.BatchTransfer(ctxs)
 	time.Sleep(20 * time.Second)
 }
 
 func (m *Monkey) Feed() uint64 {
 	fmt.Println("Deploying contract ...")
-	contract := m.deploy(m.source, bananaContract, nil, new(big.Int), math.MaxUint64)
+	contract := m.Deploy(m.source, bananaContract, nil, new(big.Int), math.MaxUint64)
 	fmt.Println("  Contract deployed: ", contract.String())
 	m.DistributeBanana(contract)
 
@@ -83,7 +84,7 @@ func (m *Monkey) Feed() uint64 {
 loop:
 	for {
 		fmt.Println("nonce", nonce)
-		ctxs := make([]*transferContext, len(m.keys))
+		ctxs := make([]*client.TransferContext, len(m.keys))
 		for i, key := range m.keys {
 			to := crypto.PubkeyToAddress(m.keys[rand.Int()%len(m.keys)].PublicKey)
 			input, err := bananaABI.Pack("transfer", to, big.NewInt(rand.Int63n(100)+1))
@@ -91,7 +92,7 @@ loop:
 				panic(err)
 			}
 
-			ctx := &transferContext{
+			ctx := &client.TransferContext{
 				Key:       key,
 				ToAddress: contract,
 				Data:      input,
@@ -101,11 +102,11 @@ loop:
 			if config.Batch {
 				ctxs[i] = ctx
 			} else {
-				m.transfer(ctx)
+				m.Transfer(ctx)
 			}
 		}
 		if config.Batch {
-			m.batchTransfer(ctxs)
+			m.BatchTransfer(ctxs)
 		}
 
 		if m.timer != nil {
@@ -116,7 +117,7 @@ loop:
 			}
 		}
 
-		nonce += 1
+		nonce++
 		time.Sleep(time.Duration(config.Sleep) * time.Millisecond)
 	}
 
diff --git a/cmd/zoo/monkey/gambler.go b/cmd/zoo/monkey/gambler.go
index 33f749343..9c9098c42 100644
--- a/cmd/zoo/monkey/gambler.go
+++ b/cmd/zoo/monkey/gambler.go
@@ -25,6 +25,7 @@ import (
 	"time"
 
 	"github.com/dexon-foundation/dexon/accounts/abi"
+	"github.com/dexon-foundation/dexon/cmd/zoo/client"
 )
 
 var betContract = string("0x60806040523480156200001157600080fd5b5060405160208062000ee083398101806040528101908080519060200190929190505050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a36200010b8162000112640100000000026401000000009004565b50620003f5565b60006200011e62000364565b6000620001396200029f640100000000026401000000009004565b15156200014557600080fd5b606484101515620001e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f4578706563746174696f6e2073686f756c64206265206c657373207468616e2081526020017f3130302e0000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b836001819055506200021061271085620002f664010000000002620008c4179091906401000000009004565b925060008260006064811015156200022457fe5b602002018181525050600190505b606481101562000285576200025f8184620003386401000000000262000902179091906401000000009004565b82826064811015156200026e57fe5b602002018181525050808060010191505062000232565b8160029060646200029892919062000388565b5050505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614905090565b60008060008414156200030d576000915062000331565b82840290508284828115156200031f57fe5b041415156200032d57600080fd5b8091505b5092915050565b6000806000831115156200034b57600080fd5b82848115156200035757fe5b0490508091505092915050565b610c8060405190810160405280606490602082028038833980820191505090505090565b8260648101928215620003ba579160200282015b82811115620003b95782518255916020019190600101906200039c565b5b509050620003c99190620003cd565b5090565b620003f291905b80821115620003ee576000816000905550600101620003d4565b5090565b90565b610adb80620004056000396000f3006080604052600436106100a4576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630c60e0c3146100a9578063379607f5146100d6578063715018a6146101035780637365870b1461011a5780638da5cb5b1461013a5780638f32d59b14610191578063e1152343146101c0578063ed88c68e14610201578063f2fde38b1461020b578063fc1c39dc1461024e575b600080fd5b3480156100b557600080fd5b506100d460048036038101908080359060200190929190505050610279565b005b3480156100e257600080fd5b50610101600480360381019080803590602001909291905050506103cb565b005b34801561010f57600080fd5b506101186104be565b005b61013860048036038101908080359060200190929190505050610590565b005b34801561014657600080fd5b5061014f610803565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561019d57600080fd5b506101a661082c565b604051808215151515815260200191505060405180910390f35b3480156101cc57600080fd5b506101eb60048036038101908080359060200190929190505050610883565b6040518082815260200191505060405180910390f35b61020961089d565b005b34801561021757600080fd5b5061024c600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061089f565b005b34801561025a57600080fd5b506102636108be565b6040518082815260200191505060405180910390f35b6000610283610a26565b600061028d61082c565b151561029857600080fd5b606484101515610336576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f4578706563746174696f6e2073686f756c64206265206c657373207468616e2081526020017f3130302e0000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b83600181905550610352612710856108c490919063ffffffff16565b9250600082600060648110151561036557fe5b602002018181525050600190505b60648110156103b35761038f818461090290919063ffffffff16565b828260648110151561039d57fe5b6020020181815250508080600101915050610373565b8160029060646103c4929190610a4a565b5050505050565b6103d361082c565b15156103de57600080fd5b3073ffffffffffffffffffffffffffffffffffffffff1631811115151561046d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601c8152602001807f4e6f20656e6f756768206f6620746f6b656e20746f20636c61696d2e0000000081525060200191505060405180910390fd5b610475610803565b73ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501580156104ba573d6000803e3d6000fd5b5050565b6104c661082c565b15156104d157600080fd5b600073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a360008060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b60008060018311151561060b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f5461726765742073686f756c64206265206269676765722e000000000000000081525060200191505060405180910390fd5b6001548311151515610685576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f5461726765742073686f756c6420626520736d616c6c65722e0000000000000081525060200191505060405180910390fd5b612710341115156106fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260158152602001807f4d696e696d756d206265742069732031303030302e000000000000000000000081525060200191505060405180910390fd5b600160642f81151561070c57fe5b0601915060009050828210156107a05761075661271061074860026001870360648110151561073757fe5b0154346108c490919063ffffffff16565b61090290919063ffffffff16565b90503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f1935050505015801561079e573d6000803e3d6000fd5b505b3373ffffffffffffffffffffffffffffffffffffffff167f97371a3349bea11f577edf6e64350a3dfb9de665d1154c7e6d08eb0805aa043084348460405180848152602001838152602001828152602001935050505060405180910390a2505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614905090565b60028160648110151561089257fe5b016000915090505481565b565b6108a761082c565b15156108b257600080fd5b6108bb8161092c565b50565b60015481565b60008060008414156108d957600091506108fb565b82840290508284828115156108ea57fe5b041415156108f757600080fd5b8091505b5092915050565b60008060008311151561091457600080fd5b828481151561091f57fe5b0490508091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415151561096857600080fd5b8073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b610c8060405190810160405280606490602082028038833980820191505090505090565b8260648101928215610a79579160200282015b82811115610a78578251825591602001919060010190610a5d565b5b509050610a869190610a8a565b5090565b610aac91905b80821115610aa8576000816000905550600101610a90565b5090565b905600a165627a7a7230582087d19571ab3ae7207fb8f6182b6b891f2414f00e1904790341a82c957044b5330029")
@@ -47,19 +48,18 @@ func init() {
 
 func (m *Monkey) Gamble() uint64 {
 	fmt.Println("Deploying contract ...")
-	contract := m.deploy(m.source, betContract, betConstructor, new(big.Int), math.MaxUint64)
+	contract := m.Deploy(m.source, betContract, betConstructor, new(big.Int), math.MaxUint64)
 	fmt.Println("  Contract deployed: ", contract.String())
 	fmt.Println("Donating ...")
 	input, err := betABI.Pack("donate")
 	if err != nil {
 		panic(err)
 	}
-	m.transfer(&transferContext{
+	m.Transfer(&client.TransferContext{
 		Key:       m.source,
 		ToAddress: contract,
 		Amount:    new(big.Int).Set(oneDEX),
 		Data:      input,
-		Gas:       0,
 		Nonce:     math.MaxUint64,
 	})
 
@@ -74,9 +74,9 @@ func (m *Monkey) Gamble() uint64 {
 loop:
 	for {
 		fmt.Println("nonce", nonce)
-		ctxs := make([]*transferContext, len(m.keys))
+		ctxs := make([]*client.TransferContext, len(m.keys))
 		for i, key := range m.keys {
-			ctx := &transferContext{
+			ctx := &client.TransferContext{
 				Key:       key,
 				ToAddress: contract,
 				Amount:    big.NewInt(100000),
@@ -87,11 +87,11 @@ loop:
 			if config.Batch {
 				ctxs[i] = ctx
 			} else {
-				m.transfer(ctx)
+				m.Transfer(ctx)
 			}
 		}
 		if config.Batch {
-			m.batchTransfer(ctxs)
+			m.BatchTransfer(ctxs)
 		}
 
 		if m.timer != nil {
@@ -102,7 +102,7 @@ loop:
 			}
 		}
 
-		nonce += 1
+		nonce++
 		time.Sleep(time.Duration(config.Sleep) * time.Millisecond)
 	}
 
diff --git a/cmd/zoo/monkey/monkey.go b/cmd/zoo/monkey/monkey.go
index a511c42fa..c228f1814 100644
--- a/cmd/zoo/monkey/monkey.go
+++ b/cmd/zoo/monkey/monkey.go
@@ -22,17 +22,15 @@ package monkey
 import (
 	"context"
 	"crypto/ecdsa"
+	"encoding/hex"
 	"fmt"
-	"math"
 	"math/big"
 	"math/rand"
+	"os"
 	"time"
 
-	dexon "github.com/dexon-foundation/dexon"
-	"github.com/dexon-foundation/dexon/common"
-	"github.com/dexon-foundation/dexon/core/types"
+	"github.com/dexon-foundation/dexon/cmd/zoo/client"
 	"github.com/dexon-foundation/dexon/crypto"
-	"github.com/dexon-foundation/dexon/ethclient"
 )
 
 var config *MonkeyConfig
@@ -53,19 +51,37 @@ func Init(cfg *MonkeyConfig) {
 }
 
 type Monkey struct {
-	client    *ethclient.Client
-	source    *ecdsa.PrivateKey
-	keys      []*ecdsa.PrivateKey
-	networkID *big.Int
-	timer     <-chan time.Time
+	client.Client
+
+	source *ecdsa.PrivateKey
+	keys   []*ecdsa.PrivateKey
+	timer  <-chan time.Time
 }
 
 func New(ep string, source *ecdsa.PrivateKey, num int, timeout time.Duration) *Monkey {
-	client, err := ethclient.Dial(ep)
+	client, err := client.New(ep)
 	if err != nil {
 		panic(err)
 	}
 
+	file := func() *os.File {
+		for i := 0; i < 100; i++ {
+			name := fmt.Sprintf("zoo-%d.keys", i)
+			file, err := os.OpenFile(name,
+				os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0600)
+			if err != nil {
+				continue
+			}
+			fmt.Printf("Save keys to file %s\n", name)
+			return file
+		}
+		return nil
+	}()
+	if file == nil {
+		panic(fmt.Errorf("Failed to create file for zoo keys"))
+	}
+	defer file.Close()
+
 	var keys []*ecdsa.PrivateKey
 
 	for i := 0; i < num; i++ {
@@ -73,19 +89,17 @@ func New(ep string, source *ecdsa.PrivateKey, num int, timeout time.Duration) *M
 		if err != nil {
 			panic(err)
 		}
+		_, err = file.Write([]byte(hex.EncodeToString(crypto.FromECDSA(key)) + "\n"))
+		if err != nil {
+			panic(err)
+		}
 		keys = append(keys, key)
 	}
 
-	networkID, err := client.NetworkID(context.Background())
-	if err != nil {
-		panic(err)
-	}
-
 	monkey := &Monkey{
-		client:    client,
-		source:    source,
-		keys:      keys,
-		networkID: networkID,
+		Client: *client,
+		source: source,
+		keys:   keys,
 	}
 
 	if timeout > 0 {
@@ -95,160 +109,29 @@ func New(ep string, source *ecdsa.PrivateKey, num int, timeout time.Duration) *M
 	return monkey
 }
 
-type transferContext struct {
-	Key       *ecdsa.PrivateKey
-	ToAddress common.Address
-	Amount    *big.Int
-	Data      []byte
-	Nonce     uint64
-	Gas       uint64
-}
-
-func (m *Monkey) prepareTx(ctx *transferContext) *types.Transaction {
-	if ctx.Nonce == math.MaxUint64 {
-		var err error
-		address := crypto.PubkeyToAddress(ctx.Key.PublicKey)
-		ctx.Nonce, err = m.client.PendingNonceAt(context.Background(), address)
-		if err != nil {
-			panic(err)
-		}
-	}
-
-	if ctx.Gas == uint64(0) {
-		var err error
-		ctx.Gas, err = m.client.EstimateGas(context.Background(), dexon.CallMsg{
-			Data: ctx.Data,
-		})
-		if err != nil {
-			panic(err)
-		}
-	}
-
-	gasPrice, err := m.client.SuggestGasPrice(context.Background())
-	if err != nil {
-		panic(err)
-	}
-
-	tx := types.NewTransaction(
-		ctx.Nonce,
-		ctx.ToAddress,
-		ctx.Amount,
-		ctx.Gas,
-		gasPrice,
-		ctx.Data)
-
-	signer := types.NewEIP155Signer(m.networkID)
-	tx, err = types.SignTx(tx, signer, ctx.Key)
-	if err != nil {
-		panic(err)
-	}
-
-	return tx
-}
-
-func (m *Monkey) transfer(ctx *transferContext) {
-	tx := m.prepareTx(ctx)
-
-	err := m.client.SendTransaction(context.Background(), tx)
-	if err != nil {
-		panic(err)
-	}
-}
-
-func (m *Monkey) batchTransfer(ctxs []*transferContext) {
-	txs := make([]*types.Transaction, len(ctxs))
-	for i, ctx := range ctxs {
-		txs[i] = m.prepareTx(ctx)
-	}
-
-	err := m.client.SendTransactions(context.Background(), txs)
-	if err != nil {
-		panic(err)
-	}
-}
-
-func (m *Monkey) deploy(
-	key *ecdsa.PrivateKey, code string, ctors []string, amount *big.Int, nonce uint64) common.Address {
-
-	address := crypto.PubkeyToAddress(key.PublicKey)
-	if nonce == math.MaxUint64 {
-		var err error
-		nonce, err = m.client.PendingNonceAt(context.Background(), address)
-		if err != nil {
-			panic(err)
-		}
-	}
-
-	var input string
-	for _, ctor := range ctors {
-		input += fmt.Sprintf("%064s", ctor)
-	}
-	data := common.Hex2Bytes(code + input)
-
-	gas, err := m.client.EstimateGas(context.Background(), dexon.CallMsg{
-		From: address,
-		Data: data,
-	})
-	if err != nil {
-		panic(err)
-	}
-
-	tx := types.NewContractCreation(
-		nonce,
-		amount,
-		gas,
-		big.NewInt(1e9),
-		data)
-
-	signer := types.NewEIP155Signer(m.networkID)
-	tx, err = types.SignTx(tx, signer, key)
-	if err != nil {
-		panic(err)
-	}
-
-	fmt.Println("Sending TX", "fullhash", tx.Hash().String())
-
-	err = m.client.SendTransaction(context.Background(), tx)
-	if err != nil {
-		panic(err)
-	}
-
-	for {
-		time.Sleep(500 * time.Millisecond)
-		recp, err := m.client.TransactionReceipt(context.Background(), tx.Hash())
-		if err != nil {
-			if err == dexon.NotFound {
-				continue
-			}
-			panic(err)
-		}
-		return recp.ContractAddress
-	}
-}
-
 func (m *Monkey) Distribute() {
 	fmt.Println("Distributing DEX to random accounts ...")
 	address := crypto.PubkeyToAddress(m.source.PublicKey)
-	nonce, err := m.client.PendingNonceAt(context.Background(), address)
+	nonce, err := m.PendingNonceAt(context.Background(), address)
 	if err != nil {
 		panic(err)
 	}
 
-	ctxs := make([]*transferContext, len(m.keys))
+	ctxs := make([]*client.TransferContext, len(m.keys))
 	for i, key := range m.keys {
 		address := crypto.PubkeyToAddress(key.PublicKey)
 		amount := new(big.Int)
 		amount.SetString("1000000000000000000", 10)
-		ctxs[i] = &transferContext{
+		ctxs[i] = &client.TransferContext{
 			Key:       m.source,
 			ToAddress: address,
 			Amount:    amount,
 			Nonce:     nonce,
 			Gas:       21000,
 		}
-		nonce += 1
+		nonce++
 	}
-	m.batchTransfer(ctxs)
+	m.BatchTransfer(ctxs)
 	time.Sleep(20 * time.Second)
 }
 
@@ -257,12 +140,12 @@ func (m *Monkey) Crazy() uint64 {
 	nonce := uint64(0)
 loop:
 	for {
-		ctxs := make([]*transferContext, len(m.keys))
+		ctxs := make([]*client.TransferContext, len(m.keys))
 		for i, key := range m.keys {
 			to := crypto.PubkeyToAddress(m.keys[rand.Int()%len(m.keys)].PublicKey)
 			amount := new(big.Int)
 			amount.SetString(fmt.Sprintf("%d0000000000000", rand.Intn(10)+1), 10)
-			ctx := &transferContext{
+			ctx := &client.TransferContext{
 				Key:       key,
 				ToAddress: to,
 				Amount:    amount,
@@ -272,11 +155,11 @@ loop:
 			if config.Batch {
 				ctxs[i] = ctx
 			} else {
-				m.transfer(ctx)
+				m.Transfer(ctx)
 			}
 		}
 		if config.Batch {
-			m.batchTransfer(ctxs)
+			m.BatchTransfer(ctxs)
 		}
 		fmt.Printf("Sent %d transactions, nonce = %d\n", len(m.keys), nonce)
 
@@ -288,7 +171,7 @@ loop:
 			}
 		}
 
-		nonce += 1
+		nonce++
 		time.Sleep(time.Duration(config.Sleep) * time.Millisecond)
 	}
 
diff --git a/cmd/zoo/utils/shutdown.go b/cmd/zoo/utils/shutdown.go
new file mode 100644
index 000000000..288f664f6
--- /dev/null
+++ b/cmd/zoo/utils/shutdown.go
@@ -0,0 +1,114 @@
+// Copyright 2019 The dexon-consensus Authors
+// This file is part of the dexon-consensus library.
+//
+// The dexon-consensus 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 dexon-consensus 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 dexon-consensus library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package utils
+
+import (
+	"bufio"
+	"context"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"math"
+	"math/big"
+	"os"
+
+	"github.com/dexon-foundation/dexon"
+	"github.com/dexon-foundation/dexon/cmd/zoo/client"
+	"github.com/dexon-foundation/dexon/crypto"
+)
+
+type ShutdownConfig struct {
+	Key      string
+	Endpoint string
+	File     string
+	Batch    bool
+}
+
+func Shutdown(config *ShutdownConfig) {
+	privKey, err := crypto.LoadECDSA(config.Key)
+	if err != nil {
+		panic(err)
+	}
+	addr := crypto.PubkeyToAddress(privKey.PublicKey)
+
+	cl, err := client.New(config.Endpoint)
+	if err != nil {
+		panic(err)
+	}
+
+	file, err := os.Open(config.File)
+	if err != nil {
+		panic(err)
+	}
+	defer file.Close()
+
+	ctxs := make([]*client.TransferContext, 0)
+	reader := bufio.NewReader(file)
+	for {
+		buf, err := reader.ReadString('\n')
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			panic(err)
+		}
+		buf = buf[:len(buf)-1]
+		key, err := hex.DecodeString(buf)
+		if err != nil {
+			panic(err)
+		}
+		prvKey, err := crypto.ToECDSA(key)
+		if err != nil {
+			panic(err)
+		}
+		balance, err := cl.BalanceAt(context.Background(),
+			crypto.PubkeyToAddress(prvKey.PublicKey), nil)
+		if err != nil {
+			panic(err)
+		}
+
+		gasLimit, err := cl.EstimateGas(context.Background(), dexon.CallMsg{})
+		if err != nil {
+			panic(err)
+		}
+		gasPrice, err := cl.SuggestGasPrice(context.Background())
+		if err != nil {
+			panic(err)
+		}
+		gas := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasLimit)))
+		if gas.Cmp(balance) >= 0 {
+			fmt.Println("Skipping account: not enough of DXN", crypto.PubkeyToAddress(prvKey.PublicKey))
+			continue
+		}
+
+		ctx := &client.TransferContext{
+			Key:       prvKey,
+			ToAddress: addr,
+			Amount:    new(big.Int).Sub(balance, gas),
+			Nonce:     math.MaxUint64,
+		}
+		if !config.Batch {
+			cl.Transfer(ctx)
+		} else {
+			ctxs = append(ctxs, ctx)
+		}
+	}
+	if config.Batch {
+		cl.BatchTransfer(ctxs)
+	}
+}
-- 
cgit v1.2.3