Merge branch 'master' into tx-broadcast
Conflicts: ethcore/light/src/net/context.rs ethcore/light/src/net/tests/mod.rs
This commit is contained in:
commit
b56f12adc6
@ -422,10 +422,10 @@ test-rust-stable:
|
|||||||
image: ethcore/rust:stable
|
image: ethcore/rust:stable
|
||||||
before_script:
|
before_script:
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v ^js/ | wc -l)
|
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||||
script:
|
script:
|
||||||
- export RUST_BACKTRACE=1
|
- export RUST_BACKTRACE=1
|
||||||
- if [ "$RUST_FILES_MODIFIED" = 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
|
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
|
||||||
tags:
|
tags:
|
||||||
- rust
|
- rust
|
||||||
- rust-stable
|
- rust-stable
|
||||||
@ -435,9 +435,9 @@ js-test:
|
|||||||
before_script:
|
before_script:
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
|
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
|
||||||
- if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
|
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
|
||||||
script:
|
script:
|
||||||
- if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi
|
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi
|
||||||
tags:
|
tags:
|
||||||
- rust
|
- rust
|
||||||
- rust-stable
|
- rust-stable
|
||||||
@ -480,9 +480,9 @@ js-release:
|
|||||||
before_script:
|
before_script:
|
||||||
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
|
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
|
||||||
- echo $JS_FILES_MODIFIED
|
- echo $JS_FILES_MODIFIED
|
||||||
- if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
|
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
|
||||||
script:
|
script:
|
||||||
- echo $JS_FILES_MODIFIED
|
- echo $JS_FILES_MODIFIED
|
||||||
- if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS rebuild since no JS files modified."; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi
|
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS rebuild since no JS files modified."; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi
|
||||||
tags:
|
tags:
|
||||||
- javascript
|
- javascript
|
||||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1290,7 +1290,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "git+https://github.com/ethcore/js-precompiled.git#3d3b2f9e8e8b0fd62c172240bfd001a317cf2979"
|
source = "git+https://github.com/ethcore/js-precompiled.git#e1c4592ba60727af643c54941bf97f63cd9b5737"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
@ -14,8 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//! Light client implementation. Used for raw data queries as well as the header
|
//! Light client implementation. Stores data from light sync
|
||||||
//! sync.
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ use ethcore::transaction::SignedTransaction;
|
|||||||
use ethcore::blockchain_info::BlockChainInfo;
|
use ethcore::blockchain_info::BlockChainInfo;
|
||||||
|
|
||||||
use io::IoChannel;
|
use io::IoChannel;
|
||||||
use util::hash::H256;
|
use util::hash::{H256, H256FastMap};
|
||||||
use util::{Bytes, Mutex};
|
use util::{Bytes, Mutex};
|
||||||
|
|
||||||
use provider::Provider;
|
use provider::Provider;
|
||||||
@ -37,9 +36,10 @@ use request;
|
|||||||
|
|
||||||
/// Light client implementation.
|
/// Light client implementation.
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
engine: Arc<Engine>,
|
_engine: Arc<Engine>,
|
||||||
header_queue: HeaderQueue,
|
header_queue: HeaderQueue,
|
||||||
message_channel: Mutex<IoChannel<ClientIoMessage>>,
|
_message_channel: Mutex<IoChannel<ClientIoMessage>>,
|
||||||
|
tx_pool: Mutex<H256FastMap<SignedTransaction>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
@ -55,12 +55,17 @@ impl Client {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch a vector of all pending transactions.
|
/// Import a local transaction.
|
||||||
pub fn pending_transactions(&self) -> Vec<SignedTransaction> {
|
pub fn import_own_transaction(&self, tx: SignedTransaction) {
|
||||||
vec![]
|
self.tx_pool.lock().insert(tx.hash(), tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inquire about the status of a given block.
|
/// Fetch a vector of all pending transactions.
|
||||||
|
pub fn pending_transactions(&self) -> Vec<SignedTransaction> {
|
||||||
|
self.tx_pool.lock().values().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inquire about the status of a given block (or header).
|
||||||
pub fn status(&self, _id: BlockId) -> BlockStatus {
|
pub fn status(&self, _id: BlockId) -> BlockStatus {
|
||||||
BlockStatus::Unknown
|
BlockStatus::Unknown
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,7 @@
|
|||||||
//! It starts by performing a header-only sync, verifying random samples
|
//! It starts by performing a header-only sync, verifying random samples
|
||||||
//! of members of the chain to varying degrees.
|
//! of members of the chain to varying degrees.
|
||||||
|
|
||||||
// TODO: remove when integrating with the rest of parity.
|
#![deny(missing_docs)]
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
|
@ -43,9 +43,10 @@ pub trait IoContext {
|
|||||||
fn protocol_version(&self, peer: PeerId) -> Option<u8>;
|
fn protocol_version(&self, peer: PeerId) -> Option<u8>;
|
||||||
|
|
||||||
/// Persistent peer id
|
/// Persistent peer id
|
||||||
fn persistent_peer_id(&self, peer: &PeerId) -> Option<NodeId>;
|
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<'a> IoContext for NetworkContext<'a> {
|
impl<'a> IoContext for NetworkContext<'a> {
|
||||||
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) {
|
fn send(&self, peer: PeerId, packet_id: u8, packet_body: Vec<u8>) {
|
||||||
if let Err(e) = self.send(peer, packet_id, packet_body) {
|
if let Err(e) = self.send(peer, packet_id, packet_body) {
|
||||||
@ -71,8 +72,8 @@ impl<'a> IoContext for NetworkContext<'a> {
|
|||||||
self.protocol_version(self.subprotocol_name(), peer)
|
self.protocol_version(self.subprotocol_name(), peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn persistent_peer_id(&self, peer: &PeerId) -> Option<NodeId> {
|
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId> {
|
||||||
self.session_info(*peer).and_then(|info| info.id)
|
self.session_info(peer).and_then(|info| info.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ pub trait EventContext {
|
|||||||
fn peer(&self) -> PeerId;
|
fn peer(&self) -> PeerId;
|
||||||
|
|
||||||
/// Returns the relevant's peer persistent Id (aka NodeId).
|
/// Returns the relevant's peer persistent Id (aka NodeId).
|
||||||
fn persistent_peer_id(&self, id: &PeerId) -> Option<NodeId>;
|
fn persistent_peer_id(&self, peer: PeerId) -> Option<NodeId>;
|
||||||
|
|
||||||
/// Make a request from a peer.
|
/// Make a request from a peer.
|
||||||
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error>;
|
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error>;
|
||||||
@ -111,14 +112,14 @@ pub struct Ctx<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EventContext for Ctx<'a> {
|
impl<'a> EventContext for Ctx<'a> {
|
||||||
|
|
||||||
fn peer(&self) -> PeerId {
|
fn peer(&self) -> PeerId {
|
||||||
self.peer
|
self.peer
|
||||||
}
|
}
|
||||||
|
|
||||||
fn persistent_peer_id(&self, id: &PeerId) -> Option<NodeId> {
|
fn persistent_peer_id(&self, id: PeerId) -> Option<NodeId> {
|
||||||
self.io.persistent_peer_id(id)
|
self.io.persistent_peer_id(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error> {
|
fn request_from(&self, peer: PeerId, request: Request) -> Result<ReqId, Error> {
|
||||||
self.proto.request_from(self.io, &peer, request)
|
self.proto.request_from(self.io, &peer, request)
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ use std::sync::Arc;
|
|||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
use provider::Provider;
|
use provider::Provider;
|
||||||
use request::{self, Request};
|
use request::{self, HashOrNumber, Request};
|
||||||
|
|
||||||
use self::buffer_flow::{Buffer, FlowParams};
|
use self::buffer_flow::{Buffer, FlowParams};
|
||||||
use self::context::Ctx;
|
use self::context::Ctx;
|
||||||
@ -57,13 +57,13 @@ const TIMEOUT_INTERVAL_MS: u64 = 1000;
|
|||||||
// minimum interval between updates.
|
// minimum interval between updates.
|
||||||
const UPDATE_INTERVAL_MS: i64 = 5000;
|
const UPDATE_INTERVAL_MS: i64 = 5000;
|
||||||
|
|
||||||
// Supported protocol versions.
|
/// Supported protocol versions.
|
||||||
pub const PROTOCOL_VERSIONS: &'static [u8] = &[1];
|
pub const PROTOCOL_VERSIONS: &'static [u8] = &[1];
|
||||||
|
|
||||||
// Max protocol version.
|
/// Max protocol version.
|
||||||
pub const MAX_PROTOCOL_VERSION: u8 = 1;
|
pub const MAX_PROTOCOL_VERSION: u8 = 1;
|
||||||
|
|
||||||
// Packet count for LES.
|
/// Packet count for LES.
|
||||||
pub const PACKET_COUNT: u8 = 15;
|
pub const PACKET_COUNT: u8 = 15;
|
||||||
|
|
||||||
// packet ID definitions.
|
// packet ID definitions.
|
||||||
@ -102,6 +102,18 @@ mod packet {
|
|||||||
pub const HEADER_PROOFS: u8 = 0x0e;
|
pub const HEADER_PROOFS: u8 = 0x0e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeouts for different kinds of requests. all values are in milliseconds.
|
||||||
|
// TODO: variable timeouts based on request count.
|
||||||
|
mod timeout {
|
||||||
|
pub const HANDSHAKE: i64 = 2500;
|
||||||
|
pub const HEADERS: i64 = 5000;
|
||||||
|
pub const BODIES: i64 = 5000;
|
||||||
|
pub const RECEIPTS: i64 = 3500;
|
||||||
|
pub const PROOFS: i64 = 4000;
|
||||||
|
pub const CONTRACT_CODES: i64 = 5000;
|
||||||
|
pub const HEADER_PROOFS: i64 = 3500;
|
||||||
|
}
|
||||||
|
|
||||||
/// A request id.
|
/// A request id.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct ReqId(usize);
|
pub struct ReqId(usize);
|
||||||
@ -111,7 +123,6 @@ pub struct ReqId(usize);
|
|||||||
struct PendingPeer {
|
struct PendingPeer {
|
||||||
sent_head: H256,
|
sent_head: H256,
|
||||||
last_update: SteadyTime,
|
last_update: SteadyTime,
|
||||||
proto_version: u8,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// data about each peer.
|
// data about each peer.
|
||||||
@ -122,7 +133,6 @@ struct Peer {
|
|||||||
remote_flow: Option<(Buffer, FlowParams)>,
|
remote_flow: Option<(Buffer, FlowParams)>,
|
||||||
sent_head: H256, // last head we've given them.
|
sent_head: H256, // last head we've given them.
|
||||||
last_update: SteadyTime,
|
last_update: SteadyTime,
|
||||||
proto_version: u8,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Peer {
|
impl Peer {
|
||||||
@ -443,17 +453,54 @@ impl LightProtocol {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// if something went wrong, figure out how much to punish the peer.
|
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
match e.punishment() {
|
punish(*peer, io, e);
|
||||||
Punishment::None => {}
|
|
||||||
Punishment::Disconnect => {
|
|
||||||
debug!(target: "les", "Disconnecting peer {}: {}", peer, e);
|
|
||||||
io.disconnect_peer(*peer)
|
|
||||||
}
|
}
|
||||||
Punishment::Disable => {
|
}
|
||||||
debug!(target: "les", "Disabling peer {}: {}", peer, e);
|
|
||||||
io.disable_peer(*peer)
|
// check timeouts and punish peers.
|
||||||
|
fn timeout_check(&self, io: &IoContext) {
|
||||||
|
let now = SteadyTime::now();
|
||||||
|
|
||||||
|
// handshake timeout
|
||||||
|
{
|
||||||
|
let mut pending = self.pending_peers.write();
|
||||||
|
let slowpokes: Vec<_> = pending.iter()
|
||||||
|
.filter(|&(_, ref peer)| {
|
||||||
|
peer.last_update + Duration::milliseconds(timeout::HANDSHAKE) <= now
|
||||||
|
})
|
||||||
|
.map(|(&p, _)| p)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for slowpoke in slowpokes {
|
||||||
|
debug!(target: "les", "Peer {} handshake timed out", slowpoke);
|
||||||
|
pending.remove(&slowpoke);
|
||||||
|
io.disconnect_peer(slowpoke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// request timeouts
|
||||||
|
{
|
||||||
|
for r in self.pending_requests.read().values() {
|
||||||
|
let kind_timeout = match r.request.kind() {
|
||||||
|
request::Kind::Headers => timeout::HEADERS,
|
||||||
|
request::Kind::Bodies => timeout::BODIES,
|
||||||
|
request::Kind::Receipts => timeout::RECEIPTS,
|
||||||
|
request::Kind::StateProofs => timeout::PROOFS,
|
||||||
|
request::Kind::Codes => timeout::CONTRACT_CODES,
|
||||||
|
request::Kind::HeaderProofs => timeout::HEADER_PROOFS,
|
||||||
|
};
|
||||||
|
|
||||||
|
if r.timestamp + Duration::milliseconds(kind_timeout) <= now {
|
||||||
|
debug!(target: "les", "Request for {:?} from peer {} timed out",
|
||||||
|
r.request.kind(), r.peer_id);
|
||||||
|
|
||||||
|
// keep the request in the `pending` set for now so
|
||||||
|
// on_disconnect will pass unfulfilled ReqIds to handlers.
|
||||||
|
// in the case that a response is received after this, the
|
||||||
|
// disconnect won't be cancelled but the ReqId won't be
|
||||||
|
// marked as abandoned.
|
||||||
|
io.disconnect_peer(r.peer_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -463,19 +510,37 @@ impl LightProtocol {
|
|||||||
impl LightProtocol {
|
impl LightProtocol {
|
||||||
// called when a peer connects.
|
// called when a peer connects.
|
||||||
fn on_connect(&self, peer: &PeerId, io: &IoContext) {
|
fn on_connect(&self, peer: &PeerId, io: &IoContext) {
|
||||||
let peer = *peer;
|
let proto_version = match io.protocol_version(*peer).ok_or(Error::WrongNetwork) {
|
||||||
|
Ok(pv) => pv,
|
||||||
|
Err(e) => { punish(*peer, io, e); return }
|
||||||
|
};
|
||||||
|
|
||||||
trace!(target: "les", "Peer {} connecting", peer);
|
if PROTOCOL_VERSIONS.iter().find(|x| **x == proto_version).is_none() {
|
||||||
|
punish(*peer, io, Error::UnsupportedProtocolVersion(proto_version));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match self.send_status(peer, io) {
|
let chain_info = self.provider.chain_info();
|
||||||
Ok(pending_peer) => {
|
|
||||||
self.pending_peers.write().insert(peer, pending_peer);
|
let status = Status {
|
||||||
}
|
head_td: chain_info.total_difficulty,
|
||||||
Err(e) => {
|
head_hash: chain_info.best_block_hash,
|
||||||
trace!(target: "les", "Error while sending status: {}", e);
|
head_num: chain_info.best_block_number,
|
||||||
io.disconnect_peer(peer);
|
genesis_hash: chain_info.genesis_hash,
|
||||||
}
|
protocol_version: proto_version as u32, // match peer proto version
|
||||||
}
|
network_id: self.network_id,
|
||||||
|
last_head: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let capabilities = self.capabilities.read().clone();
|
||||||
|
let status_packet = status::write_handshake(&status, &capabilities, Some(&self.flow_params));
|
||||||
|
|
||||||
|
self.pending_peers.write().insert(*peer, PendingPeer {
|
||||||
|
sent_head: chain_info.best_block_hash,
|
||||||
|
last_update: SteadyTime::now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
io.send(*peer, packet::STATUS, status_packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// called when a peer disconnects.
|
// called when a peer disconnects.
|
||||||
@ -508,38 +573,6 @@ impl LightProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send status to a peer.
|
|
||||||
fn send_status(&self, peer: PeerId, io: &IoContext) -> Result<PendingPeer, Error> {
|
|
||||||
let proto_version = try!(io.protocol_version(peer).ok_or(Error::WrongNetwork));
|
|
||||||
|
|
||||||
if PROTOCOL_VERSIONS.iter().find(|x| **x == proto_version).is_none() {
|
|
||||||
return Err(Error::UnsupportedProtocolVersion(proto_version));
|
|
||||||
}
|
|
||||||
|
|
||||||
let chain_info = self.provider.chain_info();
|
|
||||||
|
|
||||||
let status = Status {
|
|
||||||
head_td: chain_info.total_difficulty,
|
|
||||||
head_hash: chain_info.best_block_hash,
|
|
||||||
head_num: chain_info.best_block_number,
|
|
||||||
genesis_hash: chain_info.genesis_hash,
|
|
||||||
protocol_version: proto_version as u32, // match peer proto version
|
|
||||||
network_id: self.network_id,
|
|
||||||
last_head: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let capabilities = self.capabilities.read().clone();
|
|
||||||
let status_packet = status::write_handshake(&status, &capabilities, Some(&self.flow_params));
|
|
||||||
|
|
||||||
io.send(peer, packet::STATUS, status_packet);
|
|
||||||
|
|
||||||
Ok(PendingPeer {
|
|
||||||
sent_head: chain_info.best_block_hash,
|
|
||||||
last_update: SteadyTime::now(),
|
|
||||||
proto_version: proto_version,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle status message from peer.
|
// Handle status message from peer.
|
||||||
fn status(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> {
|
fn status(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> {
|
||||||
let pending = match self.pending_peers.write().remove(peer) {
|
let pending = match self.pending_peers.write().remove(peer) {
|
||||||
@ -570,7 +603,6 @@ impl LightProtocol {
|
|||||||
remote_flow: remote_flow,
|
remote_flow: remote_flow,
|
||||||
sent_head: pending.sent_head,
|
sent_head: pending.sent_head,
|
||||||
last_update: pending.last_update,
|
last_update: pending.last_update,
|
||||||
proto_version: pending.proto_version,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
for handler in &self.handlers {
|
for handler in &self.handlers {
|
||||||
@ -645,18 +677,21 @@ impl LightProtocol {
|
|||||||
let mut peer = peer.lock();
|
let mut peer = peer.lock();
|
||||||
|
|
||||||
let req_id: u64 = try!(data.val_at(0));
|
let req_id: u64 = try!(data.val_at(0));
|
||||||
|
let data = try!(data.at(1));
|
||||||
|
|
||||||
let block = {
|
let start_block = {
|
||||||
let rlp = try!(data.at(1));
|
if try!(data.at(0)).size() == 32 {
|
||||||
(try!(rlp.val_at(0)), try!(rlp.val_at(1)))
|
HashOrNumber::Hash(try!(data.val_at(0)))
|
||||||
|
} else {
|
||||||
|
HashOrNumber::Number(try!(data.val_at(0)))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let req = request::Headers {
|
let req = request::Headers {
|
||||||
block_num: block.0,
|
start: start_block,
|
||||||
block_hash: block.1,
|
max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(1))),
|
||||||
max: ::std::cmp::min(MAX_HEADERS, try!(data.val_at(2))),
|
skip: try!(data.val_at(2)),
|
||||||
skip: try!(data.val_at(3)),
|
reverse: try!(data.val_at(3)),
|
||||||
reverse: try!(data.val_at(4)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Headers, req.max));
|
let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Headers, req.max));
|
||||||
@ -667,8 +702,8 @@ impl LightProtocol {
|
|||||||
|
|
||||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||||
io.respond(packet::BLOCK_HEADERS, {
|
io.respond(packet::BLOCK_HEADERS, {
|
||||||
let mut stream = RlpStream::new_list(response.len() + 2);
|
let mut stream = RlpStream::new_list(3);
|
||||||
stream.append(&req_id).append(&cur_buffer);
|
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||||
|
|
||||||
for header in response {
|
for header in response {
|
||||||
stream.append_raw(&header, 1);
|
stream.append_raw(&header, 1);
|
||||||
@ -683,7 +718,7 @@ impl LightProtocol {
|
|||||||
// Receive a response for block headers.
|
// Receive a response for block headers.
|
||||||
fn block_headers(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
fn block_headers(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
||||||
let req_id = try!(self.pre_verify_response(peer, request::Kind::Headers, &raw));
|
let req_id = try!(self.pre_verify_response(peer, request::Kind::Headers, &raw));
|
||||||
let raw_headers: Vec<_> = raw.iter().skip(2).map(|x| x.as_raw().to_owned()).collect();
|
let raw_headers: Vec<_> = try!(raw.at(2)).iter().map(|x| x.as_raw().to_owned()).collect();
|
||||||
|
|
||||||
for handler in &self.handlers {
|
for handler in &self.handlers {
|
||||||
handler.on_block_headers(&Ctx {
|
handler.on_block_headers(&Ctx {
|
||||||
@ -713,7 +748,7 @@ impl LightProtocol {
|
|||||||
let req_id: u64 = try!(data.val_at(0));
|
let req_id: u64 = try!(data.val_at(0));
|
||||||
|
|
||||||
let req = request::Bodies {
|
let req = request::Bodies {
|
||||||
block_hashes: try!(data.iter().skip(1).take(MAX_BODIES).map(|x| x.as_val()).collect())
|
block_hashes: try!(try!(data.at(1)).iter().take(MAX_BODIES).map(|x| x.as_val()).collect())
|
||||||
};
|
};
|
||||||
|
|
||||||
let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Bodies, req.block_hashes.len()));
|
let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Bodies, req.block_hashes.len()));
|
||||||
@ -726,8 +761,8 @@ impl LightProtocol {
|
|||||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||||
|
|
||||||
io.respond(packet::BLOCK_BODIES, {
|
io.respond(packet::BLOCK_BODIES, {
|
||||||
let mut stream = RlpStream::new_list(response.len() + 2);
|
let mut stream = RlpStream::new_list(3);
|
||||||
stream.append(&req_id).append(&cur_buffer);
|
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||||
|
|
||||||
for body in response {
|
for body in response {
|
||||||
stream.append_raw(&body, 1);
|
stream.append_raw(&body, 1);
|
||||||
@ -742,7 +777,7 @@ impl LightProtocol {
|
|||||||
// Receive a response for block bodies.
|
// Receive a response for block bodies.
|
||||||
fn block_bodies(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
fn block_bodies(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
||||||
let req_id = try!(self.pre_verify_response(peer, request::Kind::Bodies, &raw));
|
let req_id = try!(self.pre_verify_response(peer, request::Kind::Bodies, &raw));
|
||||||
let raw_bodies: Vec<Bytes> = raw.iter().skip(2).map(|x| x.as_raw().to_owned()).collect();
|
let raw_bodies: Vec<Bytes> = try!(raw.at(2)).iter().map(|x| x.as_raw().to_owned()).collect();
|
||||||
|
|
||||||
for handler in &self.handlers {
|
for handler in &self.handlers {
|
||||||
handler.on_block_bodies(&Ctx {
|
handler.on_block_bodies(&Ctx {
|
||||||
@ -772,7 +807,7 @@ impl LightProtocol {
|
|||||||
let req_id: u64 = try!(data.val_at(0));
|
let req_id: u64 = try!(data.val_at(0));
|
||||||
|
|
||||||
let req = request::Receipts {
|
let req = request::Receipts {
|
||||||
block_hashes: try!(data.iter().skip(1).take(MAX_RECEIPTS).map(|x| x.as_val()).collect())
|
block_hashes: try!(try!(data.at(1)).iter().take(MAX_RECEIPTS).map(|x| x.as_val()).collect())
|
||||||
};
|
};
|
||||||
|
|
||||||
let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Receipts, req.block_hashes.len()));
|
let max_cost = try!(peer.deduct_max(&self.flow_params, request::Kind::Receipts, req.block_hashes.len()));
|
||||||
@ -785,8 +820,8 @@ impl LightProtocol {
|
|||||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||||
|
|
||||||
io.respond(packet::RECEIPTS, {
|
io.respond(packet::RECEIPTS, {
|
||||||
let mut stream = RlpStream::new_list(response.len() + 2);
|
let mut stream = RlpStream::new_list(3);
|
||||||
stream.append(&req_id).append(&cur_buffer);
|
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||||
|
|
||||||
for receipts in response {
|
for receipts in response {
|
||||||
stream.append_raw(&receipts, 1);
|
stream.append_raw(&receipts, 1);
|
||||||
@ -801,9 +836,8 @@ impl LightProtocol {
|
|||||||
// Receive a response for receipts.
|
// Receive a response for receipts.
|
||||||
fn receipts(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
fn receipts(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
||||||
let req_id = try!(self.pre_verify_response(peer, request::Kind::Receipts, &raw));
|
let req_id = try!(self.pre_verify_response(peer, request::Kind::Receipts, &raw));
|
||||||
let raw_receipts: Vec<Vec<Receipt>> = try!(raw
|
let raw_receipts: Vec<Vec<Receipt>> = try!(try!(raw.at(2))
|
||||||
.iter()
|
.iter()
|
||||||
.skip(2)
|
|
||||||
.map(|x| x.as_val())
|
.map(|x| x.as_val())
|
||||||
.collect());
|
.collect());
|
||||||
|
|
||||||
@ -835,7 +869,7 @@ impl LightProtocol {
|
|||||||
let req_id: u64 = try!(data.val_at(0));
|
let req_id: u64 = try!(data.val_at(0));
|
||||||
|
|
||||||
let req = {
|
let req = {
|
||||||
let requests: Result<Vec<_>, Error> = data.iter().skip(1).take(MAX_PROOFS).map(|x| {
|
let requests: Result<Vec<_>, Error> = try!(data.at(1)).iter().take(MAX_PROOFS).map(|x| {
|
||||||
Ok(request::StateProof {
|
Ok(request::StateProof {
|
||||||
block: try!(x.val_at(0)),
|
block: try!(x.val_at(0)),
|
||||||
key1: try!(x.val_at(1)),
|
key1: try!(x.val_at(1)),
|
||||||
@ -859,8 +893,8 @@ impl LightProtocol {
|
|||||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||||
|
|
||||||
io.respond(packet::PROOFS, {
|
io.respond(packet::PROOFS, {
|
||||||
let mut stream = RlpStream::new_list(response.len() + 2);
|
let mut stream = RlpStream::new_list(3);
|
||||||
stream.append(&req_id).append(&cur_buffer);
|
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||||
|
|
||||||
for proof in response {
|
for proof in response {
|
||||||
stream.append_raw(&proof, 1);
|
stream.append_raw(&proof, 1);
|
||||||
@ -876,8 +910,7 @@ impl LightProtocol {
|
|||||||
fn proofs(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
fn proofs(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
||||||
let req_id = try!(self.pre_verify_response(peer, request::Kind::StateProofs, &raw));
|
let req_id = try!(self.pre_verify_response(peer, request::Kind::StateProofs, &raw));
|
||||||
|
|
||||||
let raw_proofs: Vec<Vec<Bytes>> = raw.iter()
|
let raw_proofs: Vec<Vec<Bytes>> = try!(raw.at(2)).iter()
|
||||||
.skip(2)
|
|
||||||
.map(|x| x.iter().map(|node| node.as_raw().to_owned()).collect())
|
.map(|x| x.iter().map(|node| node.as_raw().to_owned()).collect())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -909,7 +942,7 @@ impl LightProtocol {
|
|||||||
let req_id: u64 = try!(data.val_at(0));
|
let req_id: u64 = try!(data.val_at(0));
|
||||||
|
|
||||||
let req = {
|
let req = {
|
||||||
let requests: Result<Vec<_>, Error> = data.iter().skip(1).take(MAX_CODES).map(|x| {
|
let requests: Result<Vec<_>, Error> = try!(data.at(1)).iter().take(MAX_CODES).map(|x| {
|
||||||
Ok(request::ContractCode {
|
Ok(request::ContractCode {
|
||||||
block_hash: try!(x.val_at(0)),
|
block_hash: try!(x.val_at(0)),
|
||||||
account_key: try!(x.val_at(1)),
|
account_key: try!(x.val_at(1)),
|
||||||
@ -931,8 +964,8 @@ impl LightProtocol {
|
|||||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||||
|
|
||||||
io.respond(packet::CONTRACT_CODES, {
|
io.respond(packet::CONTRACT_CODES, {
|
||||||
let mut stream = RlpStream::new_list(response.len() + 2);
|
let mut stream = RlpStream::new_list(3);
|
||||||
stream.append(&req_id).append(&cur_buffer);
|
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||||
|
|
||||||
for code in response {
|
for code in response {
|
||||||
stream.append(&code);
|
stream.append(&code);
|
||||||
@ -948,7 +981,7 @@ impl LightProtocol {
|
|||||||
fn contract_code(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
fn contract_code(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
||||||
let req_id = try!(self.pre_verify_response(peer, request::Kind::Codes, &raw));
|
let req_id = try!(self.pre_verify_response(peer, request::Kind::Codes, &raw));
|
||||||
|
|
||||||
let raw_code: Vec<Bytes> = try!(raw.iter().skip(2).map(|x| x.as_val()).collect());
|
let raw_code: Vec<Bytes> = try!(try!(raw.at(2)).iter().map(|x| x.as_val()).collect());
|
||||||
|
|
||||||
for handler in &self.handlers {
|
for handler in &self.handlers {
|
||||||
handler.on_code(&Ctx {
|
handler.on_code(&Ctx {
|
||||||
@ -978,7 +1011,7 @@ impl LightProtocol {
|
|||||||
let req_id: u64 = try!(data.val_at(0));
|
let req_id: u64 = try!(data.val_at(0));
|
||||||
|
|
||||||
let req = {
|
let req = {
|
||||||
let requests: Result<Vec<_>, Error> = data.iter().skip(1).take(MAX_PROOFS).map(|x| {
|
let requests: Result<Vec<_>, Error> = try!(data.at(1)).iter().take(MAX_PROOFS).map(|x| {
|
||||||
Ok(request::HeaderProof {
|
Ok(request::HeaderProof {
|
||||||
cht_number: try!(x.val_at(0)),
|
cht_number: try!(x.val_at(0)),
|
||||||
block_number: try!(x.val_at(1)),
|
block_number: try!(x.val_at(1)),
|
||||||
@ -1001,8 +1034,8 @@ impl LightProtocol {
|
|||||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||||
|
|
||||||
io.respond(packet::HEADER_PROOFS, {
|
io.respond(packet::HEADER_PROOFS, {
|
||||||
let mut stream = RlpStream::new_list(response.len() + 2);
|
let mut stream = RlpStream::new_list(3);
|
||||||
stream.append(&req_id).append(&cur_buffer);
|
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||||
|
|
||||||
for proof in response {
|
for proof in response {
|
||||||
stream.append_raw(&proof, 1);
|
stream.append_raw(&proof, 1);
|
||||||
@ -1023,9 +1056,8 @@ impl LightProtocol {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let req_id = try!(self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw));
|
let req_id = try!(self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw));
|
||||||
let raw_proofs: Vec<_> = try!(raw.iter().skip(2).map(decode_res).collect());
|
let raw_proofs: Vec<_> = try!(try!(raw.at(2)).iter().map(decode_res).collect());
|
||||||
|
|
||||||
for handler in &self.handlers {
|
for handler in &self.handlers {
|
||||||
handler.on_header_proofs(&Ctx {
|
handler.on_header_proofs(&Ctx {
|
||||||
@ -1058,6 +1090,21 @@ impl LightProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if something went wrong, figure out how much to punish the peer.
|
||||||
|
fn punish(peer: PeerId, io: &IoContext, e: Error) {
|
||||||
|
match e.punishment() {
|
||||||
|
Punishment::None => {}
|
||||||
|
Punishment::Disconnect => {
|
||||||
|
debug!(target: "les", "Disconnecting peer {}: {}", peer, e);
|
||||||
|
io.disconnect_peer(peer)
|
||||||
|
}
|
||||||
|
Punishment::Disable => {
|
||||||
|
debug!(target: "les", "Disabling peer {}: {}", peer, e);
|
||||||
|
io.disable_peer(peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NetworkProtocolHandler for LightProtocol {
|
impl NetworkProtocolHandler for LightProtocol {
|
||||||
fn initialize(&self, io: &NetworkContext) {
|
fn initialize(&self, io: &NetworkContext) {
|
||||||
io.register_timer(TIMEOUT, TIMEOUT_INTERVAL_MS).expect("Error registering sync timer.");
|
io.register_timer(TIMEOUT, TIMEOUT_INTERVAL_MS).expect("Error registering sync timer.");
|
||||||
@ -1075,11 +1122,9 @@ impl NetworkProtocolHandler for LightProtocol {
|
|||||||
self.on_disconnect(*peer, io);
|
self.on_disconnect(*peer, io);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn timeout(&self, _io: &NetworkContext, timer: TimerToken) {
|
fn timeout(&self, io: &NetworkContext, timer: TimerToken) {
|
||||||
match timer {
|
match timer {
|
||||||
TIMEOUT => {
|
TIMEOUT => self.timeout_check(io),
|
||||||
// broadcast transactions to peers.
|
|
||||||
}
|
|
||||||
_ => warn!(target: "les", "received timeout on unknown token {}", timer),
|
_ => warn!(target: "les", "received timeout on unknown token {}", timer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1089,20 +1134,24 @@ impl NetworkProtocolHandler for LightProtocol {
|
|||||||
fn encode_request(req: &Request, req_id: usize) -> Vec<u8> {
|
fn encode_request(req: &Request, req_id: usize) -> Vec<u8> {
|
||||||
match *req {
|
match *req {
|
||||||
Request::Headers(ref headers) => {
|
Request::Headers(ref headers) => {
|
||||||
let mut stream = RlpStream::new_list(5);
|
let mut stream = RlpStream::new_list(2);
|
||||||
|
stream.append(&req_id).begin_list(4);
|
||||||
|
|
||||||
|
match headers.start {
|
||||||
|
HashOrNumber::Hash(ref hash) => stream.append(hash),
|
||||||
|
HashOrNumber::Number(ref num) => stream.append(num),
|
||||||
|
};
|
||||||
|
|
||||||
stream
|
stream
|
||||||
.append(&req_id)
|
|
||||||
.begin_list(2)
|
|
||||||
.append(&headers.block_num)
|
|
||||||
.append(&headers.block_hash)
|
|
||||||
.append(&headers.max)
|
.append(&headers.max)
|
||||||
.append(&headers.skip)
|
.append(&headers.skip)
|
||||||
.append(&headers.reverse);
|
.append(&headers.reverse);
|
||||||
|
|
||||||
stream.out()
|
stream.out()
|
||||||
}
|
}
|
||||||
Request::Bodies(ref request) => {
|
Request::Bodies(ref request) => {
|
||||||
let mut stream = RlpStream::new_list(request.block_hashes.len() + 1);
|
let mut stream = RlpStream::new_list(2);
|
||||||
stream.append(&req_id);
|
stream.append(&req_id).begin_list(request.block_hashes.len());
|
||||||
|
|
||||||
for hash in &request.block_hashes {
|
for hash in &request.block_hashes {
|
||||||
stream.append(hash);
|
stream.append(hash);
|
||||||
@ -1111,8 +1160,8 @@ fn encode_request(req: &Request, req_id: usize) -> Vec<u8> {
|
|||||||
stream.out()
|
stream.out()
|
||||||
}
|
}
|
||||||
Request::Receipts(ref request) => {
|
Request::Receipts(ref request) => {
|
||||||
let mut stream = RlpStream::new_list(request.block_hashes.len() + 1);
|
let mut stream = RlpStream::new_list(2);
|
||||||
stream.append(&req_id);
|
stream.append(&req_id).begin_list(request.block_hashes.len());
|
||||||
|
|
||||||
for hash in &request.block_hashes {
|
for hash in &request.block_hashes {
|
||||||
stream.append(hash);
|
stream.append(hash);
|
||||||
@ -1121,8 +1170,8 @@ fn encode_request(req: &Request, req_id: usize) -> Vec<u8> {
|
|||||||
stream.out()
|
stream.out()
|
||||||
}
|
}
|
||||||
Request::StateProofs(ref request) => {
|
Request::StateProofs(ref request) => {
|
||||||
let mut stream = RlpStream::new_list(request.requests.len() + 1);
|
let mut stream = RlpStream::new_list(2);
|
||||||
stream.append(&req_id);
|
stream.append(&req_id).begin_list(request.requests.len());
|
||||||
|
|
||||||
for proof_req in &request.requests {
|
for proof_req in &request.requests {
|
||||||
stream.begin_list(4)
|
stream.begin_list(4)
|
||||||
@ -1140,8 +1189,8 @@ fn encode_request(req: &Request, req_id: usize) -> Vec<u8> {
|
|||||||
stream.out()
|
stream.out()
|
||||||
}
|
}
|
||||||
Request::Codes(ref request) => {
|
Request::Codes(ref request) => {
|
||||||
let mut stream = RlpStream::new_list(request.code_requests.len() + 1);
|
let mut stream = RlpStream::new_list(2);
|
||||||
stream.append(&req_id);
|
stream.append(&req_id).begin_list(request.code_requests.len());
|
||||||
|
|
||||||
for code_req in &request.code_requests {
|
for code_req in &request.code_requests {
|
||||||
stream.begin_list(2)
|
stream.begin_list(2)
|
||||||
@ -1152,8 +1201,8 @@ fn encode_request(req: &Request, req_id: usize) -> Vec<u8> {
|
|||||||
stream.out()
|
stream.out()
|
||||||
}
|
}
|
||||||
Request::HeaderProofs(ref request) => {
|
Request::HeaderProofs(ref request) => {
|
||||||
let mut stream = RlpStream::new_list(request.requests.len() + 1);
|
let mut stream = RlpStream::new_list(2);
|
||||||
stream.append(&req_id);
|
stream.append(&req_id).begin_list(request.requests.len());
|
||||||
|
|
||||||
for proof_req in &request.requests {
|
for proof_req in &request.requests {
|
||||||
stream.begin_list(3)
|
stream.begin_list(3)
|
||||||
|
@ -95,16 +95,27 @@ impl Provider for TestProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn block_headers(&self, req: request::Headers) -> Vec<Bytes> {
|
fn block_headers(&self, req: request::Headers) -> Vec<Bytes> {
|
||||||
let best_num = self.0.client.chain_info().best_block_number;
|
use request::HashOrNumber;
|
||||||
let start_num = req.block_num;
|
use ethcore::views::HeaderView;
|
||||||
|
|
||||||
match self.0.client.block_hash(BlockId::Number(req.block_num)) {
|
let best_num = self.chain_info().best_block_number;
|
||||||
Some(hash) if hash == req.block_hash => {}
|
let start_num = match req.start {
|
||||||
_=> {
|
HashOrNumber::Number(start_num) => start_num,
|
||||||
trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash));
|
HashOrNumber::Hash(hash) => match self.0.client.block_header(BlockId::Hash(hash)) {
|
||||||
return vec![]
|
None => {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
Some(header) => {
|
||||||
|
let num = HeaderView::new(&header).number();
|
||||||
|
if req.max == 1 || self.0.client.block_hash(BlockId::Number(num)) != Some(hash) {
|
||||||
|
// Non-canonical header or single header requested.
|
||||||
|
return vec![header];
|
||||||
|
}
|
||||||
|
|
||||||
|
num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
(0u64..req.max as u64)
|
(0u64..req.max as u64)
|
||||||
.map(|x: u64| x.saturating_mul(req.skip + 1))
|
.map(|x: u64| x.saturating_mul(req.skip + 1))
|
||||||
@ -254,8 +265,7 @@ fn buffer_overflow() {
|
|||||||
|
|
||||||
// 1000 requests is far too many for the default flow params.
|
// 1000 requests is far too many for the default flow params.
|
||||||
let request = encode_request(&Request::Headers(Headers {
|
let request = encode_request(&Request::Headers(Headers {
|
||||||
block_num: 1,
|
start: 1.into(),
|
||||||
block_hash: provider.client.chain_info().genesis_hash,
|
|
||||||
max: 1000,
|
max: 1000,
|
||||||
skip: 0,
|
skip: 0,
|
||||||
reverse: false,
|
reverse: false,
|
||||||
@ -288,8 +298,7 @@ fn get_block_headers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let request = Headers {
|
let request = Headers {
|
||||||
block_num: 1,
|
start: 1.into(),
|
||||||
block_hash: provider.client.block_hash(BlockId::Number(1)).unwrap(),
|
|
||||||
max: 10,
|
max: 10,
|
||||||
skip: 0,
|
skip: 0,
|
||||||
reverse: false,
|
reverse: false,
|
||||||
@ -303,9 +312,9 @@ fn get_block_headers() {
|
|||||||
|
|
||||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10);
|
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10);
|
||||||
|
|
||||||
let mut response_stream = RlpStream::new_list(12);
|
let mut response_stream = RlpStream::new_list(3);
|
||||||
|
|
||||||
response_stream.append(&req_id).append(&new_buf);
|
response_stream.append(&req_id).append(&new_buf).begin_list(10);
|
||||||
for header in headers {
|
for header in headers {
|
||||||
response_stream.append_raw(&header, 1);
|
response_stream.append_raw(&header, 1);
|
||||||
}
|
}
|
||||||
@ -350,9 +359,9 @@ fn get_block_bodies() {
|
|||||||
|
|
||||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10);
|
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10);
|
||||||
|
|
||||||
let mut response_stream = RlpStream::new_list(12);
|
let mut response_stream = RlpStream::new_list(3);
|
||||||
|
|
||||||
response_stream.append(&req_id).append(&new_buf);
|
response_stream.append(&req_id).append(&new_buf).begin_list(10);
|
||||||
for body in bodies {
|
for body in bodies {
|
||||||
response_stream.append_raw(&body, 1);
|
response_stream.append_raw(&body, 1);
|
||||||
}
|
}
|
||||||
@ -403,9 +412,9 @@ fn get_block_receipts() {
|
|||||||
|
|
||||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len());
|
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len());
|
||||||
|
|
||||||
let mut response_stream = RlpStream::new_list(2 + receipts.len());
|
let mut response_stream = RlpStream::new_list(3);
|
||||||
|
|
||||||
response_stream.append(&req_id).append(&new_buf);
|
response_stream.append(&req_id).append(&new_buf).begin_list(receipts.len());
|
||||||
for block_receipts in receipts {
|
for block_receipts in receipts {
|
||||||
response_stream.append_raw(&block_receipts, 1);
|
response_stream.append_raw(&block_receipts, 1);
|
||||||
}
|
}
|
||||||
@ -452,9 +461,9 @@ fn get_state_proofs() {
|
|||||||
|
|
||||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2);
|
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2);
|
||||||
|
|
||||||
let mut response_stream = RlpStream::new_list(4);
|
let mut response_stream = RlpStream::new_list(3);
|
||||||
|
|
||||||
response_stream.append(&req_id).append(&new_buf);
|
response_stream.append(&req_id).append(&new_buf).begin_list(2);
|
||||||
for proof in proofs {
|
for proof in proofs {
|
||||||
response_stream.append_raw(&proof, 1);
|
response_stream.append_raw(&proof, 1);
|
||||||
}
|
}
|
||||||
@ -501,9 +510,9 @@ fn get_contract_code() {
|
|||||||
|
|
||||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2);
|
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2);
|
||||||
|
|
||||||
let mut response_stream = RlpStream::new_list(4);
|
let mut response_stream = RlpStream::new_list(3);
|
||||||
|
|
||||||
response_stream.append(&req_id).append(&new_buf);
|
response_stream.append(&req_id).append(&new_buf).begin_list(2);
|
||||||
for code in codes {
|
for code in codes {
|
||||||
response_stream.append(&code);
|
response_stream.append(&code);
|
||||||
}
|
}
|
||||||
|
@ -97,16 +97,28 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn block_headers(&self, req: request::Headers) -> Vec<Bytes> {
|
fn block_headers(&self, req: request::Headers) -> Vec<Bytes> {
|
||||||
let best_num = self.chain_info().best_block_number;
|
use request::HashOrNumber;
|
||||||
let start_num = req.block_num;
|
use ethcore::views::HeaderView;
|
||||||
|
|
||||||
match self.block_hash(BlockId::Number(req.block_num)) {
|
let best_num = self.chain_info().best_block_number;
|
||||||
Some(hash) if hash == req.block_hash => {}
|
let start_num = match req.start {
|
||||||
_=> {
|
HashOrNumber::Number(start_num) => start_num,
|
||||||
trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash));
|
HashOrNumber::Hash(hash) => match self.block_header(BlockId::Hash(hash)) {
|
||||||
return vec![]
|
None => {
|
||||||
|
trace!(target: "les_provider", "Unknown block hash {} requested", hash);
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
Some(header) => {
|
||||||
|
let num = HeaderView::new(&header).number();
|
||||||
|
if req.max == 1 || self.block_hash(BlockId::Number(num)) != Some(hash) {
|
||||||
|
// Non-canonical header or single header requested.
|
||||||
|
return vec![header];
|
||||||
|
}
|
||||||
|
|
||||||
|
num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
(0u64..req.max as u64)
|
(0u64..req.max as u64)
|
||||||
.map(|x: u64| x.saturating_mul(req.skip + 1))
|
.map(|x: u64| x.saturating_mul(req.skip + 1))
|
||||||
|
@ -18,15 +18,34 @@
|
|||||||
|
|
||||||
use util::H256;
|
use util::H256;
|
||||||
|
|
||||||
|
/// Either a hash or a number.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "ipc", derive(Binary))]
|
||||||
|
pub enum HashOrNumber {
|
||||||
|
/// Block hash variant.
|
||||||
|
Hash(H256),
|
||||||
|
/// Block number variant.
|
||||||
|
Number(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<H256> for HashOrNumber {
|
||||||
|
fn from(hash: H256) -> Self {
|
||||||
|
HashOrNumber::Hash(hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for HashOrNumber {
|
||||||
|
fn from(num: u64) -> Self {
|
||||||
|
HashOrNumber::Number(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A request for block headers.
|
/// A request for block headers.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "ipc", derive(Binary))]
|
#[cfg_attr(feature = "ipc", derive(Binary))]
|
||||||
pub struct Headers {
|
pub struct Headers {
|
||||||
/// Starting block number
|
/// Starting block number or hash.
|
||||||
pub block_num: u64,
|
pub start: HashOrNumber,
|
||||||
/// Starting block hash. This and number could be combined but IPC codegen is
|
|
||||||
/// not robust enough to support it.
|
|
||||||
pub block_hash: H256,
|
|
||||||
/// The maximum amount of headers which can be returned.
|
/// The maximum amount of headers which can be returned.
|
||||||
pub max: usize,
|
pub max: usize,
|
||||||
/// The amount of headers to skip between each response entry.
|
/// The amount of headers to skip between each response entry.
|
||||||
|
@ -170,9 +170,6 @@
|
|||||||
"enode://cadc6e573b6bc2a9128f2f635ac0db3353e360b56deef239e9be7e7fce039502e0ec670b595f6288c0d2116812516ad6b6ff8d5728ff45eba176989e40dead1e@37.128.191.230:30303",
|
"enode://cadc6e573b6bc2a9128f2f635ac0db3353e360b56deef239e9be7e7fce039502e0ec670b595f6288c0d2116812516ad6b6ff8d5728ff45eba176989e40dead1e@37.128.191.230:30303",
|
||||||
"enode://595a9a06f8b9bc9835c8723b6a82105aea5d55c66b029b6d44f229d6d135ac3ecdd3e9309360a961ea39d7bee7bac5d03564077a4e08823acc723370aace65ec@46.20.235.22:30303",
|
"enode://595a9a06f8b9bc9835c8723b6a82105aea5d55c66b029b6d44f229d6d135ac3ecdd3e9309360a961ea39d7bee7bac5d03564077a4e08823acc723370aace65ec@46.20.235.22:30303",
|
||||||
"enode://029178d6d6f9f8026fc0bc17d5d1401aac76ec9d86633bba2320b5eed7b312980c0a210b74b20c4f9a8b0b2bf884b111fa9ea5c5f916bb9bbc0e0c8640a0f56c@216.158.85.185:30303",
|
"enode://029178d6d6f9f8026fc0bc17d5d1401aac76ec9d86633bba2320b5eed7b312980c0a210b74b20c4f9a8b0b2bf884b111fa9ea5c5f916bb9bbc0e0c8640a0f56c@216.158.85.185:30303",
|
||||||
"enode://84f5d5957b4880a8b0545e32e05472318898ad9fc8ebe1d56c90c12334a98e12351eccfdf3a2bf72432ac38b57e9d348400d17caa083879ade3822390f89773f@10.1.52.78:30303",
|
|
||||||
"enode://f90dc9b9bf7b8db97726b7849e175f1eb2707f3d8f281c929336e398dd89b0409fc6aeceb89e846278e9d3ecc3857cebfbe6758ff352ece6fe5d42921ee761db@10.1.173.87:30303",
|
|
||||||
"enode://6a868ced2dec399c53f730261173638a93a40214cf299ccf4d42a76e3fa54701db410669e8006347a4b3a74fa090bb35af0320e4bc8d04cf5b7f582b1db285f5@10.3.149.199:30303",
|
|
||||||
"enode://fdd1b9bb613cfbc200bba17ce199a9490edc752a833f88d4134bf52bb0d858aa5524cb3ec9366c7a4ef4637754b8b15b5dc913e4ed9fdb6022f7512d7b63f181@212.47.247.103:30303",
|
"enode://fdd1b9bb613cfbc200bba17ce199a9490edc752a833f88d4134bf52bb0d858aa5524cb3ec9366c7a4ef4637754b8b15b5dc913e4ed9fdb6022f7512d7b63f181@212.47.247.103:30303",
|
||||||
"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303",
|
"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303",
|
||||||
"enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303",
|
"enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303",
|
||||||
|
@ -266,7 +266,8 @@ impl<'x> OpenBlock<'x> {
|
|||||||
r.block.base.header.set_extra_data(extra_data);
|
r.block.base.header.set_extra_data(extra_data);
|
||||||
r.block.base.header.note_dirty();
|
r.block.base.header.note_dirty();
|
||||||
|
|
||||||
engine.populate_from_parent(&mut r.block.base.header, parent, gas_range_target.0, gas_range_target.1);
|
let gas_floor_target = ::std::cmp::max(gas_range_target.0, engine.params().min_gas_limit);
|
||||||
|
engine.populate_from_parent(&mut r.block.base.header, parent, gas_floor_target, gas_range_target.1);
|
||||||
engine.on_new_block(&mut r.block);
|
engine.on_new_block(&mut r.block);
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
@ -125,8 +125,9 @@ pub trait Engine : Sync + Send {
|
|||||||
self.verify_block_basic(header, None).and_then(|_| self.verify_block_unordered(header, None))
|
self.verify_block_basic(header, None).and_then(|_| self.verify_block_unordered(header, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Don't forget to call Super::populate_from_parent when subclassing & overriding.
|
/// Populate a header's fields based on its parent's header.
|
||||||
// TODO: consider including State in the params.
|
/// Takes gas floor and ceiling targets.
|
||||||
|
/// The gas floor target must not be lower than the engine's minimum gas limit.
|
||||||
fn populate_from_parent(&self, header: &mut Header, parent: &Header, _gas_floor_target: U256, _gas_ceil_target: U256) {
|
fn populate_from_parent(&self, header: &mut Header, parent: &Header, _gas_floor_target: U256, _gas_ceil_target: U256) {
|
||||||
header.set_difficulty(parent.difficulty().clone());
|
header.set_difficulty(parent.difficulty().clone());
|
||||||
header.set_gas_limit(parent.gas_limit().clone());
|
header.set_gas_limit(parent.gas_limit().clone());
|
||||||
|
@ -1092,20 +1092,6 @@ impl MinerService for Miner {
|
|||||||
fn chain_new_blocks(&self, chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) {
|
fn chain_new_blocks(&self, chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) {
|
||||||
trace!(target: "miner", "chain_new_blocks");
|
trace!(target: "miner", "chain_new_blocks");
|
||||||
|
|
||||||
fn fetch_transactions(chain: &MiningBlockChainClient, hash: &H256) -> Vec<SignedTransaction> {
|
|
||||||
let block = chain
|
|
||||||
.block(BlockId::Hash(*hash))
|
|
||||||
// Client should send message after commit to db and inserting to chain.
|
|
||||||
.expect("Expected in-chain blocks.");
|
|
||||||
let block = BlockView::new(&block);
|
|
||||||
let txs = block.transactions();
|
|
||||||
// populate sender
|
|
||||||
for tx in &txs {
|
|
||||||
let _sender = tx.sender();
|
|
||||||
}
|
|
||||||
txs
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. We ignore blocks that were `imported` (because it means that they are not in canon-chain, and transactions
|
// 1. We ignore blocks that were `imported` (because it means that they are not in canon-chain, and transactions
|
||||||
// should be still available in the queue.
|
// should be still available in the queue.
|
||||||
// 2. We ignore blocks that are `invalid` because it doesn't have any meaning in terms of the transactions that
|
// 2. We ignore blocks that are `invalid` because it doesn't have any meaning in terms of the transactions that
|
||||||
@ -1116,10 +1102,18 @@ impl MinerService for Miner {
|
|||||||
|
|
||||||
// Then import all transactions...
|
// Then import all transactions...
|
||||||
{
|
{
|
||||||
let out_of_chain = retracted
|
retracted.par_iter()
|
||||||
.par_iter()
|
.map(|hash| {
|
||||||
.map(|h| fetch_transactions(chain, h));
|
let block = chain.block(BlockId::Hash(*hash))
|
||||||
out_of_chain.for_each(|txs| {
|
.expect("Client is sending message after commit to db and inserting to chain; the block is available; qed");
|
||||||
|
let block = BlockView::new(&block);
|
||||||
|
let txs = block.transactions();
|
||||||
|
// populate sender
|
||||||
|
for tx in &txs {
|
||||||
|
let _sender = tx.sender();
|
||||||
|
}
|
||||||
|
txs
|
||||||
|
}).for_each(|txs| {
|
||||||
let mut transaction_queue = self.transaction_queue.lock();
|
let mut transaction_queue = self.transaction_queue.lock();
|
||||||
let _ = self.add_transactions_to_queue(
|
let _ = self.add_transactions_to_queue(
|
||||||
chain, txs, TransactionOrigin::RetractedBlock, &mut transaction_queue
|
chain, txs, TransactionOrigin::RetractedBlock, &mut transaction_queue
|
||||||
@ -1127,24 +1121,10 @@ impl MinerService for Miner {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...and at the end remove old ones
|
// ...and at the end remove the old ones
|
||||||
{
|
{
|
||||||
let in_chain = enacted
|
|
||||||
.par_iter()
|
|
||||||
.map(|h: &H256| fetch_transactions(chain, h));
|
|
||||||
|
|
||||||
in_chain.for_each(|mut txs| {
|
|
||||||
let mut transaction_queue = self.transaction_queue.lock();
|
let mut transaction_queue = self.transaction_queue.lock();
|
||||||
|
transaction_queue.remove_old(|sender| chain.latest_nonce(sender));
|
||||||
let to_remove = txs.drain(..)
|
|
||||||
.map(|tx| {
|
|
||||||
tx.sender().expect("Transaction is in block, so sender has to be defined.")
|
|
||||||
})
|
|
||||||
.collect::<HashSet<Address>>();
|
|
||||||
for sender in to_remove {
|
|
||||||
transaction_queue.remove_all(sender, chain.latest_nonce(&sender));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if enacted.len() > 0 {
|
if enacted.len() > 0 {
|
||||||
|
@ -81,6 +81,8 @@
|
|||||||
//! 3. `remove_all` is used to inform the queue about client (state) nonce changes.
|
//! 3. `remove_all` is used to inform the queue about client (state) nonce changes.
|
||||||
//! - It removes all transactions (either from `current` or `future`) with nonce < client nonce
|
//! - It removes all transactions (either from `current` or `future`) with nonce < client nonce
|
||||||
//! - It moves matching `future` transactions to `current`
|
//! - It moves matching `future` transactions to `current`
|
||||||
|
//! 4. `remove_old` is used as convenient method to update the state nonce for all senders in the queue.
|
||||||
|
//! - Invokes `remove_all` with latest state nonce for all senders.
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
@ -752,6 +754,26 @@ impl TransactionQueue {
|
|||||||
/// Removes all transactions from particular sender up to (excluding) given client (state) nonce.
|
/// Removes all transactions from particular sender up to (excluding) given client (state) nonce.
|
||||||
/// Client (State) Nonce = next valid nonce for this sender.
|
/// Client (State) Nonce = next valid nonce for this sender.
|
||||||
pub fn remove_all(&mut self, sender: Address, client_nonce: U256) {
|
pub fn remove_all(&mut self, sender: Address, client_nonce: U256) {
|
||||||
|
// Check if there is anything in current...
|
||||||
|
let should_check_in_current = self.current.by_address.row(&sender)
|
||||||
|
// If nonce == client_nonce nothing is changed
|
||||||
|
.and_then(|by_nonce| by_nonce.keys().find(|nonce| *nonce < &client_nonce))
|
||||||
|
.map(|_| ());
|
||||||
|
// ... or future
|
||||||
|
let should_check_in_future = self.future.by_address.row(&sender)
|
||||||
|
// if nonce == client_nonce we need to promote to current
|
||||||
|
.and_then(|by_nonce| by_nonce.keys().find(|nonce| *nonce <= &client_nonce))
|
||||||
|
.map(|_| ());
|
||||||
|
|
||||||
|
if should_check_in_current.or(should_check_in_future).is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.remove_all_internal(sender, client_nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Always updates future and moves transactions from current to future.
|
||||||
|
fn remove_all_internal(&mut self, sender: Address, client_nonce: U256) {
|
||||||
// We will either move transaction to future or remove it completely
|
// We will either move transaction to future or remove it completely
|
||||||
// so there will be no transactions from this sender in current
|
// so there will be no transactions from this sender in current
|
||||||
self.last_nonces.remove(&sender);
|
self.last_nonces.remove(&sender);
|
||||||
@ -765,6 +787,20 @@ impl TransactionQueue {
|
|||||||
assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len());
|
assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks the current nonce for all transactions' senders in the queue and removes the old transactions.
|
||||||
|
pub fn remove_old<F>(&mut self, fetch_nonce: F) where
|
||||||
|
F: Fn(&Address) -> U256,
|
||||||
|
{
|
||||||
|
let senders = self.current.by_address.keys()
|
||||||
|
.chain(self.future.by_address.keys())
|
||||||
|
.cloned()
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
for sender in senders {
|
||||||
|
self.remove_all(sender, fetch_nonce(&sender));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Penalize transactions from sender of transaction with given hash.
|
/// Penalize transactions from sender of transaction with given hash.
|
||||||
/// I.e. it should change the priority of the transaction in the queue.
|
/// I.e. it should change the priority of the transaction in the queue.
|
||||||
///
|
///
|
||||||
@ -847,7 +883,7 @@ impl TransactionQueue {
|
|||||||
if order.is_some() {
|
if order.is_some() {
|
||||||
// This will keep consistency in queue
|
// This will keep consistency in queue
|
||||||
// Moves all to future and then promotes a batch from current:
|
// Moves all to future and then promotes a batch from current:
|
||||||
self.remove_all(sender, current_nonce);
|
self.remove_all_internal(sender, current_nonce);
|
||||||
assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len());
|
assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2438,7 +2474,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_reject_transactions_below_bas_gas() {
|
fn should_reject_transactions_below_base_gas() {
|
||||||
// given
|
// given
|
||||||
let mut txq = TransactionQueue::default();
|
let mut txq = TransactionQueue::default();
|
||||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||||
@ -2457,4 +2493,26 @@ mod test {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_clear_all_old_transactions() {
|
||||||
|
// given
|
||||||
|
let mut txq = TransactionQueue::default();
|
||||||
|
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||||
|
let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into());
|
||||||
|
let nonce1 = tx1.nonce;
|
||||||
|
|
||||||
|
// Insert all transactions
|
||||||
|
txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap();
|
||||||
|
txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap();
|
||||||
|
txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap();
|
||||||
|
txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap();
|
||||||
|
assert_eq!(txq.top_transactions().len(), 4);
|
||||||
|
|
||||||
|
// when
|
||||||
|
txq.remove_old(|_| nonce1 + U256::one());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(txq.top_transactions().len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
34
js/npm/etherscan/README.md
Normal file
34
js/npm/etherscan/README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# @parity/etherscan
|
||||||
|
|
||||||
|
A thin, lightweight promise wrapper for the api.etherscan.io/apis service, exposing a common endpoint for use in JavaScript applications.
|
||||||
|
|
||||||
|
[https://github.com/ethcore/parity/tree/master/js/src/3rdparty/etherscan](https://github.com/ethcore/parity/tree/master/js/src/3rdparty/etherscan)
|
||||||
|
|
||||||
|
## usage
|
||||||
|
|
||||||
|
installation -
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save @parity/etherscan
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage -
|
||||||
|
|
||||||
|
```
|
||||||
|
const etherscan = require('@parity/etherscan');
|
||||||
|
|
||||||
|
// api calls goes here
|
||||||
|
```
|
||||||
|
|
||||||
|
## api
|
||||||
|
|
||||||
|
account (exposed on etherscan.account) -
|
||||||
|
|
||||||
|
- `balance(address)`
|
||||||
|
- `balances(addresses)` (array or addresses)
|
||||||
|
- `transactions(address, page)` (page offset starts at 0, returns 25)
|
||||||
|
|
||||||
|
stats (exposed on etherscan.stats) -
|
||||||
|
|
||||||
|
- `price()`
|
||||||
|
- `supply()`
|
33
js/npm/etherscan/package.json
Normal file
33
js/npm/etherscan/package.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@parity/etherscan",
|
||||||
|
"description": "The Parity Promise-based library for interfacing with Etherscan over HTTP",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "library.js",
|
||||||
|
"author": "Parity Team <admin@parity.io>",
|
||||||
|
"maintainers": [
|
||||||
|
"Jaco Greeff"
|
||||||
|
],
|
||||||
|
"contributors": [],
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ethcore/parity.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Ethereum",
|
||||||
|
"ABI",
|
||||||
|
"API",
|
||||||
|
"RPC",
|
||||||
|
"Parity",
|
||||||
|
"Promise"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"chai": "3.5.0",
|
||||||
|
"mocha": "3.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "~1.6.3"
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
# parity.js
|
# @parity/parity.js
|
||||||
|
|
||||||
Parity.js is a thin, fast, Promise-based wrapper around the Ethereum APIs.
|
Parity.js is a thin, fast, Promise-based wrapper around the Ethereum APIs.
|
||||||
|
|
||||||
|
[https://github.com/ethcore/parity/tree/master/js/src/api](https://github.com/ethcore/parity/tree/master/js/src/api)
|
||||||
|
|
||||||
## installation
|
## installation
|
||||||
|
|
||||||
Install the package with `npm install --save @parity/parity.js`
|
Install the package with `npm install --save @parity/parity.js`
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@parity/parity.js",
|
"name": "@parity/parity.js",
|
||||||
"description": "The Parity Promise-base API & ABI library for interfacing with Ethereum over RPC",
|
"description": "The Parity Promise-based API & ABI library for interfacing with Ethereum over RPC",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"main": "library.js",
|
"main": "library.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
@ -26,8 +26,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bignumber.js": "^2.3.0",
|
"bignumber.js": "~2.3.0",
|
||||||
"js-sha3": "^0.5.2",
|
"js-sha3": "~0.5.2",
|
||||||
"node-fetch": "^1.6.3"
|
"node-fetch": "~1.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
26
js/npm/parity/test/smoke.spec.js
Normal file
26
js/npm/parity/test/smoke.spec.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
|
||||||
|
const parity = require('../');
|
||||||
|
|
||||||
|
describe('load the Parity library', function () {
|
||||||
|
it('should no throw any error', () => {
|
||||||
|
expect(parity).to.be.ok;
|
||||||
|
|
||||||
|
expect(parity.Api).to.be.ok;
|
||||||
|
expect(parity.Abi).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
34
js/npm/shapeshift/README.md
Normal file
34
js/npm/shapeshift/README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# @parity/shapeshift
|
||||||
|
|
||||||
|
A thin ES6 promise wrapper around the shapeshift.io APIs as documented at https://shapeshift.io/api
|
||||||
|
|
||||||
|
[https://github.com/ethcore/parity/tree/master/js/src/3rdparty/shapeshift](https://github.com/ethcore/parity/tree/master/js/src/3rdparty/shapeshift)
|
||||||
|
|
||||||
|
## usage
|
||||||
|
|
||||||
|
installation -
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save @parity/shapeshift
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage -
|
||||||
|
|
||||||
|
```
|
||||||
|
const APIKEY = 'private affiliate key or undefined';
|
||||||
|
const shapeshift = require('@parity/shapeshift')(APIKEY);
|
||||||
|
|
||||||
|
// api calls goes here
|
||||||
|
```
|
||||||
|
|
||||||
|
## api
|
||||||
|
|
||||||
|
queries -
|
||||||
|
|
||||||
|
- `getCoins()` [https://shapeshift.io/api#api-104](https://shapeshift.io/api#api-104)
|
||||||
|
- `getMarketInfo(pair)` [https://shapeshift.io/api#api-103](https://shapeshift.io/api#api-103)
|
||||||
|
- `getStatus(depositAddress)` [https://shapeshift.io/api#api-5](https://shapeshift.io/api#api-5)
|
||||||
|
|
||||||
|
transactions -
|
||||||
|
|
||||||
|
- `shift(toAddress, returnAddress, pair)` [https://shapeshift.io/api#api-7](https://shapeshift.io/api#api-7)
|
31
js/npm/shapeshift/package.json
Normal file
31
js/npm/shapeshift/package.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "@parity/shapeshift",
|
||||||
|
"description": "The Parity Promise-based library for interfacing with ShapeShift over HTTP",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "library.js",
|
||||||
|
"author": "Parity Team <admin@parity.io>",
|
||||||
|
"maintainers": [
|
||||||
|
"Jaco Greeff"
|
||||||
|
],
|
||||||
|
"contributors": [],
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ethcore/parity.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Ethereum",
|
||||||
|
"ABI",
|
||||||
|
"API",
|
||||||
|
"RPC",
|
||||||
|
"Parity",
|
||||||
|
"Promise"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "~1.6.3"
|
||||||
|
}
|
||||||
|
}
|
29
js/npm/test/mocha.config.js
Normal file
29
js/npm/test/mocha.config.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
|
||||||
|
const chai = require('chai');
|
||||||
|
// const chaiAsPromised from 'chai-as-promised';
|
||||||
|
// const chaiEnzyme from 'chai-enzyme';
|
||||||
|
// const sinonChai from 'sinon-chai';
|
||||||
|
|
||||||
|
// chai.use(chaiAsPromised);
|
||||||
|
// chai.use(chaiEnzyme());
|
||||||
|
// chai.use(sinonChai);
|
||||||
|
|
||||||
|
// expose expect to global so we won't have to manually import & define it in every test
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
module.exports = {};
|
1
js/npm/test/mocha.opts
Normal file
1
js/npm/test/mocha.opts
Normal file
@ -0,0 +1 @@
|
|||||||
|
-r ./test/mocha.config
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "0.2.105",
|
"version": "0.2.112",
|
||||||
"main": "release/index.js",
|
"main": "release/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"test": "NODE_ENV=test mocha 'src/**/*.spec.js'",
|
"test": "NODE_ENV=test mocha 'src/**/*.spec.js'",
|
||||||
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'",
|
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'",
|
||||||
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",
|
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",
|
||||||
"test:npm": "(cd .npmjs && npm i) && node test/npmLibrary && (rm -rf .npmjs/node_modules)",
|
"test:npm": "(cd .npmjs && npm i) && node test/npmParity && (rm -rf .npmjs/node_modules)",
|
||||||
"prepush": "npm run lint:cached"
|
"prepush": "npm run lint:cached"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -59,6 +59,7 @@
|
|||||||
"babel-plugin-transform-runtime": "6.15.0",
|
"babel-plugin-transform-runtime": "6.15.0",
|
||||||
"babel-plugin-webpack-alias": "2.1.2",
|
"babel-plugin-webpack-alias": "2.1.2",
|
||||||
"babel-polyfill": "6.20.0",
|
"babel-polyfill": "6.20.0",
|
||||||
|
"babel-preset-env": "1.0.2",
|
||||||
"babel-preset-es2015": "6.18.0",
|
"babel-preset-es2015": "6.18.0",
|
||||||
"babel-preset-es2016": "6.16.0",
|
"babel-preset-es2016": "6.16.0",
|
||||||
"babel-preset-es2017": "6.16.0",
|
"babel-preset-es2017": "6.16.0",
|
||||||
@ -146,6 +147,7 @@
|
|||||||
"mobx-react-devtools": "4.2.10",
|
"mobx-react-devtools": "4.2.10",
|
||||||
"moment": "2.17.0",
|
"moment": "2.17.0",
|
||||||
"phoneformat.js": "1.0.3",
|
"phoneformat.js": "1.0.3",
|
||||||
|
"push.js": "0.0.11",
|
||||||
"qs": "6.3.0",
|
"qs": "6.3.0",
|
||||||
"react": "15.4.1",
|
"react": "15.4.1",
|
||||||
"react-ace": "4.1.0",
|
"react-ace": "4.1.0",
|
||||||
|
33
js/scripts/dryrun-npm.sh
Executable file
33
js/scripts/dryrun-npm.sh
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# variables
|
||||||
|
PACKAGES=( "parity" "etherscan" "shapeshift" )
|
||||||
|
|
||||||
|
# change into the build directory
|
||||||
|
BASEDIR=`dirname $0`
|
||||||
|
cd $BASEDIR/..
|
||||||
|
|
||||||
|
# build all packages
|
||||||
|
echo "*** Building packages for npmjs"
|
||||||
|
echo "$NPM_TOKEN" >> ~/.npmrc
|
||||||
|
|
||||||
|
for PACKAGE in ${PACKAGES[@]}
|
||||||
|
do
|
||||||
|
echo "*** Building $PACKAGE"
|
||||||
|
LIBRARY=$PACKAGE npm run ci:build:npm
|
||||||
|
DIRECTORY=.npmjs/$PACKAGE
|
||||||
|
|
||||||
|
cd $DIRECTORY
|
||||||
|
echo "*** Executing $PACKAGE tests from $DIRECTORY"
|
||||||
|
npm test
|
||||||
|
|
||||||
|
echo "*** Publishing $PACKAGE from $DIRECTORY"
|
||||||
|
echo "npm publish --access public || true"
|
||||||
|
cd ../..
|
||||||
|
|
||||||
|
done
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# exit with exit code
|
||||||
|
exit 0
|
@ -3,7 +3,7 @@ set -e
|
|||||||
|
|
||||||
# variables
|
# variables
|
||||||
UTCDATE=`date -u "+%Y%m%d-%H%M%S"`
|
UTCDATE=`date -u "+%Y%m%d-%H%M%S"`
|
||||||
PACKAGES=( "parity.js" )
|
PACKAGES=( "parity" "etherscan" "shapeshift" )
|
||||||
BRANCH=$CI_BUILD_REF_NAME
|
BRANCH=$CI_BUILD_REF_NAME
|
||||||
GIT_JS_PRECOMPILED="https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/js-precompiled.git"
|
GIT_JS_PRECOMPILED="https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/js-precompiled.git"
|
||||||
GIT_PARITY="https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/parity.git"
|
GIT_PARITY="https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/parity.git"
|
||||||
@ -59,19 +59,27 @@ git reset --hard origin/$BRANCH 2>$GITLOG
|
|||||||
|
|
||||||
if [ "$BRANCH" == "master" ]; then
|
if [ "$BRANCH" == "master" ]; then
|
||||||
cd js
|
cd js
|
||||||
|
|
||||||
echo "*** Bumping package.json patch version"
|
echo "*** Bumping package.json patch version"
|
||||||
npm --no-git-tag-version version
|
npm --no-git-tag-version version
|
||||||
npm version patch
|
npm version patch
|
||||||
|
|
||||||
echo "*** Building packages for npmjs"
|
echo "*** Building packages for npmjs"
|
||||||
# echo -e "$NPM_USERNAME\n$NPM_PASSWORD\n$NPM_EMAIL" | npm login
|
|
||||||
echo "$NPM_TOKEN" >> ~/.npmrc
|
echo "$NPM_TOKEN" >> ~/.npmrc
|
||||||
npm run ci:build:npm
|
|
||||||
|
|
||||||
echo "*** Publishing $PACKAGE to npmjs"
|
for PACKAGE in ${PACKAGES[@]}
|
||||||
cd .npmjs
|
do
|
||||||
|
echo "*** Building $PACKAGE"
|
||||||
|
LIBRARY=$PACKAGE npm run ci:build:npm
|
||||||
|
DIRECTORY=.npmjs/$PACKAGE
|
||||||
|
|
||||||
|
echo "*** Publishing $PACKAGE from $DIRECTORY"
|
||||||
|
cd $DIRECTORY
|
||||||
npm publish --access public || true
|
npm publish --access public || true
|
||||||
cd ../..
|
cd ../..
|
||||||
|
done
|
||||||
|
|
||||||
|
cd ..
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "*** Updating cargo parity-ui-precompiled#$PRECOMPILED_HASH"
|
echo "*** Updating cargo parity-ui-precompiled#$PRECOMPILED_HASH"
|
||||||
|
6
js/src/3rdparty/etherscan/account.spec.js
vendored
6
js/src/3rdparty/etherscan/account.spec.js
vendored
@ -14,11 +14,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import etherscan from './';
|
const etherscan = require('./');
|
||||||
|
|
||||||
const TESTADDR = '0xbf885e2b55c6bcc84556a3c5f07d3040833c8d00';
|
const TESTADDR = '0xbf885e2b55c6bcc84556a3c5f07d3040833c8d00';
|
||||||
|
|
||||||
describe.skip('etherscan/account', () => {
|
describe.skip('etherscan/account', function () {
|
||||||
|
this.timeout(60 * 1000);
|
||||||
|
|
||||||
const checkBalance = function (balance, addr) {
|
const checkBalance = function (balance, addr) {
|
||||||
expect(balance).to.be.ok;
|
expect(balance).to.be.ok;
|
||||||
expect(balance.account).to.equal(addr);
|
expect(balance.account).to.equal(addr);
|
||||||
|
6
js/src/3rdparty/etherscan/stats.spec.js
vendored
6
js/src/3rdparty/etherscan/stats.spec.js
vendored
@ -14,9 +14,11 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import etherscan from './';
|
const etherscan = require('./');
|
||||||
|
|
||||||
|
describe.skip('etherscan/stats', function () {
|
||||||
|
this.timeout(60 * 1000);
|
||||||
|
|
||||||
describe.skip('etherscan/stats', () => {
|
|
||||||
it('retrieves the latest price', () => {
|
it('retrieves the latest price', () => {
|
||||||
return etherscan.stats
|
return etherscan.stats
|
||||||
.price()
|
.price()
|
||||||
|
17
js/src/3rdparty/shapeshift/helpers.spec.js
vendored
17
js/src/3rdparty/shapeshift/helpers.spec.js
vendored
@ -14,22 +14,15 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import chai from 'chai';
|
const nock = require('nock');
|
||||||
import nock from 'nock';
|
|
||||||
|
|
||||||
global.expect = chai.expect; // eslint-disable-line no-undef
|
const ShapeShift = require('./');
|
||||||
|
const initShapeshift = (ShapeShift.default || ShapeShift);
|
||||||
import 'isomorphic-fetch';
|
|
||||||
import es6Promise from 'es6-promise';
|
|
||||||
es6Promise.polyfill();
|
|
||||||
|
|
||||||
import initShapeshift from './';
|
|
||||||
import initRpc from './rpc';
|
|
||||||
|
|
||||||
const APIKEY = '0x123454321';
|
const APIKEY = '0x123454321';
|
||||||
|
|
||||||
const shapeshift = initShapeshift(APIKEY);
|
const shapeshift = initShapeshift(APIKEY);
|
||||||
const rpc = initRpc(APIKEY);
|
const rpc = shapeshift.getRpc();
|
||||||
|
|
||||||
function mockget (requests) {
|
function mockget (requests) {
|
||||||
let scope = nock(rpc.ENDPOINT);
|
let scope = nock(rpc.ENDPOINT);
|
||||||
@ -62,7 +55,7 @@ function mockpost (requests) {
|
|||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
module.exports = {
|
||||||
APIKEY,
|
APIKEY,
|
||||||
mockget,
|
mockget,
|
||||||
mockpost,
|
mockpost,
|
||||||
|
7
js/src/3rdparty/shapeshift/rpc.spec.js
vendored
7
js/src/3rdparty/shapeshift/rpc.spec.js
vendored
@ -14,7 +14,12 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { APIKEY, mockget, mockpost, rpc } from './helpers.spec.js';
|
const helpers = require('./helpers.spec.js');
|
||||||
|
|
||||||
|
const APIKEY = helpers.APIKEY;
|
||||||
|
const mockget = helpers.mockget;
|
||||||
|
const mockpost = helpers.mockpost;
|
||||||
|
const rpc = helpers.rpc;
|
||||||
|
|
||||||
describe('shapeshift/rpc', () => {
|
describe('shapeshift/rpc', () => {
|
||||||
describe('GET', () => {
|
describe('GET', () => {
|
||||||
|
5
js/src/3rdparty/shapeshift/shapeshift.js
vendored
5
js/src/3rdparty/shapeshift/shapeshift.js
vendored
@ -26,6 +26,10 @@ export default function (rpc) {
|
|||||||
return rpc.get(`marketinfo/${pair}`);
|
return rpc.get(`marketinfo/${pair}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRpc () {
|
||||||
|
return rpc;
|
||||||
|
}
|
||||||
|
|
||||||
function getStatus (depositAddress) {
|
function getStatus (depositAddress) {
|
||||||
return rpc.get(`txStat/${depositAddress}`);
|
return rpc.get(`txStat/${depositAddress}`);
|
||||||
}
|
}
|
||||||
@ -103,6 +107,7 @@ export default function (rpc) {
|
|||||||
return {
|
return {
|
||||||
getCoins,
|
getCoins,
|
||||||
getMarketInfo,
|
getMarketInfo,
|
||||||
|
getRpc,
|
||||||
getStatus,
|
getStatus,
|
||||||
shift,
|
shift,
|
||||||
subscribe,
|
subscribe,
|
||||||
|
@ -14,7 +14,11 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { mockget, mockpost, shapeshift } from './helpers.spec.js';
|
const helpers = require('./helpers.spec.js');
|
||||||
|
|
||||||
|
const mockget = helpers.mockget;
|
||||||
|
const mockpost = helpers.mockpost;
|
||||||
|
const shapeshift = helpers.shapeshift;
|
||||||
|
|
||||||
describe('shapeshift/calls', () => {
|
describe('shapeshift/calls', () => {
|
||||||
describe('getCoins', () => {
|
describe('getCoins', () => {
|
||||||
|
@ -62,6 +62,10 @@ export default class Contracts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create (api) {
|
static create (api) {
|
||||||
|
if (instance) {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
return new Contracts(api);
|
return new Contracts(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
|
|||||||
const api = new SecureApi(`ws://${parityUrl}`, token);
|
const api = new SecureApi(`ws://${parityUrl}`, token);
|
||||||
ContractInstances.create(api);
|
ContractInstances.create(api);
|
||||||
|
|
||||||
const store = initStore(api);
|
const store = initStore(api, hashHistory);
|
||||||
store.dispatch({ type: 'initAll', api });
|
store.dispatch({ type: 'initAll', api });
|
||||||
store.dispatch(setApi(api));
|
store.dispatch(setApi(api));
|
||||||
|
|
||||||
|
34
js/src/library.etherscan.js
Normal file
34
js/src/library.etherscan.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 'babel-polyfill/dist/polyfill.js';
|
||||||
|
import es6Promise from 'es6-promise';
|
||||||
|
es6Promise.polyfill();
|
||||||
|
|
||||||
|
const isNode = typeof global !== 'undefined' && typeof global !== 'undefined';
|
||||||
|
const isBrowser = typeof self !== 'undefined' && typeof self.window !== 'undefined';
|
||||||
|
|
||||||
|
if (isBrowser) {
|
||||||
|
require('whatwg-fetch');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNode) {
|
||||||
|
global.fetch = require('node-fetch');
|
||||||
|
}
|
||||||
|
|
||||||
|
import Etherscan from './3rdparty/etherscan';
|
||||||
|
|
||||||
|
module.exports = Etherscan;
|
34
js/src/library.shapeshift.js
Normal file
34
js/src/library.shapeshift.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 'babel-polyfill/dist/polyfill.js';
|
||||||
|
import es6Promise from 'es6-promise';
|
||||||
|
es6Promise.polyfill();
|
||||||
|
|
||||||
|
const isNode = typeof global !== 'undefined' && typeof global !== 'undefined';
|
||||||
|
const isBrowser = typeof self !== 'undefined' && typeof self.window !== 'undefined';
|
||||||
|
|
||||||
|
if (isBrowser) {
|
||||||
|
require('whatwg-fetch');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNode) {
|
||||||
|
global.fetch = require('node-fetch');
|
||||||
|
}
|
||||||
|
|
||||||
|
import ShapeShift from './3rdparty/shapeshift';
|
||||||
|
|
||||||
|
module.exports = ShapeShift;
|
@ -15,7 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { Redirect, Router, Route } from 'react-router';
|
import { Redirect, Router, Route, IndexRoute } from 'react-router';
|
||||||
|
|
||||||
import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Wallet, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from '~/views';
|
import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Wallet, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from '~/views';
|
||||||
|
|
||||||
@ -26,6 +26,23 @@ export default class MainApplication extends Component {
|
|||||||
routerHistory: PropTypes.any.isRequired
|
routerHistory: PropTypes.any.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleDeprecatedRoute = (nextState, replace) => {
|
||||||
|
const { address } = nextState.params;
|
||||||
|
const redirectMap = {
|
||||||
|
account: 'accounts',
|
||||||
|
address: 'addresses',
|
||||||
|
contract: 'contracts'
|
||||||
|
};
|
||||||
|
|
||||||
|
const oldRoute = nextState.routes[0].path;
|
||||||
|
const newRoute = Object.keys(redirectMap).reduce((newRoute, key) => {
|
||||||
|
return newRoute.replace(new RegExp(`^/${key}`), '/' + redirectMap[key]);
|
||||||
|
}, oldRoute);
|
||||||
|
|
||||||
|
console.warn(`Route "${oldRoute}" is deprecated. Please use "${newRoute}"`);
|
||||||
|
replace(newRoute.replace(':address', address));
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { routerHistory } = this.props;
|
const { routerHistory } = this.props;
|
||||||
|
|
||||||
@ -34,26 +51,46 @@ export default class MainApplication extends Component {
|
|||||||
<Redirect from='/' to='/accounts' />
|
<Redirect from='/' to='/accounts' />
|
||||||
<Redirect from='/auth' to='/accounts' query={ {} } />
|
<Redirect from='/auth' to='/accounts' query={ {} } />
|
||||||
<Redirect from='/settings' to='/settings/views' />
|
<Redirect from='/settings' to='/settings/views' />
|
||||||
|
|
||||||
|
{ /** Backward Compatible links */ }
|
||||||
|
<Route path='/account/:address' onEnter={ this.handleDeprecatedRoute } />
|
||||||
|
<Route path='/address/:address' onEnter={ this.handleDeprecatedRoute } />
|
||||||
|
<Route path='/contract/:address' onEnter={ this.handleDeprecatedRoute } />
|
||||||
|
|
||||||
<Route path='/' component={ Application }>
|
<Route path='/' component={ Application }>
|
||||||
<Route path='accounts' component={ Accounts } />
|
<Route path='accounts'>
|
||||||
<Route path='account/:address' component={ Account } />
|
<IndexRoute component={ Accounts } />
|
||||||
<Route path='wallet/:address' component={ Wallet } />
|
<Route path=':address' component={ Account } />
|
||||||
<Route path='addresses' component={ Addresses } />
|
<Route path='/wallet/:address' component={ Wallet } />
|
||||||
<Route path='address/:address' component={ Address } />
|
</Route>
|
||||||
|
|
||||||
|
<Route path='addresses'>
|
||||||
|
<IndexRoute component={ Addresses } />
|
||||||
|
<Route path=':address' component={ Address } />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path='apps' component={ Dapps } />
|
<Route path='apps' component={ Dapps } />
|
||||||
<Route path='app/:id' component={ Dapp } />
|
<Route path='app/:id' component={ Dapp } />
|
||||||
<Route path='contracts' component={ Contracts } />
|
|
||||||
<Route path='contracts/write' component={ WriteContract } />
|
<Route path='contracts'>
|
||||||
<Route path='contract/:address' component={ Contract } />
|
<IndexRoute component={ Contracts } />
|
||||||
|
<Route path='develop' component={ WriteContract } />
|
||||||
|
<Route path=':address' component={ Contract } />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path='settings' component={ Settings }>
|
<Route path='settings' component={ Settings }>
|
||||||
<Route path='background' component={ SettingsBackground } />
|
<Route path='background' component={ SettingsBackground } />
|
||||||
<Route path='proxy' component={ SettingsProxy } />
|
<Route path='proxy' component={ SettingsProxy } />
|
||||||
<Route path='views' component={ SettingsViews } />
|
<Route path='views' component={ SettingsViews } />
|
||||||
<Route path='parity' component={ SettingsParity } />
|
<Route path='parity' component={ SettingsParity } />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path='signer' component={ Signer } />
|
<Route path='signer' component={ Signer } />
|
||||||
<Route path='status' component={ Status } />
|
|
||||||
<Route path='status/:subpage' component={ Status } />
|
<Route path='status'>
|
||||||
|
<IndexRoute component={ Status } />
|
||||||
|
<Route path=':subpage' component={ Status } />
|
||||||
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
@ -28,6 +28,7 @@ export default class AddAddress extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
contacts: PropTypes.object.isRequired,
|
contacts: PropTypes.object.isRequired,
|
||||||
|
address: PropTypes.string,
|
||||||
onClose: PropTypes.func
|
onClose: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -39,6 +40,12 @@ export default class AddAddress extends Component {
|
|||||||
description: ''
|
description: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
if (this.props.address) {
|
||||||
|
this.onEditAddress(null, this.props.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -77,6 +84,8 @@ export default class AddAddress extends Component {
|
|||||||
hint='the network address for the entry'
|
hint='the network address for the entry'
|
||||||
error={ addressError }
|
error={ addressError }
|
||||||
value={ address }
|
value={ address }
|
||||||
|
disabled={ !!this.props.address }
|
||||||
|
allowCopy={ false }
|
||||||
onChange={ this.onEditAddress } />
|
onChange={ this.onEditAddress } />
|
||||||
<Input
|
<Input
|
||||||
label='address name'
|
label='address name'
|
||||||
|
@ -30,10 +30,12 @@ export default class WalletInfo extends Component {
|
|||||||
owners: PropTypes.array.isRequired,
|
owners: PropTypes.array.isRequired,
|
||||||
required: PropTypes.oneOfType([
|
required: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
|
PropTypes.object,
|
||||||
PropTypes.number
|
PropTypes.number
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
daylimit: PropTypes.oneOfType([
|
daylimit: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
|
PropTypes.object,
|
||||||
PropTypes.number
|
PropTypes.number
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
|
|
||||||
|
@ -28,26 +28,25 @@ export default class DetailsStep extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
|
|
||||||
onFromAddressChange: PropTypes.func.isRequired,
|
|
||||||
onNameChange: PropTypes.func.isRequired,
|
|
||||||
onDescriptionChange: PropTypes.func.isRequired,
|
|
||||||
onAbiChange: PropTypes.func.isRequired,
|
onAbiChange: PropTypes.func.isRequired,
|
||||||
onCodeChange: PropTypes.func.isRequired,
|
onCodeChange: PropTypes.func.isRequired,
|
||||||
onParamsChange: PropTypes.func.isRequired,
|
onDescriptionChange: PropTypes.func.isRequired,
|
||||||
|
onFromAddressChange: PropTypes.func.isRequired,
|
||||||
onInputsChange: PropTypes.func.isRequired,
|
onInputsChange: PropTypes.func.isRequired,
|
||||||
|
onNameChange: PropTypes.func.isRequired,
|
||||||
|
onParamsChange: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
abi: PropTypes.string,
|
||||||
|
abiError: PropTypes.string,
|
||||||
|
balances: PropTypes.object,
|
||||||
|
code: PropTypes.string,
|
||||||
|
codeError: PropTypes.string,
|
||||||
|
description: PropTypes.string,
|
||||||
|
descriptionError: PropTypes.string,
|
||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
fromAddressError: PropTypes.string,
|
fromAddressError: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
nameError: PropTypes.string,
|
nameError: PropTypes.string,
|
||||||
description: PropTypes.string,
|
|
||||||
descriptionError: PropTypes.string,
|
|
||||||
abi: PropTypes.string,
|
|
||||||
abiError: PropTypes.string,
|
|
||||||
code: PropTypes.string,
|
|
||||||
codeError: PropTypes.string,
|
|
||||||
|
|
||||||
readOnly: PropTypes.bool
|
readOnly: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -77,6 +76,7 @@ export default class DetailsStep extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
accounts,
|
accounts,
|
||||||
|
balances,
|
||||||
readOnly,
|
readOnly,
|
||||||
|
|
||||||
fromAddress, fromAddressError,
|
fromAddress, fromAddressError,
|
||||||
@ -94,24 +94,28 @@ export default class DetailsStep extends Component {
|
|||||||
<AddressSelect
|
<AddressSelect
|
||||||
label='from account (contract owner)'
|
label='from account (contract owner)'
|
||||||
hint='the owner account for this contract'
|
hint='the owner account for this contract'
|
||||||
value={ fromAddress }
|
|
||||||
error={ fromAddressError }
|
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
onChange={ this.onFromAddressChange } />
|
balances={ balances }
|
||||||
|
error={ fromAddressError }
|
||||||
|
onChange={ this.onFromAddressChange }
|
||||||
|
value={ fromAddress }
|
||||||
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label='contract name'
|
label='contract name'
|
||||||
hint='a name for the deployed contract'
|
hint='a name for the deployed contract'
|
||||||
error={ nameError }
|
error={ nameError }
|
||||||
|
onChange={ this.onNameChange }
|
||||||
value={ name || '' }
|
value={ name || '' }
|
||||||
onChange={ this.onNameChange } />
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label='contract description (optional)'
|
label='contract description (optional)'
|
||||||
hint='a description for the contract'
|
hint='a description for the contract'
|
||||||
error={ descriptionError }
|
error={ descriptionError }
|
||||||
|
onChange={ this.onDescriptionChange }
|
||||||
value={ description }
|
value={ description }
|
||||||
onChange={ this.onDescriptionChange } />
|
/>
|
||||||
|
|
||||||
{ this.renderContractSelect() }
|
{ this.renderContractSelect() }
|
||||||
|
|
||||||
@ -119,17 +123,19 @@ export default class DetailsStep extends Component {
|
|||||||
label='abi / solc combined-output'
|
label='abi / solc combined-output'
|
||||||
hint='the abi of the contract to deploy or solc combined-output'
|
hint='the abi of the contract to deploy or solc combined-output'
|
||||||
error={ abiError }
|
error={ abiError }
|
||||||
value={ solcOutput }
|
|
||||||
onChange={ this.onSolcChange }
|
onChange={ this.onSolcChange }
|
||||||
onSubmit={ this.onSolcSubmit }
|
onSubmit={ this.onSolcSubmit }
|
||||||
readOnly={ readOnly } />
|
readOnly={ readOnly }
|
||||||
|
value={ solcOutput }
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
label='code'
|
label='code'
|
||||||
hint='the compiled code of the contract to deploy'
|
hint='the compiled code of the contract to deploy'
|
||||||
error={ codeError }
|
error={ codeError }
|
||||||
value={ code }
|
|
||||||
onSubmit={ this.onCodeChange }
|
onSubmit={ this.onCodeChange }
|
||||||
readOnly={ readOnly || solc } />
|
readOnly={ readOnly || solc }
|
||||||
|
value={ code }
|
||||||
|
/>
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui';
|
import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui';
|
||||||
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
||||||
@ -36,7 +38,7 @@ const STEPS = {
|
|||||||
COMPLETED: { title: 'completed' }
|
COMPLETED: { title: 'completed' }
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class DeployContract extends Component {
|
class DeployContract extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired,
|
api: PropTypes.object.isRequired,
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired
|
||||||
@ -45,6 +47,7 @@ export default class DeployContract extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
|
balances: PropTypes.object,
|
||||||
abi: PropTypes.string,
|
abi: PropTypes.string,
|
||||||
code: PropTypes.string,
|
code: PropTypes.string,
|
||||||
readOnly: PropTypes.bool,
|
readOnly: PropTypes.bool,
|
||||||
@ -192,7 +195,7 @@ export default class DeployContract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderStep () {
|
renderStep () {
|
||||||
const { accounts, readOnly } = this.props;
|
const { accounts, readOnly, balances } = this.props;
|
||||||
const { address, deployError, step, deployState, txhash, rejected } = this.state;
|
const { address, deployError, step, deployState, txhash, rejected } = this.state;
|
||||||
|
|
||||||
if (deployError) {
|
if (deployError) {
|
||||||
@ -216,6 +219,7 @@ export default class DeployContract extends Component {
|
|||||||
<DetailsStep
|
<DetailsStep
|
||||||
{ ...this.state }
|
{ ...this.state }
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
|
balances={ balances }
|
||||||
readOnly={ readOnly }
|
readOnly={ readOnly }
|
||||||
onFromAddressChange={ this.onFromAddressChange }
|
onFromAddressChange={ this.onFromAddressChange }
|
||||||
onDescriptionChange={ this.onDescriptionChange }
|
onDescriptionChange={ this.onDescriptionChange }
|
||||||
@ -394,3 +398,17 @@ export default class DeployContract extends Component {
|
|||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapStateToProps (initState, initProps) {
|
||||||
|
const fromAddresses = Object.keys(initProps.accounts);
|
||||||
|
|
||||||
|
return (state) => {
|
||||||
|
const balances = pick(state.balances.balances, fromAddresses);
|
||||||
|
return { balances };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps
|
||||||
|
)(DeployContract);
|
||||||
|
|
||||||
|
@ -32,25 +32,27 @@ export default class DetailsStep extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
contract: PropTypes.object.isRequired,
|
contract: PropTypes.object.isRequired,
|
||||||
amount: PropTypes.string,
|
|
||||||
amountError: PropTypes.string,
|
|
||||||
onAmountChange: PropTypes.func.isRequired,
|
onAmountChange: PropTypes.func.isRequired,
|
||||||
fromAddress: PropTypes.string,
|
|
||||||
fromAddressError: PropTypes.string,
|
|
||||||
gasEdit: PropTypes.bool,
|
|
||||||
onFromAddressChange: PropTypes.func.isRequired,
|
onFromAddressChange: PropTypes.func.isRequired,
|
||||||
func: PropTypes.object,
|
onValueChange: PropTypes.func.isRequired,
|
||||||
funcError: PropTypes.string,
|
|
||||||
onFuncChange: PropTypes.func,
|
|
||||||
onGasEditClick: PropTypes.func,
|
|
||||||
values: PropTypes.array.isRequired,
|
values: PropTypes.array.isRequired,
|
||||||
valuesError: PropTypes.array.isRequired,
|
valuesError: PropTypes.array.isRequired,
|
||||||
warning: PropTypes.string,
|
|
||||||
onValueChange: PropTypes.func.isRequired
|
amount: PropTypes.string,
|
||||||
|
amountError: PropTypes.string,
|
||||||
|
balances: PropTypes.object,
|
||||||
|
fromAddress: PropTypes.string,
|
||||||
|
fromAddressError: PropTypes.string,
|
||||||
|
func: PropTypes.object,
|
||||||
|
funcError: PropTypes.string,
|
||||||
|
gasEdit: PropTypes.bool,
|
||||||
|
onFuncChange: PropTypes.func,
|
||||||
|
onGasEditClick: PropTypes.func,
|
||||||
|
warning: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, amount, amountError, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props;
|
const { accounts, amount, amountError, balances, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
@ -61,6 +63,7 @@ export default class DetailsStep extends Component {
|
|||||||
value={ fromAddress }
|
value={ fromAddress }
|
||||||
error={ fromAddressError }
|
error={ fromAddressError }
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
|
balances={ balances }
|
||||||
onChange={ onFromAddressChange } />
|
onChange={ onFromAddressChange } />
|
||||||
{ this.renderFunctionSelect() }
|
{ this.renderFunctionSelect() }
|
||||||
{ this.renderParameters() }
|
{ this.renderParameters() }
|
||||||
|
@ -18,6 +18,8 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||||
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
||||||
@ -57,6 +59,7 @@ class ExecuteContract extends Component {
|
|||||||
isTest: PropTypes.bool,
|
isTest: PropTypes.bool,
|
||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
|
balances: PropTypes.object,
|
||||||
contract: PropTypes.object,
|
contract: PropTypes.object,
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
@ -362,10 +365,15 @@ class ExecuteContract extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (initState, initProps) {
|
||||||
|
const fromAddresses = Object.keys(initProps.accounts);
|
||||||
|
|
||||||
|
return (state) => {
|
||||||
|
const balances = pick(state.balances.balances, fromAddresses);
|
||||||
const { gasLimit } = state.nodeStatus;
|
const { gasLimit } = state.nodeStatus;
|
||||||
|
|
||||||
return { gasLimit };
|
return { gasLimit, balances };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
|
@ -134,6 +134,7 @@ export default class Details extends Component {
|
|||||||
images: PropTypes.object.isRequired,
|
images: PropTypes.object.isRequired,
|
||||||
sender: PropTypes.string,
|
sender: PropTypes.string,
|
||||||
senderError: PropTypes.string,
|
senderError: PropTypes.string,
|
||||||
|
sendersBalances: PropTypes.object,
|
||||||
recipient: PropTypes.string,
|
recipient: PropTypes.string,
|
||||||
recipientError: PropTypes.string,
|
recipientError: PropTypes.string,
|
||||||
tag: PropTypes.string,
|
tag: PropTypes.string,
|
||||||
@ -203,7 +204,7 @@ export default class Details extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderFromAddress () {
|
renderFromAddress () {
|
||||||
const { sender, senderError, senders } = this.props;
|
const { sender, senderError, senders, sendersBalances } = this.props;
|
||||||
|
|
||||||
if (!senders) {
|
if (!senders) {
|
||||||
return null;
|
return null;
|
||||||
@ -218,6 +219,7 @@ export default class Details extends Component {
|
|||||||
hint='the sender address'
|
hint='the sender address'
|
||||||
value={ sender }
|
value={ sender }
|
||||||
onChange={ this.onEditSender }
|
onChange={ this.onEditSender }
|
||||||
|
balances={ sendersBalances }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -54,6 +54,7 @@ export default class TransferStore {
|
|||||||
|
|
||||||
@observable sender = '';
|
@observable sender = '';
|
||||||
@observable senderError = null;
|
@observable senderError = null;
|
||||||
|
@observable sendersBalances = {};
|
||||||
|
|
||||||
@observable total = '0.0';
|
@observable total = '0.0';
|
||||||
@observable totalError = null;
|
@observable totalError = null;
|
||||||
@ -66,8 +67,6 @@ export default class TransferStore {
|
|||||||
onClose = null;
|
onClose = null;
|
||||||
|
|
||||||
senders = null;
|
senders = null;
|
||||||
sendersBalances = null;
|
|
||||||
|
|
||||||
isWallet = false;
|
isWallet = false;
|
||||||
wallet = null;
|
wallet = null;
|
||||||
|
|
||||||
|
@ -155,8 +155,8 @@ class Transfer extends Component {
|
|||||||
|
|
||||||
renderDetailsPage () {
|
renderDetailsPage () {
|
||||||
const { account, balance, images, senders } = this.props;
|
const { account, balance, images, senders } = this.props;
|
||||||
const { valueAll, extras, recipient, recipientError, sender, senderError } = this.store;
|
const { recipient, recipientError, sender, senderError, sendersBalances } = this.store;
|
||||||
const { tag, total, totalError, value, valueError } = this.store;
|
const { valueAll, extras, tag, total, totalError, value, valueError } = this.store;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Details
|
<Details
|
||||||
@ -170,6 +170,7 @@ class Transfer extends Component {
|
|||||||
recipientError={ recipientError }
|
recipientError={ recipientError }
|
||||||
sender={ sender }
|
sender={ sender }
|
||||||
senderError={ senderError }
|
senderError={ senderError }
|
||||||
|
sendersBalances={ sendersBalances }
|
||||||
tag={ tag }
|
tag={ tag }
|
||||||
total={ total }
|
total={ total }
|
||||||
totalError={ totalError }
|
totalError={ totalError }
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
|
import { routerMiddleware } from 'react-router-redux';
|
||||||
|
|
||||||
import ErrorsMiddleware from '~/ui/Errors/middleware';
|
import ErrorsMiddleware from '~/ui/Errors/middleware';
|
||||||
import SettingsMiddleware from '~/views/Settings/middleware';
|
import SettingsMiddleware from '~/views/Settings/middleware';
|
||||||
@ -22,12 +23,13 @@ import SignerMiddleware from './providers/signerMiddleware';
|
|||||||
import statusMiddleware from '~/views/Status/middleware';
|
import statusMiddleware from '~/views/Status/middleware';
|
||||||
import CertificationsMiddleware from './providers/certifications/middleware';
|
import CertificationsMiddleware from './providers/certifications/middleware';
|
||||||
|
|
||||||
export default function (api) {
|
export default function (api, browserHistory) {
|
||||||
const errors = new ErrorsMiddleware();
|
const errors = new ErrorsMiddleware();
|
||||||
const signer = new SignerMiddleware(api);
|
const signer = new SignerMiddleware(api);
|
||||||
const settings = new SettingsMiddleware();
|
const settings = new SettingsMiddleware();
|
||||||
const status = statusMiddleware();
|
const status = statusMiddleware();
|
||||||
const certifications = new CertificationsMiddleware();
|
const certifications = new CertificationsMiddleware();
|
||||||
|
const routeMiddleware = routerMiddleware(browserHistory);
|
||||||
|
|
||||||
const middleware = [
|
const middleware = [
|
||||||
settings.toMiddleware(),
|
settings.toMiddleware(),
|
||||||
@ -36,5 +38,5 @@ export default function (api) {
|
|||||||
certifications.toMiddleware()
|
certifications.toMiddleware()
|
||||||
];
|
];
|
||||||
|
|
||||||
return middleware.concat(status, thunk);
|
return middleware.concat(status, routeMiddleware, thunk);
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,14 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { range, uniq, isEqual } from 'lodash';
|
import { range, uniq, isEqual } from 'lodash';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { push } from 'react-router-redux';
|
||||||
|
|
||||||
import { hashToImageUrl } from './imagesReducer';
|
import { hashToImageUrl } from './imagesReducer';
|
||||||
import { setAddressImage } from './imagesActions';
|
import { setAddressImage } from './imagesActions';
|
||||||
|
|
||||||
import * as ABIS from '~/contracts/abi';
|
import * as ABIS from '~/contracts/abi';
|
||||||
|
import { notifyTransaction } from '~/util/notifications';
|
||||||
import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png';
|
import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png';
|
||||||
|
|
||||||
const ETH = {
|
const ETH = {
|
||||||
@ -28,7 +31,64 @@ const ETH = {
|
|||||||
image: imagesEthereum
|
image: imagesEthereum
|
||||||
};
|
};
|
||||||
|
|
||||||
export function setBalances (balances) {
|
function setBalances (_balances) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
const accounts = state.personal.accounts;
|
||||||
|
const nextBalances = _balances;
|
||||||
|
const prevBalances = state.balances.balances;
|
||||||
|
const balances = { ...prevBalances };
|
||||||
|
|
||||||
|
Object.keys(nextBalances).forEach((address) => {
|
||||||
|
if (!balances[address]) {
|
||||||
|
balances[address] = Object.assign({}, nextBalances[address]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const balance = Object.assign({}, balances[address]);
|
||||||
|
const { tokens, txCount = balance.txCount } = nextBalances[address];
|
||||||
|
const nextTokens = [].concat(balance.tokens);
|
||||||
|
|
||||||
|
tokens.forEach((t) => {
|
||||||
|
const { token, value } = t;
|
||||||
|
const { tag } = token;
|
||||||
|
|
||||||
|
const tokenIndex = nextTokens.findIndex((tok) => tok.token.tag === tag);
|
||||||
|
|
||||||
|
if (tokenIndex === -1) {
|
||||||
|
nextTokens.push({
|
||||||
|
token,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const oldValue = nextTokens[tokenIndex].value;
|
||||||
|
|
||||||
|
// If received a token/eth (old value < new value), notify
|
||||||
|
if (oldValue.lt(value) && accounts[address]) {
|
||||||
|
const account = accounts[address];
|
||||||
|
const txValue = value.minus(oldValue);
|
||||||
|
|
||||||
|
const redirectToAccount = () => {
|
||||||
|
const route = `/account/${account.address}`;
|
||||||
|
dispatch(push(route));
|
||||||
|
};
|
||||||
|
|
||||||
|
notifyTransaction(account, token, txValue, redirectToAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTokens[tokenIndex] = { token, value };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
balances[address] = { txCount: txCount || new BigNumber(0), tokens: nextTokens };
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(_setBalances(balances));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setBalances (balances) {
|
||||||
return {
|
return {
|
||||||
type: 'setBalances',
|
type: 'setBalances',
|
||||||
balances
|
balances
|
||||||
@ -123,14 +183,14 @@ export function fetchBalances (_addresses) {
|
|||||||
|
|
||||||
const fullFetch = addresses.length === 1;
|
const fullFetch = addresses.length === 1;
|
||||||
|
|
||||||
const fetchedAddresses = uniq(addresses.concat(Object.keys(accounts)));
|
const addressesToFetch = uniq(addresses.concat(Object.keys(accounts)));
|
||||||
|
|
||||||
return Promise
|
return Promise
|
||||||
.all(fetchedAddresses.map((addr) => fetchAccount(addr, api, fullFetch)))
|
.all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch)))
|
||||||
.then((accountsBalances) => {
|
.then((accountsBalances) => {
|
||||||
const balances = {};
|
const balances = {};
|
||||||
|
|
||||||
fetchedAddresses.forEach((addr, idx) => {
|
addressesToFetch.forEach((addr, idx) => {
|
||||||
balances[addr] = accountsBalances[idx];
|
balances[addr] = accountsBalances[idx];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -146,10 +206,12 @@ export function fetchBalances (_addresses) {
|
|||||||
export function updateTokensFilter (_addresses, _tokens) {
|
export function updateTokensFilter (_addresses, _tokens) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, balances, personal } = getState();
|
const { api, balances, personal } = getState();
|
||||||
const { visibleAccounts } = personal;
|
const { visibleAccounts, accounts } = personal;
|
||||||
const { tokensFilter } = balances;
|
const { tokensFilter } = balances;
|
||||||
|
|
||||||
const addresses = uniq(_addresses || visibleAccounts || []).sort();
|
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||||
|
const addresses = uniq(_addresses || addressesToFetch || []).sort();
|
||||||
|
|
||||||
const tokens = _tokens || Object.values(balances.tokens) || [];
|
const tokens = _tokens || Object.values(balances.tokens) || [];
|
||||||
const tokenAddresses = tokens.map((t) => t.address).sort();
|
const tokenAddresses = tokens.map((t) => t.address).sort();
|
||||||
|
|
||||||
@ -221,8 +283,10 @@ export function updateTokensFilter (_addresses, _tokens) {
|
|||||||
export function queryTokensFilter (tokensFilter) {
|
export function queryTokensFilter (tokensFilter) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, personal, balances } = getState();
|
const { api, personal, balances } = getState();
|
||||||
const { visibleAccounts } = personal;
|
const { visibleAccounts, accounts } = personal;
|
||||||
|
|
||||||
const visibleAddresses = visibleAccounts.map((a) => a.toLowerCase());
|
const visibleAddresses = visibleAccounts.map((a) => a.toLowerCase());
|
||||||
|
const addressesToFetch = uniq(visibleAddresses.concat(Object.keys(accounts)));
|
||||||
|
|
||||||
Promise
|
Promise
|
||||||
.all([
|
.all([
|
||||||
@ -237,18 +301,16 @@ export function queryTokensFilter (tokensFilter) {
|
|||||||
.concat(logsTo)
|
.concat(logsTo)
|
||||||
.forEach((log) => {
|
.forEach((log) => {
|
||||||
const tokenAddress = log.address;
|
const tokenAddress = log.address;
|
||||||
|
|
||||||
const fromAddress = '0x' + log.topics[1].slice(-40);
|
const fromAddress = '0x' + log.topics[1].slice(-40);
|
||||||
const toAddress = '0x' + log.topics[2].slice(-40);
|
const toAddress = '0x' + log.topics[2].slice(-40);
|
||||||
|
|
||||||
const fromIdx = visibleAddresses.indexOf(fromAddress);
|
if (addressesToFetch.includes(fromAddress)) {
|
||||||
const toIdx = visibleAddresses.indexOf(toAddress);
|
addresses.push(fromAddress);
|
||||||
|
|
||||||
if (fromIdx > -1) {
|
|
||||||
addresses.push(visibleAccounts[fromIdx]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toIdx > -1) {
|
if (addressesToFetch.includes(toAddress)) {
|
||||||
addresses.push(visibleAccounts[toIdx]);
|
addresses.push(toAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenAddresses.push(tokenAddress);
|
tokenAddresses.push(tokenAddress);
|
||||||
@ -269,9 +331,10 @@ export function queryTokensFilter (tokensFilter) {
|
|||||||
export function fetchTokensBalances (_addresses = null, _tokens = null) {
|
export function fetchTokensBalances (_addresses = null, _tokens = null) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, personal, balances } = getState();
|
const { api, personal, balances } = getState();
|
||||||
const { visibleAccounts } = personal;
|
const { visibleAccounts, accounts } = personal;
|
||||||
|
|
||||||
const addresses = _addresses || visibleAccounts;
|
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||||
|
const addresses = _addresses || addressesToFetch;
|
||||||
const tokens = _tokens || Object.values(balances.tokens);
|
const tokens = _tokens || Object.values(balances.tokens);
|
||||||
|
|
||||||
if (addresses.length === 0) {
|
if (addresses.length === 0) {
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { handleActions } from 'redux-actions';
|
import { handleActions } from 'redux-actions';
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
balances: {},
|
balances: {},
|
||||||
@ -26,39 +25,7 @@ const initialState = {
|
|||||||
|
|
||||||
export default handleActions({
|
export default handleActions({
|
||||||
setBalances (state, action) {
|
setBalances (state, action) {
|
||||||
const nextBalances = action.balances;
|
const { balances } = action;
|
||||||
const prevBalances = state.balances;
|
|
||||||
const balances = { ...prevBalances };
|
|
||||||
|
|
||||||
Object.keys(nextBalances).forEach((address) => {
|
|
||||||
if (!balances[address]) {
|
|
||||||
balances[address] = Object.assign({}, nextBalances[address]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const balance = Object.assign({}, balances[address]);
|
|
||||||
const { tokens, txCount = balance.txCount } = nextBalances[address];
|
|
||||||
const nextTokens = [].concat(balance.tokens);
|
|
||||||
|
|
||||||
tokens.forEach((t) => {
|
|
||||||
const { token, value } = t;
|
|
||||||
const { tag } = token;
|
|
||||||
|
|
||||||
const tokenIndex = nextTokens.findIndex((tok) => tok.token.tag === tag);
|
|
||||||
|
|
||||||
if (tokenIndex === -1) {
|
|
||||||
nextTokens.push({
|
|
||||||
token,
|
|
||||||
value
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
nextTokens[tokenIndex] = { token, value };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
balances[address] = Object.assign({}, { txCount: txCount || new BigNumber(0), tokens: nextTokens });
|
|
||||||
});
|
|
||||||
|
|
||||||
return Object.assign({}, state, { balances });
|
return Object.assign({}, state, { balances });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -21,11 +21,11 @@ export Status from './status';
|
|||||||
|
|
||||||
export apiReducer from './apiReducer';
|
export apiReducer from './apiReducer';
|
||||||
export balancesReducer from './balancesReducer';
|
export balancesReducer from './balancesReducer';
|
||||||
|
export blockchainReducer from './blockchainReducer';
|
||||||
|
export compilerReducer from './compilerReducer';
|
||||||
export imagesReducer from './imagesReducer';
|
export imagesReducer from './imagesReducer';
|
||||||
export personalReducer from './personalReducer';
|
export personalReducer from './personalReducer';
|
||||||
export signerReducer from './signerReducer';
|
export signerReducer from './signerReducer';
|
||||||
export statusReducer from './statusReducer';
|
|
||||||
export blockchainReducer from './blockchainReducer';
|
|
||||||
export compilerReducer from './compilerReducer';
|
|
||||||
export snackbarReducer from './snackbarReducer';
|
export snackbarReducer from './snackbarReducer';
|
||||||
|
export statusReducer from './statusReducer';
|
||||||
export walletReducer from './walletReducer';
|
export walletReducer from './walletReducer';
|
||||||
|
@ -54,7 +54,10 @@ export default class Status {
|
|||||||
this._api.eth
|
this._api.eth
|
||||||
.getBlockByNumber(blockNumber)
|
.getBlockByNumber(blockNumber)
|
||||||
.then((block) => {
|
.then((block) => {
|
||||||
this._store.dispatch(statusCollection({ gasLimit: block.gasLimit }));
|
this._store.dispatch(statusCollection({
|
||||||
|
blockTimestamp: block.timestamp,
|
||||||
|
gasLimit: block.gasLimit
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('status._subscribeBlockNumber', 'getBlockByNumber', error);
|
console.warn('status._subscribeBlockNumber', 'getBlockByNumber', error);
|
||||||
|
@ -19,6 +19,7 @@ import { handleActions } from 'redux-actions';
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
blockNumber: new BigNumber(0),
|
blockNumber: new BigNumber(0),
|
||||||
|
blockTimestamp: new Date(),
|
||||||
devLogs: [],
|
devLogs: [],
|
||||||
devLogsLevels: null,
|
devLogsLevels: null,
|
||||||
devLogsEnabled: false,
|
devLogsEnabled: false,
|
||||||
|
@ -17,7 +17,12 @@
|
|||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import { routerReducer } from 'react-router-redux';
|
import { routerReducer } from 'react-router-redux';
|
||||||
|
|
||||||
import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer, snackbarReducer, walletReducer } from './providers';
|
import {
|
||||||
|
apiReducer, balancesReducer, blockchainReducer,
|
||||||
|
compilerReducer, imagesReducer, personalReducer,
|
||||||
|
signerReducer, statusReducer as nodeStatusReducer,
|
||||||
|
snackbarReducer, walletReducer
|
||||||
|
} from './providers';
|
||||||
import certificationsReducer from './providers/certifications/reducer';
|
import certificationsReducer from './providers/certifications/reducer';
|
||||||
|
|
||||||
import errorReducer from '~/ui/Errors/reducers';
|
import errorReducer from '~/ui/Errors/reducers';
|
||||||
|
@ -32,9 +32,9 @@ const storeCreation = window.devToolsExtension
|
|||||||
? window.devToolsExtension()(createStore)
|
? window.devToolsExtension()(createStore)
|
||||||
: createStore;
|
: createStore;
|
||||||
|
|
||||||
export default function (api) {
|
export default function (api, browserHistory) {
|
||||||
const reducers = initReducers();
|
const reducers = initReducers();
|
||||||
const middleware = initMiddleware(api);
|
const middleware = initMiddleware(api, browserHistory);
|
||||||
const store = applyMiddleware(...middleware)(storeCreation)(reducers);
|
const store = applyMiddleware(...middleware)(storeCreation)(reducers);
|
||||||
|
|
||||||
new BalancesProvider(store, api).start();
|
new BalancesProvider(store, api).start();
|
||||||
|
@ -17,11 +17,13 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
||||||
|
|
||||||
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import styles from './actionbar.css';
|
import styles from './actionbar.css';
|
||||||
|
|
||||||
export default class Actionbar extends Component {
|
export default class Actionbar extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: nodeOrStringProptype(),
|
||||||
buttons: PropTypes.array,
|
buttons: PropTypes.array,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
className: PropTypes.string
|
className: PropTypes.string
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
.account {
|
.account {
|
||||||
padding: 4px 0 0 0;
|
padding: 0.25em 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
@ -27,6 +29,11 @@
|
|||||||
padding: 0 0 0 1em;
|
padding: 0 0 0 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.balance {
|
||||||
|
color: #aaa;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
@ -21,6 +21,8 @@ import AutoComplete from '../AutoComplete';
|
|||||||
import IdentityIcon from '../../IdentityIcon';
|
import IdentityIcon from '../../IdentityIcon';
|
||||||
import IdentityName from '../../IdentityName';
|
import IdentityName from '../../IdentityName';
|
||||||
|
|
||||||
|
import { fromWei } from '~/api/util/wei';
|
||||||
|
|
||||||
import styles from './addressSelect.css';
|
import styles from './addressSelect.css';
|
||||||
|
|
||||||
export default class AddressSelect extends Component {
|
export default class AddressSelect extends Component {
|
||||||
@ -40,27 +42,46 @@ export default class AddressSelect extends Component {
|
|||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
tokens: PropTypes.object,
|
tokens: PropTypes.object,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
allowInput: PropTypes.bool
|
allowInput: PropTypes.bool,
|
||||||
|
balances: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
autocompleteEntries: [],
|
||||||
entries: {},
|
entries: {},
|
||||||
addresses: [],
|
addresses: [],
|
||||||
value: ''
|
value: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
entriesFromProps (props = this.props) {
|
entriesFromProps (props = this.props) {
|
||||||
const { accounts, contacts, contracts, wallets } = props;
|
const { accounts = {}, contacts = {}, contracts = {}, wallets = {} } = props;
|
||||||
const entries = Object.assign({}, accounts || {}, wallets || {}, contacts || {}, contracts || {});
|
|
||||||
return entries;
|
const autocompleteEntries = [].concat(
|
||||||
|
Object.values(wallets),
|
||||||
|
'divider',
|
||||||
|
Object.values(accounts),
|
||||||
|
'divider',
|
||||||
|
Object.values(contacts),
|
||||||
|
'divider',
|
||||||
|
Object.values(contracts)
|
||||||
|
);
|
||||||
|
|
||||||
|
const entries = {
|
||||||
|
...wallets,
|
||||||
|
...accounts,
|
||||||
|
...contacts,
|
||||||
|
...contracts
|
||||||
|
};
|
||||||
|
|
||||||
|
return { autocompleteEntries, entries };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
const { value } = this.props;
|
const { value } = this.props;
|
||||||
const entries = this.entriesFromProps();
|
const { entries, autocompleteEntries } = this.entriesFromProps();
|
||||||
const addresses = Object.keys(entries).sort();
|
const addresses = Object.keys(entries).sort();
|
||||||
|
|
||||||
this.setState({ entries, addresses, value });
|
this.setState({ autocompleteEntries, entries, addresses, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (newProps) {
|
componentWillReceiveProps (newProps) {
|
||||||
@ -71,7 +92,7 @@ export default class AddressSelect extends Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { allowInput, disabled, error, hint, label } = this.props;
|
const { allowInput, disabled, error, hint, label } = this.props;
|
||||||
const { entries, value } = this.state;
|
const { autocompleteEntries, value } = this.state;
|
||||||
|
|
||||||
const searchText = this.getSearchText();
|
const searchText = this.getSearchText();
|
||||||
const icon = this.renderIdentityIcon(value);
|
const icon = this.renderIdentityIcon(value);
|
||||||
@ -89,7 +110,7 @@ export default class AddressSelect extends Component {
|
|||||||
onUpdateInput={ allowInput && this.onUpdateInput }
|
onUpdateInput={ allowInput && this.onUpdateInput }
|
||||||
value={ searchText }
|
value={ searchText }
|
||||||
filter={ this.handleFilter }
|
filter={ this.handleFilter }
|
||||||
entries={ entries }
|
entries={ autocompleteEntries }
|
||||||
entry={ this.getEntry() || {} }
|
entry={ this.getEntry() || {} }
|
||||||
renderItem={ this.renderItem }
|
renderItem={ this.renderItem }
|
||||||
/>
|
/>
|
||||||
@ -129,7 +150,34 @@ export default class AddressSelect extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderBalance (address) {
|
||||||
|
const { balances = {} } = this.props;
|
||||||
|
const balance = balances[address];
|
||||||
|
|
||||||
|
if (!balance) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ethToken = balance.tokens.find((tok) => tok.token && tok.token.tag && tok.token.tag.toLowerCase() === 'eth');
|
||||||
|
|
||||||
|
if (!ethToken) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = fromWei(ethToken.value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.balance }>
|
||||||
|
{ value.toFormat(3) }<small> { 'ETH' }</small>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderMenuItem (address) {
|
renderMenuItem (address) {
|
||||||
|
const balance = this.props.balances
|
||||||
|
? this.renderBalance(address)
|
||||||
|
: null;
|
||||||
|
|
||||||
const item = (
|
const item = (
|
||||||
<div className={ styles.account }>
|
<div className={ styles.account }>
|
||||||
<IdentityIcon
|
<IdentityIcon
|
||||||
@ -139,6 +187,7 @@ export default class AddressSelect extends Component {
|
|||||||
<IdentityName
|
<IdentityName
|
||||||
className={ styles.name }
|
className={ styles.name }
|
||||||
address={ address } />
|
address={ address } />
|
||||||
|
{ balance }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -155,11 +204,10 @@ export default class AddressSelect extends Component {
|
|||||||
|
|
||||||
getSearchText () {
|
getSearchText () {
|
||||||
const entry = this.getEntry();
|
const entry = this.getEntry();
|
||||||
const { value } = this.state;
|
|
||||||
|
|
||||||
return entry && entry.name
|
return entry && entry.name
|
||||||
? entry.name.toUpperCase()
|
? entry.name.toUpperCase()
|
||||||
: value;
|
: this.state.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntry () {
|
getEntry () {
|
||||||
|
@ -16,11 +16,24 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import keycode from 'keycode';
|
import keycode from 'keycode';
|
||||||
import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui';
|
import { MenuItem, AutoComplete as MUIAutoComplete, Divider as MUIDivider } from 'material-ui';
|
||||||
import { PopoverAnimationVertical } from 'material-ui/Popover';
|
import { PopoverAnimationVertical } from 'material-ui/Popover';
|
||||||
|
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
|
// Hack to prevent "Unknown prop `disableFocusRipple` on <hr> tag" error
|
||||||
|
class Divider extends Component {
|
||||||
|
static muiName = MUIDivider.muiName;
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div style={ { margin: '0.25em 0' } }>
|
||||||
|
<MUIDivider style={ { height: 2 } } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default class AutoComplete extends Component {
|
export default class AutoComplete extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
@ -38,15 +51,17 @@ export default class AutoComplete extends Component {
|
|||||||
PropTypes.array,
|
PropTypes.array,
|
||||||
PropTypes.object
|
PropTypes.object
|
||||||
])
|
])
|
||||||
}
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
lastChangedValue: undefined,
|
lastChangedValue: undefined,
|
||||||
entry: null,
|
entry: null,
|
||||||
open: false,
|
open: false,
|
||||||
fakeBlur: false,
|
dataSource: [],
|
||||||
dataSource: []
|
dividerBreaks: []
|
||||||
}
|
};
|
||||||
|
|
||||||
|
dividersVisibility = {};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
const dataSource = this.getDataSource();
|
const dataSource = this.getDataSource();
|
||||||
@ -64,7 +79,7 @@ export default class AutoComplete extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { disabled, error, hint, label, value, className, filter, onUpdateInput } = this.props;
|
const { disabled, error, hint, label, value, className, onUpdateInput } = this.props;
|
||||||
const { open, dataSource } = this.state;
|
const { open, dataSource } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -78,9 +93,9 @@ export default class AutoComplete extends Component {
|
|||||||
onUpdateInput={ onUpdateInput }
|
onUpdateInput={ onUpdateInput }
|
||||||
searchText={ value }
|
searchText={ value }
|
||||||
onFocus={ this.onFocus }
|
onFocus={ this.onFocus }
|
||||||
onBlur={ this.onBlur }
|
onClose={ this.onClose }
|
||||||
animation={ PopoverAnimationVertical }
|
animation={ PopoverAnimationVertical }
|
||||||
filter={ filter }
|
filter={ this.handleFilter }
|
||||||
popoverProps={ { open } }
|
popoverProps={ { open } }
|
||||||
openOnFocus
|
openOnFocus
|
||||||
menuCloseDelay={ 0 }
|
menuCloseDelay={ 0 }
|
||||||
@ -100,18 +115,76 @@ export default class AutoComplete extends Component {
|
|||||||
? entries
|
? entries
|
||||||
: Object.values(entries);
|
: Object.values(entries);
|
||||||
|
|
||||||
if (renderItem && typeof renderItem === 'function') {
|
let currentDivider = 0;
|
||||||
return entriesArray.map(entry => renderItem(entry));
|
let firstSet = false;
|
||||||
|
|
||||||
|
const dataSource = entriesArray.map((entry, index) => {
|
||||||
|
// Render divider
|
||||||
|
if (typeof entry === 'string' && entry.toLowerCase() === 'divider') {
|
||||||
|
// Don't add divider if nothing before
|
||||||
|
if (!firstSet) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return entriesArray.map(entry => ({
|
const item = {
|
||||||
|
text: '',
|
||||||
|
divider: currentDivider,
|
||||||
|
isDivider: true,
|
||||||
|
value: (
|
||||||
|
<Divider />
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
currentDivider++;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
let item;
|
||||||
|
|
||||||
|
if (renderItem && typeof renderItem === 'function') {
|
||||||
|
item = renderItem(entry);
|
||||||
|
} else {
|
||||||
|
item = {
|
||||||
text: entry,
|
text: entry,
|
||||||
value: (
|
value: (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
primaryText={ entry }
|
primaryText={ entry }
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}));
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!firstSet) {
|
||||||
|
item.first = true;
|
||||||
|
firstSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.divider = currentDivider;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}).filter((item) => item !== undefined);
|
||||||
|
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFilter = (searchText, name, item) => {
|
||||||
|
if (item.isDivider) {
|
||||||
|
return this.dividersVisibility[item.divider];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.first) {
|
||||||
|
this.dividersVisibility = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { filter } = this.props;
|
||||||
|
const show = filter(searchText, name, item);
|
||||||
|
|
||||||
|
// Show the related divider
|
||||||
|
if (show) {
|
||||||
|
this.dividersVisibility[item.divider] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return show;
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown = (event) => {
|
onKeyDown = (event) => {
|
||||||
@ -121,7 +194,6 @@ export default class AutoComplete extends Component {
|
|||||||
case 'down':
|
case 'down':
|
||||||
const { menu } = muiAutocomplete.refs;
|
const { menu } = muiAutocomplete.refs;
|
||||||
menu && menu.handleKeyDown(event);
|
menu && menu.handleKeyDown(event);
|
||||||
this.setState({ fakeBlur: true });
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'enter':
|
case 'enter':
|
||||||
@ -155,22 +227,12 @@ export default class AutoComplete extends Component {
|
|||||||
this.setState({ entry, open: false });
|
this.setState({ entry, open: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onBlur = (event) => {
|
onClose = (event) => {
|
||||||
const { onUpdateInput } = this.props;
|
const { onUpdateInput } = this.props;
|
||||||
|
|
||||||
// TODO: Handle blur gracefully where we use onUpdateInput (currently replaces
|
|
||||||
// input where text is allowed with the last selected value from the dropdown)
|
|
||||||
if (!onUpdateInput) {
|
if (!onUpdateInput) {
|
||||||
window.setTimeout(() => {
|
const { entry } = this.state;
|
||||||
const { entry, fakeBlur } = this.state;
|
|
||||||
|
|
||||||
if (fakeBlur) {
|
|
||||||
this.setState({ fakeBlur: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handleOnChange(entry);
|
this.handleOnChange(entry);
|
||||||
}, 200);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.layout {
|
.layout {
|
||||||
padding: 0.25em 0.25em 1em 0.25em;
|
padding: 0.25em;
|
||||||
|
|
||||||
> * {
|
&>div {
|
||||||
margin-bottom: 0.75em;
|
margin-bottom: 0.75em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,22 +16,39 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import Actionbar from '../Actionbar';
|
||||||
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import styles from './page.css';
|
import styles from './page.css';
|
||||||
|
|
||||||
export default class Page extends Component {
|
export default class Page extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
buttons: PropTypes.array,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
|
title: nodeOrStringProptype()
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { className, children } = this.props;
|
const { buttons, className, children, title } = this.props;
|
||||||
const classes = `${styles.layout} ${className}`;
|
const classes = `${styles.layout} ${className}`;
|
||||||
|
let actionbar = null;
|
||||||
|
|
||||||
|
if (title || buttons) {
|
||||||
|
actionbar = (
|
||||||
|
<Actionbar
|
||||||
|
buttons={ buttons }
|
||||||
|
title={ title } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
|
{ actionbar }
|
||||||
<div className={ classes }>
|
<div className={ classes }>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
js/src/util/notifications.js
Normal file
45
js/src/util/notifications.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 Push from 'push.js';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
|
import { fromWei } from '~/api/util/wei';
|
||||||
|
|
||||||
|
import ethereumIcon from '~/../assets/images/contracts/ethereum-black-64x64.png';
|
||||||
|
import unkownIcon from '~/../assets/images/contracts/unknown-64x64.png';
|
||||||
|
|
||||||
|
export function notifyTransaction (account, token, _value, onClick) {
|
||||||
|
const name = account.name || account.address;
|
||||||
|
const value = token.tag.toLowerCase() === 'eth'
|
||||||
|
? fromWei(_value)
|
||||||
|
: _value.div(new BigNumber(token.format || 1));
|
||||||
|
|
||||||
|
const icon = token.tag.toLowerCase() === 'eth'
|
||||||
|
? 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
|
||||||
|
});
|
||||||
|
}
|
@ -31,6 +31,10 @@
|
|||||||
.infoline,
|
.infoline,
|
||||||
.uuidline {
|
.uuidline {
|
||||||
line-height: 1.618em;
|
line-height: 1.618em;
|
||||||
|
|
||||||
|
&.bigaddress {
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoline,
|
.infoline,
|
||||||
|
@ -32,18 +32,20 @@ export default class Header extends Component {
|
|||||||
balance: PropTypes.object,
|
balance: PropTypes.object,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
isContract: PropTypes.bool
|
isContract: PropTypes.bool,
|
||||||
|
hideName: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
className: '',
|
className: '',
|
||||||
children: null,
|
children: null,
|
||||||
isContract: false
|
isContract: false,
|
||||||
|
hideName: false
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { account, balance, className, children } = this.props;
|
const { account, balance, className, children, hideName } = this.props;
|
||||||
const { address, meta, uuid } = account;
|
const { address, meta, uuid } = account;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
@ -60,17 +62,20 @@ export default class Header extends Component {
|
|||||||
<IdentityIcon
|
<IdentityIcon
|
||||||
address={ address } />
|
address={ address } />
|
||||||
<div className={ styles.floatleft }>
|
<div className={ styles.floatleft }>
|
||||||
<ContainerTitle title={ <IdentityName address={ address } unknown /> } />
|
{ this.renderName(address) }
|
||||||
<div className={ styles.addressline }>
|
|
||||||
|
<div className={ [ hideName ? styles.bigaddress : '', styles.addressline ].join(' ') }>
|
||||||
<CopyToClipboard data={ address } />
|
<CopyToClipboard data={ address } />
|
||||||
<div className={ styles.address }>{ address }</div>
|
<div className={ styles.address }>{ address }</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ uuidText }
|
{ uuidText }
|
||||||
<div className={ styles.infoline }>
|
<div className={ styles.infoline }>
|
||||||
{ meta.description }
|
{ meta.description }
|
||||||
</div>
|
</div>
|
||||||
{ this.renderTxCount() }
|
{ this.renderTxCount() }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={ styles.tags }>
|
<div className={ styles.tags }>
|
||||||
<Tags tags={ meta.tags } />
|
<Tags tags={ meta.tags } />
|
||||||
</div>
|
</div>
|
||||||
@ -89,6 +94,18 @@ export default class Header extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderName (address) {
|
||||||
|
const { hideName } = this.props;
|
||||||
|
|
||||||
|
if (hideName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContainerTitle title={ <IdentityName address={ address } unknown /> } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderTxCount () {
|
renderTxCount () {
|
||||||
const { balance, isContract } = this.props;
|
const { balance, isContract } = this.props;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import VerifyIcon from 'material-ui/svg-icons/action/verified-user';
|
|||||||
import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '~/modals';
|
import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '~/modals';
|
||||||
import { Actionbar, Button, Page } from '~/ui';
|
import { Actionbar, Button, Page } from '~/ui';
|
||||||
|
|
||||||
import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png';
|
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
|
||||||
|
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import Transactions from './Transactions';
|
import Transactions from './Transactions';
|
||||||
|
@ -153,7 +153,7 @@ export default class Summary extends Component {
|
|||||||
const { link, noLink, account, name } = this.props;
|
const { link, noLink, account, name } = this.props;
|
||||||
|
|
||||||
const { address } = account;
|
const { address } = account;
|
||||||
const viewLink = `/${link || 'account'}/${address}`;
|
const viewLink = `/${link || 'accounts'}/${address}`;
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<IdentityName address={ address } name={ name } unknown />
|
<IdentityName address={ address } name={ name } unknown />
|
||||||
|
@ -19,8 +19,9 @@ import { connect } from 'react-redux';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import ActionDelete from 'material-ui/svg-icons/action/delete';
|
import ActionDelete from 'material-ui/svg-icons/action/delete';
|
||||||
import ContentCreate from 'material-ui/svg-icons/content/create';
|
import ContentCreate from 'material-ui/svg-icons/content/create';
|
||||||
|
import ContentAdd from 'material-ui/svg-icons/content/add';
|
||||||
|
|
||||||
import { EditMeta } from '~/modals';
|
import { EditMeta, AddAddress } from '~/modals';
|
||||||
import { Actionbar, Button, Page } from '~/ui';
|
import { Actionbar, Button, Page } from '~/ui';
|
||||||
|
|
||||||
import Header from '../Account/Header';
|
import Header from '../Account/Header';
|
||||||
@ -32,7 +33,7 @@ class Address extends Component {
|
|||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired,
|
api: PropTypes.object.isRequired,
|
||||||
router: PropTypes.object.isRequired
|
router: PropTypes.object.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
@ -40,12 +41,13 @@ class Address extends Component {
|
|||||||
contacts: PropTypes.object,
|
contacts: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
balances: PropTypes.object,
|
||||||
params: PropTypes.object
|
params: PropTypes.object
|
||||||
}
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
showDeleteDialog: false,
|
showDeleteDialog: false,
|
||||||
showEditDialog: false
|
showEditDialog: false,
|
||||||
}
|
showAdd: false
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.setVisibleAccounts();
|
this.setVisibleAccounts();
|
||||||
@ -73,32 +75,69 @@ class Address extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const { contacts, balances } = this.props;
|
const { contacts, balances } = this.props;
|
||||||
const { address } = this.props.params;
|
const { address } = this.props.params;
|
||||||
const { showDeleteDialog } = this.state;
|
|
||||||
|
if (Object.keys(contacts).length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const contact = (contacts || {})[address];
|
const contact = (contacts || {})[address];
|
||||||
const balance = (balances || {})[address];
|
const balance = (balances || {})[address];
|
||||||
|
|
||||||
if (!contact) {
|
return (
|
||||||
|
<div>
|
||||||
|
{ this.renderAddAddress(contact, address) }
|
||||||
|
{ this.renderEditDialog(contact) }
|
||||||
|
{ this.renderActionbar(contact) }
|
||||||
|
{ this.renderDelete(contact) }
|
||||||
|
<Page>
|
||||||
|
<Header
|
||||||
|
account={ contact || { address, meta: {} } }
|
||||||
|
balance={ balance }
|
||||||
|
hideName={ !contact }
|
||||||
|
/>
|
||||||
|
<Transactions
|
||||||
|
address={ address }
|
||||||
|
/>
|
||||||
|
</Page>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAddAddress (contact, address) {
|
||||||
|
if (contact) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { contacts } = this.props;
|
||||||
|
const { showAdd } = this.state;
|
||||||
|
|
||||||
|
if (!showAdd) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<AddAddress
|
||||||
{ this.renderEditDialog(contact) }
|
contacts={ contacts }
|
||||||
{ this.renderActionbar(contact) }
|
onClose={ this.onCloseAdd }
|
||||||
|
address={ address }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDelete (contact) {
|
||||||
|
if (!contact) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { showDeleteDialog } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
<Delete
|
<Delete
|
||||||
account={ contact }
|
account={ contact }
|
||||||
visible={ showDeleteDialog }
|
visible={ showDeleteDialog }
|
||||||
route='/addresses'
|
route='/addresses'
|
||||||
onClose={ this.closeDeleteDialog } />
|
onClose={ this.closeDeleteDialog }
|
||||||
<Page>
|
/>
|
||||||
<Header
|
|
||||||
account={ contact }
|
|
||||||
balance={ balance } />
|
|
||||||
<Transactions
|
|
||||||
address={ address } />
|
|
||||||
</Page>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,17 +155,27 @@ class Address extends Component {
|
|||||||
onClick={ this.showDeleteDialog } />
|
onClick={ this.showDeleteDialog } />
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const addToBook = (
|
||||||
|
<Button
|
||||||
|
key='newAddress'
|
||||||
|
icon={ <ContentAdd /> }
|
||||||
|
label='save address'
|
||||||
|
onClick={ this.onOpenAdd }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Actionbar
|
<Actionbar
|
||||||
title='Address Information'
|
title='Address Information'
|
||||||
buttons={ !contact ? [] : buttons } />
|
buttons={ !contact ? [ addToBook ] : buttons }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEditDialog (contact) {
|
renderEditDialog (contact) {
|
||||||
const { showEditDialog } = this.state;
|
const { showEditDialog } = this.state;
|
||||||
|
|
||||||
if (!showEditDialog) {
|
if (!contact || !showEditDialog) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +200,16 @@ class Address extends Component {
|
|||||||
showDeleteDialog = () => {
|
showDeleteDialog = () => {
|
||||||
this.setState({ showDeleteDialog: true });
|
this.setState({ showDeleteDialog: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onOpenAdd = () => {
|
||||||
|
this.setState({
|
||||||
|
showAdd: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseAdd = () => {
|
||||||
|
this.setState({ showAdd: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
|
@ -23,7 +23,7 @@ import { uniq, isEqual } from 'lodash';
|
|||||||
import List from '../Accounts/List';
|
import List from '../Accounts/List';
|
||||||
import Summary from '../Accounts/Summary';
|
import Summary from '../Accounts/Summary';
|
||||||
import { AddAddress } from '~/modals';
|
import { AddAddress } from '~/modals';
|
||||||
import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page } from '~/ui';
|
import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page, Loading } from '~/ui';
|
||||||
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
||||||
|
|
||||||
import styles from './addresses.css';
|
import styles from './addresses.css';
|
||||||
@ -72,24 +72,37 @@ class Addresses extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { balances, contacts, hasContacts } = this.props;
|
|
||||||
const { searchValues, sortOrder } = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{ this.renderActionbar() }
|
{ this.renderActionbar() }
|
||||||
{ this.renderAddAddress() }
|
{ this.renderAddAddress() }
|
||||||
<Page>
|
<Page>
|
||||||
|
{ this.renderAccountsList() }
|
||||||
|
</Page>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAccountsList () {
|
||||||
|
const { balances, contacts, hasContacts } = this.props;
|
||||||
|
const { searchValues, sortOrder } = this.state;
|
||||||
|
|
||||||
|
if (hasContacts && Object.keys(balances).length === 0) {
|
||||||
|
return (
|
||||||
|
<Loading />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<List
|
<List
|
||||||
link='address'
|
link='addresses'
|
||||||
search={ searchValues }
|
search={ searchValues }
|
||||||
accounts={ contacts }
|
accounts={ contacts }
|
||||||
balances={ balances }
|
balances={ balances }
|
||||||
empty={ !hasContacts }
|
empty={ !hasContacts }
|
||||||
order={ sortOrder }
|
order={ sortOrder }
|
||||||
handleAddSearchToken={ this.onAddSearchToken } />
|
handleAddSearchToken={ this.onAddSearchToken }
|
||||||
</Page>
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,24 +30,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs button,
|
.tabLink {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tabactive, &.tabactive:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.25) !important;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabLink,
|
||||||
.settings,
|
.settings,
|
||||||
.logo,
|
.logo,
|
||||||
.last {
|
.last {
|
||||||
background: rgba(0, 0, 0, 0.5) !important; /* rgba(0, 0, 0, 0.25) !important; */
|
background: rgba(0, 0, 0, 0.5) !important; /* rgba(0, 0, 0, 0.25) !important; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs button:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.4) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.tabactive,
|
|
||||||
button.tabactive:hover {
|
|
||||||
color: white !important;
|
|
||||||
background: rgba(0, 0, 0, 0.25) !important;
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabbarTooltip {
|
.tabbarTooltip {
|
||||||
left: 3.3em;
|
left: 3.3em;
|
||||||
top: 0.5em;
|
top: 0.5em;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { Link } from 'react-router';
|
||||||
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
||||||
import { Tab as MUITab } from 'material-ui/Tabs';
|
import { Tab as MUITab } from 'material-ui/Tabs';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
@ -26,41 +26,26 @@ import { Badge, Tooltip } from '~/ui';
|
|||||||
import styles from './tabBar.css';
|
import styles from './tabBar.css';
|
||||||
import imagesEthcoreBlock from '../../../../assets/images/parity-logo-white-no-text.svg';
|
import imagesEthcoreBlock from '../../../../assets/images/parity-logo-white-no-text.svg';
|
||||||
|
|
||||||
const TABMAP = {
|
|
||||||
accounts: 'account',
|
|
||||||
wallet: 'account',
|
|
||||||
addresses: 'address',
|
|
||||||
apps: 'app',
|
|
||||||
contracts: 'contract',
|
|
||||||
deploy: 'contract'
|
|
||||||
};
|
|
||||||
|
|
||||||
class Tab extends Component {
|
class Tab extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
active: PropTypes.bool,
|
|
||||||
view: PropTypes.object,
|
view: PropTypes.object,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
pendings: PropTypes.number,
|
pendings: PropTypes.number
|
||||||
onChange: PropTypes.func
|
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps) {
|
shouldComponentUpdate (nextProps) {
|
||||||
return nextProps.active !== this.props.active ||
|
return (nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings);
|
||||||
(nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { active, view, children } = this.props;
|
const { view, children } = this.props;
|
||||||
|
|
||||||
const label = this.getLabel(view);
|
const label = this.getLabel(view);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MUITab
|
<MUITab
|
||||||
className={ active ? styles.tabactive : '' }
|
|
||||||
selected={ active }
|
|
||||||
icon={ view.icon }
|
icon={ view.icon }
|
||||||
label={ label }
|
label={ label }
|
||||||
onTouchTap={ this.handleClick }
|
|
||||||
>
|
>
|
||||||
{ children }
|
{ children }
|
||||||
</MUITab>
|
</MUITab>
|
||||||
@ -118,11 +103,6 @@ class Tab extends Component {
|
|||||||
|
|
||||||
return this.renderLabel(label, null);
|
return this.renderLabel(label, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
const { onChange, view } = this.props;
|
|
||||||
onChange(view);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabBar extends Component {
|
class TabBar extends Component {
|
||||||
@ -132,7 +112,6 @@ class TabBar extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
views: PropTypes.array.isRequired,
|
views: PropTypes.array.isRequired,
|
||||||
hash: PropTypes.string.isRequired,
|
|
||||||
pending: PropTypes.array,
|
pending: PropTypes.array,
|
||||||
isTest: PropTypes.bool,
|
isTest: PropTypes.bool,
|
||||||
netChain: PropTypes.string
|
netChain: PropTypes.string
|
||||||
@ -142,34 +121,11 @@ class TabBar extends Component {
|
|||||||
pending: []
|
pending: []
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
|
||||||
activeViewId: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
setActiveView (props = this.props) {
|
|
||||||
const { hash, views } = props;
|
|
||||||
const view = views.find((view) => view.value === hash);
|
|
||||||
|
|
||||||
this.setState({ activeViewId: view.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
this.setActiveView();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (nextProps.hash !== this.props.hash) {
|
|
||||||
this.setActiveView(nextProps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps, nextState) {
|
shouldComponentUpdate (nextProps, nextState) {
|
||||||
const prevViews = this.props.views.map((v) => v.id).sort();
|
const prevViews = this.props.views.map((v) => v.id).sort();
|
||||||
const nextViews = nextProps.views.map((v) => v.id).sort();
|
const nextViews = nextProps.views.map((v) => v.id).sort();
|
||||||
|
|
||||||
return (nextProps.hash !== this.props.hash) ||
|
return (nextProps.pending.length !== this.props.pending.length) ||
|
||||||
(nextProps.pending.length !== this.props.pending.length) ||
|
|
||||||
(nextState.activeViewId !== this.state.activeViewId) ||
|
|
||||||
(!isEqual(prevViews, nextViews));
|
(!isEqual(prevViews, nextViews));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +162,6 @@ class TabBar extends Component {
|
|||||||
|
|
||||||
renderTabs () {
|
renderTabs () {
|
||||||
const { views, pending } = this.props;
|
const { views, pending } = this.props;
|
||||||
const { activeViewId } = this.state;
|
|
||||||
|
|
||||||
const items = views
|
const items = views
|
||||||
.map((view, index) => {
|
.map((view, index) => {
|
||||||
@ -216,60 +171,66 @@ class TabBar extends Component {
|
|||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const active = activeViewId === view.id;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Link
|
||||||
active={ active }
|
|
||||||
view={ view }
|
|
||||||
onChange={ this.onChange }
|
|
||||||
key={ view.id }
|
key={ view.id }
|
||||||
|
to={ view.route }
|
||||||
|
activeClassName={ styles.tabactive }
|
||||||
|
className={ styles.tabLink }
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
view={ view }
|
||||||
pendings={ pending.length }
|
pendings={ pending.length }
|
||||||
>
|
>
|
||||||
{ body }
|
{ body }
|
||||||
</Tab>
|
</Tab>
|
||||||
|
</Link>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={ styles.tabs }>
|
||||||
className={ styles.tabs }
|
|
||||||
onChange={ this.onChange }>
|
|
||||||
{ items }
|
{ items }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (view) => {
|
|
||||||
const { router } = this.context;
|
|
||||||
|
|
||||||
router.push(view.route);
|
|
||||||
this.setState({ activeViewId: view.id });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (initState) {
|
||||||
const { views } = state.settings;
|
const { views } = initState.settings;
|
||||||
|
|
||||||
const filteredViews = Object
|
let filteredViewIds = Object
|
||||||
.keys(views)
|
.keys(views)
|
||||||
.filter((id) => views[id].fixed || views[id].active)
|
.filter((id) => views[id].fixed || views[id].active);
|
||||||
|
|
||||||
|
let filteredViews = filteredViewIds
|
||||||
.map((id) => ({
|
.map((id) => ({
|
||||||
...views[id],
|
...views[id],
|
||||||
id
|
id
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const windowHash = (window.location.hash || '').split('?')[0].split('/')[1];
|
return (state) => {
|
||||||
const hash = TABMAP[windowHash] || windowHash;
|
const { views } = state.settings;
|
||||||
|
|
||||||
return { views: filteredViews, hash };
|
const viewIds = Object
|
||||||
}
|
.keys(views)
|
||||||
|
.filter((id) => views[id].fixed || views[id].active);
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
if (isEqual(viewIds, filteredViewIds)) {
|
||||||
return bindActionCreators({}, dispatch);
|
return { views: filteredViews };
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredViewIds = viewIds;
|
||||||
|
filteredViews = viewIds
|
||||||
|
.map((id) => ({
|
||||||
|
...views[id],
|
||||||
|
id
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { views: filteredViews };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps
|
||||||
mapDispatchToProps
|
|
||||||
)(TabBar);
|
)(TabBar);
|
||||||
|
@ -20,3 +20,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
@ -30,6 +30,8 @@ import Status from './Status';
|
|||||||
import Store from './store';
|
import Store from './store';
|
||||||
import TabBar from './TabBar';
|
import TabBar from './TabBar';
|
||||||
|
|
||||||
|
import styles from './application.css';
|
||||||
|
|
||||||
const inFrame = window.parent !== window && window.parent.frames.length !== 0;
|
const inFrame = window.parent !== window && window.parent.frames.length !== 0;
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -79,7 +81,9 @@ class Application extends Component {
|
|||||||
netChain={ netChain }
|
netChain={ netChain }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
pending={ pending } />
|
pending={ pending } />
|
||||||
|
<div className={ styles.content }>
|
||||||
{ children }
|
{ children }
|
||||||
|
</div>
|
||||||
{ blockNumber ? (<Status />) : null }
|
{ blockNumber ? (<Status />) : null }
|
||||||
<Snackbar />
|
<Snackbar />
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -85,7 +85,7 @@ class Contracts extends Component {
|
|||||||
{ this.renderDeployContract() }
|
{ this.renderDeployContract() }
|
||||||
<Page>
|
<Page>
|
||||||
<List
|
<List
|
||||||
link='contract'
|
link='contracts'
|
||||||
search={ searchValues }
|
search={ searchValues }
|
||||||
accounts={ contracts }
|
accounts={ contracts }
|
||||||
balances={ balances }
|
balances={ balances }
|
||||||
@ -142,12 +142,12 @@ class Contracts extends Component {
|
|||||||
label='deploy contract'
|
label='deploy contract'
|
||||||
onClick={ this.onDeployContract } />,
|
onClick={ this.onDeployContract } />,
|
||||||
<Link
|
<Link
|
||||||
to='/contracts/write'
|
to='/contracts/develop'
|
||||||
key='writeContract'
|
key='writeContract'
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
icon={ <FileIcon /> }
|
icon={ <FileIcon /> }
|
||||||
label='write contract'
|
label='develop contract'
|
||||||
/>
|
/>
|
||||||
</Link>,
|
</Link>,
|
||||||
|
|
||||||
|
@ -51,9 +51,16 @@ export default class Dapp extends Component {
|
|||||||
src = `${dappsUrl}/${app.contentHash}/`;
|
src = `${dappsUrl}/${app.contentHash}/`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
const dapphost = process.env.NODE_ENV === 'production' && !app.secure
|
let dapphost = process.env.DAPPS_URL || (
|
||||||
|
process.env.NODE_ENV === 'production' && !app.secure
|
||||||
? `${dappsUrl}/ui`
|
? `${dappsUrl}/ui`
|
||||||
: '';
|
: ''
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dapphost === '/') {
|
||||||
|
dapphost = '';
|
||||||
|
}
|
||||||
|
|
||||||
src = `${dapphost}/${app.url}.html`;
|
src = `${dapphost}/${app.url}.html`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -72,9 +72,17 @@ export default class Dapps extends Component {
|
|||||||
] }
|
] }
|
||||||
/>
|
/>
|
||||||
<Page>
|
<Page>
|
||||||
|
<div>
|
||||||
{ this.renderList(this.store.visibleLocal) }
|
{ this.renderList(this.store.visibleLocal) }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
{ this.renderList(this.store.visibleBuiltin) }
|
{ this.renderList(this.store.visibleBuiltin) }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
{ this.renderList(this.store.visibleNetwork, externalOverlay) }
|
{ this.renderList(this.store.visibleNetwork, externalOverlay) }
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -23,8 +23,7 @@ export default class Signer extends Component {
|
|||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Actionbar
|
<Actionbar title='Trusted Signer' />
|
||||||
title='Trusted Signer' />
|
|
||||||
<RequestsPage />
|
<RequestsPage />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -30,12 +30,6 @@ export default class Store {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action unsubscribe () {
|
|
||||||
if (this._timeoutId) {
|
|
||||||
clearTimeout(this._timeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action setBalance = (address, balance) => {
|
@action setBalance = (address, balance) => {
|
||||||
this.setBalances({ [address]: balance });
|
this.setBalances({ [address]: balance });
|
||||||
}
|
}
|
||||||
@ -50,6 +44,12 @@ export default class Store {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action unsubscribe () {
|
||||||
|
if (this._timeoutId) {
|
||||||
|
clearTimeout(this._timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fetchBalance (address) {
|
fetchBalance (address) {
|
||||||
this._api.eth
|
this._api.eth
|
||||||
.getBalance(address)
|
.getBalance(address)
|
||||||
|
@ -22,7 +22,7 @@ import ReorderIcon from 'material-ui/svg-icons/action/reorder';
|
|||||||
|
|
||||||
import { Container } from '~/ui';
|
import { Container } from '~/ui';
|
||||||
|
|
||||||
import styles from './Debug.css';
|
import styles from './debug.css';
|
||||||
|
|
||||||
export default class Debug extends Component {
|
export default class Debug extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
@ -14,4 +14,4 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default from './Debug';
|
export default from './debug';
|
||||||
|
@ -14,4 +14,4 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default from './MiningSettings';
|
export default from './miningSettings';
|
||||||
|
@ -14,4 +14,4 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default from './Status';
|
export default from './status';
|
||||||
|
@ -28,10 +28,19 @@
|
|||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
.blockinfo {
|
.blockInfo {
|
||||||
font-size: 24px;
|
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
line-height: 24px;
|
font-size: 1.5em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockByline {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padBottom {
|
||||||
|
padding-bottom: 1.25em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col,
|
.col,
|
@ -14,14 +14,16 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import bytes from 'bytes';
|
import bytes from 'bytes';
|
||||||
|
import moment from 'moment';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import { Container, ContainerTitle, Input } from '~/ui';
|
import { Container, ContainerTitle, Input } from '~/ui';
|
||||||
|
|
||||||
import styles from './Status.css';
|
|
||||||
import MiningSettings from '../MiningSettings';
|
import MiningSettings from '../MiningSettings';
|
||||||
|
|
||||||
|
import styles from './status.css';
|
||||||
|
|
||||||
export default class Status extends Component {
|
export default class Status extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
nodeStatus: PropTypes.object.isRequired,
|
nodeStatus: PropTypes.object.isRequired,
|
||||||
@ -44,23 +46,26 @@ export default class Status extends Component {
|
|||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<div className={ styles.row }>
|
<div className={ styles.row }>
|
||||||
<div className={ styles.col3 }>
|
<div className={ styles.col3 }>
|
||||||
<div className={ styles.col12 }>
|
<div className={ `${styles.col12} ${styles.padBottom}` }>
|
||||||
<ContainerTitle title='best block' />
|
<ContainerTitle title='best block' />
|
||||||
<h2 { ...this._test('best-block') } className={ styles.blockinfo }>
|
<div { ...this._test('best-block') } className={ styles.blockInfo }>
|
||||||
#{ nodeStatus.blockNumber.toFormat() }
|
#{ nodeStatus.blockNumber.toFormat() }
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.col12 }>
|
<div className={ styles.blockByline }>
|
||||||
|
{ moment().calendar(nodeStatus.blockTimestamp) }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={ `${styles.col12} ${styles.padBottom}` }>
|
||||||
<ContainerTitle title='peers' />
|
<ContainerTitle title='peers' />
|
||||||
<h2 { ...this._test('peers') } className={ styles.blockinfo }>
|
<div { ...this._test('peers') } className={ styles.blockInfo }>
|
||||||
{ peers }
|
{ peers }
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.col12 }>
|
</div>
|
||||||
|
<div className={ `${styles.col12} ${styles.padBottom}` }>
|
||||||
<ContainerTitle title='hash rate' />
|
<ContainerTitle title='hash rate' />
|
||||||
<h2 { ...this._test('hashrate') } className={ styles.blockinfo }>
|
<div { ...this._test('hashrate') } className={ styles.blockInfo }>
|
||||||
{ `${hashrate} H/s` }
|
{ `${hashrate} H/s` }
|
||||||
</h2>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.col5 }>
|
<div className={ styles.col5 }>
|
@ -14,4 +14,4 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default from './StatusPage';
|
export default from './statusPage';
|
||||||
|
@ -14,5 +14,9 @@
|
|||||||
/* You should have received a copy of the GNU General Public License
|
/* You should have received a copy of the GNU General Public License
|
||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
.container {
|
|
||||||
|
.body {
|
||||||
|
&>div {
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
}
|
||||||
}
|
}
|
@ -23,6 +23,8 @@ import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from '~/redux/
|
|||||||
import Debug from '../../components/Debug';
|
import Debug from '../../components/Debug';
|
||||||
import Status from '../../components/Status';
|
import Status from '../../components/Status';
|
||||||
|
|
||||||
|
import styles from './statusPage.css';
|
||||||
|
|
||||||
class StatusPage extends Component {
|
class StatusPage extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
nodeStatus: PropTypes.object.isRequired,
|
nodeStatus: PropTypes.object.isRequired,
|
||||||
@ -39,7 +41,7 @@ class StatusPage extends Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={ styles.body }>
|
||||||
<Status { ...this.props } />
|
<Status { ...this.props } />
|
||||||
<Debug { ...this.props } />
|
<Debug { ...this.props } />
|
||||||
</div>
|
</div>
|
@ -16,22 +16,16 @@
|
|||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { Actionbar, Page } from '~/ui';
|
import { Page } from '~/ui';
|
||||||
|
|
||||||
import StatusPage from './containers/StatusPage';
|
import StatusPage from './containers/StatusPage';
|
||||||
|
|
||||||
import styles from './status.css';
|
|
||||||
|
|
||||||
export default class Status extends Component {
|
export default class Status extends Component {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<Page title='status'>
|
||||||
<Actionbar
|
|
||||||
title='status' />
|
|
||||||
<Page>
|
|
||||||
<StatusPage />
|
<StatusPage />
|
||||||
</Page>
|
</Page>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var Api = require('../.npmjs/library.js').Api;
|
var Api = require('../.npmjs/parity/library.js').Api;
|
||||||
var Abi = require('../.npmjs/library.js').Abi;
|
var Abi = require('../.npmjs/parity/library.js').Abi;
|
||||||
|
|
||||||
if (typeof Api !== 'function') {
|
if (typeof Api !== 'function') {
|
||||||
throw new Error('No Api');
|
throw new Error('No Api');
|
@ -20,6 +20,7 @@ const path = require('path');
|
|||||||
const WebpackErrorNotificationPlugin = require('webpack-error-notification');
|
const WebpackErrorNotificationPlugin = require('webpack-error-notification');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||||
|
|
||||||
const Shared = require('./shared');
|
const Shared = require('./shared');
|
||||||
const DAPPS = require('../src/dapps');
|
const DAPPS = require('../src/dapps');
|
||||||
@ -41,7 +42,7 @@ module.exports = {
|
|||||||
output: {
|
output: {
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
path: path.join(__dirname, '../', DEST),
|
path: path.join(__dirname, '../', DEST),
|
||||||
filename: '[name].[hash].js'
|
filename: '[name].[hash:10].js'
|
||||||
},
|
},
|
||||||
|
|
||||||
module: {
|
module: {
|
||||||
@ -85,13 +86,20 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
include: [ /src/ ],
|
include: [ /src/ ],
|
||||||
|
// exclude: [ /src\/dapps/ ],
|
||||||
|
loader: isProd ? ExtractTextPlugin.extract([
|
||||||
|
// 'style-loader',
|
||||||
|
'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
||||||
|
'postcss-loader'
|
||||||
|
]) : undefined,
|
||||||
// use: [ 'happypack/loader?id=css' ]
|
// use: [ 'happypack/loader?id=css' ]
|
||||||
use: [
|
use: isProd ? undefined : [
|
||||||
'style-loader',
|
'style-loader',
|
||||||
'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
||||||
'postcss-loader'
|
'postcss-loader'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
exclude: [ /src/ ],
|
exclude: [ /src/ ],
|
||||||
@ -99,11 +107,15 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jpg)$/,
|
test: /\.(png|jpg)$/,
|
||||||
use: [ 'file-loader?name=[name].[hash].[ext]' ]
|
use: [ 'file-loader?&name=assets/[name].[hash:10].[ext]' ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(woff(2)|ttf|eot|svg|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
test: /\.(woff(2)|ttf|eot|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
use: [ 'file-loader' ]
|
use: [ 'file-loader?name=fonts/[name][hash:10].[ext]' ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.svg(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
|
use: [ 'file-loader?name=assets/[name].[hash:10].[ext]' ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
noParse: [
|
noParse: [
|
||||||
@ -153,13 +165,20 @@ module.exports = {
|
|||||||
if (!isProd) {
|
if (!isProd) {
|
||||||
plugins.push(
|
plugins.push(
|
||||||
new webpack.optimize.CommonsChunkPlugin({
|
new webpack.optimize.CommonsChunkPlugin({
|
||||||
filename: 'commons.[hash].js',
|
filename: 'commons.[hash:10].js',
|
||||||
name: 'commons',
|
name: 'commons',
|
||||||
minChunks: Infinity
|
minChunks: Infinity
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isProd) {
|
||||||
|
plugins.push(new ExtractTextPlugin({
|
||||||
|
filename: 'styles/[name].[hash:10].css',
|
||||||
|
allChunks: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
}())
|
}())
|
||||||
};
|
};
|
||||||
|
@ -23,14 +23,27 @@ const Shared = require('./shared');
|
|||||||
const ENV = process.env.NODE_ENV || 'development';
|
const ENV = process.env.NODE_ENV || 'development';
|
||||||
const isProd = ENV === 'production';
|
const isProd = ENV === 'production';
|
||||||
|
|
||||||
|
const LIBRARY = process.env.LIBRARY;
|
||||||
|
if (!LIBRARY) {
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
const SRC = LIBRARY.toLowerCase();
|
||||||
|
const OUTPUT_PATH = path.join(__dirname, '../.npmjs', SRC);
|
||||||
|
|
||||||
|
const TEST_CONTEXT = SRC === 'parity'
|
||||||
|
? '../npm/parity/test/'
|
||||||
|
: `../src/3rdparty/${SRC}/`;
|
||||||
|
|
||||||
|
console.log(`Building ${LIBRARY} from library.${SRC}.js to .npmjs/${SRC}`);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
context: path.join(__dirname, '../src'),
|
context: path.join(__dirname, '../src'),
|
||||||
target: 'node',
|
target: 'node',
|
||||||
entry: 'library.js',
|
entry: `library.${SRC}.js`,
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, '../.npmjs'),
|
path: OUTPUT_PATH,
|
||||||
filename: 'library.js',
|
filename: 'library.js',
|
||||||
library: 'Parity',
|
library: LIBRARY,
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
umdNamedDefine: true
|
umdNamedDefine: true
|
||||||
},
|
},
|
||||||
@ -66,19 +79,52 @@ module.exports = {
|
|||||||
plugins: Shared.getPlugins().concat([
|
plugins: Shared.getPlugins().concat([
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin([
|
||||||
{
|
{
|
||||||
from: '../parity.package.json',
|
from: `../npm/${SRC}/package.json`,
|
||||||
to: 'package.json',
|
to: 'package.json',
|
||||||
transform: function (content, path) {
|
transform: function (content, path) {
|
||||||
const json = JSON.parse(content.toString());
|
const json = JSON.parse(content.toString());
|
||||||
json.version = packageJson.version;
|
json.version = packageJson.version;
|
||||||
|
|
||||||
|
// Add tests dependencies to Dev Deps
|
||||||
|
json.devDependencies.chai = packageJson.devDependencies.chai;
|
||||||
|
json.devDependencies.mocha = packageJson.devDependencies.mocha;
|
||||||
|
json.devDependencies.nock = packageJson.devDependencies.nock;
|
||||||
|
|
||||||
|
// Add test script
|
||||||
|
json.scripts.test = 'mocha \'test/*.spec.js\'';
|
||||||
|
|
||||||
return new Buffer(JSON.stringify(json, null, ' '), 'utf-8');
|
return new Buffer(JSON.stringify(json, null, ' '), 'utf-8');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: '../LICENSE'
|
from: '../LICENSE'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Copy the base test config
|
||||||
{
|
{
|
||||||
from: '../parity.md',
|
from: '../npm/test',
|
||||||
|
to: 'test'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Copy the actual tests
|
||||||
|
{
|
||||||
|
context: TEST_CONTEXT,
|
||||||
|
from: '**/*.spec.js',
|
||||||
|
to: 'test',
|
||||||
|
transform: function (content, path) {
|
||||||
|
let output = content.toString();
|
||||||
|
|
||||||
|
// Don't skip tests
|
||||||
|
output = output.replace(/describe\.skip/, 'describe');
|
||||||
|
|
||||||
|
// Require parent library
|
||||||
|
output = output.replace('require(\'./\')', 'require(\'../\')');
|
||||||
|
|
||||||
|
return new Buffer(output, 'utf-8');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: `../npm/${SRC}/README.md`,
|
||||||
to: 'README.md'
|
to: 'README.md'
|
||||||
}
|
}
|
||||||
], { copyUnmodified: true })
|
], { copyUnmodified: true })
|
||||||
|
@ -36,6 +36,29 @@ function getBabelrc () {
|
|||||||
// [ "es2015", { "modules": false } ]
|
// [ "es2015", { "modules": false } ]
|
||||||
babelrc.presets[es2015Index] = [ 'es2015', { modules: false } ];
|
babelrc.presets[es2015Index] = [ 'es2015', { modules: false } ];
|
||||||
babelrc['babelrc'] = false;
|
babelrc['babelrc'] = false;
|
||||||
|
|
||||||
|
const BABEL_PRESET_ENV = process.env.BABEL_PRESET_ENV;
|
||||||
|
const npmStart = process.env.npm_lifecycle_event === 'start';
|
||||||
|
const npmStartApp = process.env.npm_lifecycle_event === 'start:app';
|
||||||
|
|
||||||
|
if (BABEL_PRESET_ENV && (npmStart || npmStartApp)) {
|
||||||
|
console.log('using babel-preset-env');
|
||||||
|
|
||||||
|
babelrc.presets = [
|
||||||
|
// 'es2017',
|
||||||
|
'stage-0', 'react',
|
||||||
|
[
|
||||||
|
'env',
|
||||||
|
{
|
||||||
|
targets: { browsers: ['last 2 Chrome versions'] },
|
||||||
|
modules: false,
|
||||||
|
loose: true,
|
||||||
|
useBuiltIns: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return babelrc;
|
return babelrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +128,13 @@ impl Default for UserDefaults {
|
|||||||
impl UserDefaults {
|
impl UserDefaults {
|
||||||
pub fn load<P>(path: P) -> Result<Self, String> where P: AsRef<Path> {
|
pub fn load<P>(path: P) -> Result<Self, String> where P: AsRef<Path> {
|
||||||
match File::open(path) {
|
match File::open(path) {
|
||||||
Ok(file) => from_reader(file).map_err(|e| e.to_string()),
|
Ok(file) => match from_reader(file) {
|
||||||
|
Ok(defaults) => Ok(defaults),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Error loading user defaults file: {:?}", e);
|
||||||
|
Ok(UserDefaults::default())
|
||||||
|
},
|
||||||
|
},
|
||||||
_ => Ok(UserDefaults::default()),
|
_ => Ok(UserDefaults::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,11 +89,13 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC
|
|||||||
signer.peek(&id).map(|confirmation| {
|
signer.peek(&id).map(|confirmation| {
|
||||||
let mut payload = confirmation.payload.clone();
|
let mut payload = confirmation.payload.clone();
|
||||||
// Modify payload
|
// Modify payload
|
||||||
match (&mut payload, modification.gas_price) {
|
if let ConfirmationPayload::SendTransaction(ref mut request) = payload {
|
||||||
(&mut ConfirmationPayload::SendTransaction(ref mut request), Some(gas_price)) => {
|
if let Some(gas_price) = modification.gas_price {
|
||||||
request.gas_price = gas_price.into();
|
request.gas_price = gas_price.into();
|
||||||
},
|
}
|
||||||
_ => {},
|
if let Some(gas) = modification.gas {
|
||||||
|
request.gas = gas.into();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Execute
|
// Execute
|
||||||
let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass));
|
let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass));
|
||||||
|
@ -183,7 +183,7 @@ fn should_confirm_transaction_and_dispatch() {
|
|||||||
let t = Transaction {
|
let t = Transaction {
|
||||||
nonce: U256::zero(),
|
nonce: U256::zero(),
|
||||||
gas_price: U256::from(0x1000),
|
gas_price: U256::from(0x1000),
|
||||||
gas: U256::from(10_000_000),
|
gas: U256::from(0x50505),
|
||||||
action: Action::Call(recipient),
|
action: Action::Call(recipient),
|
||||||
value: U256::from(0x1),
|
value: U256::from(0x1),
|
||||||
data: vec![]
|
data: vec![]
|
||||||
@ -198,7 +198,7 @@ fn should_confirm_transaction_and_dispatch() {
|
|||||||
let request = r#"{
|
let request = r#"{
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc":"2.0",
|
||||||
"method":"signer_confirmRequest",
|
"method":"signer_confirmRequest",
|
||||||
"params":["0x1", {"gasPrice":"0x1000"}, "test"],
|
"params":["0x1", {"gasPrice":"0x1000","gas":"0x50505"}, "test"],
|
||||||
"id":1
|
"id":1
|
||||||
}"#;
|
}"#;
|
||||||
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
|
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
|
||||||
|
@ -142,6 +142,8 @@ pub struct TransactionModification {
|
|||||||
/// Modified gas price
|
/// Modified gas price
|
||||||
#[serde(rename="gasPrice")]
|
#[serde(rename="gasPrice")]
|
||||||
pub gas_price: Option<U256>,
|
pub gas_price: Option<U256>,
|
||||||
|
/// Modified gas
|
||||||
|
pub gas: Option<U256>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents two possible return values.
|
/// Represents two possible return values.
|
||||||
@ -275,18 +277,26 @@ mod tests {
|
|||||||
let s1 = r#"{
|
let s1 = r#"{
|
||||||
"gasPrice":"0xba43b7400"
|
"gasPrice":"0xba43b7400"
|
||||||
}"#;
|
}"#;
|
||||||
let s2 = r#"{}"#;
|
let s2 = r#"{"gas": "0x1233"}"#;
|
||||||
|
let s3 = r#"{}"#;
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let res1: TransactionModification = serde_json::from_str(s1).unwrap();
|
let res1: TransactionModification = serde_json::from_str(s1).unwrap();
|
||||||
let res2: TransactionModification = serde_json::from_str(s2).unwrap();
|
let res2: TransactionModification = serde_json::from_str(s2).unwrap();
|
||||||
|
let res3: TransactionModification = serde_json::from_str(s3).unwrap();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(res1, TransactionModification {
|
assert_eq!(res1, TransactionModification {
|
||||||
gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
|
gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
|
||||||
|
gas: None,
|
||||||
});
|
});
|
||||||
assert_eq!(res2, TransactionModification {
|
assert_eq!(res2, TransactionModification {
|
||||||
gas_price: None,
|
gas_price: None,
|
||||||
|
gas: Some(U256::from_str("1233").unwrap()),
|
||||||
|
});
|
||||||
|
assert_eq!(res3, TransactionModification {
|
||||||
|
gas_price: None,
|
||||||
|
gas: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1685,7 +1685,7 @@ impl ChainSync {
|
|||||||
|
|
||||||
pub fn on_packet(&mut self, io: &mut SyncIo, peer: PeerId, packet_id: u8, data: &[u8]) {
|
pub fn on_packet(&mut self, io: &mut SyncIo, peer: PeerId, packet_id: u8, data: &[u8]) {
|
||||||
if packet_id != STATUS_PACKET && !self.peers.contains_key(&peer) {
|
if packet_id != STATUS_PACKET && !self.peers.contains_key(&peer) {
|
||||||
debug!(target:"sync", "Unexpected packet from unregistered peer: {}:{}", peer, io.peer_info(peer));
|
debug!(target:"sync", "Unexpected packet {} from unregistered peer: {}:{}", packet_id, peer, io.peer_info(peer));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let rlp = UntrustedRlp::new(data);
|
let rlp = UntrustedRlp::new(data);
|
||||||
|
@ -131,6 +131,10 @@ impl<'s, 'h> SyncIo for NetSyncIo<'s, 'h> {
|
|||||||
fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8 {
|
fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8 {
|
||||||
self.network.protocol_version(*protocol, peer_id).unwrap_or(0)
|
self.network.protocol_version(*protocol, peer_id).unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn peer_info(&self, peer_id: PeerId) -> String {
|
||||||
|
self.network.peer_client_version(peer_id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering};
|
|||||||
use std::ops::*;
|
use std::ops::*;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write, ErrorKind};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use ethkey::{KeyPair, Secret, Random, Generator};
|
use ethkey::{KeyPair, Secret, Random, Generator};
|
||||||
use mio::*;
|
use mio::*;
|
||||||
@ -381,8 +381,6 @@ pub struct Host {
|
|||||||
impl Host {
|
impl Host {
|
||||||
/// Create a new instance
|
/// Create a new instance
|
||||||
pub fn new(mut config: NetworkConfiguration, stats: Arc<NetworkStats>) -> Result<Host, NetworkError> {
|
pub fn new(mut config: NetworkConfiguration, stats: Arc<NetworkStats>) -> Result<Host, NetworkError> {
|
||||||
trace!(target: "host", "Creating new Host object");
|
|
||||||
|
|
||||||
let mut listen_address = match config.listen_address {
|
let mut listen_address = match config.listen_address {
|
||||||
None => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), DEFAULT_PORT)),
|
None => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), DEFAULT_PORT)),
|
||||||
Some(addr) => addr,
|
Some(addr) => addr,
|
||||||
@ -405,6 +403,7 @@ impl Host {
|
|||||||
// Setup the server socket
|
// Setup the server socket
|
||||||
let tcp_listener = try!(TcpListener::bind(&listen_address));
|
let tcp_listener = try!(TcpListener::bind(&listen_address));
|
||||||
listen_address = SocketAddr::new(listen_address.ip(), try!(tcp_listener.local_addr()).port());
|
listen_address = SocketAddr::new(listen_address.ip(), try!(tcp_listener.local_addr()).port());
|
||||||
|
debug!(target: "network", "Listening at {:?}", listen_address);
|
||||||
let udp_port = config.udp_port.unwrap_or(listen_address.port());
|
let udp_port = config.udp_port.unwrap_or(listen_address.port());
|
||||||
let local_endpoint = NodeEndpoint { address: listen_address, udp_port: udp_port };
|
let local_endpoint = NodeEndpoint { address: listen_address, udp_port: udp_port };
|
||||||
|
|
||||||
@ -707,7 +706,10 @@ impl Host {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
match TcpStream::connect(&address) {
|
match TcpStream::connect(&address) {
|
||||||
Ok(socket) => socket,
|
Ok(socket) => {
|
||||||
|
trace!(target: "network", "Connecting to {:?}", address);
|
||||||
|
socket
|
||||||
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!(target: "network", "Can't connect to address {:?}: {:?}", address, e);
|
debug!(target: "network", "Can't connect to address {:?}: {:?}", address, e);
|
||||||
return;
|
return;
|
||||||
@ -749,7 +751,9 @@ impl Host {
|
|||||||
let socket = match self.tcp_listener.lock().accept() {
|
let socket = match self.tcp_listener.lock().accept() {
|
||||||
Ok((sock, _addr)) => sock,
|
Ok((sock, _addr)) => sock,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
if e.kind() != ErrorKind::WouldBlock {
|
||||||
debug!(target: "network", "Error accepting connection: {:?}", e);
|
debug!(target: "network", "Error accepting connection: {:?}", e);
|
||||||
|
}
|
||||||
break
|
break
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user