Backporting to beta (#4118)

* Ignore get_price_info test by default. (#4112)

* Auto-detect hex encoded bytes in sha3 (#4108)

* Auto-detect hex encoded bytes in sha3

* Using types/isHex

* Removing unused imports

* Use binary chop to estimate gas accurately (#4100)

* Initial sketch.

* Building.

* Fix a few things.

* Fix issue, add tracing.

* Address grumbles

* Raise upper limit if needed

* Fix test.

* Fixing decoding API with signatures in names (#4125)

* Fix call/estimate_gas (#4121)

* Return 0 instead of error with out of gas on estimate_gas

* Fix stuff up.
This commit is contained in:
Arkadiy Paronyan 2017-01-11 20:03:08 +01:00 committed by Gav Wood
parent 2b588d57f0
commit 5e70507c78
29 changed files with 243 additions and 60 deletions

View File

@ -873,6 +873,81 @@ impl BlockChainClient for Client {
Ok(ret)
}
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError> {
let header = self.block_header(block).ok_or(CallError::StatePruned)?;
let last_hashes = self.build_last_hashes(header.parent_hash());
let env_info = EnvInfo {
number: header.number(),
author: header.author(),
timestamp: header.timestamp(),
difficulty: header.difficulty(),
last_hashes: last_hashes,
gas_used: U256::zero(),
gas_limit: U256::max_value(),
};
// that's just a copy of the state.
let mut original_state = self.state_at(block).ok_or(CallError::StatePruned)?;
let sender = t.sender().map_err(|e| {
let message = format!("Transaction malformed: {:?}", e);
ExecutionError::TransactionMalformed(message)
})?;
let balance = original_state.balance(&sender);
let needed_balance = t.value + t.gas * t.gas_price;
if balance < needed_balance {
// give the sender a sufficient balance
original_state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty);
}
let options = TransactOptions { tracing: true, vm_tracing: false, check_nonce: false };
let mut tx = t.clone();
let mut cond = |gas| {
let mut state = original_state.clone();
tx.gas = gas;
Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm)
.transact(&tx, options.clone())
.map(|r| r.trace[0].result.succeeded())
.unwrap_or(false)
};
let mut upper = env_info.gas_limit;
if !cond(upper) {
// impossible at block gas limit - try `UPPER_CEILING` instead.
// TODO: consider raising limit by powers of two.
const UPPER_CEILING: u64 = 1_000_000_000_000u64;
upper = UPPER_CEILING.into();
if !cond(upper) {
trace!(target: "estimate_gas", "estimate_gas failed with {}", upper);
return Err(CallError::Execution(ExecutionError::Internal))
}
}
let lower = t.gas_required(&self.engine.schedule(&env_info)).into();
if cond(lower) {
trace!(target: "estimate_gas", "estimate_gas succeeded with {}", lower);
return Ok(lower)
}
/// Find transition point between `lower` and `upper` where `cond` changes from `false` to `true`.
/// Returns the lowest value between `lower` and `upper` for which `cond` returns true.
/// We assert: `cond(lower) = false`, `cond(upper) = true`
fn binary_chop<F>(mut lower: U256, mut upper: U256, mut cond: F) -> U256 where F: FnMut(U256) -> bool {
while upper - lower > 1.into() {
let mid = (lower + upper) / 2.into();
trace!(target: "estimate_gas", "{} .. {} .. {}", lower, mid, upper);
let c = cond(mid);
match c {
true => upper = mid,
false => lower = mid,
};
trace!(target: "estimate_gas", "{} => {} .. {}", c, lower, upper);
}
upper
}
// binary chop to non-excepting call with gas somewhere between 21000 and block gas limit
trace!(target: "estimate_gas", "estimate_gas chopping {} .. {}", lower, upper);
Ok(binary_chop(lower, upper, cond))
}
fn replay(&self, id: TransactionId, analytics: CallAnalytics) -> Result<Executed, CallError> {
let address = self.transaction_address(id).ok_or(CallError::TransactionNotFound)?;
let header = self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?;

View File

@ -379,6 +379,10 @@ impl BlockChainClient for TestBlockChainClient {
self.execution_result.read().clone().unwrap()
}
fn estimate_gas(&self, _t: &SignedTransaction, _block: BlockId) -> Result<U256, CallError> {
Ok(21000.into())
}
fn replay(&self, _id: TransactionId, _analytics: CallAnalytics) -> Result<Executed, CallError> {
self.execution_result.read().clone().unwrap()
}

View File

@ -184,6 +184,9 @@ pub trait BlockChainClient : Sync + Send {
/// Makes a non-persistent transaction call.
fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError>;
/// Estimates how much gas will be necessary for a call.
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError>;
/// Replays a given transaction for inspection.
fn replay(&self, t: TransactionId, analytics: CallAnalytics) -> Result<Executed, CallError>;

View File

@ -45,7 +45,7 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address {
}
/// Transaction execution options.
#[derive(Default)]
#[derive(Default, Copy, Clone, PartialEq)]
pub struct TransactOptions {
/// Enable call tracing.
pub tracing: bool,

View File

@ -673,7 +673,7 @@ impl MinerService for Miner {
}
}
fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
fn call(&self, client: &MiningBlockChainClient, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
let sealing_work = self.sealing_work.lock();
match sealing_work.queue.peek_last_ref() {
Some(work) => {
@ -681,7 +681,7 @@ impl MinerService for Miner {
// TODO: merge this code with client.rs's fn call somwhow.
let header = block.header();
let last_hashes = Arc::new(chain.last_hashes());
let last_hashes = Arc::new(client.last_hashes());
let env_info = EnvInfo {
number: header.number(),
author: *header.author(),
@ -706,16 +706,14 @@ impl MinerService for Miner {
state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty);
}
let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false };
let mut ret = Executive::new(&mut state, &env_info, &*self.engine, chain.vm_factory()).transact(t, options)?;
let mut ret = Executive::new(&mut state, &env_info, &*self.engine, client.vm_factory()).transact(t, options)?;
// TODO gav move this into Executive.
ret.state_diff = original_state.map(|original| state.diff_from(original));
Ok(ret)
},
None => {
chain.call(t, BlockId::Latest, analytics)
}
None => client.call(t, BlockId::Latest, analytics)
}
}

View File

@ -82,7 +82,7 @@ impl PriceInfo {
}
}
#[test]
#[test] #[ignore]
fn should_get_price_info() {
use std::sync::Arc;
use std::time::Duration;

View File

@ -391,6 +391,14 @@ impl Res {
Res::Call(_) | Res::FailedCall(_) | Res::FailedCreate(_) | Res::None => Default::default(),
}
}
/// Did this call fail?
pub fn succeeded(&self) -> bool {
match *self {
Res::Call(_) | Res::Create(_) => true,
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
@ -561,4 +569,3 @@ impl Decodable for VMTrace {
Ok(res)
}
}

View File

@ -16,7 +16,7 @@
//! Transaction data structure.
use std::ops::Deref;
use std::ops::{Deref, DerefMut};
use std::cell::*;
use rlp::*;
use util::sha3::Hashable;
@ -239,6 +239,12 @@ impl Deref for SignedTransaction {
}
}
impl DerefMut for SignedTransaction {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.unsigned
}
}
impl Decodable for SignedTransaction {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();

View File

@ -23,12 +23,12 @@ import { eventSignature } from '../../util/signature';
export default class Event {
constructor (abi) {
this._name = abi.name;
this._inputs = EventParam.toEventParams(abi.inputs || []);
this._anonymous = !!abi.anonymous;
const { id, signature } = eventSignature(this._name, this.inputParamTypes());
const { id, name, signature } = eventSignature(abi.name, this.inputParamTypes());
this._id = id;
this._name = name;
this._signature = signature;
}

View File

@ -22,14 +22,14 @@ import { methodSignature } from '../util/signature';
export default class Func {
constructor (abi) {
this._abi = abi;
this._name = abi.name;
this._constant = !!abi.constant;
this._payable = abi.payable;
this._inputs = Param.toParams(abi.inputs || []);
this._outputs = Param.toParams(abi.outputs || []);
const { id, signature } = methodSignature(this._name, this.inputParamTypes());
const { id, name, signature } = methodSignature(abi.name, this.inputParamTypes());
this._id = id;
this._name = name;
this._signature = signature;
}

View File

@ -35,6 +35,18 @@ describe('abi/spec/Function', () => {
});
describe('constructor', () => {
it('returns signature correctly if name already contains it', () => {
const func = new Func({
name: 'test(bool,string)',
inputs: inputsArr,
outputs: outputsArr
});
expect(func.name).to.equal('test');
expect(func.id).to.equal('test(bool,string)');
expect(func.signature).to.equal('02356205');
});
it('stores the parameters as received', () => {
expect(func.name).to.equal('test');
expect(func.constant).to.be.false;

View File

@ -17,15 +17,31 @@
import { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase
import { fromParamType } from '../spec/paramType/format';
export function eventSignature (name, params) {
export function eventSignature (eventName, params) {
const { strName, name } = parseName(eventName);
const types = (params || []).map(fromParamType).join(',');
const id = `${name || ''}(${types})`;
const id = `${strName}(${types})`;
return { id, signature: keccak_256(id) };
return { id, name, signature: keccak_256(id) };
}
export function methodSignature (name, params) {
const { id, signature } = eventSignature(name, params);
export function methodSignature (methodName, params) {
const { id, name, signature } = eventSignature(methodName, params);
return { id, signature: signature.substr(0, 8) };
return { id, name, signature: signature.substr(0, 8) };
}
function parseName (name) {
const strName = `${name || ''}`;
const idx = strName.indexOf('(');
if (idx === -1) {
return { strName, name };
}
const trimmedName = strName.slice(0, idx);
return {
strName: trimmedName,
name: trimmedName
};
}

View File

@ -19,50 +19,93 @@ import { eventSignature, methodSignature } from './signature';
describe('abi/util/signature', () => {
describe('eventSignature', () => {
it('encodes signature baz() correctly', () => {
expect(eventSignature('baz', []))
.to.deep.equal({ id: 'baz()', signature: 'a7916fac4f538170f7cd12c148552e2cba9fcd72329a2dd5b07a6fa906488ddf' });
expect(eventSignature('baz', [])).to.deep.equal({
id: 'baz()',
name: 'baz',
signature: 'a7916fac4f538170f7cd12c148552e2cba9fcd72329a2dd5b07a6fa906488ddf'
});
});
it('encodes signature baz(uint32) correctly', () => {
expect(eventSignature('baz', [{ type: 'uint', length: 32 }]))
.to.deep.equal({ id: 'baz(uint32)', signature: '7d68785e8fc871be024b75964bd86d093511d4bc2dc7cf7bea32c48a0efaecb1' });
expect(eventSignature('baz', [{ type: 'uint', length: 32 }])).to.deep.equal({
id: 'baz(uint32)',
name: 'baz',
signature: '7d68785e8fc871be024b75964bd86d093511d4bc2dc7cf7bea32c48a0efaecb1'
});
});
it('encodes signature baz(uint32, bool) correctly', () => {
expect(eventSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }]))
.to.deep.equal({ id: 'baz(uint32,bool)', signature: 'cdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2' });
expect(eventSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({
id: 'baz(uint32,bool)',
name: 'baz',
signature: 'cdcd77c0992ec5bbfc459984220f8c45084cc24d9b6efed1fae540db8de801d2'
});
});
it('encodes no-name signature correctly as ()', () => {
expect(eventSignature(undefined, []))
.to.deep.equal({ id: '()', signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' });
expect(eventSignature(undefined, [])).to.deep.equal({
id: '()',
name: undefined,
signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe'
});
});
it('encodes no-params signature correctly as ()', () => {
expect(eventSignature(undefined, undefined))
.to.deep.equal({ id: '()', signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe' });
expect(eventSignature(undefined, undefined)).to.deep.equal({
id: '()',
name: undefined,
signature: '861731d50c3880a2ca1994d5ec287b94b2f4bd832a67d3e41c08177bdd5674fe'
});
});
});
describe('methodSignature', () => {
it('encodes signature baz() correctly', () => {
expect(methodSignature('baz', [])).to.deep.equal({ id: 'baz()', signature: 'a7916fac' });
expect(methodSignature('baz', [])).to.deep.equal({
id: 'baz()',
name: 'baz',
signature: 'a7916fac'
});
});
it('encodes signature baz(uint32) correctly', () => {
expect(methodSignature('baz', [{ type: 'uint', length: 32 }])).to.deep.equal({ id: 'baz(uint32)', signature: '7d68785e' });
expect(methodSignature('baz', [{ type: 'uint', length: 32 }])).to.deep.equal({
id: 'baz(uint32)',
name: 'baz',
signature: '7d68785e'
});
});
it('encodes signature baz(uint32, bool) correctly', () => {
expect(methodSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({ id: 'baz(uint32,bool)', signature: 'cdcd77c0' });
expect(methodSignature('baz', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({
id: 'baz(uint32,bool)',
name: 'baz',
signature: 'cdcd77c0'
});
});
it('encodes signature in name correctly', () => {
expect(methodSignature('baz(uint32,bool)', [{ type: 'uint', length: 32 }, { type: 'bool' }])).to.deep.equal({
id: 'baz(uint32,bool)',
name: 'baz',
signature: 'cdcd77c0'
});
});
it('encodes no-name signature correctly as ()', () => {
expect(methodSignature(undefined, [])).to.deep.equal({ id: '()', signature: '861731d5' });
expect(methodSignature(undefined, [])).to.deep.equal({
id: '()',
name: undefined,
signature: '861731d5'
});
});
it('encodes no-params signature correctly as ()', () => {
expect(methodSignature(undefined, undefined)).to.deep.equal({ id: '()', signature: '861731d5' });
expect(methodSignature(undefined, undefined)).to.deep.equal({
id: '()',
name: undefined,
signature: '861731d5'
});
});
});
});

View File

@ -17,9 +17,12 @@
import { keccak_256 } from 'js-sha3'; // eslint-disable-line
import { hexToBytes } from './format';
import { isHex } from './types';
export function sha3 (value, options) {
if (options && options.encoding === 'hex') {
const forceHex = options && options.encoding === 'hex';
if (forceHex || (!options && isHex(value))) {
const bytes = hexToBytes(value);
return sha3(bytes);
}
@ -28,3 +31,5 @@ export function sha3 (value, options) {
return `0x${hash}`;
}
sha3.text = (val) => sha3(val, { encoding: 'raw' });

View File

@ -32,5 +32,14 @@ describe('api/util/sha3', () => {
expect(sha3('01020304', { encoding: 'hex' })).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b');
expect(sha3(Uint8Array.from([1, 2, 3, 4]))).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b');
});
it('should interpret as bytes by default', () => {
expect(sha3('0x01020304')).to.equal('0xa6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b');
});
it('should force text if option is passed', () => {
expect(sha3('0x01020304', { encoding: 'raw' })).to.equal('0x16bff43de576d28857dcba65a56fc17c5e93c09bd6a709268eff8e62025ae869');
expect(sha3.text('0x01020304')).to.equal('0x16bff43de576d28857dcba65a56fc17c5e93c09bd6a709268eff8e62025ae869');
});
});
});

View File

@ -29,6 +29,10 @@ export function isFunction (test) {
}
export function isHex (_test) {
if (!isString(_test)) {
return false;
}
if (_test.substr(0, 2) === '0x') {
return isHex(_test.slice(2));
}

View File

@ -66,6 +66,12 @@ describe('api/util/types', () => {
it('correctly identifies non-hex values', () => {
expect(isHex('123j')).to.be.false;
});
it('correctly indentifies non-string values', () => {
expect(isHex(false)).to.be.false;
expect(isHex()).to.be.false;
expect(isHex([1, 2, 3])).to.be.false;
});
});
describe('isInstanceOf', () => {

View File

@ -91,7 +91,7 @@ export default class Registry {
lookupAddress (_name) {
const name = _name.toLowerCase();
const sha3 = this._api.util.sha3(name);
const sha3 = this._api.util.sha3.text(name);
return this.getInstance().then((instance) => {
return instance.getAddress.call({}, [sha3, 'A']);

View File

@ -39,7 +39,7 @@ export const lookup = (name, key) => (dispatch, getState) => {
name = name.toLowerCase();
dispatch(lookupStart(name, key));
getAddress.call({}, [ sha3(name), key ])
getAddress.call({}, [ sha3.text(name), key ])
.then((address) => dispatch(success('lookup', address)))
.catch((err) => {
console.error(`could not lookup ${key} for ${name}`);

View File

@ -62,7 +62,7 @@ export const reserve = (name) => (dispatch, getState) => {
value: fee
};
const values = [
sha3(name)
sha3.text(name)
];
return postTx(api, reserve, options, values);
@ -116,7 +116,7 @@ export const drop = (name) => (dispatch, getState) => {
};
const values = [
sha3(name)
sha3.text(name)
];
return postTx(api, drop, options, values);

View File

@ -54,7 +54,7 @@ export const update = (name, key, value) => (dispatch, getState) => {
};
const values = [
sha3(name),
sha3.text(name),
key,
value
];

View File

@ -17,7 +17,7 @@
export const getOwner = (contract, name) => {
const { address, api } = contract;
const key = api.util.sha3(name) + '0000000000000000000000000000000000000000000000000000000000000001';
const key = api.util.sha3.text(name) + '0000000000000000000000000000000000000000000000000000000000000001';
const position = api.util.sha3(key, { encoding: 'hex' });
return api

View File

@ -68,13 +68,13 @@ const STORE = {
};
function createApi (result = true) {
const sha3 = sinon.stub().resolves('0x0000000000000000000000000000000000000000');
sha3.text = sha3;
return {
parity: {
registryAddress: sinon.stub().resolves('0x0000000000000000000000000000000000000000')
},
util: {
sha3: sinon.stub().resolves('0x0000000000000000000000000000000000000000')
}
util: { sha3 }
};
}

View File

@ -59,7 +59,7 @@ export default class EmailVerificationStore extends VerificationStore {
super(api, EmailVerificationABI, EMAIL_VERIFICATION, account, isTestnet);
}
requestValues = () => [ sha3(this.email) ]
requestValues = () => [ sha3.text(this.email) ]
@action setEmail = (email) => {
this.email = email;

View File

@ -120,7 +120,7 @@ export default class VerificationStore {
const confirm = contract.functions.find((fn) => fn.name === 'confirm');
const options = { from: account };
const values = [ sha3(code) ];
const values = [ sha3.text(code) ];
this.code = code;
this.isCodeValid = null;
@ -192,7 +192,7 @@ export default class VerificationStore {
@action sendConfirmation = () => {
const { api, account, contract, code } = this;
const token = sha3(code);
const token = sha3.text(code);
const confirm = contract.functions.find((fn) => fn.name === 'confirm');
const options = { from: account };

View File

@ -85,7 +85,7 @@ export default class AddressSelectStore {
return emailVerification
.instance
.reverse
.call({}, [ sha3(email) ])
.call({}, [ sha3.text(email) ])
.then((address) => {
return {
address,
@ -109,7 +109,7 @@ export default class AddressSelectStore {
this.regLookups.push((name) => {
return registryInstance
.getAddress
.call({}, [ sha3(name), 'A' ])
.call({}, [ sha3.text(name), 'A' ])
.then((address) => {
return {
address,

View File

@ -141,7 +141,7 @@ class Background extends Component {
generateSeed () {
const { api, muiTheme } = this.context;
return api.util.sha3(`${muiTheme.backgroundSeed}${Math.random()}${counter++}`);
return api.util.sha3.text(`${muiTheme.backgroundSeed}${Math.random()}${counter++}`);
}
}

View File

@ -671,13 +671,8 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
let request = CallRequest::into(request);
let signed = self.sign_call(request)?;
let result = match num.0 {
BlockNumber::Pending => take_weak!(self.miner).call(&*take_weak!(self.client), &signed, Default::default()),
num => take_weak!(self.client).call(&signed, num.into(), Default::default()),
};
result
.map(|res| (res.gas_used + res.refunded).into())
take_weak!(self.client).estimate_gas(&signed, num.0.into())
.map(Into::into)
.map_err(errors::from_call_error)
}

View File

@ -691,7 +691,7 @@ fn rpc_eth_estimate_gas() {
"latest"],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":"0xff35","id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x5208","id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
@ -725,7 +725,7 @@ fn rpc_eth_estimate_gas_default_block() {
}],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":"0xff35","id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x5208","id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}