[stable] Backports (#8782)

* Fix light sync with initial validator-set contract (#8528)

* Fix #8468

* Use U256::max_value() instead

* Fix again

* Also change initial transaction gas

* Don't open Browser post-install on Mac (#8641)

Since we start parity with the UI disabled per default now, opening the browser post installation will show an annoying error message, confusing the user. This patch removes opening the browser to prevent that annoyance.

fixes #8194

* Prefix uint fmt with `0x` with alternate flag

* Set the request index to that of the current request (#8683)

* Set the request index to that of the current request

When setting up the chain of (two) requests to look up a block by hash, the second need to refer to the first. This fixes an issue where the back ref was set to the subsequent request, not the current one. When the requests are executed we loop through them in order and ensure the requests that should produce headers all match up. We do this by index so they better be right.

In other words: off by one.

* Node table sorting according to last contact data (#8541)

* network-devp2p: sort nodes in node table using last contact data

* network-devp2p: rename node contact types in node table json output

* network-devp2p: fix node table tests

* network-devp2p: note node failure when failed to establish connection

* network-devp2p: handle UselessPeer error

* network-devp2p: note failure when marking node as useless

* network-devp2p: handle UselessPeer disconnect (#8686)
This commit is contained in:
André Silva 2018-06-04 21:28:15 +01:00 committed by Afri Schoedon
parent 02ffb9be42
commit c7202a771d
7 changed files with 219 additions and 83 deletions

View File

@ -709,7 +709,7 @@ impl Spec {
author: *genesis.author(),
timestamp: genesis.timestamp(),
difficulty: *genesis.difficulty(),
gas_limit: *genesis.gas_limit(),
gas_limit: U256::max_value(),
last_hashes: Arc::new(Vec::new()),
gas_used: 0.into(),
};
@ -718,7 +718,7 @@ impl Spec {
let tx = Transaction {
nonce: self.engine.account_start_nonce(0),
action: Action::Call(a),
gas: U256::from(50_000_000), // TODO: share with client.
gas: U256::max_value(),
gas_price: U256::default(),
value: U256::default(),
data: d,

View File

@ -3,6 +3,4 @@
test -f /usr/local/libexec/uninstall-parity.sh && /usr/local/libexec/uninstall-parity.sh || true
killall -9 parity && sleep 5
su $USER -c "open /Applications/Parity\ Ethereum.app"
sleep 5
su $USER -c "open http://127.0.0.1:8180/"
exit 0

View File

@ -129,9 +129,8 @@ impl LightFetch {
}
}
BlockId::Hash(h) => {
reqs.push(request::HeaderByHash(h.into()).into());
let idx = reqs.len();
reqs.push(request::HeaderByHash(h.into()).into());
Ok(HeaderRef::Unresolved(idx, h.into()))
}
_ => Err(errors::unknown_block()) // latest, earliest, and pending will have all already returned.

View File

@ -230,6 +230,18 @@ fn eth_get_block() {
assert_eq!(tester.handler.handle_request_sync(req_block).unwrap(), res_block);
}
#[test]
fn eth_get_block_by_hash() {
let chain = extract_chain!("BlockchainTests/bcGasPricerTest/RPC_API_Test");
let tester = EthTester::from_chain(&chain);
// We're looking for block number 4 from "RPC_API_Test_Frontier"
let req_block = r#"{"method":"eth_getBlockByHash","params":["0x9c9bdab4cb53fd834e790b13545597f026494d42112e84c0aca9dd6bcc545295",false],"id":1,"jsonrpc":"2.0"}"#;
let res_block = r#"{"jsonrpc":"2.0","result":{"author":"0x8888f1f195afa192cfee860698584c030f4c9db1","difficulty":"0x200c0","extraData":"0x","gasLimit":"0x1dd8112","gasUsed":"0x5458","hash":"0x9c9bdab4cb53fd834e790b13545597f026494d42112e84c0aca9dd6bcc545295","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x8888f1f195afa192cfee860698584c030f4c9db1","mixHash":"0xaddea8d25bb0f955fa6c1d58d74ab8a3fec99d37943e2a261e3b12f97d6bff7c","nonce":"0x8e18bed16d5a88da","number":"0x4","parentHash":"0x2cbf4fc930c5b4c87598f43fc8eb26dccdab2f58a7d0d3ca92ec60a5444a330e","receiptsRoot":"0x7ed8026cf72ed0e98e6fd53ab406e51ffd34397d9da0052494ff41376fda7b5f","sealFields":["0xa0addea8d25bb0f955fa6c1d58d74ab8a3fec99d37943e2a261e3b12f97d6bff7c","0x888e18bed16d5a88da"],"sha3Uncles":"0x75cc08a7cb2cf8081446659fecb2633fb6b922d26edd59bd2272b1f5cae1c78b","size":"0x661","stateRoot":"0x68805721294e365020aca15ed56c360d9dc2cf03cbeff84c9b84b8aed023bfb5","timestamp":"0x59d662ff","totalDifficulty":"0xa0180","transactions":["0xb094b9dc356dbb8b256402c6d5709288066ad6a372c90c9c516f14277545fd58"],"transactionsRoot":"0x97a593d8d7e15b57f5c6bb25bc6c325463ef99f874bc08a78656c3ab5cb23262","uncles":["0xa1e9c9ecd2af999e0723aae1dc55dd9789ca618e0b34badcc8ac7d9a3dad3af2","0x81d429b6b6635214a2b0f976cc4b2ed49808140d6bede50129bc10d22ac9249e"]},"id":1}"#;
assert_eq!(tester.handler.handle_request_sync(req_block).unwrap(), res_block);
}
// a frontier-like test with an expanded gas limit and balance on known account.
const TRANSACTION_COUNT_SPEC: &'static [u8] = br#"{
"name": "Frontier (Test)",

View File

@ -55,7 +55,13 @@ macro_rules! impl_uint {
impl fmt::LowerHex for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:#x}", self.0)
// TODO: remove this once updated to new version of primitives
// including https://github.com/paritytech/primitives/pull/33
// replace with `::core::fmt::LowerHex::fmt(self.0, f)`
if f.alternate() {
write!(f, "0x");
}
write!(f, "{:x}", self.0)
}
}
@ -102,19 +108,19 @@ impl_uint!(U64, u64, 1);
impl serde::Serialize for U128 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
serializer.serialize_str(&format!("0x{:x}", self.0))
serializer.serialize_str(&format!("{:#x}", self))
}
}
impl serde::Serialize for U256 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
serializer.serialize_str(&format!("0x{:x}", self.0))
serializer.serialize_str(&format!("{:#x}", self))
}
}
impl serde::Serialize for U64 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
serializer.serialize_str(&format!("0x{:x}", self.0))
serializer.serialize_str(&format!("{:#x}", self))
}
}

