Skip to content

Commit

Permalink
Add ValidatorInfo to the PeerContacts. Do basic verification
Browse files Browse the repository at this point in the history
  • Loading branch information
nibhar committed Nov 12, 2024
1 parent a272d80 commit 6b23f69
Show file tree
Hide file tree
Showing 15 changed files with 390 additions and 113 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dht/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ nimiq-blockchain-interface = { workspace = true }
nimiq-blockchain-proxy = { workspace = true }
nimiq-keys = { workspace = true }
nimiq-log = { workspace = true, optional = true }
nimiq-network-interface = { workspace = true }
nimiq-network-libp2p = { workspace = true }
nimiq-serde = { workspace = true }
nimiq-utils = { workspace = true }
Expand Down
89 changes: 64 additions & 25 deletions dht/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use nimiq_blockchain_proxy::BlockchainProxy;
use nimiq_keys::{Address, KeyPair};
use nimiq_network_interface::network::Network as NetworkInterface;
use nimiq_network_libp2p::{
dht::{DhtRecord, DhtVerifierError, Verifier as DhtVerifier},
discovery::peer_contacts::{ValidatorInfoError, ValidatorRecordVerifier},
libp2p::kad::Record,
PeerId,
Network, PeerId,
};
use nimiq_serde::Deserialize;
use nimiq_utils::tagged_signing::{TaggedSignable, TaggedSigned};
Expand All @@ -17,21 +19,20 @@ impl Verifier {
pub fn new(blockchain: BlockchainProxy) -> Self {
Self { blockchain }
}
}

