-
Notifications
You must be signed in to change notification settings - Fork 37
simln-lib/feature: Add ability to run with mocked ln network #248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
simln-lib/feature: Add ability to run with mocked ln network #248
Conversation
6795897
to
3d2685b
Compare
Is this ready for a first review pass? |
Sure, @carlaKC, it is ready for a first review pass. I have been thinking about how to add some tests |
3d2685b
to
140f087
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few big picture comments on how we're going to split this up, now that I've had some time to think about this (and live with these decisions on a downstream fork) I think we can cut it up a bit cleaner.
Also feel free to cherry pick carlaKC@d1afc33 for docs once this reaches the stage where you need then (removing the embarrassing rebase booger in there, kek).
sim-cli/src/parsing.rs
Outdated
@@ -96,6 +101,27 @@ enum NodeConnection { | |||
Eclair(eclair::EclairConnection), | |||
} | |||
|
|||
/// Data structure that is used to parse information from the simulation file, used to pair two node policies together | |||
/// without the other internal state that is used in our simulated network. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wdym by "without the other internal state that is used in our simulated network?"
Without all the stuff in SimulatedChannel
? I don't think the parsing module needs to worry too much about noting that - we have a few structs across modules that are quite duplicated but different for the sake of separation.
sim-cli/src/main.rs
Outdated
@@ -20,7 +20,7 @@ async fn main() -> anyhow::Result<()> { | |||
.init() | |||
.unwrap(); | |||
|
|||
let sim = create_simulation(&cli).await?; | |||
let (sim, sim_network) = create_simulation(&cli).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm torn about whether to have all the logic about our two different run "modes" here vs hidden in create_simulation
(I know I did that in my branch, but I didn't love it).
WDYT of something like this which separates validation and has two dedicated functions that assumes we've done some validation on cli:
cli.validate()?; // does all the checks on nodes and sim_network
let tasks = TaskTracker::new();
let sim = if sim.network.is_none() {
create_simulation(cli, tasks)
} else {
create_simulation_with_network(cli, tasks);
}
sim.run()
tasks.wait
I don't like that we have some duplication in the different create network functions, but it is nice to clearly see the two cases and have more specialized functions.
(Related to comment about getting rid of new_with_sim_network
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree; it makes the code more concise
simln-lib/src/sim_node.rs
Outdated
@@ -654,7 +655,7 @@ pub struct SimGraph { | |||
|
|||
/// track all tasks spawned to process payments in the graph. Note that handling the shutdown of tasks | |||
/// in this tracker must be done externally. | |||
tasks: TaskTracker, | |||
pub tasks: TaskTracker, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're passing tasks
into the constructor, we should have it available at our call site and then don't need to make it pub
?
simln-lib/src/lib.rs
Outdated
@@ -527,6 +528,42 @@ impl Simulation { | |||
} | |||
} | |||
|
|||
pub async fn new_with_sim_network( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second look, this seems like a bit of a layering violation to have Simulation
be aware of SimGraph
stuff - it should just care about getting some arbitrary set of lightning nodes.
If we have a dedicated create_simulation_with_network
in the parsing module, we can just create the graph with SimGraph::new
and then use ln_node_from_graph
to get the nodes to pass into Simulation::new
.
The nice side effect of passing tasks in is that we don't actually need the graph to wait on anymore, we can just tasks.wait
at the end of the sim.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking really nice with the last refactor!
sim-cli/src/main.rs
Outdated
let sim_path = read_sim_path(cli.data_dir.clone(), cli.sim_file.clone()).await?; | ||
let sim_params = serde_json::from_str(&std::fs::read_to_string(sim_path)?).map_err(|e| { | ||
anyhow!( | ||
"Could not deserialize node connection data or activity description from simulation file (line {}, col {}, err: {}).", | ||
e.line(), | ||
e.column(), | ||
e.to_string() | ||
) | ||
})?; | ||
|
||
cli.validate(&sim_params)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nittynit: move all of this + Cli::parse
into a helper to keep main thin
sim-cli/src/parsing.rs
Outdated
@@ -22,7 +27,7 @@ pub const DEFAULT_DATA_DIR: &str = "."; | |||
/// The default simulation file to be used by the simulator. | |||
pub const DEFAULT_SIM_FILE: &str = "sim.json"; | |||
|
|||
/// The default expected payment amount for the simulation, around ~$10 at the time of writing. | |||
/// The default expected payment amount for the simulation, around ~$10 at the time of w. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
acciental change
sim-cli/src/parsing.rs
Outdated
@@ -81,9 +86,31 @@ pub struct Cli { | |||
pub fix_seed: Option<u64>, | |||
} | |||
|
|||
impl Cli { | |||
pub fn validate(&self, sim_params: &SimParams) -> Result<(), anyhow::Error> { | |||
// Validate that nodes and sim_graph are exclusively set, and setup node clients from the populated field. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"and setup node clients from the populated field." out of date now
sim-cli/src/parsing.rs
Outdated
type NodeMapping = | ||
Result<(HashMap<PublicKey, NodeInfo>, HashMap<String, NodeInfo>), LightningError>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a struct rather than a type? little more readable IMO
sim-cli/src/parsing.rs
Outdated
for sim_channel in sim_network { | ||
nodes_info.insert( | ||
sim_channel.node_1.pubkey, | ||
node_info(sim_channel.node_1.pubkey), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way around using this internal function? Rather iter through channels
and go via the API?
Parsing shouldn't really have to know the underlying details of how we create mocked node info.
sim-cli/src/parsing.rs
Outdated
let (pk_node_map, alias_node_map) = add_node_to_maps(&nodes_info)?; | ||
let validated_activities = validate_activities( | ||
activity.to_vec(), | ||
pk_node_map, | ||
alias_node_map, | ||
get_node_info, | ||
) | ||
.await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This duplication (here in the other simulation creation function) makes me think that we don't have the right API for Simulation::new()
.
Maybe we should be passing activities
into simulation.run()
instead of requiring that we have them on hand when the simulation is created? Although we would then need access to the nodes to validate our activities (which is something I'm certain belongs here, not in the simulator), so tradeoffs.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree @carlaKC that those lines should be abstracted away.
I think passing activities
into simulation.run()
is a great idea, but maybe the validation could be done in main.rs
let (mut sim, nodes_info) = if sim_params.sim_network.is_empty() {
create_simulation(&cli, &sim_params, tasks.clone()).await?
} else {
create_simulation_with_network(&cli, &sim_params, tasks.clone()).await?
};
let sim2 = sim.clone();
let validated_activities =
get_validated_activities(&sim.nodes, nodes_info, sim_params.activity).await?;
sim.run(validated_activities).await?;
pub async fn run(
&mut self,
activities: Vec<ActivityDefinition>,
) -> Result<(), SimulationError> {
self.internal_run().await?;
self.activity = activities;
// Close our TaskTracker and wait for any background tasks
// spawned during internal_run to complete.
self.tasks.close();
self.tasks.wait().await;
Ok(())
}
get_validated_activities
would be a function located in parsing.rs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good!
I believe that we can get rid of self.activity
on Simulation
completely if we pass activities into Simulation.run
?
ie, not sure why we need self.activity = activities;
?
5e2151e
to
b09ac15
Compare
sim-cli/src/main.rs
Outdated
@@ -29,6 +37,7 @@ async fn main() -> anyhow::Result<()> { | |||
})?; | |||
|
|||
sim.run().await?; | |||
tasks.wait().await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait
is covered in sim.run
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unaddressed, believe that this can be removed?
sim-cli/src/parsing.rs
Outdated
@@ -140,23 +188,94 @@ impl TryFrom<&Cli> for SimulationCfg { | |||
} | |||
} | |||
|
|||
struct NodeMapping(HashMap<PublicKey, NodeInfo>, HashMap<String, NodeInfo>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's name these fields? Original motivation to ask for the struct was to make this more easily readable
sim-cli/src/parsing.rs
Outdated
// Copy all simulated channels into a read-only routing graph, allowing to pathfind for | ||
// individual payments without locking th simulation graph (this is a duplication of the channels, but the performance tradeoff is worthwhile for concurrent pathfinding). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: line wrapping off here
.map_err(|e| SimulationError::SimulatedNetworkError(format!("{:?}", e)))?, | ||
); | ||
|
||
let nodes = ln_node_from_graph(simulation_graph.clone(), routing_graph).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: inline nodes
in Simulation::new
sim-cli/src/parsing.rs
Outdated
@@ -362,3 +485,17 @@ fn mkdir(dir: PathBuf) -> anyhow::Result<PathBuf> { | |||
fs::create_dir_all(&dir)?; | |||
Ok(dir) | |||
} | |||
|
|||
pub async fn parse_cli() -> anyhow::Result<(Cli, SimParams)> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slight personal preference to call Cli::parse
at the call site and pass in &cli
but not strongly held opinion
sim-cli/src/parsing.rs
Outdated
"Simulation file cannot contain {} nodes and {} sim_graph entries, simulation can only be run with real | ||
or simulated nodes not both.", sim_params.nodes.len(), sim_params.sim_network.len(), | ||
)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: formatting looks odd here?
rustfmt doesn't work well inside of macros, maybe you can manually clean this up a bit?
sim-cli/src/parsing.rs
Outdated
let (pubkey, node_info) = channel.create_simulated_node(); | ||
nodes_info.insert(pubkey, node_info); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we have to return node info both node_1
and node_2
here?
If we have a simulated channel with:
node_1: { pubkey: A }
node_2: {pubkey: B }
And then we want to send payments:
"activity": [
{
"source": "B",
"destination": "A",
"interval_secs": 1,
"amount_msat": 2000
}
]
As we have this now, we won't be able to look up the source node in our map?
Shaping up really well! I think that we can make the validation changes discussed here and then this will be good to go. If possible, let's do the refactor to move validation in a commit before this one (just add a new one before it, rather than fixup). |
cac73aa
to
c57a299
Compare
OK, @carlaKC. I think I was able to insert the abstraction of the validation of activities commit before all others. (I have not done this process before. sweat_smile: I hope I did it right.) Let me know if it is okay. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job on the git surgery!
Some comments seem to be unaddressed since last review (eg). GH sometimes hides them after a push, so worth another look.
sim-cli/src/main.rs
Outdated
@@ -29,6 +37,7 @@ async fn main() -> anyhow::Result<()> { | |||
})?; | |||
|
|||
sim.run().await?; | |||
tasks.wait().await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unaddressed, believe that this can be removed?
simln-lib/src/lib.rs
Outdated
nodes: HashMap<PublicKey, Arc<Mutex<dyn LightningNode>>>, | ||
/// The activity that are to be executed on the node. | ||
activity: Vec<ActivityDefinition>, | ||
pub nodes: HashMap<PublicKey, Arc<Mutex<dyn LightningNode>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't love that we have to make this pub
Now that activity validation has been DRY-ed up, I think that we can have an in-between approach where the create_simulation...
functions return them. Rather than exposing the client info to main
just so we can validate the activites.
Patch here for suggestion, I think it hits a good middle ground.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is getting really close, just a few cleanups left. Tested and works like a charm with the mainnet graph 🙌
Feel free to squash and fix up the last few nits. Would also like to cherry pick this docs commit + fixup so that we have a write up of how to run this.
sim-cli/src/parsing.rs
Outdated
let validated_activities = | ||
validate_activities(activity.to_vec(), pk_node_map, alias_node_map, get_node).await?; | ||
Ok(validated_activities) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can inline to Ok(validate_activities(...))
, intermediate var doesn't add much
sim-cli/src/parsing.rs
Outdated
/// Data structure that is used to parse information from the simulation file. It is used to | ||
/// create a mocked network |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: comments should end with .
sim-cli/src/parsing.rs
Outdated
struct NodeMapping { | ||
///Tracks nodes-info by pubkey | ||
pk_node_map: HashMap<PublicKey, NodeInfo>, | ||
///Tracks nodes-info by aliases | ||
alias_node_map: HashMap<String, NodeInfo>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing space after //
and needs full stop or delete the comment, I think the field names are quite self-explanatory
sim-cli/src/parsing.rs
Outdated
@@ -290,7 +412,7 @@ async fn validate_activities( | |||
Ok(validated_activities) | |||
} | |||
|
|||
async fn read_sim_path(data_dir: PathBuf, sim_file: PathBuf) -> anyhow::Result<PathBuf> { | |||
pub async fn read_sim_path(data_dir: PathBuf, sim_file: PathBuf) -> anyhow::Result<PathBuf> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this function need to be made pub
?
simln-lib/src/sim_node.rs
Outdated
@@ -269,6 +270,14 @@ impl ChannelState { | |||
} | |||
} | |||
|
|||
///Represents the necessary simulated nodes in order to create a simulation network |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing space after ///
simln-lib/src/sim_node.rs
Outdated
@@ -269,6 +270,14 @@ impl ChannelState { | |||
} | |||
} | |||
|
|||
///Represents the necessary simulated nodes in order to create a simulation network | |||
pub struct SimulatedNodes { | |||
pub pubkey_node_1: PublicKey, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pubkey
fields are unnecessary here, we have this in NodeInfo
simln-lib/src/sim_node.rs
Outdated
pub fn create_simulated_nodes(&self) -> SimulatedNodes { | ||
SimulatedNodes { | ||
pubkey_node_1: self.node_1.policy.pubkey, | ||
node_1_info: node_info(self.node_1.policy.pubkey), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this can just return (NodeInfo, NodeInfo)
because we don't need the pubkeys
simln-lib/src/sim_node.rs
Outdated
@@ -488,7 +506,7 @@ impl<'a, T: SimNetwork> SimNode<'a, T> { | |||
} | |||
|
|||
/// Produces the node info for a mocked node, filling in the features that the simulator requires. | |||
fn node_info(pubkey: PublicKey) -> NodeInfo { | |||
pub fn node_info(pubkey: PublicKey) -> NodeInfo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not need to be public
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tACK, go ahead and squash 🎉
I'm going to hold off on merging this until we can cut 2.4, so that we don't ship this totally new feature in a point release. Will merge once that's done 👌
* Liquidity checks: HTLCs are only forwarded if the node has sufficient | ||
liquidity in the mocked channel. | ||
|
||
Not included: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a followup: we should also mention that we don't have a concept of expiry/force closes in the simulator
349e09c
to
0c91cf7
Compare
address #215