View File

@ -227,10 +227,13 @@ pub struct NetworkContext<'s> {
impl<'s> NetworkContext<'s> {
/// Create a new network IO access point. Takes references to all the data that can be updated within the IO handler.
fn new(io: &'s IoContext<NetworkIoMessage>,
fn new(
io: &'s IoContext<NetworkIoMessage>,
protocol: ProtocolId,
session: Option<SharedSession>, sessions: Arc<RwLock<Slab<SharedSession>>>,
reserved_peers: &'s HashSet<NodeId>) -> NetworkContext<'s> {
session: Option<SharedSession>,
sessions: Arc<RwLock<Slab<SharedSession>>>,
reserved_peers: &'s HashSet<NodeId>,
) -> NetworkContext<'s> {
let id = session.as_ref().map(|s| s.lock().token());
NetworkContext {
io: io,
@ -722,10 +725,8 @@ impl Host {
let address = {
let mut nodes = self.nodes.write();
if let Some(node) = nodes.get_mut(id) {
node.attempts += 1;
node.endpoint.address
}
else {
} else {
debug!(target: "network", "Connection to expired node aborted");
return;
}
@ -737,6 +738,7 @@ impl Host {
},
Err(e) => {
debug!(target: "network", "{}: Can't connect to address {:?}: {:?}", id, address, e);
self.nodes.write().note_failure(&id);
return;
}
}
@ -822,12 +824,17 @@ impl Host {
Err(e) => {
let s = session.lock();
trace!(target: "network", "Session read error: {}:{:?} ({:?}) {:?}", token, s.id(), s.remote_addr(), e);
if let ErrorKind::Disconnect(DisconnectReason::IncompatibleProtocol) = *e.kind() {
if let Some(id) = s.id() {
if !self.reserved_nodes.read().contains(id) {
self.nodes.write().mark_as_useless(id);
match *e.kind() {
ErrorKind::Disconnect(DisconnectReason::IncompatibleProtocol) | ErrorKind::Disconnect(DisconnectReason::UselessPeer) => {
if let Some(id) = s.id() {
if !self.reserved_nodes.read().contains(id) {
let mut nodes = self.nodes.write();
nodes.note_failure(&id);
nodes.mark_as_useless(id);
}
}
}
},
_ => {},
}
kill = true;
break;
@ -891,6 +898,10 @@ impl Host {
}
}
}
// Note connection success
self.nodes.write().note_success(&id);
for (p, _) in self.handlers.read().iter() {
if s.have_capability(*p) {
ready_data.push(*p);
@ -1162,7 +1173,9 @@ impl IoHandler<NetworkIoMessage> for Host {
if let Some(session) = session {
session.lock().disconnect(io, DisconnectReason::DisconnectRequested);
if let Some(id) = session.lock().id() {
self.nodes.write().mark_as_useless(id)
let mut nodes = self.nodes.write();
nodes.note_failure(&id);
nodes.mark_as_useless(id);
}
}
trace!(target: "network", "Disabling peer {}", peer);

View File

@ -21,6 +21,8 @@ use std::net::{SocketAddr, ToSocketAddrs, SocketAddrV4, SocketAddrV6, Ipv4Addr,
use std::path::PathBuf;
use std::str::FromStr;
use std::{fs, mem, slice};
use std::time::{self, Duration, SystemTime};
use rand::{self, Rng};
use ethereum_types::H512;
use rlp::*;
use error::{Error, ErrorKind};
@ -128,40 +130,64 @@ impl FromStr for NodeEndpoint {
}
}
#[derive(PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum PeerType {
_Required,
Optional
}
/// A type for representing an interaction (contact) with a node at a given time
/// that was either a success or a failure.
#[derive(Clone, Copy, Debug)]
pub enum NodeContact {
Success(SystemTime),
Failure(SystemTime),
}
impl NodeContact {
fn success() -> NodeContact {
NodeContact::Success(SystemTime::now())
}
fn failure() -> NodeContact {
NodeContact::Failure(SystemTime::now())
}
fn time(&self) -> SystemTime {
match *self {
NodeContact::Success(t) | NodeContact::Failure(t) => t
}
}
/// Filters and old contact, returning `None` if it happened longer than a
/// week ago.
fn recent(&self) -> Option<&NodeContact> {
let t = self.time();
if let Ok(d) = t.elapsed() {
if d < Duration::from_secs(60 * 60 * 24 * 7) {
return Some(self);
}
}
None
}
}
#[derive(Debug)]
pub struct Node {
pub id: NodeId,
pub endpoint: NodeEndpoint,
pub peer_type: PeerType,
pub attempts: u32,
pub failures: u32,
pub last_contact: Option<NodeContact>,
}
const DEFAULT_FAILURE_PERCENTAGE: usize = 50;
impl Node {
pub fn new(id: NodeId, endpoint: NodeEndpoint) -> Node {
Node {
id: id,
endpoint: endpoint,
peer_type: PeerType::Optional,
attempts: 0,
failures: 0,
}
}
/// Returns the node's failure percentage (0..100) in buckets of 5%. If there are 0 connection attempts for this
/// node the default failure percentage is returned (50%).
pub fn failure_percentage(&self) -> usize {
if self.attempts == 0 {
DEFAULT_FAILURE_PERCENTAGE
} else {
(self.failures * 100 / self.attempts / 5 * 5) as usize
last_contact: None,
}
}
}
@ -191,8 +217,7 @@ impl FromStr for Node {
id: id,
endpoint: endpoint,
peer_type: PeerType::Optional,
attempts: 0,
failures: 0,
last_contact: None,
})
}
}
@ -231,28 +256,61 @@ impl NodeTable {
/// Add a node to table
pub fn add_node(&mut self, mut node: Node) {
// preserve attempts and failure counter
let (attempts, failures) =
self.nodes.get(&node.id).map_or((0, 0), |n| (n.attempts, n.failures));
node.attempts = attempts;
node.failures = failures;
// preserve node last_contact
node.last_contact = self.nodes.get(&node.id).and_then(|n| n.last_contact);
self.nodes.insert(node.id.clone(), node);
}
/// Returns a list of ordered nodes according to their most recent contact
/// and filtering useless nodes. The algorithm for creating the sorted nodes
/// is:
/// - Contacts that aren't recent (older than 1 week) are discarded
/// - (1) Nodes with a successful contact are ordered (most recent success first)
/// - (2) Nodes with unknown contact (older than 1 week or new nodes) are randomly shuffled
/// - (3) Nodes with a failed contact are ordered (oldest failure first)
/// - The final result is the concatenation of (1), (2) and (3)
fn ordered_entries(&self) -> Vec<&Node> {
let mut refs: Vec<&Node> = self.nodes.values()
.filter(|n| !self.useless_nodes.contains(&n.id))
.collect();
let mut success = Vec::new();
let mut failures = Vec::new();
let mut unknown = Vec::new();
refs.sort_by(|a, b| {
a.failure_percentage().cmp(&b.failure_percentage())
.then_with(|| a.failures.cmp(&b.failures))
.then_with(|| b.attempts.cmp(&a.attempts)) // we use reverse ordering for number of attempts
let nodes = self.nodes.values()
.filter(|n| !self.useless_nodes.contains(&n.id));
for node in nodes {
// discard contact points older that aren't recent
match node.last_contact.as_ref().and_then(|c| c.recent()) {
Some(&NodeContact::Success(_)) => {
success.push(node);
},
Some(&NodeContact::Failure(_)) => {
failures.push(node);
},
None => {
unknown.push(node);
},
}
}
success.sort_by(|a, b| {
let a = a.last_contact.expect("vector only contains values with defined last_contact; qed");
let b = b.last_contact.expect("vector only contains values with defined last_contact; qed");
// inverse ordering, most recent successes come first
b.time().cmp(&a.time())
});
refs
failures.sort_by(|a, b| {
let a = a.last_contact.expect("vector only contains values with defined last_contact; qed");
let b = b.last_contact.expect("vector only contains values with defined last_contact; qed");
// normal ordering, most distant failures come first
a.time().cmp(&b.time())
});
rand::thread_rng().shuffle(&mut unknown);
success.append(&mut unknown);
success.append(&mut failures);
success
}
/// Returns node ids sorted by failure percentage, for nodes with the same failure percentage the absolute number of
@ -296,10 +354,17 @@ impl NodeTable {
}
}
/// Increase failure counte for a node
/// Set last contact as failure for a node
pub fn note_failure(&mut self, id: &NodeId) {
if let Some(node) = self.nodes.get_mut(id) {
node.failures += 1;
node.last_contact = Some(NodeContact::failure());
}
}
/// Set last contact as success for a node
pub fn note_success(&mut self, id: &NodeId) {
if let Some(node) = self.nodes.get_mut(id) {
node.last_contact = Some(NodeContact::success());
}
}
@ -396,19 +461,38 @@ mod json {
pub nodes: Vec<Node>,
}
#[derive(Serialize, Deserialize)]
pub enum NodeContact {
#[serde(rename = "success")]
Success(u64),
#[serde(rename = "failure")]
Failure(u64),
}
impl NodeContact {
pub fn into_node_contact(self) -> super::NodeContact {
match self {
NodeContact::Success(s) => super::NodeContact::Success(
time::UNIX_EPOCH + Duration::from_secs(s)
),
NodeContact::Failure(s) => super::NodeContact::Failure(
time::UNIX_EPOCH + Duration::from_secs(s)
),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct Node {
pub url: String,
pub attempts: u32,
pub failures: u32,
pub last_contact: Option<NodeContact>,
}
impl Node {
pub fn into_node(self) -> Option<super::Node> {
match super::Node::from_str(&self.url) {
Ok(mut node) => {
node.attempts = self.attempts;
node.failures = self.failures;
node.last_contact = self.last_contact.map(|c| c.into_node_contact());
Some(node)
},
_ => None,
@ -418,10 +502,18 @@ mod json {
impl<'a> From<&'a super::Node> for Node {
fn from(node: &'a super::Node) -> Self {
let last_contact = node.last_contact.and_then(|c| {
match c {
super::NodeContact::Success(t) =>
t.duration_since(time::UNIX_EPOCH).ok().map(|d| NodeContact::Success(d.as_secs())),
super::NodeContact::Failure(t) =>
t.duration_since(time::UNIX_EPOCH).ok().map(|d| NodeContact::Failure(d.as_secs())),
}
});
Node {
url: format!("{}", node),
attempts: node.attempts,
failures: node.failures,
last_contact
}
}
}
@ -464,42 +556,54 @@ mod tests {
}
#[test]
fn table_failure_percentage_order() {
fn table_last_contact_order() {
let node1 = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node2 = Node::from_str("enode://b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node3 = Node::from_str("enode://c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node4 = Node::from_str("enode://d979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node5 = Node::from_str("enode://e979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node6 = Node::from_str("enode://f979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let id1 = H512::from_str("a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id2 = H512::from_str("b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id3 = H512::from_str("c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id4 = H512::from_str("d979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id5 = H512::from_str("e979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id6 = H512::from_str("f979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let mut table = NodeTable::new(None);
table.add_node(node1);
table.add_node(node2);
table.add_node(node3);
table.add_node(node4);
table.add_node(node5);
table.add_node(node6);
// node 1 - failure percentage 100%
table.get_mut(&id1).unwrap().attempts = 2;
// failures - nodes 1 & 2
table.note_failure(&id1);
table.note_failure(&id1);
// node2 - failure percentage 33%
table.get_mut(&id2).unwrap().attempts = 3;
table.note_failure(&id2);
// node3 - failure percentage 0%
table.get_mut(&id3).unwrap().attempts = 1;
// success - nodes 3 & 4
table.note_success(&id3);
table.note_success(&id4);
// node4 - failure percentage 50% (default when no attempts)
// success - node 5 (old contact)
table.get_mut(&id5).unwrap().last_contact = Some(NodeContact::Success(time::UNIX_EPOCH));
// unknown - node 6
let r = table.nodes(IpFilter::default());
assert_eq!(r[0][..], id3[..]);
assert_eq!(r[1][..], id2[..]);
assert_eq!(r[2][..], id4[..]);
assert_eq!(r[3][..], id1[..]);
assert_eq!(r[0][..], id4[..]); // most recent success
assert_eq!(r[1][..], id3[..]);
// unknown (old contacts and new nodes), randomly shuffled
assert!(
r[2][..] == id5[..] && r[3][..] == id6[..] ||
r[2][..] == id6[..] && r[3][..] == id5[..]
);
assert_eq!(r[4][..], id1[..]); // oldest failure
assert_eq!(r[5][..], id2[..]);
}
#[test]
@ -507,23 +611,27 @@ mod tests {
let tempdir = TempDir::new("").unwrap();
let node1 = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node2 = Node::from_str("enode://b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let node3 = Node::from_str("enode://c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
let id1 = H512::from_str("a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id2 = H512::from_str("b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
let id3 = H512::from_str("c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
{
let mut table = NodeTable::new(Some(tempdir.path().to_str().unwrap().to_owned()));
table.add_node(node1);
table.add_node(node2);
table.add_node(node3);
table.get_mut(&id1).unwrap().attempts = 1;
table.get_mut(&id2).unwrap().attempts = 1;
table.note_failure(&id2);
table.note_success(&id2);
table.note_failure(&id3);
}
{
let table = NodeTable::new(Some(tempdir.path().to_str().unwrap().to_owned()));
let r = table.nodes(IpFilter::default());
assert_eq!(r[0][..], id1[..]);
assert_eq!(r[1][..], id2[..]);
assert_eq!(r[0][..], id2[..]); // latest success
assert_eq!(r[1][..], id1[..]); // unknown
assert_eq!(r[2][..], id3[..]); // oldest failure
}
}