fn verify_validator_record(&self, record: &Record) -> Result<DhtRecord, DhtVerifierError> {
// Deserialize the value of the record, which is a ValidatorRecord. If it fails return an error.
let validator_record =
TaggedSigned::<ValidatorRecord<PeerId>, KeyPair>::deserialize_from_vec(&record.value)
.map_err(DhtVerifierError::MalformedValue)?;

// Deserialize the key of the record which is an Address. If it fails return an error.
let validator_address = Address::deserialize_from_vec(record.key.as_ref())
.map_err(DhtVerifierError::MalformedKey)?;

impl ValidatorRecordVerifier for Verifier {
fn verify_validator_record(
&self,
signed_record: &TaggedSigned<
ValidatorRecord<<Network as NetworkInterface>::PeerId>,
KeyPair,
>,
) -> Result<(), ValidatorInfoError> {
// Acquire blockchain read access. For now exclude Light clients.
let blockchain = match self.blockchain {
BlockchainProxy::Light(ref _light_blockchain) => {
return Err(DhtVerifierError::UnknownTag)
panic!("Light Blockchains cannot verify validator records.")
}
BlockchainProxy::Full(ref full_blockchain) => full_blockchain,
};
Expand All @@ -40,27 +41,26 @@ impl Verifier {
// Get the staking contract to retrieve the public key for verification.
let staking_contract = blockchain_read
.get_staking_contract_if_complete(None)
.ok_or(DhtVerifierError::StateIncomplete)?;
.ok_or(ValidatorInfoError::StateIncomplete)?;

// Get the public key needed for verification.
let data_store = blockchain_read.get_staking_contract_store();
let txn = blockchain_read.read_transaction();
let public_key = staking_contract
.get_validator(&data_store.read(&txn), &validator_address)
.ok_or(DhtVerifierError::UnknownValidator(validator_address))?
.get_validator(
&data_store.read(&txn),
&signed_record.record.validator_address,
)
.ok_or(ValidatorInfoError::UnknownValidator(
signed_record.record.validator_address.clone(),
))?
.signing_key;

// Verify the record.
validator_record
signed_record
.verify(&public_key)
.then(|| {
DhtRecord::Validator(
record.publisher.unwrap(),
validator_record.record,
record.clone(),
)
})
.ok_or(DhtVerifierError::InvalidSignature)
.then(|| ())

Check warning on line 62 in dht/src/lib.rs

View workflow job for this annotation

GitHub Actions / Clippy Report

unnecessary closure used with `bool::then`

warning: unnecessary closure used with `bool::then` --> dht/src/lib.rs:60:9 | 60 | / signed_record 61 | | .verify(&public_key) 62 | | .then(|| ()) | |________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations = note: `#[warn(clippy::unnecessary_lazy_evaluations)]` on by default help: use `then_some` instead | 62 | .then_some(()) | ~~~~~~~~~~~~~
.ok_or(ValidatorInfoError::InvalidSignature)
}
}

Expand All @@ -75,7 +75,46 @@ impl DhtVerifier for Verifier {

// Depending on tag perform the verification.
match tag {
ValidatorRecord::<PeerId>::TAG => self.verify_validator_record(record),
ValidatorRecord::<PeerId>::TAG => {
// Deserialize the value of the record, which is a ValidatorRecord. If it fails return an error.
let validator_record =
TaggedSigned::<ValidatorRecord<PeerId>, KeyPair>::deserialize_from_vec(
&record.value,
)
.map_err(DhtVerifierError::MalformedValue)?;

// Make sure the peer who published the record is also the one signed into the record.
if record.publisher.ok_or(DhtVerifierError::MissingPublisher)?
!= validator_record.record.peer_id
{
return Err(DhtVerifierError::PublisherMismatch(
record.publisher.unwrap(),
validator_record.record.peer_id,
));
}

// Deserialize the key of the record which is an Address. If it fails return an error.
let validator_address = Address::deserialize_from_vec(record.key.as_ref())
.map_err(DhtVerifierError::MalformedKey)?;

// Make sure the address used as key is also the one signed into the record.
if validator_address != validator_record.record.validator_address {
return Err(DhtVerifierError::AddressMismatch(
validator_address,
validator_record.record.validator_address,
));
}

self.verify_validator_record(&validator_record)
.map_err(DhtVerifierError::ValidatorInfoError)
.and_then(|_| {
Ok(DhtRecord::Validator(
validator_record.record.peer_id,
validator_record.record,
record.clone(),
))
})

Check warning on line 116 in dht/src/lib.rs

View workflow job for this annotation

GitHub Actions / Clippy Report

using `Result.and_then(|x| Ok(y))`, which is more succinctly expressed as `map(|x| y)`

warning: using `Result.and_then(|x| Ok(y))`, which is more succinctly expressed as `map(|x| y)` --> dht/src/lib.rs:108:17 | 108 | / self.verify_validator_record(&validator_record) 109 | | .map_err(DhtVerifierError::ValidatorInfoError) 110 | | .and_then(|_| { 111 | | Ok(DhtRecord::Validator( ... | 115 | | )) 116 | | }) | |______________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bind_instead_of_map = note: `#[warn(clippy::bind_instead_of_map)]` on by default help: try | 108 ~ self.verify_validator_record(&validator_record) 109 + .map_err(DhtVerifierError::ValidatorInfoError).map(|_| DhtRecord::Validator( 110 + validator_record.record.peer_id, 111 + validator_record.record, 112 + record.clone(), 113 + )) |
}
_ => {
log::error!(tag, "DHT invalid record tag received");
Err(DhtVerifierError::UnknownTag)
Expand Down
11 changes: 8 additions & 3 deletions network-libp2p/src/behaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ use parking_lot::RwLock;
use rand::rngs::OsRng;

use crate::{
connection_pool,
connection_pool::behaviour::Config as PoolConfig,
discovery::{self, peer_contacts::PeerContactBook},
connection_pool::{self, behaviour::Config as PoolConfig},
discovery::{
self,
peer_contacts::{PeerContactBook, ValidatorRecordVerifier},
},
dispatch::codecs::MessageCodec,
Config,
};
Expand Down Expand Up @@ -50,6 +52,7 @@ impl Behaviour {
contacts: Arc<RwLock<PeerContactBook>>,
peer_score_params: gossipsub::PeerScoreParams,
force_dht_server_mode: bool,
#[cfg(feature = "kad")] verifier: Arc<dyn ValidatorRecordVerifier>,
) -> Self {
let public_key = config.keypair.public();
let peer_id = public_key.to_peer_id();
Expand All @@ -68,6 +71,8 @@ impl Behaviour {
config.discovery.clone(),
config.keypair.clone(),
Arc::clone(&contacts),
#[cfg(feature = "kad")]
verifier,
);

// Gossipsub behaviour
Expand Down
4 changes: 2 additions & 2 deletions network-libp2p/src/connection_pool/behaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ impl Behaviour {
let own_peer_id = own_contact.peer_id();

contacts
.query(self.required_services)
.query(self.required_services, true)
.filter_map(|contact| {
let peer_id = contact.peer_id();
if peer_id != own_peer_id
Expand Down Expand Up @@ -562,7 +562,7 @@ impl Behaviour {
let own_peer_id = own_contact.peer_id();

contacts
.query(services)
.query(services, true)
.filter_map(|contact| {
let peer_id = contact.peer_id();
if peer_id != own_peer_id
Expand Down
19 changes: 13 additions & 6 deletions network-libp2p/src/dht.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
use libp2p::{kad::Record, PeerId};
use nimiq_keys::Address;
use nimiq_network_interface::network::Network as NetworkInterface;
use nimiq_serde::DeserializeError;
use nimiq_validator_network::validator_record::ValidatorRecord;

pub use crate::network_types::DhtRecord;
use crate::{discovery::peer_contacts::ValidatorInfoError, Network};

#[derive(Debug)]
pub enum DhtVerifierError {
MalformedTag,
UnknownTag,
MalformedKey(DeserializeError),
MalformedValue(DeserializeError),
UnknownTag,
UnknownValidator(Address),
StateIncomplete,
InvalidSignature,
MissingPublisher,
PublisherMismatch(
<Network as NetworkInterface>::PeerId,
<Network as NetworkInterface>::PeerId,
),
AddressMismatch(Address, Address),
ValidatorInfoError(ValidatorInfoError),
}

pub trait Verifier: Send + Sync {
Expand All @@ -23,9 +29,10 @@ pub trait Verifier: Send + Sync {
/// Dummy implementation for testcases
impl Verifier for () {
fn verify(&self, record: &Record) -> Result<DhtRecord, DhtVerifierError> {
let peer_id = PeerId::random();
Ok(DhtRecord::Validator(
PeerId::random(),
ValidatorRecord::<PeerId>::new(PeerId::random(), 0u64),
peer_id,
ValidatorRecord::<PeerId>::new(peer_id, Address::default(), 0u64),
record.clone(),
))
}
Expand Down
21 changes: 15 additions & 6 deletions network-libp2p/src/discovery/behaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use libp2p::{
identity::Keypair,
swarm::{
behaviour::{ConnectionClosed, ConnectionEstablished},
CloseConnection, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, ToSwarm,
ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, ToSwarm,
},
Multiaddr, PeerId,
};
Expand All @@ -22,7 +22,7 @@ use parking_lot::RwLock;

use super::{
handler::{Handler, HandlerOutEvent},
peer_contacts::{PeerContact, PeerContactBook},
peer_contacts::{PeerContact, PeerContactBook, ValidatorRecordVerifier},
};

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -115,13 +115,18 @@ pub struct Behaviour {

/// Timer to do house-keeping in the peer address book.
house_keeping_timer: Interval,

/// dht verifier TODO
#[cfg(feature = "kad")]
verifier: Arc<dyn ValidatorRecordVerifier>,
}

impl Behaviour {
pub fn new(
config: Config,
keypair: Keypair,
peer_contact_book: Arc<RwLock<PeerContactBook>>,
#[cfg(feature = "kad")] verifier: Arc<dyn ValidatorRecordVerifier>,
) -> Self {
let house_keeping_timer = interval(config.house_keeping_interval);
peer_contact_book.write().update_own_contact(&keypair);
Expand All @@ -139,6 +144,8 @@ impl Behaviour {
peer_contact_book,
events,
house_keeping_timer,
#[cfg(feature = "kad")]
verifier,
}
}

Expand Down Expand Up @@ -177,6 +184,8 @@ impl NetworkBehaviour for Behaviour {
self.keypair.clone(),
self.peer_contact_book(),
remote_addr.clone(),
#[cfg(feature = "kad")]
Arc::clone(&self.verifier),
))
}

Expand All @@ -194,6 +203,8 @@ impl NetworkBehaviour for Behaviour {
self.keypair.clone(),
self.peer_contact_book(),
addr.clone(),
#[cfg(feature = "kad")]
Arc::clone(&self.verifier),
))
}

Expand Down Expand Up @@ -288,10 +299,8 @@ impl NetworkBehaviour for Behaviour {
.push_back(ToSwarm::NewExternalAddrCandidate(observed_address));
}
HandlerOutEvent::Update => self.events.push_back(ToSwarm::GenerateEvent(Event::Update)),
HandlerOutEvent::Error(_) => self.events.push_back(ToSwarm::CloseConnection {
peer_id,
connection: CloseConnection::All,
}),
// Errors must not result in a closed connection as light clients are unable to verify ValidatorRecord.
HandlerOutEvent::Error(error) => log::trace!(?error, "Received invalid contact"),
}
}
}
Loading

0 comments on commit 6b23f69

Please sign in to comment.