Backporting to beta (#4152)
* Fix broken transfer total balance (#4127) * Add proper label to method decoding inputs (#4136) * Another minor estimation fix (#4133) * Return 0 instead of error with out of gas on estimate_gas * Fix stuff up. * Another estimate gas fix. * Alter balance to maximum possible rather than GP=0. * Only increase to amount strictly necessary. * Get rid of unsafe code in ethkey, propagate incorrect Secret errors. (#4119) * Implementing secret * Fixing tests * Refactor VoteCollector (#4101) * dir * simple validator list * stub validator contract * make the engine hold Weak<Client> instead of IoChannel * validator set factory * register weak client with ValidatorContract * check chain security * add address array to generator * register provider contract * update validator set on notify * add validator contract spec * simple list test * split update and contract test * contract change * use client in tendermint * fix deadlock * step duration in params * adapt tendermint tests * add storage fields to test spec * constructor spec * execute under wrong address * create under correct address * revert * validator contract constructor * move genesis block lookup * add removal ability to contract * validator contract adding validators * fix basic authority * validator changing test * more docs * update sync tests * remove env_logger * another env_logger * cameltoe * hold EngineClient instead of Client * return error on misbehaviour * nicer return * sprinkle docs * Reenable mainnet update server. (#4137) * basic tests for subscribeToEvents (#4115) * subscribeToEvent fixtures ✅ * subscribeToEvent tests ✅ * temporarily skip failing test (#4138) * Improvements and optimisations to estimate_gas (#4142) * Return 0 instead of error with out of gas on estimate_gas * Fix stuff up. * Another estimate gas fix. * Alter balance to maximum possible rather than GP=0. * Only increase to amount strictly necessary. * Improvements and optimisations to estimate_gas. - Introduce proper error type - Avoid building costly traces * Fix tests. * Actually fix testsActually fix tests * Use estimateGas error (as per updated implementation) (#4131) * Use estimateGas error (as per updated implementation) * EXCEPTION_ERROR as per #4142 * Better error log reporting & handling (#4128) * Don't pop-up notifications after network switch (#4076) * Better notifications * Don't pollute with notifs if switched networks * Better connection close/open events / No more notifs on change network * PR Grumbles * Add close and open events to HTTP // Add tests * Fix tests * WIP Signer Fix * Fix Signer // Better reconnection handling * PR Grumbles * PR Grumbles * Fixes wrong fetching of balances + Notifications * Secure API WIP * Updated Secure API Connection + Status * Linting * Linting * Updated Secure API Logic * Proper handling of token updates // Fixing poping notifications * PR Grumbles * PR Grumbles * Fixing tests * Trim spaces from InputAddress (#4126) * Trim spaces for addresses * onSubmit has only value, not event * onSubmit (again) * Length check on trimmed value * Remove bindActionCreators({}, dispatch) (empty) (#4135)
This commit is contained in:
parent
72bb687f5e
commit
1e212771b5
@ -38,7 +38,7 @@ linux-stable:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
tags:
|
||||
- rust
|
||||
@ -107,7 +107,7 @@ linux-centos:
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@ -146,7 +146,7 @@ linux-i686:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@ -192,7 +192,7 @@ linux-armv7:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@ -238,7 +238,7 @@ linux-arm:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://icarus.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@ -277,7 +277,7 @@ linux-armv6:
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@ -322,7 +322,7 @@ linux-aarch64:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
@ -359,7 +359,7 @@ darwin:
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
|
||||
# - curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- osx
|
||||
@ -421,7 +421,7 @@ windows:
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
|
||||
# - curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
|
||||
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
|
||||
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1338/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
|
||||
tags:
|
||||
- rust-windows
|
||||
@ -535,7 +535,7 @@ push-release:
|
||||
- triggers
|
||||
image: ethcore/rust:stable
|
||||
script:
|
||||
# - curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1338/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
tags:
|
||||
- curl
|
||||
|
@ -1329,6 +1329,7 @@ mod tests {
|
||||
use transaction::{Transaction, Action};
|
||||
use log_entry::{LogEntry, LocalizedLogEntry};
|
||||
use spec::Spec;
|
||||
use ethkey::Secret;
|
||||
|
||||
fn new_db(path: &str) -> Arc<Database> {
|
||||
Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap())
|
||||
@ -1467,6 +1468,10 @@ mod tests {
|
||||
// TODO: insert block that already includes one of them as an uncle to check it's not allowed.
|
||||
}
|
||||
|
||||
fn secret() -> Secret {
|
||||
Secret::from_slice(&"".sha3()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fork_transaction_addresses() {
|
||||
let mut canon_chain = ChainGenerator::default();
|
||||
@ -1482,7 +1487,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
|
||||
let b1a = canon_chain
|
||||
@ -1546,7 +1551,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let t2 = Transaction {
|
||||
nonce: 1.into(),
|
||||
@ -1555,7 +1560,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let t3 = Transaction {
|
||||
nonce: 2.into(),
|
||||
@ -1564,7 +1569,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let b1a = canon_chain
|
||||
.with_transaction(t1.clone())
|
||||
@ -1870,7 +1875,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 101.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
let t2 = Transaction {
|
||||
nonce: 0.into(),
|
||||
gas_price: 0.into(),
|
||||
@ -1878,7 +1883,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 102.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
let t3 = Transaction {
|
||||
nonce: 0.into(),
|
||||
gas_price: 0.into(),
|
||||
@ -1886,7 +1891,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 103.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
let tx_hash1 = t1.hash();
|
||||
let tx_hash2 = t2.hash();
|
||||
let tx_hash3 = t3.hash();
|
||||
|
@ -874,6 +874,7 @@ impl BlockChainClient for Client {
|
||||
}
|
||||
|
||||
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError> {
|
||||
const UPPER_CEILING: u64 = 1_000_000_000_000u64;
|
||||
let header = self.block_header(block).ok_or(CallError::StatePruned)?;
|
||||
let last_hashes = self.build_last_hashes(header.parent_hash());
|
||||
let env_info = EnvInfo {
|
||||
@ -883,37 +884,38 @@ impl BlockChainClient for Client {
|
||||
difficulty: header.difficulty(),
|
||||
last_hashes: last_hashes,
|
||||
gas_used: U256::zero(),
|
||||
gas_limit: U256::max_value(),
|
||||
gas_limit: UPPER_CEILING.into(),
|
||||
};
|
||||
// that's just a copy of the state.
|
||||
let mut original_state = self.state_at(block).ok_or(CallError::StatePruned)?;
|
||||
let 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;
|
||||
|
||||
let mut state = original_state.clone();
|
||||
let needed_balance = tx.value + tx.gas * tx.gas_price;
|
||||
if balance < needed_balance {
|
||||
// give the sender a sufficient balance
|
||||
state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty);
|
||||
}
|
||||
|
||||
Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm)
|
||||
.transact(&tx, options.clone())
|
||||
.map(|r| r.trace[0].result.succeeded())
|
||||
.map(|r| r.exception.is_some())
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
let mut upper = env_info.gas_limit;
|
||||
let mut upper = header.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);
|
||||
@ -1669,7 +1671,7 @@ mod tests {
|
||||
use util::Hashable;
|
||||
|
||||
// given
|
||||
let key = KeyPair::from_secret("test".sha3()).unwrap();
|
||||
let key = KeyPair::from_secret_slice(&"test".sha3()).unwrap();
|
||||
let secret = key.secret();
|
||||
|
||||
let block_number = 1;
|
||||
|
@ -354,6 +354,7 @@ mod tests {
|
||||
use env_info::EnvInfo;
|
||||
use header::Header;
|
||||
use error::{Error, BlockError};
|
||||
use ethkey::Secret;
|
||||
use rlp::encode;
|
||||
use block::*;
|
||||
use tests::helpers::*;
|
||||
@ -411,8 +412,8 @@ mod tests {
|
||||
#[test]
|
||||
fn generates_seal_and_does_not_double_propose() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr1 = tap.insert_account("1".sha3(), "1").unwrap();
|
||||
let addr2 = tap.insert_account("2".sha3(), "2").unwrap();
|
||||
let addr1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "1").unwrap();
|
||||
let addr2 = tap.insert_account(Secret::from_slice(&"2".sha3()).unwrap(), "2").unwrap();
|
||||
|
||||
let spec = Spec::new_test_round();
|
||||
let engine = &*spec.engine;
|
||||
@ -445,7 +446,7 @@ mod tests {
|
||||
fn proposer_switching() {
|
||||
let mut header: Header = Header::default();
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
|
||||
header.set_author(addr);
|
||||
|
||||
@ -464,7 +465,7 @@ mod tests {
|
||||
fn rejects_future_block() {
|
||||
let mut header: Header = Header::default();
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
|
||||
header.set_author(addr);
|
||||
|
||||
|
@ -201,6 +201,7 @@ mod tests {
|
||||
use error::{BlockError, Error};
|
||||
use tests::helpers::*;
|
||||
use account_provider::AccountProvider;
|
||||
use ethkey::Secret;
|
||||
use header::Header;
|
||||
use spec::Spec;
|
||||
use engines::Seal;
|
||||
@ -261,7 +262,7 @@ mod tests {
|
||||
#[test]
|
||||
fn can_generate_seal() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("".sha3(), "").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||
|
||||
let spec = new_test_authority();
|
||||
let engine = &*spec.engine;
|
||||
@ -281,7 +282,7 @@ mod tests {
|
||||
#[test]
|
||||
fn seals_internally() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let authority = tap.insert_account("".sha3(), "").unwrap();
|
||||
let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||
|
||||
let engine = new_test_authority().engine;
|
||||
assert!(!engine.is_sealer(&Address::default()).unwrap());
|
||||
|
@ -23,15 +23,35 @@ use header::Header;
|
||||
use rlp::*;
|
||||
use ethkey::{recover, public_to_address};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
/// Message transmitted between consensus participants.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct ConsensusMessage {
|
||||
pub vote_step: VoteStep,
|
||||
pub block_hash: Option<BlockHash>,
|
||||
pub signature: H520,
|
||||
}
|
||||
|
||||
/// Complete step of the consensus process.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct VoteStep {
|
||||
pub height: Height,
|
||||
pub round: Round,
|
||||
pub step: Step,
|
||||
pub block_hash: Option<BlockHash>,
|
||||
}
|
||||
|
||||
impl VoteStep {
|
||||
pub fn new(height: Height, round: Round, step: Step) -> Self {
|
||||
VoteStep { height: height, round: round, step: step }
|
||||
}
|
||||
|
||||
pub fn is_height(&self, height: Height) -> bool {
|
||||
self.height == height
|
||||
}
|
||||
|
||||
pub fn is_round(&self, height: Height, round: Round) -> bool {
|
||||
self.height == height && self.round == round
|
||||
}
|
||||
}
|
||||
|
||||
fn consensus_round(header: &Header) -> Result<Round, ::rlp::DecoderError> {
|
||||
let round_rlp = header.seal().get(0).expect("seal passed basic verification; seal has 3 fields; qed");
|
||||
@ -42,53 +62,29 @@ impl ConsensusMessage {
|
||||
pub fn new(signature: H520, height: Height, round: Round, step: Step, block_hash: Option<BlockHash>) -> Self {
|
||||
ConsensusMessage {
|
||||
signature: signature,
|
||||
height: height,
|
||||
round: round,
|
||||
step: step,
|
||||
block_hash: block_hash,
|
||||
vote_step: VoteStep::new(height, round, step),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_proposal(header: &Header) -> Result<Self, ::rlp::DecoderError> {
|
||||
Ok(ConsensusMessage {
|
||||
vote_step: VoteStep::new(header.number() as Height, consensus_round(header)?, Step::Propose),
|
||||
signature: UntrustedRlp::new(header.seal().get(1).expect("seal passed basic verification; seal has 3 fields; qed").as_slice()).as_val()?,
|
||||
height: header.number() as Height,
|
||||
round: consensus_round(header)?,
|
||||
step: Step::Propose,
|
||||
block_hash: Some(header.bare_hash()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_commit(proposal: &ConsensusMessage, signature: H520) -> Self {
|
||||
let mut vote_step = proposal.vote_step.clone();
|
||||
vote_step.step = Step::Precommit;
|
||||
ConsensusMessage {
|
||||
signature: signature,
|
||||
height: proposal.height,
|
||||
round: proposal.round,
|
||||
step: Step::Precommit,
|
||||
vote_step: vote_step,
|
||||
block_hash: proposal.block_hash,
|
||||
signature: signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_height(&self, height: Height) -> bool {
|
||||
self.height == height
|
||||
}
|
||||
|
||||
pub fn is_round(&self, height: Height, round: Round) -> bool {
|
||||
self.height == height && self.round == round
|
||||
}
|
||||
|
||||
pub fn is_step(&self, height: Height, round: Round, step: Step) -> bool {
|
||||
self.height == height && self.round == round && self.step == step
|
||||
}
|
||||
|
||||
pub fn is_block_hash(&self, h: Height, r: Round, s: Step, block_hash: Option<BlockHash>) -> bool {
|
||||
self.height == h && self.round == r && self.step == s && self.block_hash == block_hash
|
||||
}
|
||||
|
||||
pub fn is_aligned(&self, m: &ConsensusMessage) -> bool {
|
||||
self.is_block_hash(m.height, m.round, m.step, m.block_hash)
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<Address, Error> {
|
||||
let full_rlp = ::rlp::encode(self);
|
||||
let block_info = Rlp::new(&full_rlp).at(1);
|
||||
@ -97,16 +93,30 @@ impl ConsensusMessage {
|
||||
}
|
||||
|
||||
pub fn precommit_hash(&self) -> H256 {
|
||||
message_info_rlp(self.height, self.round, Step::Precommit, self.block_hash).sha3()
|
||||
let mut vote_step = self.vote_step.clone();
|
||||
vote_step.step = Step::Precommit;
|
||||
message_info_rlp(&vote_step, self.block_hash).sha3()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ConsensusMessage {
|
||||
fn partial_cmp(&self, m: &ConsensusMessage) -> Option<Ordering> {
|
||||
impl PartialOrd for VoteStep {
|
||||
fn partial_cmp(&self, m: &VoteStep) -> Option<Ordering> {
|
||||
Some(self.cmp(m))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VoteStep {
|
||||
fn cmp(&self, m: &VoteStep) -> Ordering {
|
||||
if self.height != m.height {
|
||||
self.height.cmp(&m.height)
|
||||
} else if self.round != m.round {
|
||||
self.round.cmp(&m.round)
|
||||
} else {
|
||||
self.step.number().cmp(&m.step.number())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Step {
|
||||
fn number(&self) -> u8 {
|
||||
match *self {
|
||||
@ -118,20 +128,6 @@ impl Step {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ConsensusMessage {
|
||||
fn cmp(&self, m: &ConsensusMessage) -> Ordering {
|
||||
if self.height != m.height {
|
||||
self.height.cmp(&m.height)
|
||||
} else if self.round != m.round {
|
||||
self.round.cmp(&m.round)
|
||||
} else if self.step != m.step {
|
||||
self.step.number().cmp(&m.step.number())
|
||||
} else {
|
||||
self.signature.cmp(&m.signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Step {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
match decoder.as_rlp().as_val()? {
|
||||
@ -149,42 +145,39 @@ impl Encodable for Step {
|
||||
}
|
||||
}
|
||||
|
||||
/// (signature, height, round, step, block_hash)
|
||||
/// (signature, (height, round, step, block_hash))
|
||||
impl Decodable for ConsensusMessage {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let rlp = decoder.as_rlp();
|
||||
let m = rlp.at(1)?;
|
||||
let block_message: H256 = m.val_at(3)?;
|
||||
Ok(ConsensusMessage {
|
||||
signature: rlp.val_at(0)?,
|
||||
height: m.val_at(0)?,
|
||||
round: m.val_at(1)?,
|
||||
step: m.val_at(2)?,
|
||||
vote_step: VoteStep::new(m.val_at(0)?, m.val_at(1)?, m.val_at(2)?),
|
||||
block_hash: match block_message.is_zero() {
|
||||
true => None,
|
||||
false => Some(block_message),
|
||||
}
|
||||
},
|
||||
signature: rlp.val_at(0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for ConsensusMessage {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
let info = message_info_rlp(self.height, self.round, self.step, self.block_hash);
|
||||
let info = message_info_rlp(&self.vote_step, self.block_hash);
|
||||
s.begin_list(2)
|
||||
.append(&self.signature)
|
||||
.append_raw(&info, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option<BlockHash>) -> Bytes {
|
||||
pub fn message_info_rlp(vote_step: &VoteStep, block_hash: Option<BlockHash>) -> Bytes {
|
||||
// TODO: figure out whats wrong with nested list encoding
|
||||
let mut s = RlpStream::new_list(5);
|
||||
s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or_else(H256::zero));
|
||||
s.append(&vote_step.height).append(&vote_step.round).append(&vote_step.step).append(&block_hash.unwrap_or_else(H256::zero));
|
||||
s.out()
|
||||
}
|
||||
|
||||
|
||||
pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes {
|
||||
let mut s = RlpStream::new_list(2);
|
||||
s.append(signature).append_raw(vote_info, 1);
|
||||
@ -199,14 +192,17 @@ mod tests {
|
||||
use super::*;
|
||||
use account_provider::AccountProvider;
|
||||
use header::Header;
|
||||
use ethkey::Secret;
|
||||
|
||||
#[test]
|
||||
fn encode_decode() {
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 10,
|
||||
round: 123,
|
||||
step: Step::Precommit,
|
||||
vote_step: VoteStep {
|
||||
height: 10,
|
||||
round: 123,
|
||||
step: Step::Precommit,
|
||||
},
|
||||
block_hash: Some("1".sha3())
|
||||
};
|
||||
let raw_rlp = ::rlp::encode(&message).to_vec();
|
||||
@ -215,9 +211,11 @@ mod tests {
|
||||
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 1314,
|
||||
round: 0,
|
||||
step: Step::Prevote,
|
||||
vote_step: VoteStep {
|
||||
height: 1314,
|
||||
round: 0,
|
||||
step: Step::Prevote,
|
||||
},
|
||||
block_hash: None
|
||||
};
|
||||
let raw_rlp = ::rlp::encode(&message);
|
||||
@ -228,10 +226,10 @@ mod tests {
|
||||
#[test]
|
||||
fn generate_and_verify() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
tap.unlock_account_permanently(addr, "0".into()).unwrap();
|
||||
|
||||
let mi = message_info_rlp(123, 2, Step::Precommit, Some(H256::default()));
|
||||
let mi = message_info_rlp(&VoteStep::new(123, 2, Step::Precommit), Some(H256::default()));
|
||||
|
||||
let raw_rlp = message_full_rlp(&tap.sign(addr, None, mi.sha3()).unwrap().into(), &mi);
|
||||
|
||||
@ -254,9 +252,11 @@ mod tests {
|
||||
message,
|
||||
ConsensusMessage {
|
||||
signature: Default::default(),
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
vote_step: VoteStep {
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
},
|
||||
block_hash: Some(header.bare_hash())
|
||||
}
|
||||
);
|
||||
@ -267,12 +267,10 @@ mod tests {
|
||||
let header = Header::default();
|
||||
let pro = ConsensusMessage {
|
||||
signature: Default::default(),
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
vote_step: VoteStep::new(0, 0, Step::Propose),
|
||||
block_hash: Some(header.bare_hash())
|
||||
};
|
||||
let pre = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash()));
|
||||
let pre = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash()));
|
||||
|
||||
assert_eq!(pro.precommit_hash(), pre.sha3());
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ use self::transition::TransitionHandler;
|
||||
use self::params::TendermintParams;
|
||||
use self::vote_collector::VoteCollector;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum Step {
|
||||
Propose,
|
||||
Prevote,
|
||||
@ -163,13 +163,13 @@ impl Tendermint {
|
||||
let h = self.height.load(AtomicOrdering::SeqCst);
|
||||
let r = self.round.load(AtomicOrdering::SeqCst);
|
||||
let s = self.step.read();
|
||||
let vote_info = message_info_rlp(h, r, *s, block_hash);
|
||||
let vote_info = message_info_rlp(&VoteStep::new(h, r, *s), block_hash);
|
||||
let authority = self.authority.read();
|
||||
match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) {
|
||||
Ok(signature) => {
|
||||
let message_rlp = message_full_rlp(&signature, &vote_info);
|
||||
let message = ConsensusMessage::new(signature, h, r, *s, block_hash);
|
||||
self.votes.vote(message.clone(), *authority);
|
||||
self.votes.vote(message.clone(), &*authority);
|
||||
debug!(target: "poa", "Generated {:?} as {}.", message, *authority);
|
||||
self.handle_valid_message(&message);
|
||||
|
||||
@ -221,7 +221,7 @@ impl Tendermint {
|
||||
},
|
||||
Step::Prevote => {
|
||||
let block_hash = match *self.lock_change.read() {
|
||||
Some(ref m) if !self.should_unlock(m.round) => m.block_hash,
|
||||
Some(ref m) if !self.should_unlock(m.vote_step.round) => m.block_hash,
|
||||
_ => self.proposal.read().clone(),
|
||||
};
|
||||
self.generate_and_broadcast_message(block_hash);
|
||||
@ -230,8 +230,8 @@ impl Tendermint {
|
||||
trace!(target: "poa", "to_step: Precommit.");
|
||||
let block_hash = match *self.lock_change.read() {
|
||||
Some(ref m) if self.is_round(m) && m.block_hash.is_some() => {
|
||||
trace!(target: "poa", "Setting last lock: {}", m.round);
|
||||
self.last_lock.store(m.round, AtomicOrdering::SeqCst);
|
||||
trace!(target: "poa", "Setting last lock: {}", m.vote_step.round);
|
||||
self.last_lock.store(m.vote_step.round, AtomicOrdering::SeqCst);
|
||||
m.block_hash
|
||||
},
|
||||
_ => None,
|
||||
@ -246,7 +246,7 @@ impl Tendermint {
|
||||
if let Some(block_hash) = *self.proposal.read() {
|
||||
// Generate seal and remove old votes.
|
||||
if self.is_proposer(&*self.authority.read()).is_ok() {
|
||||
if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) {
|
||||
if let Some(seal) = self.votes.seal_signatures(height, round, &block_hash) {
|
||||
trace!(target: "poa", "Collected seal: {:?}", seal);
|
||||
let seal = vec![
|
||||
::rlp::encode(&round).to_vec(),
|
||||
@ -290,11 +290,11 @@ impl Tendermint {
|
||||
}
|
||||
|
||||
fn is_height(&self, message: &ConsensusMessage) -> bool {
|
||||
message.is_height(self.height.load(AtomicOrdering::SeqCst))
|
||||
message.vote_step.is_height(self.height.load(AtomicOrdering::SeqCst))
|
||||
}
|
||||
|
||||
fn is_round(&self, message: &ConsensusMessage) -> bool {
|
||||
message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst))
|
||||
message.vote_step.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst))
|
||||
}
|
||||
|
||||
fn increment_round(&self, n: Round) {
|
||||
@ -302,20 +302,20 @@ impl Tendermint {
|
||||
self.round.fetch_add(n, AtomicOrdering::SeqCst);
|
||||
}
|
||||
|
||||
fn should_unlock(&self, lock_change_round: Round) -> bool {
|
||||
fn should_unlock(&self, lock_change_round: Round) -> bool {
|
||||
self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round
|
||||
&& lock_change_round < self.round.load(AtomicOrdering::SeqCst)
|
||||
}
|
||||
|
||||
|
||||
fn has_enough_any_votes(&self) -> bool {
|
||||
let step_votes = self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read());
|
||||
let step_votes = self.votes.count_step_votes(&VoteStep::new(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()));
|
||||
self.is_above_threshold(step_votes)
|
||||
}
|
||||
|
||||
fn has_enough_future_step_votes(&self, message: &ConsensusMessage) -> bool {
|
||||
if message.round > self.round.load(AtomicOrdering::SeqCst) {
|
||||
let step_votes = self.votes.count_step_votes(message.height, message.round, message.step);
|
||||
fn has_enough_future_step_votes(&self, vote_step: &VoteStep) -> bool {
|
||||
if vote_step.round > self.round.load(AtomicOrdering::SeqCst) {
|
||||
let step_votes = self.votes.count_step_votes(vote_step);
|
||||
self.is_above_threshold(step_votes)
|
||||
} else {
|
||||
false
|
||||
@ -328,12 +328,13 @@ impl Tendermint {
|
||||
}
|
||||
|
||||
fn handle_valid_message(&self, message: &ConsensusMessage) {
|
||||
let ref vote_step = message.vote_step;
|
||||
let is_newer_than_lock = match *self.lock_change.read() {
|
||||
Some(ref lock) => message > lock,
|
||||
Some(ref lock) => vote_step > &lock.vote_step,
|
||||
None => true,
|
||||
};
|
||||
let lock_change = is_newer_than_lock
|
||||
&& message.step == Step::Prevote
|
||||
&& vote_step.step == Step::Prevote
|
||||
&& message.block_hash.is_some()
|
||||
&& self.has_enough_aligned_votes(message);
|
||||
if lock_change {
|
||||
@ -351,15 +352,15 @@ impl Tendermint {
|
||||
Some(Step::Commit)
|
||||
}
|
||||
},
|
||||
Step::Precommit if self.has_enough_future_step_votes(message) => {
|
||||
self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Step::Precommit if self.has_enough_future_step_votes(&vote_step) => {
|
||||
self.increment_round(vote_step.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Some(Step::Precommit)
|
||||
},
|
||||
// Avoid counting twice.
|
||||
Step::Prevote if lock_change => Some(Step::Precommit),
|
||||
Step::Prevote if self.has_enough_aligned_votes(message) => Some(Step::Precommit),
|
||||
Step::Prevote if self.has_enough_future_step_votes(message) => {
|
||||
self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Step::Prevote if self.has_enough_future_step_votes(&vote_step) => {
|
||||
self.increment_round(vote_step.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Some(Step::Prevote)
|
||||
},
|
||||
_ => None,
|
||||
@ -390,8 +391,8 @@ impl Engine for Tendermint {
|
||||
let message = ConsensusMessage::new_proposal(header).expect("Invalid header.");
|
||||
map![
|
||||
"signature".into() => message.signature.to_string(),
|
||||
"height".into() => message.height.to_string(),
|
||||
"round".into() => message.round.to_string(),
|
||||
"height".into() => message.vote_step.height.to_string(),
|
||||
"round".into() => message.vote_step.round.to_string(),
|
||||
"block_hash".into() => message.block_hash.as_ref().map(ToString::to_string).unwrap_or("".into())
|
||||
]
|
||||
}
|
||||
@ -431,11 +432,11 @@ impl Engine for Tendermint {
|
||||
let height = header.number() as Height;
|
||||
let round = self.round.load(AtomicOrdering::SeqCst);
|
||||
let bh = Some(header.bare_hash());
|
||||
let vote_info = message_info_rlp(height, round, Step::Propose, bh.clone());
|
||||
let vote_info = message_info_rlp(&VoteStep::new(height, round, Step::Propose), bh.clone());
|
||||
if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) {
|
||||
// Insert Propose vote.
|
||||
debug!(target: "poa", "Submitting proposal {} at height {} round {}.", header.bare_hash(), height, round);
|
||||
self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author);
|
||||
self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), author);
|
||||
// Remember proposal for later seal submission.
|
||||
*self.proposal.write() = bh;
|
||||
Seal::Proposal(vec![
|
||||
@ -461,9 +462,11 @@ impl Engine for Tendermint {
|
||||
if !self.is_authority(&sender) {
|
||||
Err(EngineError::NotAuthorized(sender))?;
|
||||
}
|
||||
self.broadcast_message(rlp.as_raw().to_vec());
|
||||
if self.votes.vote(message.clone(), &sender).is_some() {
|
||||
Err(EngineError::DoubleVote(sender))?
|
||||
}
|
||||
trace!(target: "poa", "Handling a valid {:?} from {}.", message, sender);
|
||||
self.votes.vote(message.clone(), sender);
|
||||
self.broadcast_message(rlp.as_raw().to_vec());
|
||||
self.handle_valid_message(&message);
|
||||
}
|
||||
Ok(())
|
||||
@ -502,7 +505,7 @@ impl Engine for Tendermint {
|
||||
}
|
||||
|
||||
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
let proposal = ConsensusMessage::new_proposal(header)?;
|
||||
let proposal = ConsensusMessage::new_proposal(header)?;
|
||||
let proposer = proposal.verify()?;
|
||||
if !self.is_authority(&proposer) {
|
||||
Err(EngineError::NotAuthorized(proposer))?
|
||||
@ -541,7 +544,7 @@ impl Engine for Tendermint {
|
||||
found: signatures_len
|
||||
}))?;
|
||||
}
|
||||
self.is_round_proposer(proposal.height, proposal.round, &proposer)?;
|
||||
self.is_round_proposer(proposal.vote_step.height, proposal.vote_step.round, &proposer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -607,16 +610,16 @@ impl Engine for Tendermint {
|
||||
let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed");
|
||||
if signatures_len != 1 {
|
||||
// New Commit received, skip to next height.
|
||||
trace!(target: "poa", "Received a commit for height {}, round {}.", proposal.height, proposal.round);
|
||||
self.to_next_height(proposal.height);
|
||||
trace!(target: "poa", "Received a commit: {:?}.", proposal.vote_step);
|
||||
self.to_next_height(proposal.vote_step.height);
|
||||
return false;
|
||||
}
|
||||
let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed");
|
||||
debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer);
|
||||
debug!(target: "poa", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer);
|
||||
if self.is_round(&proposal) {
|
||||
*self.proposal.write() = proposal.block_hash.clone();
|
||||
}
|
||||
self.votes.vote(proposal, proposer);
|
||||
self.votes.vote(proposal, &proposer);
|
||||
true
|
||||
}
|
||||
|
||||
@ -671,6 +674,7 @@ mod tests {
|
||||
use error::{Error, BlockError};
|
||||
use header::Header;
|
||||
use env_info::EnvInfo;
|
||||
use ethkey::Secret;
|
||||
use client::chain_notify::ChainNotify;
|
||||
use miner::MinerService;
|
||||
use tests::helpers::*;
|
||||
@ -703,7 +707,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn vote<F>(engine: &Engine, signer: F, height: usize, round: usize, step: Step, block_hash: Option<H256>) -> Bytes where F: FnOnce(H256) -> Result<H520, ::account_provider::Error> {
|
||||
let mi = message_info_rlp(height, round, step, block_hash);
|
||||
let mi = message_info_rlp(&VoteStep::new(height, round, step), block_hash);
|
||||
let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi);
|
||||
engine.handle_message(&m).unwrap();
|
||||
m
|
||||
@ -711,7 +715,7 @@ mod tests {
|
||||
|
||||
fn proposal_seal(tap: &Arc<AccountProvider>, header: &Header, round: Round) -> Vec<Bytes> {
|
||||
let author = header.author();
|
||||
let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash()));
|
||||
let vote_info = message_info_rlp(&VoteStep::new(header.number() as Height, round, Step::Propose), Some(header.bare_hash()));
|
||||
let signature = tap.sign(*author, None, vote_info.sha3()).unwrap();
|
||||
vec![
|
||||
::rlp::encode(&round).to_vec(),
|
||||
@ -721,7 +725,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address {
|
||||
let addr = tap.insert_account(acc.sha3(), acc).unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&acc.sha3()).unwrap(), acc).unwrap();
|
||||
tap.unlock_account_permanently(addr, acc.into()).unwrap();
|
||||
addr
|
||||
}
|
||||
@ -825,7 +829,7 @@ mod tests {
|
||||
header.set_author(proposer);
|
||||
let mut seal = proposal_seal(&tap, &header, 0);
|
||||
|
||||
let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash()));
|
||||
let vote_info = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash()));
|
||||
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();
|
||||
|
||||
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec();
|
||||
@ -886,7 +890,7 @@ mod tests {
|
||||
fn relays_messages() {
|
||||
let (spec, tap) = setup();
|
||||
let engine = spec.engine.clone();
|
||||
|
||||
|
||||
let v0 = insert_and_register(&tap, engine.as_ref(), "0");
|
||||
let v1 = insert_and_register(&tap, engine.as_ref(), "1");
|
||||
|
||||
|
@ -17,13 +17,50 @@
|
||||
//! Collects votes on hashes at each height and round.
|
||||
|
||||
use util::*;
|
||||
use super::message::ConsensusMessage;
|
||||
use super::{Height, Round, Step};
|
||||
use super::message::*;
|
||||
use super::{Height, Round, Step, BlockHash};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VoteCollector {
|
||||
/// Storing all Proposals, Prevotes and Precommits.
|
||||
votes: RwLock<BTreeMap<ConsensusMessage, Address>>,
|
||||
votes: RwLock<BTreeMap<VoteStep, StepCollector>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct StepCollector {
|
||||
voted: HashSet<Address>,
|
||||
pub block_votes: HashMap<Option<BlockHash>, HashMap<H520, Address>>,
|
||||
messages: HashSet<ConsensusMessage>,
|
||||
}
|
||||
|
||||
impl StepCollector {
|
||||
/// Returns Some(&Address) when validator is double voting.
|
||||
fn insert<'a>(&mut self, message: ConsensusMessage, address: &'a Address) -> Option<&'a Address> {
|
||||
// Do nothing when message was seen.
|
||||
if self.messages.insert(message.clone()) {
|
||||
if self.voted.insert(address.clone()) {
|
||||
self
|
||||
.block_votes
|
||||
.entry(message.block_hash)
|
||||
.or_insert_with(HashMap::new)
|
||||
.insert(message.signature, address.clone());
|
||||
} else {
|
||||
// Bad validator sent a different message.
|
||||
return Some(address);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Count all votes for the given block hash at this step.
|
||||
fn count_block(&self, block_hash: &Option<BlockHash>) -> usize {
|
||||
self.block_votes.get(block_hash).map_or(0, HashMap::len)
|
||||
}
|
||||
|
||||
/// Count all votes collected for the given step.
|
||||
fn count(&self) -> usize {
|
||||
self.block_votes.values().map(HashMap::len).sum()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -42,109 +79,105 @@ impl PartialEq for SealSignatures {
|
||||
impl Eq for SealSignatures {}
|
||||
|
||||
impl VoteCollector {
|
||||
pub fn new() -> VoteCollector {
|
||||
pub fn new() -> Self {
|
||||
let mut collector = BTreeMap::new();
|
||||
// Insert dummy message to fulfill invariant: "only messages newer than the oldest are inserted".
|
||||
collector.insert(ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
block_hash: None
|
||||
},
|
||||
Address::default());
|
||||
// Insert dummy entry to fulfill invariant: "only messages newer than the oldest are inserted".
|
||||
collector.insert(VoteStep::new(0, 0, Step::Propose), Default::default());
|
||||
VoteCollector { votes: RwLock::new(collector) }
|
||||
}
|
||||
|
||||
/// Insert vote if it is newer than the oldest one.
|
||||
pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option<Address> {
|
||||
self.votes.write().insert(message, voter)
|
||||
pub fn vote<'a>(&self, message: ConsensusMessage, voter: &'a Address) -> Option<&'a Address> {
|
||||
self
|
||||
.votes
|
||||
.write()
|
||||
.entry(message.vote_step.clone())
|
||||
.or_insert_with(Default::default)
|
||||
.insert(message, voter)
|
||||
}
|
||||
|
||||
/// Checks if the message should be ignored.
|
||||
pub fn is_old_or_known(&self, message: &ConsensusMessage) -> bool {
|
||||
self.votes.read().get(message).map_or(false, |a| {
|
||||
trace!(target: "poa", "Known message from {}: {:?}.", a, message);
|
||||
true
|
||||
}) || {
|
||||
self
|
||||
.votes
|
||||
.read()
|
||||
.get(&message.vote_step)
|
||||
.map_or(false, |c| {
|
||||
let is_known = c.messages.contains(message);
|
||||
if is_known { trace!(target: "poa", "Known message: {:?}.", message); }
|
||||
is_known
|
||||
})
|
||||
|| {
|
||||
let guard = self.votes.read();
|
||||
let is_old = guard.keys().next().map_or(true, |oldest| message <= oldest);
|
||||
let is_old = guard.keys().next().map_or(true, |oldest| message.vote_step <= *oldest);
|
||||
if is_old { trace!(target: "poa", "Old message {:?}.", message); }
|
||||
is_old
|
||||
}
|
||||
}
|
||||
|
||||
/// Throws out messages older than message, leaves message as marker for the oldest.
|
||||
pub fn throw_out_old(&self, message: &ConsensusMessage) {
|
||||
pub fn throw_out_old(&self, vote_step: &VoteStep) {
|
||||
let mut guard = self.votes.write();
|
||||
let new_collector = guard.split_off(message);
|
||||
let new_collector = guard.split_off(vote_step);
|
||||
*guard = new_collector;
|
||||
}
|
||||
|
||||
pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option<SealSignatures> {
|
||||
let bh = Some(block_hash);
|
||||
let (proposal, votes) = {
|
||||
/// Collects the signatures used to seal a block.
|
||||
pub fn seal_signatures(&self, height: Height, round: Round, block_hash: &H256) -> Option<SealSignatures> {
|
||||
let ref bh = Some(*block_hash);
|
||||
let precommit_step = VoteStep::new(height, round, Step::Precommit);
|
||||
let maybe_seal = {
|
||||
let guard = self.votes.read();
|
||||
let mut current_signatures = guard.keys().skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh));
|
||||
let proposal = current_signatures.next().cloned();
|
||||
let votes = current_signatures
|
||||
.skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh))
|
||||
.filter(|m| m.is_block_hash(height, round, Step::Precommit, bh))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
(proposal, votes)
|
||||
guard
|
||||
.get(&VoteStep::new(height, round, Step::Propose))
|
||||
.and_then(|c| c.block_votes.get(bh))
|
||||
.and_then(|proposals| proposals.keys().next())
|
||||
.map(|proposal| SealSignatures {
|
||||
proposal: proposal.clone(),
|
||||
votes: guard
|
||||
.get(&precommit_step)
|
||||
.and_then(|c| c.block_votes.get(bh))
|
||||
.map(|precommits| precommits.keys().cloned().collect())
|
||||
.unwrap_or_else(Vec::new),
|
||||
})
|
||||
.and_then(|seal| if seal.votes.is_empty() { None } else { Some(seal) })
|
||||
};
|
||||
if votes.is_empty() {
|
||||
return None;
|
||||
if maybe_seal.is_some() {
|
||||
// Remove messages that are no longer relevant.
|
||||
self.throw_out_old(&precommit_step);
|
||||
}
|
||||
// Remove messages that are no longer relevant.
|
||||
votes.last().map(|m| self.throw_out_old(m));
|
||||
let mut votes_vec: Vec<_> = votes.into_iter().map(|m| m.signature).collect();
|
||||
votes_vec.sort();
|
||||
proposal.map(|p| SealSignatures {
|
||||
proposal: p.signature,
|
||||
votes: votes_vec,
|
||||
})
|
||||
maybe_seal
|
||||
}
|
||||
|
||||
/// Count votes which agree with the given message.
|
||||
pub fn count_aligned_votes(&self, message: &ConsensusMessage) -> usize {
|
||||
let guard = self.votes.read();
|
||||
guard.keys()
|
||||
.skip_while(|m| !m.is_aligned(message))
|
||||
// sorted by signature so might not be continuous
|
||||
.filter(|m| m.is_aligned(message))
|
||||
.count()
|
||||
self
|
||||
.votes
|
||||
.read()
|
||||
.get(&message.vote_step)
|
||||
.map_or(0, |m| m.count_block(&message.block_hash))
|
||||
}
|
||||
|
||||
pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize {
|
||||
let guard = self.votes.read();
|
||||
let current = guard.iter().skip_while(|&(m, _)| !m.is_step(height, round, step));
|
||||
let mut origins = HashSet::new();
|
||||
let mut n = 0;
|
||||
for (message, origin) in current {
|
||||
if message.is_step(height, round, step) {
|
||||
if origins.insert(origin) {
|
||||
n += 1;
|
||||
} else {
|
||||
warn!("count_step_votes: Authority {} has cast multiple step votes, this indicates malicious behaviour.", origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
n
|
||||
/// Count all votes collected for a given step.
|
||||
pub fn count_step_votes(&self, vote_step: &VoteStep) -> usize {
|
||||
self.votes.read().get(vote_step).map_or(0, StepCollector::count)
|
||||
}
|
||||
|
||||
/// Get all messages older than the height.
|
||||
pub fn get_up_to(&self, height: Height) -> Vec<Bytes> {
|
||||
let guard = self.votes.read();
|
||||
guard
|
||||
.keys()
|
||||
.filter(|m| m.step.is_pre())
|
||||
.take_while(|m| m.height <= height)
|
||||
.map(|m| ::rlp::encode(m).to_vec())
|
||||
.collect()
|
||||
.iter()
|
||||
.filter(|&(s, _)| s.step.is_pre())
|
||||
.take_while(|&(s, _)| s.height <= height)
|
||||
.map(|(_, c)| c.messages.iter().map(|m| ::rlp::encode(m).to_vec()).collect::<Vec<_>>())
|
||||
.fold(Vec::new(), |mut acc, mut messages| { acc.append(&mut messages); acc })
|
||||
}
|
||||
|
||||
/// Retrieve address from which the message was sent from cache.
|
||||
pub fn get(&self, message: &ConsensusMessage) -> Option<Address> {
|
||||
let guard = self.votes.read();
|
||||
guard.get(message).cloned()
|
||||
guard.get(&message.vote_step).and_then(|c| c.block_votes.get(&message.block_hash)).and_then(|origins| origins.get(&message.signature).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,15 +185,15 @@ impl VoteCollector {
|
||||
mod tests {
|
||||
use util::*;
|
||||
use super::*;
|
||||
use super::super::{Height, Round, BlockHash, Step};
|
||||
use super::super::message::ConsensusMessage;
|
||||
use super::super::{BlockHash, Step};
|
||||
use super::super::message::*;
|
||||
|
||||
fn random_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option<BlockHash>) -> Option<H160> {
|
||||
full_vote(collector, signature, h, r, step, block_hash, H160::random())
|
||||
fn random_vote(collector: &VoteCollector, signature: H520, vote_step: VoteStep, block_hash: Option<BlockHash>) -> bool {
|
||||
full_vote(collector, signature, vote_step, block_hash, &H160::random()).is_none()
|
||||
}
|
||||
|
||||
fn full_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option<BlockHash>, address: Address) -> Option<H160> {
|
||||
collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, address)
|
||||
fn full_vote<'a>(collector: &VoteCollector, signature: H520, vote_step: VoteStep, block_hash: Option<BlockHash>, address: &'a Address) -> Option<&'a Address> {
|
||||
collector.vote(ConsensusMessage { signature: signature, vote_step: vote_step, block_hash: block_hash }, address)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -173,68 +206,71 @@ mod tests {
|
||||
for _ in 0..5 {
|
||||
signatures.push(H520::random());
|
||||
}
|
||||
let propose_step = VoteStep::new(h, r, Step::Propose);
|
||||
let prevote_step = VoteStep::new(h, r, Step::Prevote);
|
||||
let precommit_step = VoteStep::new(h, r, Step::Precommit);
|
||||
// Wrong height proposal.
|
||||
random_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone());
|
||||
random_vote(&collector, signatures[4].clone(), VoteStep::new(h - 1, r, Step::Propose), bh.clone());
|
||||
// Good proposal
|
||||
random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone());
|
||||
random_vote(&collector, signatures[0].clone(), propose_step.clone(), bh.clone());
|
||||
// Wrong block proposal.
|
||||
random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3()));
|
||||
random_vote(&collector, signatures[0].clone(), propose_step.clone(), Some("0".sha3()));
|
||||
// Wrong block precommit.
|
||||
random_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, signatures[3].clone(), precommit_step.clone(), Some("0".sha3()));
|
||||
// Wrong round proposal.
|
||||
random_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone());
|
||||
random_vote(&collector, signatures[0].clone(), VoteStep::new(h, r - 1, Step::Propose), bh.clone());
|
||||
// Prevote.
|
||||
random_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone());
|
||||
random_vote(&collector, signatures[0].clone(), prevote_step.clone(), bh.clone());
|
||||
// Relevant precommit.
|
||||
random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[2].clone(), precommit_step.clone(), bh.clone());
|
||||
// Replcated vote.
|
||||
random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[2].clone(), precommit_step.clone(), bh.clone());
|
||||
// Wrong round precommit.
|
||||
random_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[4].clone(), VoteStep::new(h, r + 1, Step::Precommit), bh.clone());
|
||||
// Wrong height precommit.
|
||||
random_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[3].clone(), VoteStep::new(h + 1, r, Step::Precommit), bh.clone());
|
||||
// Relevant precommit.
|
||||
random_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[1].clone(), precommit_step.clone(), bh.clone());
|
||||
// Wrong round precommit, same signature.
|
||||
random_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[1].clone(), VoteStep::new(h, r + 1, Step::Precommit), bh.clone());
|
||||
// Wrong round precommit.
|
||||
random_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[4].clone(), VoteStep::new(h, r - 1, Step::Precommit), bh.clone());
|
||||
let seal = SealSignatures {
|
||||
proposal: signatures[0],
|
||||
votes: signatures[1..3].to_vec()
|
||||
};
|
||||
assert_eq!(seal, collector.seal_signatures(h, r, bh.unwrap()).unwrap());
|
||||
assert_eq!(seal, collector.seal_signatures(h, r, &bh.unwrap()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_votes() {
|
||||
let collector = VoteCollector::new();
|
||||
let prevote_step = VoteStep::new(3, 2, Step::Prevote);
|
||||
let precommit_step = VoteStep::new(3, 2, Step::Precommit);
|
||||
// good prevote
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), prevote_step.clone(), Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), VoteStep::new(3, 1, Step::Prevote), Some("0".sha3()));
|
||||
// good precommit
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), precommit_step.clone(), Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), VoteStep::new(3, 3, Step::Precommit), Some("0".sha3()));
|
||||
// good prevote
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), prevote_step.clone(), Some("1".sha3()));
|
||||
// good prevote
|
||||
let same_sig = H520::random();
|
||||
random_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, same_sig.clone(), prevote_step.clone(), Some("1".sha3()));
|
||||
random_vote(&collector, same_sig, prevote_step.clone(), Some("1".sha3()));
|
||||
// good precommit
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), precommit_step.clone(), Some("1".sha3()));
|
||||
// good prevote
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3()));
|
||||
random_vote(&collector, H520::random(), prevote_step.clone(), Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), VoteStep::new(2, 2, Step::Precommit), Some("2".sha3()));
|
||||
|
||||
assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 4);
|
||||
assert_eq!(collector.count_step_votes(3, 2, Step::Precommit), 2);
|
||||
assert_eq!(collector.count_step_votes(&prevote_step), 4);
|
||||
assert_eq!(collector.count_step_votes(&precommit_step), 2);
|
||||
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 3,
|
||||
round: 2,
|
||||
step: Step::Prevote,
|
||||
vote_step: prevote_step,
|
||||
block_hash: Some("1".sha3())
|
||||
};
|
||||
assert_eq!(collector.count_aligned_votes(&message), 2);
|
||||
@ -243,30 +279,29 @@ mod tests {
|
||||
#[test]
|
||||
fn remove_old() {
|
||||
let collector = VoteCollector::new();
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3()));
|
||||
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 3,
|
||||
round: 2,
|
||||
step: Step::Precommit,
|
||||
block_hash: Some("1".sha3())
|
||||
let vote = |height, round, step, hash| {
|
||||
random_vote(&collector, H520::random(), VoteStep::new(height, round, step), hash);
|
||||
};
|
||||
collector.throw_out_old(&message);
|
||||
vote(3, 2, Step::Prevote, Some("0".sha3()));
|
||||
vote(3, 1, Step::Prevote, Some("0".sha3()));
|
||||
vote(3, 3, Step::Precommit, Some("0".sha3()));
|
||||
vote(3, 2, Step::Prevote, Some("1".sha3()));
|
||||
vote(3, 2, Step::Prevote, Some("1".sha3()));
|
||||
vote(3, 2, Step::Prevote, Some("0".sha3()));
|
||||
vote(2, 2, Step::Precommit, Some("2".sha3()));
|
||||
|
||||
collector.throw_out_old(&VoteStep::new(3, 2, Step::Precommit));
|
||||
assert_eq!(collector.votes.read().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malicious_authority() {
|
||||
let collector = VoteCollector::new();
|
||||
full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()), Address::default());
|
||||
full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()), Address::default());
|
||||
assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 1);
|
||||
let vote_step = VoteStep::new(3, 2, Step::Prevote);
|
||||
// Vote is inserted fine.
|
||||
assert!(full_vote(&collector, H520::random(), vote_step.clone(), Some("0".sha3()), &Address::default()).is_none());
|
||||
// Returns the double voting address.
|
||||
full_vote(&collector, H520::random(), vote_step.clone(), Some("1".sha3()), &Address::default()).unwrap();
|
||||
assert_eq!(collector.count_step_votes(&vote_step), 1);
|
||||
}
|
||||
}
|
||||
|
@ -118,17 +118,17 @@ mod provider {
|
||||
}
|
||||
}
|
||||
fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
|
||||
|
||||
|
||||
/// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
|
||||
#[allow(dead_code)]
|
||||
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> {
|
||||
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> {
|
||||
let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?;
|
||||
let data = call.encode_call(
|
||||
vec![]
|
||||
).map_err(Self::as_string)?;
|
||||
let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
|
||||
let mut result = output.into_iter().rev().collect::<Vec<_>>();
|
||||
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() }))
|
||||
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -140,6 +140,7 @@ mod tests {
|
||||
use account_provider::AccountProvider;
|
||||
use transaction::{Transaction, Action};
|
||||
use client::{BlockChainClient, EngineClient};
|
||||
use ethkey::Secret;
|
||||
use miner::MinerService;
|
||||
use tests::helpers::generate_dummy_client_with_spec_and_data;
|
||||
use super::super::ValidatorSet;
|
||||
@ -158,8 +159,9 @@ mod tests {
|
||||
#[test]
|
||||
fn changes_validators() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let v0 = tap.insert_account("1".sha3(), "").unwrap();
|
||||
let v1 = tap.insert_account("0".sha3(), "").unwrap();
|
||||
let s0 = Secret::from_slice(&"1".sha3()).unwrap();
|
||||
let v0 = tap.insert_account(s0.clone(), "").unwrap();
|
||||
let v1 = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "").unwrap();
|
||||
let spec_factory = || {
|
||||
let spec = Spec::new_validator_contract();
|
||||
spec.engine.register_account_provider(tap.clone());
|
||||
@ -178,7 +180,7 @@ mod tests {
|
||||
action: Action::Call(validator_contract),
|
||||
value: 0.into(),
|
||||
data: "f94e18670000000000000000000000000000000000000000000000000000000000000001".from_hex().unwrap(),
|
||||
}.sign(&"1".sha3(), None);
|
||||
}.sign(&s0, None);
|
||||
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
|
||||
client.update_sealing();
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
@ -190,7 +192,7 @@ mod tests {
|
||||
action: Action::Call(validator_contract),
|
||||
value: 0.into(),
|
||||
data: "4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".from_hex().unwrap(),
|
||||
}.sign(&"1".sha3(), None);
|
||||
}.sign(&s0, None);
|
||||
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
|
||||
client.update_sealing();
|
||||
// The transaction is not yet included so still unable to seal.
|
||||
@ -209,7 +211,7 @@ mod tests {
|
||||
action: Action::Call(Address::default()),
|
||||
value: 0.into(),
|
||||
data: Vec::new(),
|
||||
}.sign(&"1".sha3(), None);
|
||||
}.sign(&s0, None);
|
||||
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
|
||||
client.update_sealing();
|
||||
// Able to seal again.
|
||||
|
@ -22,7 +22,7 @@ use action_params::ActionParams;
|
||||
use evm::Ext;
|
||||
|
||||
/// Evm errors.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Error {
|
||||
/// `OutOfGas` is returned when transaction execution runs out of gas.
|
||||
/// The state should be reverted to the state from before the
|
||||
|
@ -463,8 +463,9 @@ impl<'a> Executive<'a> {
|
||||
|
||||
match result {
|
||||
Err(evm::Error::Internal) => Err(ExecutionError::Internal),
|
||||
Err(_) => {
|
||||
Err(exception) => {
|
||||
Ok(Executed {
|
||||
exception: Some(exception),
|
||||
gas: t.gas,
|
||||
gas_used: t.gas,
|
||||
refunded: U256::zero(),
|
||||
@ -479,6 +480,7 @@ impl<'a> Executive<'a> {
|
||||
},
|
||||
_ => {
|
||||
Ok(Executed {
|
||||
exception: None,
|
||||
gas: t.gas,
|
||||
gas_used: gas_used,
|
||||
refunded: refunded,
|
||||
|
@ -844,6 +844,7 @@ mod tests {
|
||||
use std::str::FromStr;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use super::*;
|
||||
use ethkey::Secret;
|
||||
use util::{U256, H256, FixedHash, Address, Hashable};
|
||||
use tests::helpers::*;
|
||||
use devtools::*;
|
||||
@ -854,6 +855,10 @@ mod tests {
|
||||
use trace::{FlatTrace, TraceError, trace};
|
||||
use types::executed::CallType;
|
||||
|
||||
fn secret() -> Secret {
|
||||
Secret::from_slice(&"".sha3()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_apply_create_transaction() {
|
||||
init_log();
|
||||
@ -872,7 +877,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: FromHex::from_hex("601080600c6000396000f3006000355415600957005b60203560003555").unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
let result = state.apply(&info, &engine, &t, true).unwrap();
|
||||
@ -932,7 +937,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: FromHex::from_hex("5b600056").unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
let result = state.apply(&info, &engine, &t, true).unwrap();
|
||||
@ -969,7 +974,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("6000").unwrap());
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@ -1012,7 +1017,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
let result = state.apply(&info, &engine, &t, true).unwrap();
|
||||
@ -1054,7 +1059,7 @@ mod tests {
|
||||
action: Action::Call(0x1.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let result = state.apply(&info, engine, &t, true).unwrap();
|
||||
|
||||
@ -1096,7 +1101,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060006001610be0f1").unwrap());
|
||||
let result = state.apply(&info, engine, &t, true).unwrap();
|
||||
@ -1139,7 +1144,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b611000f2").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap());
|
||||
@ -1201,7 +1206,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("6000600060006000600b618000f4").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap());
|
||||
@ -1260,7 +1265,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("5b600056").unwrap());
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@ -1300,7 +1305,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap());
|
||||
@ -1360,7 +1365,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006045600b6000f1").unwrap());
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@ -1415,7 +1420,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060ff600b6000f1").unwrap()); // not enough funds.
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@ -1458,7 +1463,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],//600480600b6000396000f35b600056
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("5b600056").unwrap());
|
||||
@ -1514,7 +1519,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1").unwrap());
|
||||
@ -1589,7 +1594,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],//600480600b6000396000f35b600056
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1505b601256").unwrap());
|
||||
@ -1662,7 +1667,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("73000000000000000000000000000000000000000bff").unwrap());
|
||||
state.add_balance(&0xa.into(), &50.into(), CleanupMode::NoEmpty);
|
||||
|
@ -28,7 +28,7 @@ use rlp::View;
|
||||
use spec::Spec;
|
||||
use views::BlockView;
|
||||
use util::stats::Histogram;
|
||||
use ethkey::KeyPair;
|
||||
use ethkey::{KeyPair, Secret};
|
||||
use transaction::{PendingTransaction, Transaction, Action};
|
||||
use miner::MinerService;
|
||||
|
||||
@ -290,7 +290,7 @@ fn change_history_size() {
|
||||
|
||||
#[test]
|
||||
fn does_not_propagate_delayed_transactions() {
|
||||
let key = KeyPair::from_secret("test".sha3()).unwrap();
|
||||
let key = KeyPair::from_secret(Secret::from_slice(&"test".sha3()).unwrap()).unwrap();
|
||||
let secret = key.secret();
|
||||
let tx0 = PendingTransaction::new(Transaction {
|
||||
nonce: 0.into(),
|
||||
|
@ -163,7 +163,7 @@ pub fn generate_dummy_client_with_spec_and_data<F>(get_test_spec: F, block_numbe
|
||||
let mut last_hashes = vec![];
|
||||
let mut last_header = genesis_header.clone();
|
||||
|
||||
let kp = KeyPair::from_secret("".sha3()).unwrap();
|
||||
let kp = KeyPair::from_secret_slice(&"".sha3()).unwrap();
|
||||
let author = kp.address();
|
||||
|
||||
let mut n = 0;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
use util::{Bytes, U256, Address, U512};
|
||||
use rlp::*;
|
||||
use evm;
|
||||
use trace::{VMTrace, FlatTrace};
|
||||
use types::log_entry::LogEntry;
|
||||
use types::state_diff::StateDiff;
|
||||
@ -65,6 +66,9 @@ impl Decodable for CallType {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct Executed {
|
||||
/// True if the outer call/create resulted in an exceptional exit.
|
||||
pub exception: Option<evm::Error>,
|
||||
|
||||
/// Gas paid up front for execution of transaction.
|
||||
pub gas: U256,
|
||||
|
||||
@ -178,6 +182,8 @@ pub enum CallError {
|
||||
TransactionNotFound,
|
||||
/// Couldn't find requested block's state in the chain.
|
||||
StatePruned,
|
||||
/// Couldn't find an amount of gas that didn't result in an exception.
|
||||
Exceptional,
|
||||
/// Error executing.
|
||||
Execution(ExecutionError),
|
||||
}
|
||||
@ -195,6 +201,7 @@ impl fmt::Display for CallError {
|
||||
let msg = match *self {
|
||||
TransactionNotFound => "Transaction couldn't be found in the chain".into(),
|
||||
StatePruned => "Couldn't find the transaction block's state in the chain".into(),
|
||||
Exceptional => "An exception happened in the execution".into(),
|
||||
Execution(ref e) => format!("{}", e),
|
||||
};
|
||||
|
||||
|
@ -102,6 +102,7 @@ impl HeapSizeOf for Transaction {
|
||||
impl From<ethjson::state::Transaction> for SignedTransaction {
|
||||
fn from(t: ethjson::state::Transaction) -> Self {
|
||||
let to: Option<ethjson::hash::Address> = t.to.into();
|
||||
let secret = Secret::from_slice(&t.secret.0).expect("Valid secret expected.");
|
||||
Transaction {
|
||||
nonce: t.nonce.into(),
|
||||
gas_price: t.gas_price.into(),
|
||||
@ -112,7 +113,7 @@ impl From<ethjson::state::Transaction> for SignedTransaction {
|
||||
},
|
||||
value: t.value.into(),
|
||||
data: t.data.into(),
|
||||
}.sign(&t.secret.into(), None)
|
||||
}.sign(&secret, None)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ pub mod aes {
|
||||
/// ECDH functions
|
||||
#[cfg_attr(feature="dev", allow(similar_names))]
|
||||
pub mod ecdh {
|
||||
use secp256k1::{ecdh, key};
|
||||
use secp256k1::{ecdh, key, Error as SecpError};
|
||||
use ethkey::{Secret, Public, SECP256K1};
|
||||
use Error;
|
||||
|
||||
@ -180,13 +180,11 @@ pub mod ecdh {
|
||||
};
|
||||
|
||||
let publ = key::PublicKey::from_slice(context, &pdata)?;
|
||||
// no way to create SecretKey from raw byte array.
|
||||
let sec: &key::SecretKey = unsafe { ::std::mem::transmute(secret) };
|
||||
let shared = ecdh::SharedSecret::new_raw(context, &publ, sec);
|
||||
let sec = key::SecretKey::from_slice(context, &secret)?;
|
||||
let shared = ecdh::SharedSecret::new_raw(context, &publ, &sec);
|
||||
|
||||
let mut s = Secret::default();
|
||||
s.copy_from_slice(&shared[0..32]);
|
||||
Ok(s)
|
||||
Secret::from_slice(&shared[0..32])
|
||||
.map_err(|_| Error::Secp(SecpError::InvalidSecretKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use keccak::Keccak256;
|
||||
use super::{KeyPair, Error, Generator};
|
||||
use super::{KeyPair, Error, Generator, Secret};
|
||||
|
||||
/// Simple brainwallet.
|
||||
pub struct Brain(String);
|
||||
@ -34,13 +34,15 @@ impl Generator for Brain {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
secret = secret.keccak256();
|
||||
|
||||
|
||||
match i > 16384 {
|
||||
false => i += 1,
|
||||
true => {
|
||||
let result = KeyPair::from_secret(secret.clone().into());
|
||||
if result.as_ref().ok().map_or(false, |r| r.address()[0] == 0) {
|
||||
return result;
|
||||
if let Ok(secret) = Secret::from_slice(&secret) {
|
||||
let result = KeyPair::from_secret(secret);
|
||||
if result.as_ref().ok().map_or(false, |r| r.address()[0] == 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -60,11 +60,14 @@ impl KeyPair {
|
||||
Ok(keypair)
|
||||
}
|
||||
|
||||
pub fn from_secret_slice(slice: &[u8]) -> Result<KeyPair, Error> {
|
||||
Self::from_secret(Secret::from_slice(slice)?)
|
||||
}
|
||||
|
||||
pub fn from_keypair(sec: key::SecretKey, publ: key::PublicKey) -> Self {
|
||||
let context = &SECP256K1;
|
||||
let serialized = publ.serialize_vec(context, false);
|
||||
let mut secret = Secret::default();
|
||||
secret.copy_from_slice(&sec[0..32]);
|
||||
let secret = Secret::from(sec);
|
||||
let mut public = Public::default();
|
||||
public.copy_from_slice(&serialized[1..65]);
|
||||
|
||||
|
@ -29,6 +29,7 @@ mod keccak;
|
||||
mod prefix;
|
||||
mod random;
|
||||
mod signature;
|
||||
mod secret;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new();
|
||||
@ -46,10 +47,10 @@ pub use self::keypair::{KeyPair, public_to_address};
|
||||
pub use self::prefix::Prefix;
|
||||
pub use self::random::Random;
|
||||
pub use self::signature::{sign, verify_public, verify_address, recover, Signature};
|
||||
pub use self::secret::Secret;
|
||||
|
||||
use bigint::hash::{H160, H256, H512};
|
||||
|
||||
pub type Address = H160;
|
||||
pub type Secret = H256;
|
||||
pub type Message = H256;
|
||||
pub type Public = H512;
|
||||
|
69
ethkey/src/secret.rs
Normal file
69
ethkey/src/secret.rs
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity 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 General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use secp256k1::key;
|
||||
use bigint::hash::H256;
|
||||
use {Error};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Secret {
|
||||
inner: H256,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Secret {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "Secret: 0x{:x}{:x}..{:x}{:x}", self.inner[0], self.inner[1], self.inner[30], self.inner[31])
|
||||
}
|
||||
}
|
||||
|
||||
impl Secret {
|
||||
pub fn from_slice(key: &[u8]) -> Result<Self, Error> {
|
||||
if key.len() != 32 {
|
||||
return Err(Error::InvalidSecret);
|
||||
}
|
||||
|
||||
let mut h = H256::default();
|
||||
h.copy_from_slice(&key[0..32]);
|
||||
Ok(Secret { inner: h })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Secret {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let hash = H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
Self::from_slice(&hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<key::SecretKey> for Secret {
|
||||
fn from(key: key::SecretKey) -> Self {
|
||||
Self::from_slice(&key[0..32])
|
||||
.expect("`key::SecretKey` is valid (no way to construct invalid one); qed")
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Secret {
|
||||
type Target = H256;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::cmp::PartialEq;
|
||||
use std::{mem, fmt};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use secp256k1::{Message as SecpMessage, RecoverableSignature, RecoveryId, Error as SecpError};
|
||||
@ -169,9 +169,8 @@ impl DerefMut for Signature {
|
||||
|
||||
pub fn sign(secret: &Secret, message: &Message) -> Result<Signature, Error> {
|
||||
let context = &SECP256K1;
|
||||
// no way to create from raw byte array.
|
||||
let sec: &SecretKey = unsafe { mem::transmute(secret) };
|
||||
let s = context.sign_recoverable(&SecpMessage::from_slice(&message[..])?, sec)?;
|
||||
let sec = SecretKey::from_slice(context, &secret)?;
|
||||
let s = context.sign_recoverable(&SecpMessage::from_slice(&message[..])?, &sec)?;
|
||||
let (rec_id, data) = s.serialize_compact(context);
|
||||
let mut data_arr = [0; 65];
|
||||
|
||||
|
@ -122,16 +122,14 @@ impl Crypto {
|
||||
return Err(Error::InvalidPassword);
|
||||
}
|
||||
|
||||
let mut secret = Secret::default();
|
||||
|
||||
match self.cipher {
|
||||
Cipher::Aes128Ctr(ref params) => {
|
||||
let from = 32 - self.ciphertext.len();
|
||||
crypto::aes::decrypt(&derived_left_bits, ¶ms.iv, &self.ciphertext, &mut (&mut *secret)[from..])
|
||||
let mut secret = [0; 32];
|
||||
crypto::aes::decrypt(&derived_left_bits, ¶ms.iv, &self.ciphertext, &mut secret[from..]);
|
||||
Ok(Secret::from_slice(&secret)?)
|
||||
},
|
||||
}
|
||||
|
||||
Ok(secret)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ impl PresaleWallet {
|
||||
let len = crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword)?;
|
||||
let unpadded = &key[..len];
|
||||
|
||||
let secret = Secret::from(unpadded.keccak256());
|
||||
let secret = Secret::from_slice(&unpadded.keccak256())?;
|
||||
if let Ok(kp) = KeyPair::from_secret(secret) {
|
||||
if kp.address() == self.address {
|
||||
return Ok(kp)
|
||||
|
@ -133,9 +133,9 @@ fn secret_store_load_pat_files() {
|
||||
#[test]
|
||||
fn test_decrypting_files_with_short_ciphertext() {
|
||||
// 31e9d1e6d844bd3a536800ef8d8be6a9975db509, 30
|
||||
let kp1 = KeyPair::from_secret("000081c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018".into()).unwrap();
|
||||
let kp1 = KeyPair::from_secret("000081c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018".parse().unwrap()).unwrap();
|
||||
// d1e64e5480bfaf733ba7d48712decb8227797a4e , 31
|
||||
let kp2 = KeyPair::from_secret("00fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35".into()).unwrap();
|
||||
let kp2 = KeyPair::from_secret("00fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35".parse().unwrap()).unwrap();
|
||||
let dir = DiskDirectory::at(ciphertext_path());
|
||||
let store = EthStore::open(Box::new(dir)).unwrap();
|
||||
let accounts = store.accounts().unwrap();
|
||||
|
@ -14,6 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
import { Http, Ws } from './transport';
|
||||
import Contract from './contract';
|
||||
|
||||
@ -22,8 +24,10 @@ import Subscriptions from './subscriptions';
|
||||
import util from './util';
|
||||
import { isFunction } from './util/types';
|
||||
|
||||
export default class Api {
|
||||
export default class Api extends EventEmitter {
|
||||
constructor (transport) {
|
||||
super();
|
||||
|
||||
if (!transport || !isFunction(transport.execute)) {
|
||||
throw new Error('EthApi needs transport with execute() function defined');
|
||||
}
|
||||
|
@ -248,18 +248,32 @@ export default class Contract {
|
||||
.call(callParams)
|
||||
.then((encoded) => func.decodeOutput(encoded))
|
||||
.then((tokens) => tokens.map((token) => token.value))
|
||||
.then((returns) => returns.length === 1 ? returns[0] : returns);
|
||||
.then((returns) => returns.length === 1 ? returns[0] : returns)
|
||||
.catch((error) => {
|
||||
console.warn(`${func.name}.call`, values, error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
if (!func.constant) {
|
||||
func.postTransaction = (options, values = []) => {
|
||||
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||
return this._api.parity.postTransaction(_options);
|
||||
return this._api.parity
|
||||
.postTransaction(_options)
|
||||
.catch((error) => {
|
||||
console.warn(`${func.name}.postTransaction`, values, error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
func.estimateGas = (options, values = []) => {
|
||||
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||
return this._api.eth.estimateGas(_options);
|
||||
return this._api.eth
|
||||
.estimateGas(_options)
|
||||
.catch((error) => {
|
||||
console.warn(`${func.name}.estimateGas`, values, error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@ -385,6 +399,10 @@ export default class Contract {
|
||||
this._subscribeToChanges();
|
||||
return subscriptionId;
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('subscribe', event, _options, error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ export const ERROR_CODES = {
|
||||
UNKNOWN_ERROR: -32009,
|
||||
TRANSACTION_ERROR: -32010,
|
||||
EXECUTION_ERROR: -32015,
|
||||
EXCEPTION_ERROR: -32016,
|
||||
ACCOUNT_LOCKED: -32020,
|
||||
PASSWORD_INVALID: -32021,
|
||||
ACCOUNT_ERROR: -32023,
|
||||
|
@ -51,14 +51,14 @@ export default class Http extends JsonRpcBase {
|
||||
|
||||
return fetch(this._url, request)
|
||||
.catch((error) => {
|
||||
this._connected = false;
|
||||
this._setDisconnected();
|
||||
throw error;
|
||||
})
|
||||
.then((response) => {
|
||||
this._connected = true;
|
||||
this._setConnected();
|
||||
|
||||
if (response.status !== 200) {
|
||||
this._connected = false;
|
||||
this._setDisconnected();
|
||||
this.error(JSON.stringify({ status: response.status, statusText: response.statusText }));
|
||||
console.error(`${method}(${JSON.stringify(params)}): ${response.status}: ${response.statusText}`);
|
||||
|
||||
|
@ -37,6 +37,26 @@ describe('api/transport/Http', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('transport emitter', () => {
|
||||
it('emits close event', (done) => {
|
||||
transport.once('close', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
transport.execute('eth_call');
|
||||
});
|
||||
|
||||
it('emits open event', (done) => {
|
||||
mockHttp([{ method: 'eth_call', reply: { result: '' } }]);
|
||||
|
||||
transport.once('open', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
transport.execute('eth_call');
|
||||
});
|
||||
});
|
||||
|
||||
describe('transport', () => {
|
||||
const RESULT = ['this is some result'];
|
||||
|
||||
|
@ -14,8 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default class JsonRpcBase {
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
export default class JsonRpcBase extends EventEmitter {
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
this._id = 1;
|
||||
this._debug = false;
|
||||
this._connected = false;
|
||||
@ -32,6 +36,20 @@ export default class JsonRpcBase {
|
||||
return json;
|
||||
}
|
||||
|
||||
_setConnected () {
|
||||
if (!this._connected) {
|
||||
this._connected = true;
|
||||
this.emit('open');
|
||||
}
|
||||
}
|
||||
|
||||
_setDisconnected () {
|
||||
if (this._connected) {
|
||||
this._connected = false;
|
||||
this.emit('close');
|
||||
}
|
||||
}
|
||||
|
||||
get id () {
|
||||
return this._id;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import TransportError from '../error';
|
||||
|
||||
/* global WebSocket */
|
||||
export default class Ws extends JsonRpcBase {
|
||||
constructor (url, token, connect = true) {
|
||||
constructor (url, token, autoconnect = true) {
|
||||
super();
|
||||
|
||||
this._url = url;
|
||||
@ -32,14 +32,14 @@ export default class Ws extends JsonRpcBase {
|
||||
this._connecting = false;
|
||||
this._connected = false;
|
||||
this._lastError = null;
|
||||
this._autoConnect = false;
|
||||
this._autoConnect = autoconnect;
|
||||
this._retries = 0;
|
||||
this._reconnectTimeoutId = null;
|
||||
|
||||
this._connectPromise = null;
|
||||
this._connectPromiseFunctions = {};
|
||||
|
||||
if (connect) {
|
||||
if (autoconnect) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
@ -124,11 +124,8 @@ export default class Ws extends JsonRpcBase {
|
||||
}
|
||||
|
||||
_onOpen = (event) => {
|
||||
console.log('ws:onOpen');
|
||||
|
||||
this._connected = true;
|
||||
this._setConnected();
|
||||
this._connecting = false;
|
||||
this._autoConnect = true;
|
||||
this._retries = 0;
|
||||
|
||||
Object.keys(this._messages)
|
||||
@ -142,7 +139,7 @@ export default class Ws extends JsonRpcBase {
|
||||
}
|
||||
|
||||
_onClose = (event) => {
|
||||
this._connected = false;
|
||||
this._setDisconnected();
|
||||
this._connecting = false;
|
||||
|
||||
event.timestamp = Date.now();
|
||||
@ -209,8 +206,8 @@ export default class Ws extends JsonRpcBase {
|
||||
if (result.error) {
|
||||
this.error(event.data);
|
||||
|
||||
// Don't print error if request rejected...
|
||||
if (!/rejected/.test(result.error.message)) {
|
||||
// Don't print error if request rejected or not is not yet up...
|
||||
if (!/(rejected|not yet up)/.test(result.error.message)) {
|
||||
console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,37 @@ describe('api/transport/Ws', () => {
|
||||
let transport;
|
||||
let scope;
|
||||
|
||||
describe('transport emitter', () => {
|
||||
const connect = () => {
|
||||
const scope = mockWs();
|
||||
const transport = new Ws(TEST_WS_URL);
|
||||
|
||||
return { transport, scope };
|
||||
};
|
||||
|
||||
it('emits open event', (done) => {
|
||||
const { transport, scope } = connect();
|
||||
|
||||
transport.once('open', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
it('emits close event', (done) => {
|
||||
const { transport, scope } = connect();
|
||||
|
||||
transport.once('open', () => {
|
||||
scope.server.close();
|
||||
});
|
||||
|
||||
transport.once('close', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('transport', () => {
|
||||
let result;
|
||||
|
||||
|
@ -17,12 +17,20 @@
|
||||
import LogLevel from 'loglevel';
|
||||
|
||||
export const LOG_KEYS = {
|
||||
Balances: {
|
||||
key: 'balances',
|
||||
desc: 'Balances fetching'
|
||||
},
|
||||
TransferModalStore: {
|
||||
path: 'modals/Transfer/store',
|
||||
desc: 'Transfer Modal MobX Store'
|
||||
key: 'modalsTransferStore',
|
||||
desc: 'Transfer modal MobX store'
|
||||
},
|
||||
Signer: {
|
||||
key: 'secureApi',
|
||||
desc: 'The Signer and the Secure API'
|
||||
}
|
||||
};
|
||||
|
||||
export const getLogger = (LOG_KEY) => {
|
||||
return LogLevel.getLogger(LOG_KEY.path);
|
||||
return LogLevel.getLogger(LOG_KEY.key);
|
||||
};
|
||||
|
@ -391,6 +391,10 @@ class DeployContract extends Component {
|
||||
.then(([gasEst, gas]) => {
|
||||
this.gasStore.setEstimated(gasEst.toFixed(0));
|
||||
this.gasStore.setGas(gas.toFixed(0));
|
||||
})
|
||||
.catch((error) => {
|
||||
this.gasStore.setEstimatedError();
|
||||
console.warn('estimateGas', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@ import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { toWei } from '~/api/util/wei';
|
||||
import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash, Warning } from '~/ui';
|
||||
@ -156,8 +155,7 @@ class ExecuteContract extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Warning
|
||||
warning={ errorEstimated } />
|
||||
<Warning warning={ errorEstimated } />
|
||||
);
|
||||
}
|
||||
|
||||
@ -379,6 +377,7 @@ class ExecuteContract extends Component {
|
||||
this.gasStore.setGas(gas.toFixed(0));
|
||||
})
|
||||
.catch((error) => {
|
||||
this.gasStore.setEstimatedError();
|
||||
console.warn('estimateGas', error);
|
||||
});
|
||||
}
|
||||
@ -470,11 +469,7 @@ function mapStateToProps (initState, initProps) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(ExecuteContract);
|
||||
|
@ -23,7 +23,7 @@ import { bytesToHex } from '~/api/util/format';
|
||||
import Contract from '~/api/contract';
|
||||
import ERRORS from './errors';
|
||||
import { ERROR_CODES } from '~/api/transport/error';
|
||||
import { DEFAULT_GAS, MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||
import GasPriceStore from '~/ui/GasPriceEditor/store';
|
||||
import { getLogger, LOG_KEYS } from '~/config';
|
||||
|
||||
@ -357,6 +357,7 @@ export default class TransferStore {
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.gasStore.setEstimatedError();
|
||||
console.warn('etimateGas', error);
|
||||
this.recalculate(redo);
|
||||
});
|
||||
@ -441,6 +442,8 @@ export default class TransferStore {
|
||||
const gasTotal = new BigNumber(_gasTotal || 0);
|
||||
const { valueAll, isEth, isWallet } = this;
|
||||
|
||||
log.debug('@getValues', 'gas', gasTotal.toFormat());
|
||||
|
||||
if (!valueAll) {
|
||||
const value = this.getTokenValue();
|
||||
|
||||
@ -568,6 +571,7 @@ export default class TransferStore {
|
||||
send () {
|
||||
const { options, values } = this._getTransferParams();
|
||||
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
|
||||
log.debug('@send', 'transfer value', options.value && options.value.toFormat());
|
||||
|
||||
return this._getTransferMethod().postTransaction(options, values);
|
||||
}
|
||||
@ -626,7 +630,8 @@ export default class TransferStore {
|
||||
options.gas = MAX_GAS_ESTIMATION;
|
||||
}
|
||||
|
||||
const { token } = this.getValues(options.gas);
|
||||
const gasTotal = new BigNumber(options.gas || DEFAULT_GAS).mul(options.gasPrice || DEFAULT_GASPRICE);
|
||||
const { token } = this.getValues(gasTotal);
|
||||
|
||||
if (isEth && !isWallet && !forceToken) {
|
||||
options.value = token;
|
||||
|
@ -21,51 +21,164 @@ import { padRight } from '~/api/util/format';
|
||||
|
||||
import Contracts from '~/contracts';
|
||||
|
||||
let instance = null;
|
||||
|
||||
export default class Balances {
|
||||
constructor (store, api) {
|
||||
this._api = api;
|
||||
this._store = store;
|
||||
|
||||
this._tokenregSubId = null;
|
||||
this._tokenregMetaSubId = null;
|
||||
this._tokenreg = null;
|
||||
this._tokenregSID = null;
|
||||
this._tokenMetaSID = null;
|
||||
|
||||
// Throttled `retrieveTokens` function
|
||||
this._blockNumberSID = null;
|
||||
this._accountsInfoSID = null;
|
||||
|
||||
// Throtthled load tokens (no more than once
|
||||
// every minute)
|
||||
this.loadTokens = throttle(
|
||||
this._loadTokens,
|
||||
60 * 1000,
|
||||
{ leading: true, trailing: true }
|
||||
);
|
||||
|
||||
// Throttled `_fetchBalances` function
|
||||
// that gets called max once every 40s
|
||||
this.longThrottledFetch = throttle(
|
||||
this.fetchBalances,
|
||||
this._fetchBalances,
|
||||
40 * 1000,
|
||||
{ trailing: true }
|
||||
{ leading: false, trailing: true }
|
||||
);
|
||||
|
||||
this.shortThrottledFetch = throttle(
|
||||
this.fetchBalances,
|
||||
this._fetchBalances,
|
||||
2 * 1000,
|
||||
{ trailing: true }
|
||||
{ leading: false, trailing: true }
|
||||
);
|
||||
|
||||
// Fetch all tokens every 2 minutes
|
||||
this.throttledTokensFetch = throttle(
|
||||
this.fetchTokens,
|
||||
60 * 1000,
|
||||
{ trailing: true }
|
||||
this._fetchTokens,
|
||||
2 * 60 * 1000,
|
||||
{ leading: false, trailing: true }
|
||||
);
|
||||
|
||||
// Unsubscribe previous instance if it exists
|
||||
if (instance) {
|
||||
Balances.stop();
|
||||
}
|
||||
}
|
||||
|
||||
start () {
|
||||
this.subscribeBlockNumber();
|
||||
this.subscribeAccountsInfo();
|
||||
static get (store = {}) {
|
||||
if (!instance && store) {
|
||||
const { api } = store.getState();
|
||||
return Balances.instantiate(store, api);
|
||||
}
|
||||
|
||||
this.loadTokens();
|
||||
return instance;
|
||||
}
|
||||
|
||||
static instantiate (store, api) {
|
||||
if (!instance) {
|
||||
instance = new Balances(store, api);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static start () {
|
||||
if (!instance) {
|
||||
return Promise.reject('BalancesProvider has not been intiated yet');
|
||||
}
|
||||
|
||||
const self = instance;
|
||||
|
||||
// Unsubscribe from previous subscriptions
|
||||
return Balances
|
||||
.stop()
|
||||
.then(() => self.loadTokens())
|
||||
.then(() => {
|
||||
const promises = [
|
||||
self.subscribeBlockNumber(),
|
||||
self.subscribeAccountsInfo()
|
||||
];
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
static stop () {
|
||||
if (!instance) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const self = instance;
|
||||
const promises = [];
|
||||
|
||||
if (self._blockNumberSID) {
|
||||
const p = self._api
|
||||
.unsubscribe(self._blockNumberSID)
|
||||
.then(() => {
|
||||
self._blockNumberSID = null;
|
||||
});
|
||||
|
||||
promises.push(p);
|
||||
}
|
||||
|
||||
if (self._accountsInfoSID) {
|
||||
const p = self._api
|
||||
.unsubscribe(self._accountsInfoSID)
|
||||
.then(() => {
|
||||
self._accountsInfoSID = null;
|
||||
});
|
||||
|
||||
promises.push(p);
|
||||
}
|
||||
|
||||
// Unsubscribe without adding the promises
|
||||
// to the result, since it would have to wait for a
|
||||
// reconnection to resolve if the Node is disconnected
|
||||
if (self._tokenreg) {
|
||||
if (self._tokenregSID) {
|
||||
const tokenregSID = self._tokenregSID;
|
||||
|
||||
self._tokenreg
|
||||
.unsubscribe(tokenregSID)
|
||||
.then(() => {
|
||||
if (self._tokenregSID === tokenregSID) {
|
||||
self._tokenregSID = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (self._tokenMetaSID) {
|
||||
const tokenMetaSID = self._tokenMetaSID;
|
||||
|
||||
self._tokenreg
|
||||
.unsubscribe(tokenMetaSID)
|
||||
.then(() => {
|
||||
if (self._tokenMetaSID === tokenMetaSID) {
|
||||
self._tokenMetaSID = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
subscribeAccountsInfo () {
|
||||
this._api
|
||||
return this._api
|
||||
.subscribe('parity_allAccountsInfo', (error, accountsInfo) => {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetchBalances();
|
||||
this.fetchAllBalances();
|
||||
})
|
||||
.then((accountsInfoSID) => {
|
||||
this._accountsInfoSID = accountsInfoSID;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('_subscribeAccountsInfo', error);
|
||||
@ -73,49 +186,97 @@ export default class Balances {
|
||||
}
|
||||
|
||||
subscribeBlockNumber () {
|
||||
this._api
|
||||
return this._api
|
||||
.subscribe('eth_blockNumber', (error) => {
|
||||
if (error) {
|
||||
return console.warn('_subscribeBlockNumber', error);
|
||||
}
|
||||
|
||||
const { syncing } = this._store.getState().nodeStatus;
|
||||
|
||||
this.throttledTokensFetch();
|
||||
|
||||
// If syncing, only retrieve balances once every
|
||||
// few seconds
|
||||
if (syncing) {
|
||||
this.shortThrottledFetch.cancel();
|
||||
return this.longThrottledFetch();
|
||||
}
|
||||
|
||||
this.longThrottledFetch.cancel();
|
||||
return this.shortThrottledFetch();
|
||||
return this.fetchAllBalances();
|
||||
})
|
||||
.then((blockNumberSID) => {
|
||||
this._blockNumberSID = blockNumberSID;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('_subscribeBlockNumber', error);
|
||||
});
|
||||
}
|
||||
|
||||
fetchBalances () {
|
||||
this._store.dispatch(fetchBalances());
|
||||
fetchAllBalances (options = {}) {
|
||||
// If it's a network change, reload the tokens
|
||||
// ( and then fetch the tokens balances ) and fetch
|
||||
// the accounts balances
|
||||
if (options.changedNetwork) {
|
||||
this.loadTokens({ skipNotifications: true });
|
||||
this.loadTokens.flush();
|
||||
|
||||
this.fetchBalances({
|
||||
force: true,
|
||||
skipNotifications: true
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetchTokensBalances(options);
|
||||
this.fetchBalances(options);
|
||||
}
|
||||
|
||||
fetchTokens () {
|
||||
this._store.dispatch(fetchTokensBalances());
|
||||
fetchTokensBalances (options) {
|
||||
const { skipNotifications = false, force = false } = options;
|
||||
|
||||
this.throttledTokensFetch(skipNotifications);
|
||||
|
||||
if (force) {
|
||||
this.throttledTokensFetch.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fetchBalances (options) {
|
||||
const { skipNotifications = false, force = false } = options;
|
||||
const { syncing } = this._store.getState().nodeStatus;
|
||||
|
||||
// If syncing, only retrieve balances once every
|
||||
// few seconds
|
||||
if (syncing) {
|
||||
this.shortThrottledFetch.cancel();
|
||||
this.longThrottledFetch(skipNotifications);
|
||||
|
||||
if (force) {
|
||||
this.longThrottledFetch.flush();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.longThrottledFetch.cancel();
|
||||
this.shortThrottledFetch(skipNotifications);
|
||||
|
||||
if (force) {
|
||||
this.shortThrottledFetch.flush();
|
||||
}
|
||||
}
|
||||
|
||||
_fetchBalances (skipNotifications = false) {
|
||||
this._store.dispatch(fetchBalances(null, skipNotifications));
|
||||
}
|
||||
|
||||
_fetchTokens (skipNotifications = false) {
|
||||
this._store.dispatch(fetchTokensBalances(null, null, skipNotifications));
|
||||
}
|
||||
|
||||
getTokenRegistry () {
|
||||
return Contracts.get().tokenReg.getContract();
|
||||
}
|
||||
|
||||
loadTokens () {
|
||||
this
|
||||
_loadTokens (options = {}) {
|
||||
return this
|
||||
.getTokenRegistry()
|
||||
.then((tokenreg) => {
|
||||
this._tokenreg = tokenreg;
|
||||
|
||||
this._store.dispatch(setTokenReg(tokenreg));
|
||||
this._store.dispatch(loadTokens());
|
||||
this._store.dispatch(loadTokens(options));
|
||||
|
||||
return this.attachToTokens(tokenreg);
|
||||
})
|
||||
@ -133,7 +294,7 @@ export default class Balances {
|
||||
}
|
||||
|
||||
attachToNewToken (tokenreg) {
|
||||
if (this._tokenregSubId) {
|
||||
if (this._tokenregSID) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@ -149,13 +310,13 @@ export default class Balances {
|
||||
|
||||
this.handleTokensLogs(logs);
|
||||
})
|
||||
.then((tokenregSubId) => {
|
||||
this._tokenregSubId = tokenregSubId;
|
||||
.then((tokenregSID) => {
|
||||
this._tokenregSID = tokenregSID;
|
||||
});
|
||||
}
|
||||
|
||||
attachToTokenMetaChange (tokenreg) {
|
||||
if (this._tokenregMetaSubId) {
|
||||
if (this._tokenMetaSID) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@ -172,8 +333,8 @@ export default class Balances {
|
||||
|
||||
this.handleTokensLogs(logs);
|
||||
})
|
||||
.then((tokenregMetaSubId) => {
|
||||
this._tokenregMetaSubId = tokenregMetaSubId;
|
||||
.then((tokenMetaSID) => {
|
||||
this._tokenMetaSID = tokenMetaSID;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -23,18 +23,27 @@ import { setAddressImage } from './imagesActions';
|
||||
|
||||
import * as ABIS from '~/contracts/abi';
|
||||
import { notifyTransaction } from '~/util/notifications';
|
||||
import { LOG_KEYS, getLogger } from '~/config';
|
||||
import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png';
|
||||
|
||||
const log = getLogger(LOG_KEYS.Balances);
|
||||
|
||||
const ETH = {
|
||||
name: 'Ethereum',
|
||||
tag: 'ETH',
|
||||
image: imagesEthereum
|
||||
};
|
||||
|
||||
function setBalances (_balances) {
|
||||
function setBalances (_balances, skipNotifications = false) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
const currentTokens = Object.values(state.balances.tokens || {});
|
||||
const currentTags = [ 'eth' ]
|
||||
.concat(currentTokens.map((token) => token.tag))
|
||||
.filter((tag) => tag)
|
||||
.map((tag) => tag.toLowerCase());
|
||||
|
||||
const accounts = state.personal.accounts;
|
||||
const nextBalances = _balances;
|
||||
const prevBalances = state.balances.balances;
|
||||
@ -48,38 +57,55 @@ function setBalances (_balances) {
|
||||
|
||||
const balance = Object.assign({}, balances[address]);
|
||||
const { tokens, txCount = balance.txCount } = nextBalances[address];
|
||||
const nextTokens = balance.tokens.slice();
|
||||
|
||||
tokens.forEach((t) => {
|
||||
const { token, value } = t;
|
||||
const { tag } = token;
|
||||
const prevTokens = balance.tokens.slice();
|
||||
const nextTokens = [];
|
||||
|
||||
const tokenIndex = nextTokens.findIndex((tok) => tok.token.tag === tag);
|
||||
currentTags
|
||||
.forEach((tag) => {
|
||||
const prevToken = prevTokens.find((tok) => tok.token.tag.toLowerCase() === tag);
|
||||
const nextToken = tokens.find((tok) => tok.token.tag.toLowerCase() === tag);
|
||||
|
||||
if (tokenIndex === -1) {
|
||||
nextTokens.push({
|
||||
token,
|
||||
value
|
||||
});
|
||||
} else {
|
||||
const oldValue = nextTokens[tokenIndex].value;
|
||||
// If the given token is not in the current tokens, skip
|
||||
if (!nextToken && !prevToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No updates
|
||||
if (!nextToken) {
|
||||
return nextTokens.push(prevToken);
|
||||
}
|
||||
|
||||
const { token, value } = nextToken;
|
||||
|
||||
// If it's a new token, push it
|
||||
if (!prevToken) {
|
||||
return nextTokens.push({
|
||||
token, value
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise, update the value
|
||||
const prevValue = prevToken.value;
|
||||
|
||||
// If received a token/eth (old value < new value), notify
|
||||
if (oldValue.lt(value) && accounts[address]) {
|
||||
if (prevValue.lt(value) && accounts[address] && !skipNotifications) {
|
||||
const account = accounts[address];
|
||||
const txValue = value.minus(oldValue);
|
||||
const txValue = value.minus(prevValue);
|
||||
|
||||
const redirectToAccount = () => {
|
||||
const route = `/account/${account.address}`;
|
||||
const route = `/accounts/${account.address}`;
|
||||
dispatch(push(route));
|
||||
};
|
||||
|
||||
notifyTransaction(account, token, txValue, redirectToAccount);
|
||||
}
|
||||
|
||||
nextTokens[tokenIndex] = { token, value };
|
||||
}
|
||||
});
|
||||
return nextTokens.push({
|
||||
...prevToken,
|
||||
value
|
||||
});
|
||||
});
|
||||
|
||||
balances[address] = { txCount: txCount || new BigNumber(0), tokens: nextTokens };
|
||||
});
|
||||
@ -123,7 +149,9 @@ export function setTokenImage (tokenAddress, image) {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadTokens () {
|
||||
export function loadTokens (options = {}) {
|
||||
log.debug('loading tokens', Object.keys(options).length ? options : '');
|
||||
|
||||
return (dispatch, getState) => {
|
||||
const { tokenreg } = getState().balances;
|
||||
|
||||
@ -131,7 +159,7 @@ export function loadTokens () {
|
||||
.call()
|
||||
.then((numTokens) => {
|
||||
const tokenIds = range(numTokens.toNumber());
|
||||
dispatch(fetchTokens(tokenIds));
|
||||
dispatch(fetchTokens(tokenIds, options));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::loadTokens', error);
|
||||
@ -139,8 +167,9 @@ export function loadTokens () {
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchTokens (_tokenIds) {
|
||||
export function fetchTokens (_tokenIds, options = {}) {
|
||||
const tokenIds = uniq(_tokenIds || []);
|
||||
|
||||
return (dispatch, getState) => {
|
||||
const { api, images, balances } = getState();
|
||||
const { tokenreg } = balances;
|
||||
@ -161,8 +190,9 @@ export function fetchTokens (_tokenIds) {
|
||||
dispatch(setAddressImage(address, image, true));
|
||||
});
|
||||
|
||||
log.debug('fetched token', tokens);
|
||||
dispatch(setTokens(tokens));
|
||||
dispatch(fetchBalances());
|
||||
dispatch(updateTokensFilter(null, null, options));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::fetchTokens', error);
|
||||
@ -170,7 +200,7 @@ export function fetchTokens (_tokenIds) {
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBalances (_addresses) {
|
||||
export function fetchBalances (_addresses, skipNotifications = false) {
|
||||
return (dispatch, getState) => {
|
||||
const { api, personal } = getState();
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
@ -192,8 +222,7 @@ export function fetchBalances (_addresses) {
|
||||
balances[addr] = accountsBalances[idx];
|
||||
});
|
||||
|
||||
dispatch(setBalances(balances));
|
||||
updateTokensFilter(addresses)(dispatch, getState);
|
||||
dispatch(setBalances(balances, skipNotifications));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::fetchBalances', error);
|
||||
@ -201,7 +230,7 @@ export function fetchBalances (_addresses) {
|
||||
};
|
||||
}
|
||||
|
||||
export function updateTokensFilter (_addresses, _tokens) {
|
||||
export function updateTokensFilter (_addresses, _tokens, options = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const { api, balances, personal } = getState();
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
@ -214,27 +243,32 @@ export function updateTokensFilter (_addresses, _tokens) {
|
||||
const tokenAddresses = tokens.map((t) => t.address).sort();
|
||||
|
||||
if (tokensFilter.filterFromId || tokensFilter.filterToId) {
|
||||
// Has the tokens addresses changed (eg. a network change)
|
||||
const sameTokens = isEqual(tokenAddresses, tokensFilter.tokenAddresses);
|
||||
const sameAddresses = isEqual(addresses, tokensFilter.addresses);
|
||||
|
||||
if (sameTokens && sameAddresses) {
|
||||
// Addresses that are not in the current filter (omit those
|
||||
// that the filter includes)
|
||||
const newAddresses = addresses.filter((address) => !tokensFilter.addresses.includes(address));
|
||||
|
||||
// If no new addresses and the same tokens, don't change the filter
|
||||
if (sameTokens && newAddresses.length === 0) {
|
||||
log.debug('no need to update token filter', addresses, tokenAddresses, tokensFilter);
|
||||
return queryTokensFilter(tokensFilter)(dispatch, getState);
|
||||
}
|
||||
}
|
||||
|
||||
let promise = Promise.resolve();
|
||||
log.debug('updating the token filter', addresses, tokenAddresses);
|
||||
const promises = [];
|
||||
|
||||
if (tokensFilter.filterFromId) {
|
||||
promise = promise.then(() => api.eth.uninstallFilter(tokensFilter.filterFromId));
|
||||
promises.push(api.eth.uninstallFilter(tokensFilter.filterFromId));
|
||||
}
|
||||
|
||||
if (tokensFilter.filterToId) {
|
||||
promise = promise.then(() => api.eth.uninstallFilter(tokensFilter.filterToId));
|
||||
promises.push(api.eth.uninstallFilter(tokensFilter.filterToId));
|
||||
}
|
||||
|
||||
if (tokenAddresses.length === 0 || addresses.length === 0) {
|
||||
return promise;
|
||||
}
|
||||
const promise = Promise.all(promises);
|
||||
|
||||
const TRANSFER_SIGNATURE = api.util.sha3('Transfer(address,address,uint256)');
|
||||
const topicsFrom = [ TRANSFER_SIGNATURE, addresses, null ];
|
||||
@ -269,8 +303,10 @@ export function updateTokensFilter (_addresses, _tokens) {
|
||||
addresses, tokenAddresses
|
||||
};
|
||||
|
||||
const { skipNotifications } = options;
|
||||
|
||||
dispatch(setTokensFilter(nextTokensFilter));
|
||||
fetchTokensBalances(addresses, tokens)(dispatch, getState);
|
||||
fetchTokensBalances(addresses, tokens, skipNotifications)(dispatch, getState);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::updateTokensFilter', error);
|
||||
@ -326,7 +362,7 @@ export function queryTokensFilter (tokensFilter) {
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchTokensBalances (_addresses = null, _tokens = null) {
|
||||
export function fetchTokensBalances (_addresses = null, _tokens = null, skipNotifications = false) {
|
||||
return (dispatch, getState) => {
|
||||
const { api, personal, balances } = getState();
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
@ -348,7 +384,7 @@ export function fetchTokensBalances (_addresses = null, _tokens = null) {
|
||||
balances[addr] = tokensBalances[idx];
|
||||
});
|
||||
|
||||
dispatch(setBalances(balances));
|
||||
dispatch(setBalances(balances, skipNotifications));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('balances::fetchTokensBalances', error);
|
||||
|
@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BalancesProvider from './balances';
|
||||
import { showSnackbar } from './snackbarActions';
|
||||
import { DEFAULT_NETCHAIN } from './statusReducer';
|
||||
|
||||
@ -29,6 +30,11 @@ export default class ChainMiddleware {
|
||||
|
||||
if (newChain !== nodeStatus.netChain && nodeStatus.netChain !== DEFAULT_NETCHAIN) {
|
||||
store.dispatch(showSnackbar(`Switched to ${newChain}. Please reload the page.`, 60000));
|
||||
|
||||
// Fetch the new balances without notifying the user of any change
|
||||
BalancesProvider.get(store).fetchAllBalances({
|
||||
changedNetwork: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,13 +16,18 @@
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Contracts from '~/contracts';
|
||||
import { initialState as defaultNodeStatusState } from './statusReducer';
|
||||
import ChainMiddleware from './chainMiddleware';
|
||||
import { createWsApi } from '~/../test/e2e/ethapi';
|
||||
|
||||
let middleware;
|
||||
let next;
|
||||
let store;
|
||||
|
||||
const api = createWsApi();
|
||||
Contracts.create(api);
|
||||
|
||||
function createMiddleware (collection = {}) {
|
||||
middleware = new ChainMiddleware().toMiddleware();
|
||||
next = sinon.stub();
|
||||
@ -30,6 +35,7 @@ function createMiddleware (collection = {}) {
|
||||
dispatch: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
api: api,
|
||||
nodeStatus: Object.assign({}, defaultNodeStatusState, collection)
|
||||
};
|
||||
}
|
||||
|
@ -16,7 +16,8 @@
|
||||
|
||||
import { isEqual, intersection } from 'lodash';
|
||||
|
||||
import { fetchBalances } from './balancesActions';
|
||||
import BalancesProvider from './balances';
|
||||
import { updateTokensFilter } from './balancesActions';
|
||||
import { attachWallets } from './walletActions';
|
||||
|
||||
import Contract from '~/api/contract';
|
||||
@ -75,6 +76,9 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
return wallet;
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
return [];
|
||||
})
|
||||
.then((_wallets) => {
|
||||
_wallets.forEach((wallet) => {
|
||||
const owners = wallet.owners.map((o) => o.address);
|
||||
@ -95,7 +99,14 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
|
||||
dispatch(_personalAccountsInfo(data));
|
||||
dispatch(attachWallets(wallets));
|
||||
dispatch(fetchBalances());
|
||||
|
||||
BalancesProvider.get().fetchAllBalances({
|
||||
force: true
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('personalAccountsInfo', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -123,6 +134,18 @@ export function setVisibleAccounts (addresses) {
|
||||
}
|
||||
|
||||
dispatch(_setVisibleAccounts(addresses));
|
||||
dispatch(fetchBalances(addresses));
|
||||
|
||||
// Don't update the balances if no new addresses displayed
|
||||
if (addresses.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Tokens filter to take into account the new
|
||||
// addresses
|
||||
dispatch(updateTokensFilter());
|
||||
|
||||
BalancesProvider.get().fetchBalances({
|
||||
force: true
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -14,9 +14,15 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BalancesProvider from './balances';
|
||||
import { statusBlockNumber, statusCollection, statusLogs } from './statusActions';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { LOG_KEYS, getLogger } from '~/config';
|
||||
|
||||
const log = getLogger(LOG_KEYS.Signer);
|
||||
let instance = null;
|
||||
|
||||
export default class Status {
|
||||
constructor (store, api) {
|
||||
this._api = api;
|
||||
@ -27,20 +33,90 @@ export default class Status {
|
||||
this._longStatus = {};
|
||||
this._minerSettings = {};
|
||||
|
||||
this._longStatusTimeoutId = null;
|
||||
this._timeoutIds = {};
|
||||
this._blockNumberSubscriptionId = null;
|
||||
|
||||
this._timestamp = Date.now();
|
||||
|
||||
// On connecting, stop all subscriptions
|
||||
api.on('connecting', this.stop, this);
|
||||
|
||||
// On connected, start the subscriptions
|
||||
api.on('connected', this.start, this);
|
||||
|
||||
// On disconnected, stop all subscriptions
|
||||
api.on('disconnected', this.stop, this);
|
||||
|
||||
this.updateApiStatus();
|
||||
}
|
||||
|
||||
static instantiate (store, api) {
|
||||
if (!instance) {
|
||||
instance = new Status(store, api);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
start () {
|
||||
this._subscribeBlockNumber();
|
||||
this._pollStatus();
|
||||
this._pollLongStatus();
|
||||
this._pollLogs();
|
||||
log.debug('status::start');
|
||||
|
||||
Promise
|
||||
.all([
|
||||
this._subscribeBlockNumber(),
|
||||
|
||||
this._pollLogs(),
|
||||
this._pollLongStatus(),
|
||||
this._pollStatus()
|
||||
])
|
||||
.then(() => {
|
||||
return BalancesProvider.start();
|
||||
});
|
||||
}
|
||||
|
||||
_subscribeBlockNumber () {
|
||||
this._api
|
||||
stop () {
|
||||
log.debug('status::stop');
|
||||
|
||||
const promises = [];
|
||||
|
||||
if (this._blockNumberSubscriptionId) {
|
||||
const promise = this._api
|
||||
.unsubscribe(this._blockNumberSubscriptionId)
|
||||
.then(() => {
|
||||
this._blockNumberSubscriptionId = null;
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
Object.values(this._timeoutIds).forEach((timeoutId) => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
const promise = BalancesProvider.stop();
|
||||
promises.push(promise);
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
console.error('status::stop', error);
|
||||
return true;
|
||||
})
|
||||
.then(() => this.updateApiStatus());
|
||||
}
|
||||
|
||||
updateApiStatus () {
|
||||
const apiStatus = this.getApiStatus();
|
||||
log.debug('status::updateApiStatus', apiStatus);
|
||||
|
||||
if (!isEqual(apiStatus, this._apiStatus)) {
|
||||
this._store.dispatch(statusCollection(apiStatus));
|
||||
this._apiStatus = apiStatus;
|
||||
}
|
||||
}
|
||||
|
||||
_subscribeBlockNumber = () => {
|
||||
return this._api
|
||||
.subscribe('eth_blockNumber', (error, blockNumber) => {
|
||||
if (error) {
|
||||
return;
|
||||
@ -51,6 +127,10 @@ export default class Status {
|
||||
this._api.eth
|
||||
.getBlockByNumber(blockNumber)
|
||||
.then((block) => {
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._store.dispatch(statusCollection({
|
||||
blockTimestamp: block.timestamp,
|
||||
gasLimit: block.gasLimit
|
||||
@ -59,6 +139,9 @@ export default class Status {
|
||||
.catch((error) => {
|
||||
console.warn('status._subscribeBlockNumber', 'getBlockByNumber', error);
|
||||
});
|
||||
})
|
||||
.then((blockNumberSubscriptionId) => {
|
||||
this._blockNumberSubscriptionId = blockNumberSubscriptionId;
|
||||
});
|
||||
}
|
||||
|
||||
@ -72,11 +155,7 @@ export default class Status {
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
_pollStatus = () => {
|
||||
const nextTimeout = (timeout = 1000) => {
|
||||
setTimeout(() => this._pollStatus(), timeout);
|
||||
};
|
||||
|
||||
getApiStatus = () => {
|
||||
const { isConnected, isConnecting, needsToken, secureToken } = this._api;
|
||||
|
||||
const apiStatus = {
|
||||
@ -86,19 +165,23 @@ export default class Status {
|
||||
secureToken
|
||||
};
|
||||
|
||||
const gotConnected = !this._apiStatus.isConnected && apiStatus.isConnected;
|
||||
return apiStatus;
|
||||
}
|
||||
|
||||
if (gotConnected) {
|
||||
this._pollLongStatus();
|
||||
}
|
||||
_pollStatus = () => {
|
||||
const nextTimeout = (timeout = 1000) => {
|
||||
if (this._timeoutIds.status) {
|
||||
clearTimeout(this._timeoutIds.status);
|
||||
}
|
||||
|
||||
if (!isEqual(apiStatus, this._apiStatus)) {
|
||||
this._store.dispatch(statusCollection(apiStatus));
|
||||
this._apiStatus = apiStatus;
|
||||
}
|
||||
this._timeoutIds.status = setTimeout(() => this._pollStatus(), timeout);
|
||||
};
|
||||
|
||||
if (!isConnected) {
|
||||
return nextTimeout(250);
|
||||
this.updateApiStatus();
|
||||
|
||||
if (!this._api.isConnected) {
|
||||
nextTimeout(250);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const { refreshStatus } = this._store.getState().nodeStatus;
|
||||
@ -110,7 +193,7 @@ export default class Status {
|
||||
statusPromises.push(this._api.eth.hashrate());
|
||||
}
|
||||
|
||||
Promise
|
||||
return Promise
|
||||
.all(statusPromises)
|
||||
.then(([ syncing, ...statusResults ]) => {
|
||||
const status = statusResults.length === 0
|
||||
@ -125,11 +208,11 @@ export default class Status {
|
||||
this._store.dispatch(statusCollection(status));
|
||||
this._status = status;
|
||||
}
|
||||
|
||||
nextTimeout();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('_pollStatus', error);
|
||||
})
|
||||
.then(() => {
|
||||
nextTimeout();
|
||||
});
|
||||
}
|
||||
@ -140,7 +223,7 @@ export default class Status {
|
||||
* from the UI
|
||||
*/
|
||||
_pollMinerSettings = () => {
|
||||
Promise
|
||||
return Promise
|
||||
.all([
|
||||
this._api.eth.coinbase(),
|
||||
this._api.parity.extraData(),
|
||||
@ -175,21 +258,21 @@ export default class Status {
|
||||
*/
|
||||
_pollLongStatus = () => {
|
||||
if (!this._api.isConnected) {
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const nextTimeout = (timeout = 30000) => {
|
||||
if (this._longStatusTimeoutId) {
|
||||
clearTimeout(this._longStatusTimeoutId);
|
||||
if (this._timeoutIds.longStatus) {
|
||||
clearTimeout(this._timeoutIds.longStatus);
|
||||
}
|
||||
|
||||
this._longStatusTimeoutId = setTimeout(this._pollLongStatus, timeout);
|
||||
this._timeoutIds.longStatus = setTimeout(() => this._pollLongStatus(), timeout);
|
||||
};
|
||||
|
||||
// Poll Miner settings just in case
|
||||
this._pollMinerSettings();
|
||||
const minerPromise = this._pollMinerSettings();
|
||||
|
||||
Promise
|
||||
const mainPromise = Promise
|
||||
.all([
|
||||
this._api.parity.netPeers(),
|
||||
this._api.web3.clientVersion(),
|
||||
@ -225,21 +308,31 @@ export default class Status {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('_pollLongStatus', error);
|
||||
})
|
||||
.then(() => {
|
||||
nextTimeout(60000);
|
||||
});
|
||||
|
||||
nextTimeout(60000);
|
||||
return Promise.all([ minerPromise, mainPromise ]);
|
||||
}
|
||||
|
||||
_pollLogs = () => {
|
||||
const nextTimeout = (timeout = 1000) => setTimeout(this._pollLogs, timeout);
|
||||
const nextTimeout = (timeout = 1000) => {
|
||||
if (this._timeoutIds.logs) {
|
||||
clearTimeout(this._timeoutIds.logs);
|
||||
}
|
||||
|
||||
this._timeoutIds.logs = setTimeout(this._pollLogs, timeout);
|
||||
};
|
||||
|
||||
const { devLogsEnabled } = this._store.getState().nodeStatus;
|
||||
|
||||
if (!devLogsEnabled) {
|
||||
nextTimeout();
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
Promise
|
||||
return Promise
|
||||
.all([
|
||||
this._api.parity.devLogs(),
|
||||
this._api.parity.devLogsLevels()
|
||||
@ -249,11 +342,12 @@ export default class Status {
|
||||
devLogs: devLogs.slice(-1024),
|
||||
devLogsLevels
|
||||
}));
|
||||
nextTimeout();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('_pollLogs', error);
|
||||
nextTimeout();
|
||||
})
|
||||
.then(() => {
|
||||
return nextTimeout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -38,10 +38,10 @@ export default function (api, browserHistory) {
|
||||
const middleware = initMiddleware(api, browserHistory);
|
||||
const store = applyMiddleware(...middleware)(storeCreation)(reducers);
|
||||
|
||||
new BalancesProvider(store, api).start();
|
||||
BalancesProvider.instantiate(store, api);
|
||||
StatusProvider.instantiate(store, api);
|
||||
new PersonalProvider(store, api).start();
|
||||
new SignerProvider(store, api).start();
|
||||
new StatusProvider(store, api).start();
|
||||
|
||||
store.dispatch(loadWallet(api));
|
||||
setupWorker(store);
|
||||
|
@ -17,133 +17,34 @@
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
import Api from './api';
|
||||
import { LOG_KEYS, getLogger } from '~/config';
|
||||
|
||||
const log = getLogger(LOG_KEYS.Signer);
|
||||
const sysuiToken = window.localStorage.getItem('sysuiToken');
|
||||
|
||||
export default class SecureApi extends Api {
|
||||
_isConnecting = false;
|
||||
_needsToken = false;
|
||||
_tokens = [];
|
||||
|
||||
_dappsInterface = null;
|
||||
_dappsPort = 8080;
|
||||
_signerPort = 8180;
|
||||
|
||||
constructor (url, nextToken) {
|
||||
super(new Api.Transport.Ws(url, sysuiToken, false));
|
||||
const transport = new Api.Transport.Ws(url, sysuiToken, false);
|
||||
super(transport);
|
||||
|
||||
this._url = url;
|
||||
this._isConnecting = true;
|
||||
this._needsToken = false;
|
||||
|
||||
this._dappsPort = 8080;
|
||||
this._dappsInterface = null;
|
||||
this._signerPort = 8180;
|
||||
|
||||
// Try tokens from localstorage, then from hash
|
||||
// Try tokens from localstorage, from hash and 'initial'
|
||||
this._tokens = uniq([sysuiToken, nextToken, 'initial'])
|
||||
.filter((token) => token)
|
||||
.map((token) => ({ value: token, tried: false }));
|
||||
|
||||
this._tryNextToken();
|
||||
}
|
||||
|
||||
saveToken = () => {
|
||||
window.localStorage.setItem('sysuiToken', this._transport.token);
|
||||
// DEBUG: console.log('SecureApi:saveToken', this._transport.token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise that gets resolved with
|
||||
* a boolean: `true` if the node is up, `false`
|
||||
* otherwise
|
||||
*/
|
||||
_checkNodeUp () {
|
||||
const url = this._url.replace(/wss?/, 'http');
|
||||
return fetch(url, { method: 'HEAD' })
|
||||
.then(
|
||||
(r) => r.status === 200,
|
||||
() => false
|
||||
)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
_setManual () {
|
||||
this._needsToken = true;
|
||||
this._isConnecting = false;
|
||||
}
|
||||
|
||||
_tryNextToken () {
|
||||
const nextTokenIndex = this._tokens.findIndex((t) => !t.tried);
|
||||
|
||||
if (nextTokenIndex < 0) {
|
||||
return this._setManual();
|
||||
}
|
||||
|
||||
const nextToken = this._tokens[nextTokenIndex];
|
||||
nextToken.tried = true;
|
||||
|
||||
this.updateToken(nextToken.value);
|
||||
}
|
||||
|
||||
_followConnection = () => {
|
||||
const token = this.transport.token;
|
||||
|
||||
return this
|
||||
.transport
|
||||
.connect()
|
||||
.then(() => {
|
||||
if (token === 'initial') {
|
||||
return this.signer
|
||||
.generateAuthorizationToken()
|
||||
.then((token) => {
|
||||
return this.updateToken(token);
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
}
|
||||
|
||||
this.connectSuccess();
|
||||
return true;
|
||||
})
|
||||
.catch((e) => {
|
||||
this
|
||||
._checkNodeUp()
|
||||
.then((isNodeUp) => {
|
||||
// Try again in a few...
|
||||
if (!isNodeUp) {
|
||||
this._isConnecting = false;
|
||||
const timeout = this.transport.retryTimeout;
|
||||
|
||||
window.setTimeout(() => {
|
||||
this._followConnection();
|
||||
}, timeout);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._tryNextToken();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
connectSuccess () {
|
||||
this._isConnecting = false;
|
||||
this._needsToken = false;
|
||||
|
||||
this.saveToken();
|
||||
|
||||
Promise
|
||||
.all([
|
||||
this.parity.dappsPort(),
|
||||
this.parity.dappsInterface(),
|
||||
this.parity.signerPort()
|
||||
])
|
||||
.then(([dappsPort, dappsInterface, signerPort]) => {
|
||||
this._dappsPort = dappsPort.toNumber();
|
||||
this._dappsInterface = dappsInterface;
|
||||
this._signerPort = signerPort.toNumber();
|
||||
});
|
||||
|
||||
// DEBUG: console.log('SecureApi:connectSuccess', this._transport.token);
|
||||
}
|
||||
|
||||
updateToken (token) {
|
||||
this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, ''), false);
|
||||
return this._followConnection();
|
||||
// DEBUG: console.log('SecureApi:updateToken', this._transport.token, connectState);
|
||||
// When the transport is closed, try to reconnect
|
||||
transport.on('close', this.connect, this);
|
||||
this.connect();
|
||||
}
|
||||
|
||||
get dappsPort () {
|
||||
@ -151,17 +52,19 @@ export default class SecureApi extends Api {
|
||||
}
|
||||
|
||||
get dappsUrl () {
|
||||
let hostname;
|
||||
return `http://${this.hostname}:${this.dappsPort}`;
|
||||
}
|
||||
|
||||
get hostname () {
|
||||
if (window.location.hostname === 'home.parity') {
|
||||
hostname = 'dapps.parity';
|
||||
} else if (!this._dappsInterface || this._dappsInterface === '0.0.0.0') {
|
||||
hostname = window.location.hostname;
|
||||
} else {
|
||||
hostname = this._dappsInterface;
|
||||
return 'dapps.parity';
|
||||
}
|
||||
|
||||
return `http://${hostname}:${this._dappsPort}`;
|
||||
if (!this._dappsInterface || this._dappsInterface === '0.0.0.0') {
|
||||
return window.location.hostname;
|
||||
}
|
||||
|
||||
return this._dappsInterface;
|
||||
}
|
||||
|
||||
get signerPort () {
|
||||
@ -183,4 +86,279 @@ export default class SecureApi extends Api {
|
||||
get secureToken () {
|
||||
return this._transport.token;
|
||||
}
|
||||
|
||||
connect () {
|
||||
if (this._isConnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug('trying to connect...');
|
||||
|
||||
this._isConnecting = true;
|
||||
|
||||
this.emit('connecting');
|
||||
|
||||
// Reset the tested Tokens
|
||||
this._resetTokens();
|
||||
|
||||
// Try to connect
|
||||
return this._connect()
|
||||
.then((connected) => {
|
||||
this._isConnecting = false;
|
||||
|
||||
if (connected) {
|
||||
const token = this.secureToken;
|
||||
log.debug('got connected ; saving token', token);
|
||||
|
||||
// Save the sucessful token
|
||||
this._saveToken(token);
|
||||
this._needsToken = false;
|
||||
|
||||
// Emit the connected event
|
||||
return this.emit('connected');
|
||||
}
|
||||
|
||||
// If not connected, we need a new token
|
||||
log.debug('needs a token');
|
||||
this._needsToken = true;
|
||||
|
||||
return this.emit('disconnected');
|
||||
})
|
||||
.catch((error) => {
|
||||
this._isConnecting = false;
|
||||
|
||||
log.debug('emitting "disconnected"');
|
||||
this.emit('disconnected');
|
||||
console.error('unhandled error in secureApi', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise that gets resolved with
|
||||
* a boolean: `true` if the node is up, `false`
|
||||
* otherwise (HEAD request to the Node)
|
||||
*/
|
||||
isNodeUp () {
|
||||
const url = this._url.replace(/wss?/, 'http');
|
||||
return fetch(url, { method: 'HEAD' })
|
||||
.then(
|
||||
(r) => r.status === 200,
|
||||
() => false
|
||||
)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given token, ie. add it to the token
|
||||
* list, and then try to connect (if not already connecting)
|
||||
*/
|
||||
updateToken (_token) {
|
||||
const token = this._sanitiseToken(_token);
|
||||
log.debug('updating token', token);
|
||||
|
||||
// Update the tokens list: put the new one on first position
|
||||
this._tokens = [ { value: token, tried: false } ].concat(this._tokens);
|
||||
|
||||
// Try to connect with the new token added
|
||||
return this.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to connect to the Node with the next Token in
|
||||
* the list
|
||||
*/
|
||||
_connect () {
|
||||
log.debug('trying next token');
|
||||
|
||||
// Get the first not-tried token
|
||||
const nextToken = this._getNextToken();
|
||||
|
||||
// If no more tokens to try, user has to enter a new one
|
||||
if (!nextToken) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
nextToken.tried = true;
|
||||
|
||||
return this._connectWithToken(nextToken.value)
|
||||
.then((validToken) => {
|
||||
// If not valid, try again with the next token in the list
|
||||
if (!validToken) {
|
||||
return this._connect();
|
||||
}
|
||||
|
||||
// If correct and valid token, wait until the Node is ready
|
||||
// and resolve as connected
|
||||
return this._waitUntilNodeReady()
|
||||
.then(() => this._fetchSettings())
|
||||
.then(() => true);
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error('unkown error in _connect', error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect with the given token.
|
||||
* It returns a Promise that gets resolved
|
||||
* with `validToken` as argument, whether the given token
|
||||
* is valid or not
|
||||
*/
|
||||
_connectWithToken (_token) {
|
||||
// Sanitize the token first
|
||||
const token = this._sanitiseToken(_token);
|
||||
|
||||
// Update the token in the transport layer
|
||||
this.transport.updateToken(token, false);
|
||||
log.debug('connecting with token', token);
|
||||
|
||||
return this.transport.connect()
|
||||
.then(() => {
|
||||
log.debug('connected with', token);
|
||||
|
||||
if (token === 'initial') {
|
||||
return this._generateAuthorizationToken();
|
||||
}
|
||||
|
||||
// The token is valid !
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
// Log if it's not a close error (ie. wrong token)
|
||||
if (error && error.type !== 'close') {
|
||||
log.debug('did not connect ; error', error);
|
||||
}
|
||||
|
||||
// Check if the Node is up
|
||||
return this.isNodeUp()
|
||||
.then((isNodeUp) => {
|
||||
// If it's not up, try again in a few...
|
||||
if (!isNodeUp) {
|
||||
const timeout = this.transport.retryTimeout;
|
||||
|
||||
log.debug('node is not up ; will try again in', timeout, 'ms');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
window.setTimeout(() => {
|
||||
this._connectWithToken(token).then(resolve).catch(reject);
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
// The token is invalid
|
||||
log.debug('tried with a wrong token', token);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the correct ports from the Node
|
||||
*/
|
||||
_fetchSettings () {
|
||||
return Promise
|
||||
.all([
|
||||
this.parity.dappsPort(),
|
||||
this.parity.dappsInterface(),
|
||||
this.parity.signerPort()
|
||||
])
|
||||
.then(([dappsPort, dappsInterface, signerPort]) => {
|
||||
this._dappsPort = dappsPort.toNumber();
|
||||
this._dappsInterface = dappsInterface;
|
||||
this._signerPort = signerPort.toNumber();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to generate an Authorization Token.
|
||||
* Then try to connect with the new token.
|
||||
*/
|
||||
_generateAuthorizationToken () {
|
||||
return this.signer
|
||||
.generateAuthorizationToken()
|
||||
.then((token) => this._connectWithToken(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next token to try, if any left
|
||||
*/
|
||||
_getNextToken () {
|
||||
// Get the first not-tried token
|
||||
const nextTokenIndex = this._tokens.findIndex((t) => !t.tried);
|
||||
|
||||
// If no more tokens to try, user has to enter a new one
|
||||
if (nextTokenIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextToken = this._tokens[nextTokenIndex];
|
||||
return nextToken;
|
||||
}
|
||||
|
||||
_resetTokens () {
|
||||
this._tokens = this._tokens.map((token) => ({
|
||||
...token,
|
||||
tried: false
|
||||
}));
|
||||
}
|
||||
|
||||
_sanitiseToken (token) {
|
||||
return token.replace(/[^a-zA-Z0-9]/g, '');
|
||||
}
|
||||
|
||||
_saveToken (token) {
|
||||
window.localStorage.setItem('sysuiToken', token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise gets resolved when the node is up
|
||||
* and running (it might take some time before
|
||||
* the node is actually ready even when the client
|
||||
* is connected).
|
||||
*
|
||||
* We check that the `parity_enode` RPC calls
|
||||
* returns successfully
|
||||
*/
|
||||
_waitUntilNodeReady (_timeleft) {
|
||||
// Default timeout to 30 seconds
|
||||
const timeleft = Number.isFinite(_timeleft)
|
||||
? _timeleft
|
||||
: 30 * 1000;
|
||||
|
||||
// After timeout, just resolve the promise...
|
||||
if (timeleft <= 0) {
|
||||
console.warn('node is still not ready after 30 seconds...');
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
return this
|
||||
.parity.enode()
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
if (!error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (error.type !== 'NETWORK_DISABLED') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Timeout between 250ms and 750ms
|
||||
const timeout = Math.floor(250 + (500 * Math.random()));
|
||||
|
||||
log.debug('waiting until node is ready', 'retry in', timeout, 'ms');
|
||||
|
||||
// Retry in a few...
|
||||
return new Promise((resolve, reject) => {
|
||||
window.setTimeout(() => {
|
||||
const duration = Date.now() - start;
|
||||
|
||||
this._waitUntilNodeReady(timeleft - duration).then(resolve).catch(reject);
|
||||
}, timeout);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import unknownImage from '../../../assets/images/contracts/unknown-64x64.png';
|
||||
import styles from './balance.css';
|
||||
@ -107,11 +106,7 @@ function mapStateToProps (state) {
|
||||
return { images };
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(Balance);
|
||||
|
@ -17,7 +17,6 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import styles from './blockStatus.css';
|
||||
|
||||
@ -113,11 +112,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(BlockStatus);
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import util from '~/api/util';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
@ -58,7 +57,7 @@ class InputAddress extends Component {
|
||||
|
||||
render () {
|
||||
const { accountsInfo, allowCopy, className, disabled, error, focused, hint } = this.props;
|
||||
const { hideUnderline, label, onClick, onFocus, onSubmit, readOnly, small } = this.props;
|
||||
const { hideUnderline, label, onClick, onFocus, readOnly, small } = this.props;
|
||||
const { tabIndex, text, tokens, value } = this.props;
|
||||
|
||||
const account = value && (accountsInfo[value] || tokens[value]);
|
||||
@ -91,10 +90,10 @@ class InputAddress extends Component {
|
||||
hideUnderline={ hideUnderline }
|
||||
hint={ hint }
|
||||
label={ label }
|
||||
onChange={ this.handleInputChange }
|
||||
onChange={ this.onChange }
|
||||
onClick={ onClick }
|
||||
onFocus={ onFocus }
|
||||
onSubmit={ onSubmit }
|
||||
onSubmit={ this.onSubmit }
|
||||
readOnly={ readOnly }
|
||||
tabIndex={ tabIndex }
|
||||
value={
|
||||
@ -133,22 +132,34 @@ class InputAddress extends Component {
|
||||
return (
|
||||
<div className={ classes.join(' ') }>
|
||||
<IdentityIcon
|
||||
inline center
|
||||
address={ value } />
|
||||
address={ value }
|
||||
center
|
||||
inline />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleInputChange = (event, value) => {
|
||||
const isEmpty = (value.length === 0);
|
||||
onChange = (event, _value) => {
|
||||
let address = _value.trim();
|
||||
const isEmpty = (address.length === 0);
|
||||
|
||||
this.setState({ isEmpty });
|
||||
|
||||
if (!/^0x/.test(value) && util.isAddressValid(`0x${value}`)) {
|
||||
return this.props.onChange(event, `0x${value}`);
|
||||
}
|
||||
if (this.props.onChange) {
|
||||
if (!/^0x/.test(address) && util.isAddressValid(`0x${address}`)) {
|
||||
address = `0x${address}`;
|
||||
}
|
||||
|
||||
this.props.onChange(event, value);
|
||||
this.props.onChange(event, address);
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit = (_value) => {
|
||||
const address = _value.trim();
|
||||
|
||||
if (this.props.onSubmit) {
|
||||
this.props.onSubmit(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,11 +173,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(InputAddress);
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import AddressSelect from '../AddressSelect';
|
||||
|
||||
@ -68,11 +67,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(InputAddressSelect);
|
||||
|
@ -62,6 +62,10 @@ export default class GasPriceEditor {
|
||||
this.errorTotal = errorTotal;
|
||||
}
|
||||
|
||||
@action setEstimatedError = (errorEstimated = ERRORS.gasException) => {
|
||||
this.errorEstimated = errorEstimated;
|
||||
}
|
||||
|
||||
@action setEstimated = (estimated) => {
|
||||
transaction(() => {
|
||||
const bn = new BigNumber(estimated);
|
||||
@ -69,11 +73,11 @@ export default class GasPriceEditor {
|
||||
this.estimated = estimated;
|
||||
|
||||
if (bn.gte(MAX_GAS_ESTIMATION)) {
|
||||
this.errorEstimated = ERRORS.gasException;
|
||||
this.setEstimatedError(ERRORS.gasException);
|
||||
} else if (bn.gte(this.gasLimit)) {
|
||||
this.errorEstimated = ERRORS.gasBlockLimit;
|
||||
this.setEstimatedError(ERRORS.gasBlockLimit);
|
||||
} else {
|
||||
this.errorEstimated = null;
|
||||
this.setEstimatedError(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -82,6 +82,24 @@ describe('ui/GasPriceEditor/store', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEstimatedError', () => {
|
||||
it('sets the value as provided', () => {
|
||||
store.setEstimatedError('errorTest');
|
||||
expect(store.errorEstimated).to.equal('errorTest');
|
||||
});
|
||||
|
||||
it('sets the null value as provided', () => {
|
||||
store.setEstimatedError('errorTest');
|
||||
store.setEstimatedError(null);
|
||||
expect(store.errorEstimated).to.be.null;
|
||||
});
|
||||
|
||||
it('sets a default error when none provided', () => {
|
||||
store.setEstimatedError();
|
||||
expect(store.errorEstimated).to.equal(ERRORS.gasException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEstimated', () => {
|
||||
it('sets the value', () => {
|
||||
store.setEstimated('789');
|
||||
|
@ -377,11 +377,15 @@ class MethodDecoding extends Component {
|
||||
}
|
||||
|
||||
const inputs = methodInputs.map((input, index) => {
|
||||
const label = input.name
|
||||
? `${input.name}: ${input.type}`
|
||||
: input.type;
|
||||
|
||||
return (
|
||||
<TypedInput
|
||||
allowCopy
|
||||
className={ styles.input }
|
||||
label={ input.type }
|
||||
label={ label }
|
||||
key={ index }
|
||||
param={ input.type }
|
||||
readOnly
|
||||
|
@ -164,8 +164,8 @@ export default class MethodDecodingStore {
|
||||
methodInputs = this.api.util
|
||||
.decodeMethodInput(abi, paramdata)
|
||||
.map((value, index) => {
|
||||
const type = abi.inputs[index].type;
|
||||
return { type, value };
|
||||
const { name, type } = abi.inputs[index];
|
||||
return { name, type, value };
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -16,9 +16,8 @@
|
||||
|
||||
import { Dialog } from 'material-ui';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
@ -113,11 +112,7 @@ function mapStateToProps (state) {
|
||||
return { settings };
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(Modal);
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase
|
||||
import ActionFingerprint from 'material-ui/svg-icons/action/fingerprint';
|
||||
|
||||
@ -54,11 +53,7 @@ function mapStateToProps (state) {
|
||||
return { secureToken };
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(SignerIcon);
|
||||
|
@ -15,11 +15,10 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { LinearProgress } from 'material-ui';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { LinearProgress } from 'material-ui';
|
||||
|
||||
import { txLink } from '~/3rdparty/etherscan/links';
|
||||
import ShortenedHash from '../ShortenedHash';
|
||||
@ -169,11 +168,7 @@ function mapStateToProps (state) {
|
||||
return { isTest };
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(TxHash);
|
||||
|
@ -14,10 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import Store from './store';
|
||||
import TxRow from './TxRow';
|
||||
@ -92,11 +91,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(TxList);
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
import Push from 'push.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import { fromWei } from '~/api/util/wei';
|
||||
|
||||
@ -33,13 +32,34 @@ export function notifyTransaction (account, token, _value, onClick) {
|
||||
? ethereumIcon
|
||||
: (token.image || unkownIcon);
|
||||
|
||||
Push.create(`${name}`, {
|
||||
body: `You just received ${value.toFormat()} ${token.tag.toUpperCase()}`,
|
||||
icon: {
|
||||
x16: icon,
|
||||
x32: icon
|
||||
},
|
||||
timeout: 20000,
|
||||
onClick: onClick || noop
|
||||
});
|
||||
let _notification = null;
|
||||
|
||||
Push
|
||||
.create(`${name}`, {
|
||||
body: `You just received ${value.toFormat(3)} ${token.tag.toUpperCase()}`,
|
||||
icon: {
|
||||
x16: icon,
|
||||
x32: icon
|
||||
},
|
||||
timeout: 20000,
|
||||
onClick: () => {
|
||||
// Focus on the UI
|
||||
try {
|
||||
window.focus();
|
||||
} catch (e) {}
|
||||
|
||||
if (onClick && typeof onClick === 'function') {
|
||||
onClick();
|
||||
}
|
||||
|
||||
// Close the notification
|
||||
if (_notification) {
|
||||
_notification.close();
|
||||
_notification = null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((notification) => {
|
||||
_notification = notification;
|
||||
});
|
||||
}
|
||||
|
115
js/src/util/subscribe-to-events.spec.js
Normal file
115
js/src/util/subscribe-to-events.spec.js
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity 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 General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { spy, stub } from 'sinon';
|
||||
|
||||
import subscribeToEvents from './subscribe-to-events';
|
||||
import {
|
||||
pastLogs, liveLogs, createApi, createContract
|
||||
} from './subscribe-to-events.test.js';
|
||||
|
||||
const delay = (t) => new Promise((resolve) => {
|
||||
setTimeout(resolve, t);
|
||||
});
|
||||
|
||||
describe('util/subscribe-to-events', () => {
|
||||
beforeEach(function () {
|
||||
this.api = createApi();
|
||||
this.contract = createContract(this.api);
|
||||
});
|
||||
|
||||
it('installs a filter', async function () {
|
||||
const { api, contract } = this;
|
||||
|
||||
subscribeToEvents(contract, [ 'Foo', 'Bar' ]);
|
||||
await delay(0);
|
||||
|
||||
expect(api.eth.newFilter.calledOnce).to.equal(true);
|
||||
expect(api.eth.newFilter.firstCall.args).to.eql([ {
|
||||
fromBlock: 0, toBlock: 'latest',
|
||||
address: contract.address,
|
||||
topics: [ [
|
||||
contract.instance.Foo.signature,
|
||||
contract.instance.Bar.signature
|
||||
] ]
|
||||
} ]);
|
||||
});
|
||||
|
||||
it('queries & parses logs in the beginning', async function () {
|
||||
const { api, contract } = this;
|
||||
|
||||
subscribeToEvents(contract, [ 'Foo', 'Bar' ]);
|
||||
|
||||
await delay(0);
|
||||
expect(api.eth.getFilterLogs.callCount).to.equal(1);
|
||||
expect(api.eth.getFilterLogs.firstCall.args).to.eql([ 123 ]);
|
||||
|
||||
await delay(0);
|
||||
expect(contract.parseEventLogs.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('emits logs in the beginning', async function () {
|
||||
const { contract } = this;
|
||||
|
||||
const onLog = spy();
|
||||
const onFoo = spy();
|
||||
const onBar = spy();
|
||||
subscribeToEvents(contract, [ 'Foo', 'Bar' ])
|
||||
.on('log', onLog)
|
||||
.on('Foo', onFoo)
|
||||
.on('Bar', onBar);
|
||||
|
||||
await delay(0);
|
||||
|
||||
expect(onLog.callCount).to.equal(2);
|
||||
expect(onLog.firstCall.args).to.eql([ pastLogs[0] ]);
|
||||
expect(onLog.secondCall.args).to.eql([ pastLogs[1] ]);
|
||||
expect(onFoo.callCount).to.equal(1);
|
||||
expect(onFoo.firstCall.args).to.eql([ pastLogs[0] ]);
|
||||
expect(onBar.callCount).to.equal(1);
|
||||
expect(onBar.firstCall.args).to.eql([ pastLogs[1] ]);
|
||||
});
|
||||
|
||||
it('uninstalls the filter on sunsubscribe', async function () {
|
||||
const { api, contract } = this;
|
||||
|
||||
const s = subscribeToEvents(contract, [ 'Foo', 'Bar' ]);
|
||||
await delay(0);
|
||||
s.unsubscribe();
|
||||
await delay(0);
|
||||
|
||||
expect(api.eth.uninstallFilter.calledOnce).to.equal(true);
|
||||
expect(api.eth.uninstallFilter.firstCall.args).to.eql([ 123 ]);
|
||||
});
|
||||
|
||||
it.skip('checks for new events regularly', async function () {
|
||||
const { api, contract } = this;
|
||||
api.eth.getFilterLogs = stub().resolves([]);
|
||||
|
||||
const onLog = spy();
|
||||
const onBar = spy();
|
||||
const s = subscribeToEvents(contract, [ 'Bar' ], { interval: 5 })
|
||||
.on('log', onLog)
|
||||
.on('Bar', onBar);
|
||||
await delay(9);
|
||||
s.unsubscribe();
|
||||
|
||||
expect(onLog.callCount).to.equal(1);
|
||||
expect(onLog.firstCall.args).to.eql([ liveLogs[0] ]);
|
||||
expect(onBar.callCount).to.equal(1);
|
||||
expect(onBar.firstCall.args).to.eql([ liveLogs[0] ]);
|
||||
});
|
||||
});
|
53
js/src/util/subscribe-to-events.test.js
Normal file
53
js/src/util/subscribe-to-events.test.js
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity 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 General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { stub } from 'sinon';
|
||||
|
||||
export const ADDRESS = '0x1111111111111111111111111111111111111111';
|
||||
|
||||
export const pastLogs = [
|
||||
{ event: 'Foo', type: 'mined', address: ADDRESS, params: {} },
|
||||
{ event: 'Bar', type: 'mined', address: ADDRESS, params: {} }
|
||||
];
|
||||
|
||||
export const liveLogs = [
|
||||
{ event: 'Bar', type: 'mined', address: ADDRESS, params: { foo: 'bar' } }
|
||||
];
|
||||
|
||||
export const createApi = () => ({
|
||||
eth: {
|
||||
newFilter: stub().resolves(123),
|
||||
uninstallFilter: stub()
|
||||
.rejects(new Error('unknown filter id'))
|
||||
.withArgs(123).resolves(null),
|
||||
getFilterLogs: stub()
|
||||
.rejects(new Error('unknown filter id'))
|
||||
.withArgs(123).resolves(pastLogs),
|
||||
getFilterChanges: stub()
|
||||
.rejects(new Error('unknown filter id'))
|
||||
.withArgs(123).resolves(liveLogs)
|
||||
}
|
||||
});
|
||||
|
||||
export const createContract = (api) => ({
|
||||
api,
|
||||
address: ADDRESS,
|
||||
instance: {
|
||||
Foo: { signature: 'Foo signature' },
|
||||
Bar: { signature: 'Bar signature' }
|
||||
},
|
||||
parseEventLogs: stub().returnsArg(0)
|
||||
});
|
@ -18,7 +18,6 @@ import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { Container, TxList, Loading } from '~/ui';
|
||||
|
||||
@ -118,11 +117,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(Transactions);
|
||||
|
@ -17,7 +17,6 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { BlockStatus } from '~/ui';
|
||||
|
||||
@ -60,6 +59,10 @@ class Status extends Component {
|
||||
renderConsensus () {
|
||||
const { upgradeStore } = this.props;
|
||||
|
||||
if (!upgradeStore || !upgradeStore.consensusCapability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (upgradeStore.consensusCapability === 'capable') {
|
||||
return (
|
||||
<div>
|
||||
@ -68,7 +71,9 @@ class Status extends Component {
|
||||
defaultMessage='Capable' />
|
||||
</div>
|
||||
);
|
||||
} else if (upgradeStore.consensusCapability.capableUntil) {
|
||||
}
|
||||
|
||||
if (upgradeStore.consensusCapability.capableUntil) {
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
@ -79,7 +84,9 @@ class Status extends Component {
|
||||
} } />
|
||||
</div>
|
||||
);
|
||||
} else if (upgradeStore.consensusCapability.incapableSince) {
|
||||
}
|
||||
|
||||
if (upgradeStore.consensusCapability.incapableSince) {
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
@ -133,11 +140,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(Status);
|
||||
|
@ -14,10 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import UpgradeStore from '~/modals/UpgradeParity/store';
|
||||
|
||||
@ -123,11 +122,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(Application);
|
||||
|
@ -41,9 +41,9 @@ class Connection extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { isConnected, needsToken } = this.props;
|
||||
const { isConnecting, isConnected, needsToken } = this.props;
|
||||
|
||||
if (isConnected) {
|
||||
if (!isConnecting && isConnected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -14,13 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { omitBy } from 'lodash';
|
||||
import { Checkbox } from 'material-ui';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { omitBy } from 'lodash';
|
||||
|
||||
import { AddDapps, DappPermissions } from '~/modals';
|
||||
import PermissionStore from '~/modals/DappPermissions/store';
|
||||
@ -169,11 +168,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(Dapps);
|
||||
|
@ -256,7 +256,7 @@ export default class DappsStore {
|
||||
store.set(LS_KEY_DISPLAY, this.displayApps);
|
||||
}
|
||||
|
||||
@action addApps = (_apps) => {
|
||||
@action addApps = (_apps = []) => {
|
||||
transaction(() => {
|
||||
const apps = _apps.filter((app) => app);
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import ActionFingerprint from 'material-ui/svg-icons/action/fingerprint';
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
|
||||
@ -162,11 +161,7 @@ function mapStateToProps (state) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(ParityBar);
|
||||
|
@ -51,7 +51,7 @@ export default class Parity extends Component {
|
||||
Object.keys(LOG_KEYS).map((logKey) => {
|
||||
const log = LOG_KEYS[logKey];
|
||||
|
||||
const logger = LogLevel.getLogger(log.path);
|
||||
const logger = LogLevel.getLogger(log.key);
|
||||
const level = logger.getLevel();
|
||||
|
||||
nextState[logKey] = { level, log };
|
||||
@ -133,11 +133,11 @@ export default class Parity extends Component {
|
||||
|
||||
return Object.keys(logLevels).map((logKey) => {
|
||||
const { level, log } = logLevels[logKey];
|
||||
const { path, desc } = log;
|
||||
const { key, desc } = log;
|
||||
|
||||
const onChange = (_, index) => {
|
||||
const nextLevel = Object.values(selectValues)[index].value;
|
||||
LogLevel.getLogger(path).setLevel(nextLevel);
|
||||
LogLevel.getLogger(key).setLevel(nextLevel);
|
||||
this.loadLogLevels();
|
||||
};
|
||||
|
||||
|
@ -14,13 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import keycode from 'keycode';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import keycode from 'keycode';
|
||||
|
||||
import { Form, Input, IdentityIcon } from '~/ui';
|
||||
|
||||
@ -258,11 +257,7 @@ function mapStateToProps (_, initProps) {
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
null
|
||||
)(TransactionPendingFormConfirm);
|
||||
|
@ -37,6 +37,14 @@ export default class MiningSettings extends Component {
|
||||
const { nodeStatus } = this.props;
|
||||
const { coinbase, defaultExtraData, extraData, gasFloorTarget, minGasPrice } = nodeStatus;
|
||||
|
||||
const extradata = extraData
|
||||
? decodeExtraData(extraData)
|
||||
: '';
|
||||
|
||||
const defaultExtradata = defaultExtraData
|
||||
? decodeExtraData(defaultExtraData)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div { ...this._testInherit() }>
|
||||
<ContainerTitle title='mining settings' />
|
||||
@ -53,9 +61,9 @@ export default class MiningSettings extends Component {
|
||||
<Input
|
||||
label='extradata'
|
||||
hint='extra data for mined blocks'
|
||||
value={ decodeExtraData(extraData) }
|
||||
value={ extradata }
|
||||
onSubmit={ this.onExtraDataChange }
|
||||
defaultValue={ decodeExtraData(defaultExtraData) }
|
||||
defaultValue={ defaultExtradata }
|
||||
allowCopy
|
||||
floatCopy
|
||||
{ ...this._test('extra-data') }
|
||||
|
@ -95,9 +95,15 @@ export default class Status extends Component {
|
||||
|
||||
renderSettings () {
|
||||
const { nodeStatus } = this.props;
|
||||
const { rpcSettings, netPeers } = nodeStatus;
|
||||
const { rpcSettings, netPeers, netPort = '' } = nodeStatus;
|
||||
const peers = `${netPeers.active}/${netPeers.connected}/${netPeers.max}`;
|
||||
|
||||
if (!rpcSettings) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rpcPort = rpcSettings.port || '';
|
||||
|
||||
return (
|
||||
<div { ...this._test('settings') }>
|
||||
<ContainerTitle title='network settings' />
|
||||
@ -121,7 +127,7 @@ export default class Status extends Component {
|
||||
allowCopy
|
||||
readOnly
|
||||
label='network port'
|
||||
value={ nodeStatus.netPort.toString() }
|
||||
value={ netPort.toString() }
|
||||
{ ...this._test('network-port') } />
|
||||
</div>
|
||||
</div>
|
||||
@ -146,7 +152,7 @@ export default class Status extends Component {
|
||||
allowCopy
|
||||
readOnly
|
||||
label='rpc port'
|
||||
value={ rpcSettings.port.toString() }
|
||||
value={ rpcPort.toString() }
|
||||
{ ...this._test('rpc-port') } />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -47,8 +47,8 @@ export function mockHttp (requests) {
|
||||
}
|
||||
|
||||
export function mockWs (requests) {
|
||||
const scope = { requests: 0, body: {} };
|
||||
let mockServer = new MockWsServer(TEST_WS_URL);
|
||||
const scope = { requests: 0, body: {}, server: mockServer };
|
||||
|
||||
scope.isDone = () => scope.requests === requests.length;
|
||||
scope.stop = () => {
|
||||
|
@ -20,9 +20,10 @@ use std::net::SocketAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::cmp::max;
|
||||
use cli::{Args, ArgsError};
|
||||
use util::{Hashable, U256, Uint, Bytes, version_data, Secret, Address};
|
||||
use util::{Hashable, U256, Uint, Bytes, version_data, Address};
|
||||
use util::log::Colour;
|
||||
use ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP};
|
||||
use ethcore::ethstore::ethkey::Secret;
|
||||
use ethcore::client::{VMType};
|
||||
use ethcore::miner::{MinerOptions, Banning};
|
||||
use ethcore::verification::queue::VerifierSettings;
|
||||
@ -603,7 +604,13 @@ impl Configuration {
|
||||
let (listen, public) = self.net_addresses()?;
|
||||
ret.listen_address = listen.map(|l| format!("{}", l));
|
||||
ret.public_address = public.map(|p| format!("{}", p));
|
||||
ret.use_secret = self.args.flag_node_key.as_ref().map(|s| s.parse::<Secret>().unwrap_or_else(|_| s.sha3()));
|
||||
ret.use_secret = match self.args.flag_node_key.as_ref()
|
||||
.map(|s| s.parse::<Secret>().or_else(|_| Secret::from_slice(&s.sha3())).map_err(|e| format!("Invalid key: {:?}", e))
|
||||
) {
|
||||
None => None,
|
||||
Some(Ok(key)) => Some(key),
|
||||
Some(Err(err)) => return Err(err),
|
||||
};
|
||||
ret.discovery_enabled = !self.args.flag_no_discovery && !self.args.flag_nodiscover;
|
||||
ret.max_peers = self.max_peers();
|
||||
ret.min_peers = self.min_peers();
|
||||
|
@ -40,6 +40,6 @@ pub fn execute(cmd: ImportWallet) -> Result<String, String> {
|
||||
let acc_provider = AccountProvider::new(secret_store);
|
||||
let wallet = PresaleWallet::open(cmd.wallet_path).map_err(|_| "Unable to open presale wallet.")?;
|
||||
let kp = wallet.decrypt(&password).map_err(|_| "Invalid password.")?;
|
||||
let address = acc_provider.insert_account(*kp.secret(), &password).unwrap();
|
||||
let address = acc_provider.insert_account(kp.secret().clone(), &password).unwrap();
|
||||
Ok(format!("{:?}", address))
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ mod codes {
|
||||
pub const UNKNOWN_ERROR: i64 = -32009;
|
||||
pub const TRANSACTION_ERROR: i64 = -32010;
|
||||
pub const EXECUTION_ERROR: i64 = -32015;
|
||||
pub const EXCEPTION_ERROR: i64 = -32016;
|
||||
pub const ACCOUNT_LOCKED: i64 = -32020;
|
||||
pub const PASSWORD_INVALID: i64 = -32021;
|
||||
pub const ACCOUNT_ERROR: i64 = -32023;
|
||||
@ -130,6 +131,14 @@ pub fn state_pruned() -> Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exceptional() -> Error {
|
||||
Error {
|
||||
code: ErrorCode::ServerError(codes::EXCEPTION_ERROR),
|
||||
message: "The execution failed due to an exception.".into(),
|
||||
data: None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_work() -> Error {
|
||||
Error {
|
||||
code: ErrorCode::ServerError(codes::NO_WORK),
|
||||
@ -286,6 +295,7 @@ pub fn from_rlp_error(error: DecoderError) -> Error {
|
||||
pub fn from_call_error(error: CallError) -> Error {
|
||||
match error {
|
||||
CallError::StatePruned => state_pruned(),
|
||||
CallError::Exceptional => exceptional(),
|
||||
CallError::Execution(e) => execution(e),
|
||||
CallError::TransactionNotFound => internal("{}, this should not be the case with eth_call, most likely a bug.", CallError::TransactionNotFound),
|
||||
}
|
||||
|
@ -660,7 +660,6 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> Eth for EthClient<C, SN, S, M, EM> where
|
||||
num => take_weak!(self.client).call(&signed, num.into(), Default::default()),
|
||||
};
|
||||
|
||||
|
||||
result
|
||||
.map(|b| b.output.into())
|
||||
.map_err(errors::from_call_error)
|
||||
|
@ -19,7 +19,7 @@ use std::sync::{Arc, Weak};
|
||||
use std::collections::BTreeMap;
|
||||
use util::{Address};
|
||||
|
||||
use ethkey::{Brain, Generator};
|
||||
use ethkey::{Brain, Generator, Secret};
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::client::MiningBlockChainClient;
|
||||
|
||||
@ -73,7 +73,8 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
||||
self.active()?;
|
||||
let store = take_weak!(self.accounts);
|
||||
|
||||
store.insert_account(*Brain::new(phrase).generate().unwrap().secret(), &pass)
|
||||
let brain = Brain::new(phrase).generate().unwrap();
|
||||
store.insert_account(brain.secret().clone(), &pass)
|
||||
.map(Into::into)
|
||||
.map_err(|e| errors::account("Could not create account.", e))
|
||||
}
|
||||
@ -92,7 +93,9 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
|
||||
self.active()?;
|
||||
let store = take_weak!(self.accounts);
|
||||
|
||||
store.insert_account(secret.into(), &pass)
|
||||
let secret = Secret::from_slice(&secret.0)
|
||||
.map_err(|e| errors::account("Could not create account.", e))?;
|
||||
store.insert_account(secret, &pass)
|
||||
.map(Into::into)
|
||||
.map_err(|e| errors::account("Could not create account.", e))
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ const POSITIVE_NONCE_SPEC: &'static [u8] = br#"{
|
||||
|
||||
#[test]
|
||||
fn eth_transaction_count() {
|
||||
let secret = "8a283037bb19c4fed7b1c569e40c7dcff366165eb869110a1b11532963eb9cb2".into();
|
||||
let secret = "8a283037bb19c4fed7b1c569e40c7dcff366165eb869110a1b11532963eb9cb2".parse().unwrap();
|
||||
let tester = EthTester::from_spec(Spec::load(TRANSACTION_COUNT_SPEC).expect("invalid chain spec"));
|
||||
let address = tester.accounts.insert_account(secret, "").unwrap();
|
||||
tester.accounts.unlock_account_permanently(address, "".into()).unwrap();
|
||||
|
@ -561,6 +561,7 @@ fn rpc_eth_code() {
|
||||
fn rpc_eth_call_latest() {
|
||||
let tester = EthTester::default();
|
||||
tester.client.set_execution_result(Ok(Executed {
|
||||
exception: None,
|
||||
gas: U256::zero(),
|
||||
gas_used: U256::from(0xff30),
|
||||
refunded: U256::from(0x5),
|
||||
@ -596,6 +597,7 @@ fn rpc_eth_call_latest() {
|
||||
fn rpc_eth_call() {
|
||||
let tester = EthTester::default();
|
||||
tester.client.set_execution_result(Ok(Executed {
|
||||
exception: None,
|
||||
gas: U256::zero(),
|
||||
gas_used: U256::from(0xff30),
|
||||
refunded: U256::from(0x5),
|
||||
@ -631,6 +633,7 @@ fn rpc_eth_call() {
|
||||
fn rpc_eth_call_default_block() {
|
||||
let tester = EthTester::default();
|
||||
tester.client.set_execution_result(Ok(Executed {
|
||||
exception: None,
|
||||
gas: U256::zero(),
|
||||
gas_used: U256::from(0xff30),
|
||||
refunded: U256::from(0x5),
|
||||
@ -665,6 +668,7 @@ fn rpc_eth_call_default_block() {
|
||||
fn rpc_eth_estimate_gas() {
|
||||
let tester = EthTester::default();
|
||||
tester.client.set_execution_result(Ok(Executed {
|
||||
exception: None,
|
||||
gas: U256::zero(),
|
||||
gas_used: U256::from(0xff30),
|
||||
refunded: U256::from(0x5),
|
||||
@ -700,6 +704,7 @@ fn rpc_eth_estimate_gas() {
|
||||
fn rpc_eth_estimate_gas_default_block() {
|
||||
let tester = EthTester::default();
|
||||
tester.client.set_execution_result(Ok(Executed {
|
||||
exception: None,
|
||||
gas: U256::zero(),
|
||||
gas_used: U256::from(0xff30),
|
||||
refunded: U256::from(0x5),
|
||||
|
@ -51,6 +51,7 @@ fn io() -> Tester {
|
||||
block_hash: 10.into(),
|
||||
}]);
|
||||
*client.execution_result.write() = Some(Ok(Executed {
|
||||
exception: None,
|
||||
gas: 20_000.into(),
|
||||
gas_used: 10_000.into(),
|
||||
refunded: 0.into(),
|
||||
|
@ -25,7 +25,7 @@ use util::{H64 as Eth64, H160 as Eth160, H256 as Eth256, H520 as Eth520, H512 as
|
||||
macro_rules! impl_hash {
|
||||
($name: ident, $other: ident, $size: expr) => {
|
||||
/// Hash serialization
|
||||
pub struct $name([u8; $size]);
|
||||
pub struct $name(pub [u8; $size]);
|
||||
|
||||
impl Eq for $name { }
|
||||
|
||||
|
@ -23,6 +23,7 @@ use network::{NetworkProtocolHandler, NetworkService, NetworkContext, PeerId, Pr
|
||||
AllowIP as NetworkAllowIP};
|
||||
use util::{U256, H256, H512};
|
||||
use io::{TimerToken};
|
||||
use ethcore::ethstore::ethkey::Secret;
|
||||
use ethcore::client::{BlockChainClient, ChainNotify};
|
||||
use ethcore::snapshot::SnapshotService;
|
||||
use ethcore::header::BlockNumber;
|
||||
@ -476,7 +477,7 @@ pub struct NetworkConfiguration {
|
||||
/// List of initial node addresses
|
||||
pub boot_nodes: Vec<String>,
|
||||
/// Use provided node key instead of default
|
||||
pub use_secret: Option<H256>,
|
||||
pub use_secret: Option<Secret>,
|
||||
/// Max number of connected peers to maintain
|
||||
pub max_peers: u32,
|
||||
/// Min number of connected peers to maintain
|
||||
@ -667,3 +668,4 @@ impl ManageNetwork for LightSync {
|
||||
NetworkConfiguration::from(self.network.config().clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ use ethcore::spec::Spec;
|
||||
use ethcore::miner::MinerService;
|
||||
use ethcore::transaction::*;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethkey::KeyPair;
|
||||
use ethkey::{KeyPair, Secret};
|
||||
use super::helpers::*;
|
||||
use SyncConfig;
|
||||
|
||||
@ -41,7 +41,7 @@ impl IoHandler<ClientIoMessage> for TestIoHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_tx(secret: &H256, nonce: U256) -> PendingTransaction {
|
||||
fn new_tx(secret: &Secret, nonce: U256) -> PendingTransaction {
|
||||
let signed = Transaction {
|
||||
nonce: nonce.into(),
|
||||
gas_price: 0.into(),
|
||||
@ -55,8 +55,8 @@ fn new_tx(secret: &H256, nonce: U256) -> PendingTransaction {
|
||||
|
||||
#[test]
|
||||
fn authority_round() {
|
||||
let s0 = KeyPair::from_secret("1".sha3()).unwrap();
|
||||
let s1 = KeyPair::from_secret("0".sha3()).unwrap();
|
||||
let s0 = KeyPair::from_secret_slice(&"1".sha3()).unwrap();
|
||||
let s1 = KeyPair::from_secret_slice(&"0".sha3()).unwrap();
|
||||
let spec_factory = || {
|
||||
let spec = Spec::new_test_round();
|
||||
let account_provider = AccountProvider::transient_provider();
|
||||
@ -118,8 +118,8 @@ fn authority_round() {
|
||||
|
||||
#[test]
|
||||
fn tendermint() {
|
||||
let s0 = KeyPair::from_secret("1".sha3()).unwrap();
|
||||
let s1 = KeyPair::from_secret("0".sha3()).unwrap();
|
||||
let s0 = KeyPair::from_secret_slice(&"1".sha3()).unwrap();
|
||||
let s1 = KeyPair::from_secret_slice(&"0".sha3()).unwrap();
|
||||
let spec_factory = || {
|
||||
let spec = Spec::new_test_tendermint();
|
||||
let account_provider = AccountProvider::transient_provider();
|
||||
|
@ -165,7 +165,7 @@ impl Handshake {
|
||||
self.id.clone_from_slice(remote_public);
|
||||
self.remote_nonce.clone_from_slice(remote_nonce);
|
||||
self.remote_version = remote_version;
|
||||
let shared = ecdh::agree(host_secret, &self.id)?;
|
||||
let shared = *ecdh::agree(host_secret, &self.id)?;
|
||||
let signature = H520::from_slice(sig);
|
||||
self.remote_ephemeral = recover(&signature.into(), &(&shared ^ &self.remote_nonce))?;
|
||||
Ok(())
|
||||
@ -271,7 +271,7 @@ impl Handshake {
|
||||
let (nonce, _) = rest.split_at_mut(32);
|
||||
|
||||
// E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0)
|
||||
let shared = ecdh::agree(secret, &self.id)?;
|
||||
let shared = *ecdh::agree(secret, &self.id)?;
|
||||
sig.copy_from_slice(&*sign(self.ecdhe.secret(), &(&shared ^ &self.nonce))?);
|
||||
self.ecdhe.public().sha3_into(hepubk);
|
||||
pubk.copy_from_slice(public);
|
||||
@ -366,7 +366,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_handshake_auth_plain() {
|
||||
let mut h = create_handshake(None);
|
||||
let secret = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291".into();
|
||||
let secret = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291".parse().unwrap();
|
||||
let auth =
|
||||
"\
|
||||
048ca79ad18e4b0659fab4853fe5bc58eb83992980f4c9cc147d2aa31532efd29a3d3dc6a3d89eaf\
|
||||
@ -387,7 +387,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_handshake_auth_eip8() {
|
||||
let mut h = create_handshake(None);
|
||||
let secret = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291".into();
|
||||
let secret = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291".parse().unwrap();
|
||||
let auth =
|
||||
"\
|
||||
01b304ab7578555167be8154d5cc456f567d5ba302662433674222360f08d5f1534499d3678b513b\
|
||||
@ -413,7 +413,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_handshake_auth_eip8_2() {
|
||||
let mut h = create_handshake(None);
|
||||
let secret = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291".into();
|
||||
let secret = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291".parse().unwrap();
|
||||
let auth =
|
||||
"\
|
||||
01b8044c6c312173685d1edd268aa95e1d495474c6959bcdd10067ba4c9013df9e40ff45f5bfd6f7\
|
||||
@ -444,7 +444,7 @@ mod test {
|
||||
fn test_handshake_ack_plain() {
|
||||
let remote = "fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877".into();
|
||||
let mut h = create_handshake(Some(&remote));
|
||||
let secret = "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee".into();
|
||||
let secret = "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee".parse().unwrap();
|
||||
let ack =
|
||||
"\
|
||||
049f8abcfa9c0dc65b982e98af921bc0ba6e4243169348a236abe9df5f93aa69d99cadddaa387662\
|
||||
@ -464,7 +464,7 @@ mod test {
|
||||
fn test_handshake_ack_eip8() {
|
||||
let remote = "fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877".into();
|
||||
let mut h = create_handshake(Some(&remote));
|
||||
let secret = "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee".into();
|
||||
let secret = "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee".parse().unwrap();
|
||||
let ack =
|
||||
"\
|
||||
01ea0451958701280a56482929d3b0757da8f7fbe5286784beead59d95089c217c9b917788989470\
|
||||
@ -493,7 +493,7 @@ mod test {
|
||||
fn test_handshake_ack_eip8_2() {
|
||||
let remote = "fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877".into();
|
||||
let mut h = create_handshake(Some(&remote));
|
||||
let secret = "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee".into();
|
||||
let secret = "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee".parse().unwrap();
|
||||
let ack =
|
||||
"\
|
||||
01f004076e58aae772bb101ab1a8e64e01ee96e64857ce82b1113817c6cdd52c09d26f7b90981cd7\
|
||||
|
@ -1207,7 +1207,7 @@ fn load_key(path: &Path) -> Option<Secret> {
|
||||
fn key_save_load() {
|
||||
use ::devtools::RandomTempPath;
|
||||
let temp_path = RandomTempPath::create_dir();
|
||||
let key = H256::random();
|
||||
let key = Secret::from_slice(&H256::random()).unwrap();
|
||||
save_key(temp_path.as_path(), &key);
|
||||
let r = load_key(temp_path.as_path());
|
||||
assert_eq!(key, r.unwrap());
|
||||
@ -1217,8 +1217,9 @@ fn key_save_load() {
|
||||
#[test]
|
||||
fn host_client_url() {
|
||||
let mut config = NetworkConfiguration::new_local();
|
||||
let key = "6f7b0d801bc7b5ce7bbd930b84fd0369b3eb25d09be58d64ba811091046f3aa2".into();
|
||||
let key = "6f7b0d801bc7b5ce7bbd930b84fd0369b3eb25d09be58d64ba811091046f3aa2".parse().unwrap();
|
||||
config.use_secret = Some(key);
|
||||
let host: Host = Host::new(config, Arc::new(NetworkStats::new())).unwrap();
|
||||
assert!(host.local_url().starts_with("enode://101b3ef5a4ea7a1c7928e24c4c75fd053c235d7b80c22ae5c03d145d0ac7396e2a4ffff9adee3133a7b05044a5cee08115fd65145e5165d646bde371010d803c@"));
|
||||
}
|
||||
|
||||
|
@ -164,6 +164,3 @@ pub use timer::*;
|
||||
|
||||
/// 160-bit integer representing account address
|
||||
pub type Address = H160;
|
||||
|
||||
/// Secret
|
||||
pub type Secret = H256;
|
||||
|
Loading…
Reference in New Issue
Block a user