accounts/abi/bind: call gas limit and transact gas tuning

This commit is contained in:
Péter Szilágyi 2016-09-04 15:23:04 +03:00
parent 2c6be49d20
commit 77d47ce651
2 changed files with 84 additions and 5 deletions

View file

@ -33,9 +33,15 @@ import (
// sign the transaction before submission. // sign the transaction before submission.
type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error)
// GasTunerFn is a callback function to modify the gas limit of a transaction
// estimated by the backend node. It's purpose is to allow providing some wiggle
// room for approximate estimations and/or concurrent state changes.
type GasTunerFn func(gas *big.Int) *big.Int
// CallOpts is the collection of options to fine tune a contract call request. // CallOpts is the collection of options to fine tune a contract call request.
type CallOpts struct { type CallOpts struct {
Pending bool // Whether to operate on the pending state or the last known one Pending bool // Whether to operate on the pending state or the last known one
GasLimit *big.Int // Gas limit allowance of the call operation (nil = use backend default)
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
} }
@ -47,9 +53,10 @@ type TransactOpts struct {
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state) Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
Signer SignerFn // Method to use for signing the transaction (mandatory) Signer SignerFn // Method to use for signing the transaction (mandatory)
Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds) Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds)
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle) GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
GasLimit *big.Int // Gas limit to set for the transaction execution (nil = estimate + 10%) GasLimit *big.Int // Gas limit to set for the transaction execution (nil = estimate)
GasTuner GasTunerFn // Modifier for the gas limit estimated by the node (nil = identity)
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
} }
@ -108,7 +115,7 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
return err return err
} }
var ( var (
msg = ethereum.CallMsg{To: &c.address, Data: input} msg = ethereum.CallMsg{To: &c.address, Data: input, Gas: opts.GasLimit}
ctx = ensureContext(opts.Context) ctx = ensureContext(opts.Context)
code []byte code []byte
output []byte output []byte
@ -203,6 +210,12 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to estimate gas needed: %v", err) return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
} }
// If custom gas estimation post-processing was requested, execute it
if opts.GasTuner != nil {
if gasLimit = opts.GasTuner(gasLimit); gasLimit == nil {
return nil, fmt.Errorf("gas tuner returned nil")
}
}
} }
// Create the transaction, sign it and schedule it for execution // Create the transaction, sign it and schedule it for execution
var rawTx *types.Transaction var rawTx *types.Transaction

View file

@ -365,6 +365,72 @@ var bindTests = []struct {
} }
`, `,
}, },
// Tests that gas limits can be configured for calls, transacts and also estimation tunings
{
`GasLimiter`,
`
contract GasLimiter {
string public field;
function SetField(string value) {
// This check will screw gas estimation! Good, good!
if (msg.gas < 100000) {
throw;
}
field = value;
}
}
`,
`606060405261021c806100126000396000f3606060405260e060020a600035046323fcf32a81146100265780634f28bf0e1461007b575b005b6040805160206004803580820135601f8101849004840285018401909552848452610024949193602493909291840191908190840183828082843750949650505050505050620186a05a101561014e57610002565b6100db60008054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281529291908301828280156102145780601f106101e957610100808354040283529160200191610214565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561013b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b505050565b8060006000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106101b557805160ff19168380011785555b506101499291505b808211156101e557600081556001016101a1565b82800160010185558215610199579182015b828111156101995782518260005055916020019190600101906101c7565b5090565b820191906000526020600020905b8154815290600101906020018083116101f757829003601f168201915b50505050508156`,
`[{"constant":false,"inputs":[{"name":"value","type":"string"}],"name":"SetField","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"field","outputs":[{"name":"","type":"string"}],"type":"function"}]`,
`
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)})
// Deploy a gas limiter contract and check that estimation fails miserably indeed
_, _, limiter, err := DeployGasLimiter(auth, sim)
if err != nil {
t.Fatalf("Failed to deploy limiter contract: %v", err)
}
sim.Commit()
if _, err := limiter.SetField(auth, "abc"); err != nil {
t.Fatalf("Failed to call bad gased transaction: %v", err)
}
sim.Commit()
if field, _ := limiter.Field(nil); field != "" {
t.Fatalf("Field mismatch: have %v, want %v", field, "")
}
// Set the field with a manual high gas limit and check that it succeeds
auth.GasLimit = big.NewInt(200000)
if _, err := limiter.SetField(auth, "manual"); err != nil {
t.Fatalf("Failed to call bad gased transaction: %v", err)
}
sim.Commit()
if field, _ := limiter.Field(nil); field != "manual" {
t.Fatalf("Field mismatch: have %v, want %v", field, "manual")
}
// Set the field with a tuned estimation and chck that it succeeds
auth.GasLimit = nil
auth.GasTuner = func(gas *big.Int) *big.Int { return new(big.Int).Add(gas, big.NewInt(100000)) }
if _, err := limiter.SetField(auth, "tuned"); err != nil {
t.Fatalf("Failed to call bad gased transaction: %v", err)
}
sim.Commit()
if field, _ := limiter.Field(nil); field != "tuned" {
t.Fatalf("Field mismatch: have %v, want %v", field, "tuned")
}
// Check that manual CALL limiting also works
if _, err := limiter.Field(&bind.CallOpts{GasLimit: big.NewInt(21000)}); err == nil {
t.Fatalf("Gas limited CALL didn't go out of gas")
}
`,
},
} }
// Tests that packages generated by the binder can be successfully compiled and // Tests that packages generated by the binder can be successfully compiled and