refactor(wire): make node interface a trait#1035
Conversation
|
You already did the heavy lifting, but if you only need to mock one method I find it sometimes useful to impl a trait only for the method. Example: struct Carlos {}
impl Carlos {
fn say_hi() {
println!("hi");
}
}
trait SayBye {
fn say_bye(&self);
}
impl SayBye for Carlos {
fn say_bye(&self) {
println!("bye");
}
}
fn example(c: &impl SayBye) {
c.say_bye();
}
#[cfg(test)]
mod tests {
struct MockCarlos {}
impl crate::SayBye for MockCarlos {
fn say_bye(&self) {
println!("mock bye");
}
}
} |
That's a fair approach, but we need a few functions for each test, but they need different functions themselves. Overall they fit almost the whole API. I thought about breaking this trait up into several traits and tying them up with Something like pub trait NodeInterface: Network + Blocks + Filters {} |
This makes sense. Me and @oleonardolima are planning something similar for |
Looks like were going to need https://github.com/Davidson-Souza/maybe-async2 |
| Onetry((IpAddr, u16)), | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
There was a problem hiding this comment.
derive statements should be before or after docs ?
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub struct RequestData { |
| //! under `node_handle.rs`. We do this to make our testing easier, since we can mock a node while | ||
| //! testing other modules, and to allow people to reuse other crates without wire: simply | ||
| //! re-implement the relevant parts of node interface and you are fine! |
There was a problem hiding this comment.
To provide better ergonomics for mocking we could offer default implementation for our main traits. partially addressing #776, which avoids garbage code trough the codebase such as
(the unimplemented call which could be avoid by defining it as a default)There was a problem hiding this comment.
But how wold a default implementation look like here? Returning hardcoded data? I think this is pretty error-prone. I woud prefer adding a test-only implementation that assists mocking (I will open a new PR with this soon).
There was a problem hiding this comment.
just unimplemented! as a default would reduce a lot the code, I see returning hardcoded data and no-ops like
unimplemented! on every trait definition.
Also, the trait splits that we are working on will mitigate this already but these cases where defaults would reduce code will still exist i think
So, how about: MempoolOps: (Open for naming suggestion). On my filters one I would use @moisesPompilio is doing the same for RPC, following Core docs's categories.
Not really, it's a different problem. There is the |
36e9bc7 to
f2c2d01
Compare
Node interface is currently a concrete type. While it make adding new methods and passing it as parameter easier, it has the major drawback of making testing for modules using it incredibly hard, due to requiring a full `UtreexoNode` to request messages. By traiting it out, we can build minimal mocks that only reply to what we need, with pre-filled data. This commit introduces several traits, named: MempoolOperations, Networking, BlockData and NodeConfig. Each trait gives the handle a subset of functions. They are all tied-up by the meta-trait `NodeInterface`. If you don't need all methods, you may only use the cathegories that makes sense to you. For example: `floresta-electrum` only needs `MempoolOperations` and `BlockData`. Finally, `NodeHandle` was moved to its own module, and left `NodeInterface` as just the interface and types.
f2c2d01 to
38485e8
Compare
|
Alright, created four new traits: If I get a green light for this approach, I can refactor existing core. I believe |
|
I'll review this in a few hours. |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
| /// A request to addnode that can be made to the node. | ||
| /// | ||
| /// This enum represents all the possible requests that can be made to the node to add, remove | ||
| /// or just try to connect to a peer, following the same pattern as the `addnode` command in [Bitcoin Core]. | ||
| /// | ||
| /// [Bitcoin Core]: (https://bitcoincore.org/en/doc/29.0.0/rpc/network/addnode/) | ||
| pub enum AddNode { | ||
| /// The `Add` variant is used to add a peer to the node's peer list | ||
| Add((IpAddr, u16)), | ||
|
|
||
| /// The `Remove` variant is used to remove a peer from the node's peer list | ||
| Remove((IpAddr, u16)), | ||
|
|
||
| /// The `Onetry` variant is used to try a connection to the peer once, but not add it to the peer list. | ||
| Onetry((IpAddr, u16)), | ||
| } |
There was a problem hiding this comment.
Use SocketAddr instead of IpAddr + u16.
There was a problem hiding this comment.
Or maybe leave it to a follow up, as this change would be out of scope for this PR. But there's no reason to use IpAddr and u16 instead of a SocketAddr.
| /// Add a peer to the node's peer list. | ||
| /// | ||
| /// This function will add this peer to a special list of peers such that, if we lose the | ||
| /// connection, we will keep trying to connect to it until we succeed. | ||
| Add((IpAddr, u16, bool)), | ||
|
|
||
| /// Removes a node from the node's peer list. | ||
| /// | ||
| /// This function will remove a node that was added with [`AddNode::Add`]. This will **not** | ||
| /// disconnect the peer, but if it disconnects, it will not be reconnected again. | ||
| Remove((IpAddr, u16)), | ||
|
|
||
| /// Attempts to connect to a peer once. | ||
| /// | ||
| /// Different from [`AddNode::Add`], this function will try to connect to the peer once, but | ||
| /// will not add it to the node's added peers list. | ||
| Onetry((IpAddr, u16, bool)), | ||
|
|
||
| /// Attempt to disconnect from a peer. | ||
| Disconnect((IpAddr, u16)), |
There was a problem hiding this comment.
Use SocketAddr instead of IpAddr + u16.
There was a problem hiding this comment.
I would rename the traits to:
ChainNetworkMempoolFiltersWallet
There was a problem hiding this comment.
I didn't want to give it names that will clash with other structs, like Mempool
There was a problem hiding this comment.
You could add Methods as a suffix.
Description and Notes
While working on my filters PR, I've reached a roadblock on how to test it. I would need to create a utreexo node with mocked peers to reply with the data I've needed. This would be very convoluted and very out of scope. I've noticed that traiting out the node interface would make it way easier, since I can simply mock the node interface to return my test data. No
UtreexoNodewould be used here.This also aligns with out roadmap, and can easily be ported to
floresta-domain.Changelog
Note to reviewers
I know it's pretty disgusting to look at a function signature like:
but we can't use
asyncsyntax sugar in trait declarations. This is basically the de-sugar ofasyncdone manually.