From e79dcc385d37eefb121f675d3800dbfe59b515c7 Mon Sep 17 00:00:00 2001 From: Wei-Ning Huang Date: Fri, 23 Nov 2018 12:12:10 +0800 Subject: api: allow sending batch of raw transactions --- cmd/monkey/gambler.go | 33 +++++++---- cmd/monkey/key | 1 - cmd/monkey/monkey.go | 140 +++++++++++++++++++++++---------------------- dex/api_backend.go | 4 ++ eth/api_backend.go | 4 ++ ethclient/ethclient.go | 16 ++++++ internal/ethapi/api.go | 39 +++++++++++++ internal/ethapi/backend.go | 1 + les/api_backend.go | 5 ++ 9 files changed, 164 insertions(+), 79 deletions(-) delete mode 100644 cmd/monkey/key diff --git a/cmd/monkey/gambler.go b/cmd/monkey/gambler.go index ecddb7d0f..8151f2c22 100644 --- a/cmd/monkey/gambler.go +++ b/cmd/monkey/gambler.go @@ -18,7 +18,6 @@ package main import ( - "crypto/ecdsa" "fmt" "math" "math/big" @@ -55,7 +54,14 @@ func (m *Monkey) Gamble() { if err != nil { panic(err) } - m.call(m.source, contract, input, new(big.Int).Set(oneDEX), 0, math.MaxUint64) + m.transfer(&transferContext{ + Key: m.source, + ToAddress: contract, + Amount: new(big.Int).Set(oneDEX), + Data: input, + Gas: 0, + Nonce: math.MaxUint64, + }) time.Sleep(5 * time.Second) @@ -64,20 +70,27 @@ func (m *Monkey) Gamble() { panic(err) } - call := func(key *ecdsa.PrivateKey, nonce uint64) { - m.call(key, contract, input, big.NewInt(100000), uint64(32740), nonce) - } - nonce := uint64(0) for { fmt.Println("nonce", nonce) - for _, key := range m.keys { - if *parallel { - go call(key, nonce) + ctxs := make([]*transferContext, len(m.keys)) + for i, key := range m.keys { + ctx := &transferContext{ + Key: key, + ToAddress: contract, + Amount: big.NewInt(1), + Data: input, + Nonce: nonce, + } + if *batch { + ctxs[i] = ctx } else { - call(key, nonce) + m.transfer(ctx) } } + if *batch { + m.batchTransfer(ctxs) + } nonce += 1 time.Sleep(time.Duration(*sleep) * time.Millisecond) } diff --git a/cmd/monkey/key b/cmd/monkey/key deleted file mode 100644 index a9fe18413..000000000 --- a/cmd/monkey/key +++ /dev/null @@ -1 +0,0 @@ -fa30b47a7a3d5ab6935d873ffaeb8ca5b9782d102c4094be6da6b7f2fc04b5bd \ No newline at end of file diff --git a/cmd/monkey/monkey.go b/cmd/monkey/monkey.go index 6ac9be5f3..f55d195a6 100644 --- a/cmd/monkey/monkey.go +++ b/cmd/monkey/monkey.go @@ -40,7 +40,7 @@ var key = flag.String("key", "", "private key path") var endpoint = flag.String("endpoint", "http://127.0.0.1:8545", "JSON RPC endpoint") var n = flag.Int("n", 100, "number of random accounts") var gambler = flag.Bool("gambler", false, "make this monkey a gambler") -var parallel = flag.Bool("parallel", false, "monkeys will send transaction in parallel") +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") type Monkey struct { @@ -79,35 +79,70 @@ func New(ep string, source *ecdsa.PrivateKey, num int) *Monkey { } } -func (m *Monkey) transfer( - key *ecdsa.PrivateKey, toAddress common.Address, amount *big.Int, nonce uint64) { +type transferContext struct { + Key *ecdsa.PrivateKey + ToAddress common.Address + Amount *big.Int + Data []byte + Nonce uint64 + Gas uint64 +} - if nonce == math.MaxUint64 { +func (m *Monkey) prepareTx(ctx *transferContext) *types.Transaction { + if ctx.Nonce == math.MaxUint64 { var err error - address := crypto.PubkeyToAddress(key.PublicKey) - nonce, err = m.client.PendingNonceAt(context.Background(), address) + 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) } } tx := types.NewTransaction( - uint64(nonce), - toAddress, - amount, + ctx.Nonce, + ctx.ToAddress, + ctx.Amount, uint64(21000), big.NewInt(1e9), nil) signer := types.NewEIP155Signer(m.networkID) - tx, err := types.SignTx(tx, signer, key) + 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) + fmt.Println("Sending TX", "fullhash", tx.Hash().String()) + err := m.client.SendTransaction(context.Background(), tx) + if err != nil { + panic(err) + } +} - err = m.client.SendTransaction(context.Background(), tx) +func (m *Monkey) batchTransfer(ctxs []*transferContext) { + txs := make([]*types.Transaction, len(ctxs)) + for i, ctx := range ctxs { + txs[i] = m.prepareTx(ctx) + fmt.Println("Sending TX", "fullhash", txs[i].Hash().String()) + } + + err := m.client.SendTransactions(context.Background(), txs) if err != nil { panic(err) } @@ -139,7 +174,7 @@ func (m *Monkey) deploy( } tx := types.NewContractCreation( - uint64(nonce), + nonce, amount, gas, big.NewInt(1e9), @@ -171,53 +206,6 @@ func (m *Monkey) deploy( } } -func (m *Monkey) call( - key *ecdsa.PrivateKey, contract common.Address, input []byte, amount *big.Int, gas uint64, nonce uint64) { - - 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) - } - } - - if gas == uint64(0) { - var err error - gas, err = m.client.EstimateGas(context.Background(), dexon.CallMsg{ - From: address, - To: &contract, - Value: amount, - Data: input, - }) - if err != nil { - panic(err) - } - } - - tx := types.NewTransaction( - uint64(nonce), - contract, - amount, - gas, - big.NewInt(1e9), - input) - - 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) - } -} - func (m *Monkey) Distribute() { fmt.Println("Distributing DEX to random accounts ...") address := crypto.PubkeyToAddress(m.source.PublicKey) @@ -226,32 +214,48 @@ func (m *Monkey) Distribute() { panic(err) } - for _, key := range m.keys { + ctxs := make([]*transferContext, len(m.keys)) + for i, key := range m.keys { address := crypto.PubkeyToAddress(key.PublicKey) amount := new(big.Int) amount.SetString("1000000000000000000", 10) - m.transfer(m.source, address, amount, nonce) + ctxs[i] = &transferContext{ + Key: m.source, + ToAddress: address, + Amount: amount, + Nonce: nonce, + Gas: 21000, + } nonce += 1 } + m.batchTransfer(ctxs) time.Sleep(20 * time.Second) } func (m *Monkey) Crazy() { fmt.Println("Performing random transfers ...") nonce := uint64(0) - transfer := func(key *ecdsa.PrivateKey, to common.Address, nonce uint64) { - m.transfer(key, to, big.NewInt(1), nonce) - } for { fmt.Println("nonce", nonce) - for _, key := range m.keys { + ctxs := make([]*transferContext, len(m.keys)) + for i, key := range m.keys { to := crypto.PubkeyToAddress(m.keys[rand.Int()%len(m.keys)].PublicKey) - if *parallel { - go transfer(key, to, nonce) + ctx := &transferContext{ + Key: key, + ToAddress: to, + Amount: big.NewInt(1), + Nonce: nonce, + Gas: 21000, + } + if *batch { + ctxs[i] = ctx } else { - transfer(key, to, nonce) + m.transfer(ctx) } } + if *batch { + m.batchTransfer(ctxs) + } nonce += 1 time.Sleep(time.Duration(*sleep) * time.Millisecond) } diff --git a/dex/api_backend.go b/dex/api_backend.go index 9929b062d..80fe1e39b 100644 --- a/dex/api_backend.go +++ b/dex/api_backend.go @@ -154,6 +154,10 @@ func (b *DexAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) return b.dex.txPool.AddLocal(signedTx) } +func (b *DexAPIBackend) SendTxs(ctx context.Context, signedTxs []*types.Transaction) []error { + return b.dex.txPool.AddLocals(signedTxs) +} + func (b *DexAPIBackend) GetPoolTransactions() (types.Transactions, error) { pending, err := b.dex.txPool.Pending() if err != nil { diff --git a/eth/api_backend.go b/eth/api_backend.go index 1c27b5d5c..696b6c64a 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -157,6 +157,10 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) return b.eth.txPool.AddLocal(signedTx) } +func (b *EthAPIBackend) SendTxs(ctx context.Context, signedTxs []*types.Transaction) []error { + return b.eth.txPool.AddLocals(signedTxs) +} + func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { pending, err := b.eth.txPool.Pending() if err != nil { diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 12eeaf0f6..c8a8e5eb5 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -506,6 +506,22 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data)) } +// SendTransactions injects a batch of signed transactions into the pending pool for execution. +// +// If a transaction was a contract creation use the TransactionReceipt method to get the +// contract address after the transaction has been mined. +func (ec *Client) SendTransactions(ctx context.Context, txs []*types.Transaction) error { + txData := make([]interface{}, len(txs)) + for i, tx := range txs { + data, err := rlp.EncodeToBytes(tx) + if err != nil { + return err + } + txData[i] = common.ToHex(data) + } + return ec.c.CallContext(ctx, nil, "eth_sendRawTransactions", txData) +} + func toCallArg(msg ethereum.CallMsg) interface{} { arg := map[string]interface{}{ "from": msg.From, diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8c950d5cf..3dec0b114 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1288,6 +1288,31 @@ func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c return tx.Hash(), nil } +// submitTransactions is a helper function that submits batch of tx to txPool and logs a message. +func submitTransactions(ctx context.Context, b Backend, txs []*types.Transaction) ([]common.Hash, error) { + errs := b.SendTxs(ctx, txs) + var hashes []common.Hash + for i, err := range errs { + if err != nil { + return nil, err + } + tx := txs[i] + if tx.To() == nil { + signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) + from, err := types.Sender(signer, tx) + if err != nil { + return nil, err + } + addr := crypto.CreateAddress(from, tx.Nonce()) + log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex()) + } else { + log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To()) + } + hashes = append(hashes, tx.Hash()) + } + return hashes, nil +} + // SendTransaction creates a transaction for the given argument, sign it and submit it to the // transaction pool. func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { @@ -1335,6 +1360,20 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod return submitTransaction(ctx, s.b, tx) } +// SendRawTransactions will add the signed transaction to the transaction pool. +// The sender is responsible for signing the transaction and using the correct nonce. +func (s *PublicTransactionPoolAPI) SendRawTransactions(ctx context.Context, encodedTxs []hexutil.Bytes) ([]common.Hash, error) { + var txs []*types.Transaction + for _, encodedTx := range encodedTxs { + tx := new(types.Transaction) + if err := rlp.DecodeBytes(encodedTx, tx); err != nil { + return nil, err + } + txs = append(txs, tx) + } + return submitTransactions(ctx, s.b, txs) +} + // Sign calculates an ECDSA signature for: // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message). // diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 801b55d5c..da13d3661 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -65,6 +65,7 @@ type Backend interface { // TxPool API SendTx(ctx context.Context, signedTx *types.Transaction) error + SendTxs(ctx context.Context, signedTxs []*types.Transaction) []error GetPoolTransactions() (types.Transactions, error) GetPoolTransaction(txHash common.Hash) *types.Transaction GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) diff --git a/les/api_backend.go b/les/api_backend.go index 148b29c0b..f69e67c60 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -115,6 +115,11 @@ func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) return b.eth.txPool.Add(ctx, signedTx) } +func (b *LesApiBackend) SendTxs(ctx context.Context, signedTxs []*types.Transaction) []error { + b.eth.txPool.AddBatch(ctx, signedTxs) + return nil +} + func (b *LesApiBackend) RemoveTx(txHash common.Hash) { b.eth.txPool.RemoveTx(txHash) } -- cgit v1.2.3