diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index 25b61928e1..c0c1002a4e 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -88,6 +88,8 @@ type ContractTransactor interface { EstimateGas(ctx context.Context, call ethereum.CallMsg) (usedGas *big.Int, err error) // SendTransaction injects the transaction into the pending pool for execution. SendTransaction(ctx context.Context, tx *types.Transaction) error + // SendUnsignedTransaction injects an unsigned transaction into the pending pool for execution. + SendUnsignedTransaction(ctx context.Context, tx *types.Transaction, sender common.Address) error } // ContractBackend defines the methods needed to work with contracts on a read-write basis. diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index e1d5745caa..456e651e2b 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -287,6 +287,28 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa return nil } +// SendUnsignedTransaction updates the pending block to include the given raw transaction. +// It panics if the transaction is invalid. +func (b *SimulatedBackend) SendUnsignedTransaction(ctx context.Context, tx *types.Transaction, sender common.Address) error { + b.mu.Lock() + defer b.mu.Unlock() + + nonce := b.pendingState.GetNonce(sender) + if tx.Nonce() != nonce { + panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)) + } + + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) { + for _, tx := range b.pendingBlock.Transactions() { + block.AddTx(tx) + } + block.AddTx(tx) + }) + b.pendingBlock = blocks[0] + b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database)) + return nil +} + // JumpTimeInSeconds adds skip seconds to the clock func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { b.mu.Lock() diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index b40bd65e80..c6f154658d 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -18,7 +18,6 @@ package bind import ( "context" - "errors" "fmt" "math/big" @@ -213,16 +212,20 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input) } if opts.Signer == nil { - return nil, errors.New("no signer to authorize the transaction with") + if err := c.transactor.SendUnsignedTransaction(ensureContext(opts.Context), rawTx, opts.From); err != nil { + return nil, err + } + return rawTx, nil + } else { + signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) + if err != nil { + return nil, err + } + if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { + return nil, err + } + return signedTx, nil } - signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) - if err != nil { - return nil, err - } - if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { - return nil, err - } - return signedTx, nil } func ensureContext(ctx context.Context) context.Context { diff --git a/eth/bind.go b/eth/bind.go index d09977dbc2..d9ef4e989c 100644 --- a/eth/bind.go +++ b/eth/bind.go @@ -136,3 +136,21 @@ func (b *ContractBackend) SendTransaction(ctx context.Context, tx *types.Transac _, err := b.txapi.SendRawTransaction(ctx, raw) return err } + +// SendUnsignedTransaction implements bind.ContractTransactor injects an +// unsigned transaction into the pending pool for execution +func (b *ContractBackend) SendUnsignedTransaction(ctx context.Context, tx *types.Transaction, sender common.Address) error { + // Convert our transaction + sender into internal struct SendTxArgs + nonce := tx.Nonce() + args := ethapi.SendTxArgs{ + From: sender, + To: tx.To(), + Gas: (*hexutil.Big)(tx.Gas()), + GasPrice: (*hexutil.Big)(tx.GasPrice()), + Value: (*hexutil.Big)(tx.Value()), + Data: tx.Data(), + Nonce: (*hexutil.Uint64)(&nonce), + } + _, err := b.txapi.SendTransaction(ctx, args) + return err +} diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 7f73ab113c..835225c523 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -476,6 +476,12 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data)) } +// SendUnsignedTransaction injects an unsigned transaction into the pending pool +// for execution, depending on the RPC server to calculate the signature. +func (ec *Client) SendUnsignedTransaction(ctx context.Context, tx *types.Transaction, sender common.Address) error { + return ec.c.CallContext(ctx, nil, "eth_sendTransaction", toTransactArg(tx, sender)) +} + func toCallArg(msg ethereum.CallMsg) interface{} { arg := map[string]interface{}{ "from": msg.From, @@ -495,3 +501,19 @@ func toCallArg(msg ethereum.CallMsg) interface{} { } return arg } + +func toTransactArg(tx *types.Transaction, sender common.Address) interface{} { + arg := map[string]interface{}{ + "from": sender, + "to": tx.To(), + "value": (*hexutil.Big)(tx.Value()), + "gas": (*hexutil.Big)(tx.Gas()), + "gasPrice": (*hexutil.Big)(tx.GasPrice()), + } + + if len(tx.Data()) > 0 { + arg["data"] = hexutil.Bytes(tx.Data()) + } + + return arg +}