Repository navigation
smite: add channel_ready oracle - #260
Conversation
| if context | ||
| .negotiated_features | ||
| .supports_feature(Features::OPTION_SCID_ALIAS) | ||
| && context.channel_ready.tlvs.short_channel_id.is_none() |
There was a problem hiding this comment.
Is it the alias feature from init that matters, or the one from channel_type? BOLT 2 seems to say both at different places:
There was a problem hiding this comment.
I think it is init rather than channel_type specifically here (see: lightning/bolts#910 (comment)). I think the reason for separating the two is that the one coupled with channel_type enables required privacy, while the one coupled with init says that if the alias is present (which it must be if option_scid_alias is negotiated in init), the peer can resend multiple channel_ready with a different alias as well (see: lightning/bolts#910 (comment), lightning/bolts#910 (comment)).
12ede2f to
e99f4c9
Compare
|
Some additional checks that could be added:
|
| assert_pass( | ||
| &channel_ready(), | ||
| Some(&channel_state()), | ||
| &Features::new(), | ||
| &HashMap::new(), | ||
| CONFIRMED_DEPTH, | ||
| &HashMap::new(), | ||
| ); |
There was a problem hiding this comment.
These parameter lists add a lot of bloat to the tests. It might be worth a test fixture to simplify things:
struct Inputs {
channel_ready: &ChannelReady,
channel: Option<&ChannelState>,
funding_confirmations: u32,
negotiated_features: &Features,
revealed_pubkeys: &HashMap<PublicKey, KeyOrigin>,
revealed_scids: &HashMap<ShortChannelId, ChannelId>,
}
// ... implement ::default() values
// ... implement needed helper methods
fn assert_pass(inputs: &Inputs);
fn assert_fail(inputs: &Inputs, expected: &str);Then a test only needs to specify the non-default fields:
let inputs = Inputs { funding_confirmations: MINIMUM_DEPTH - 1, ..Inputs::default() };
assert_fail(&inputs, ...);To avoid churn on this PR, probably better as a follow-up PR.
| /// neither an `M` nor implicitly handled, or any error from [`recv_non_ping`]. | ||
| fn recv_bolt<M: FromMessage>( | ||
| conn: &mut impl Connection, | ||
| channel_states: &mut HashMap<ChannelId, ChannelState>, | ||
| revealed_pubkeys: &mut HashMap<PublicKey, KeyOrigin>, | ||
| ) -> Result<(), ExecuteError> { | ||
| let cr: ChannelReady = recv_bolt(conn, RECV_CHANNEL_READY_TIMEOUT)?; | ||
|
|
||
| let state = channel_states | ||
| .get_mut(&cr.channel_id) | ||
| .ok_or(Violation::UnknownChannel(cr.channel_id))?; | ||
| *state.next_counterparty_per_commitment_point_mut() = Some(cr.second_per_commitment_point); | ||
|
|
||
| // Record the `second_per_commitment_point` as a per-commitment point the | ||
| // target revealed, for commitment number 1. It is unique, so record its | ||
| // only origin. | ||
| // | ||
| // TODO: Once we have the channel_ready oracle, flag a reused | ||
| // `second_per_commitment_point`, i.e. one already present in | ||
| // `revealed_pubkeys`, unless the entry is a `TargetPcp` for this same | ||
| // channel and commitment number. That exception allows the two legitimate | ||
| // repeats: the target may resend `channel_ready` with a different alias but | ||
| // the same point, and we may receive the same point again after a | ||
| // reconnection. | ||
| revealed_pubkeys.insert( | ||
| cr.second_per_commitment_point, | ||
| KeyOrigin::TargetPcp { | ||
| channel: cr.channel_id, | ||
| commitment_number: 1, | ||
| }, | ||
| ); | ||
|
|
||
| Ok(()) | ||
| revealed_scids: &mut HashMap<ShortChannelId, ChannelId>, | ||
| context: &ProgramContext, | ||
| bitcoin_cli: &mut impl BitcoinRpc, | ||
| timeout: Duration, | ||
| ) -> Result<M, ExecuteError> { |
There was a problem hiding this comment.
The parameter lists for recv_bolt and recv_non_ping are getting very large. We should probably convert them to methods instead (as a follow-up PR).
Why not
These seem good to me, though I'd rather add them in follow-ups so this PR doesn't continue to grow. I also wonder if it's worth checking for premature/unexpected |
I was thinking that, on mainnet, 2-block reorgs have already been observed (1 are frequent, while 2 are rare), so it's better to have
Yes, I was thinking of adding these and the other mentioned checks in a follow-up |
| } | ||
|
|
||
| #[test] | ||
| fn execute_recv_channel_ready_rejects_alias_reused_by_another_channel() { |
There was a problem hiding this comment.
Today we could also add a test for one channel's real SCID being reused as another channel's alias.
There was a problem hiding this comment.
Yes, I am aware of it, but that is a gap we currently have, and it is not allowed behaviour. So I don't think we should add tests allowing it. I think we should add only a rejection test in a follow-up PR when we reject those cases
There was a problem hiding this comment.
Actually I think we could write the test today, since we record the real SCID in revealed_scids.
The test we couldn't write yet is for the real SCID being used as the alias for the same channel, though an oracle unit test is probably good enough for that one.
Either way works for me, whether we add the test in this PR or a follow up.
There was a problem hiding this comment.
I added the case where the second channel_ready uses an alias that matches the real_scid of the first channel. I think the inverse case, where the first channel uses an alias that matches the real_scid of the second channel, could be added in a follow-up once we add the corresponding check
morehouse
left a comment
There was a problem hiding this comment.
Ready for squash and rebase.
| } | ||
|
|
||
| #[test] | ||
| fn execute_recv_channel_ready_rejects_alias_reused_by_another_channel() { |
There was a problem hiding this comment.
Actually I think we could write the test today, since we record the real SCID in revealed_scids.
The test we couldn't write yet is for the real SCID being used as the alias for the same channel, though an oracle unit test is probably good enough for that one.
Either way works for me, whether we add the test in this PR or a follow up.
| Message::ChannelReady(ref cr) => { | ||
| log::debug!("received channel_ready on {}", cr.channel_id); | ||
| let channel = channel_states.get(&cr.channel_id); | ||
| // TODO: Both lookups below call `getrawtransaction`. Returning | ||
| // confirmations alongside the block position would save one RPC. | ||
| let funding_outpoint = channel | ||
| .filter(|c| c.is_funding_outpoint_valid) | ||
| .map(|c| c.config.funding_outpoint); | ||
| let funding_confirmations = funding_outpoint | ||
| .map_or(0, |o| bitcoin_cli.get_transaction_confirmations(o.txid)); | ||
| let funding_scid = | ||
| funding_outpoint.and_then(|o| lookup_short_channel_id(bitcoin_cli, o)); | ||
| ChannelReadyOracle.evaluate(&ChannelReadyContext { | ||
| channel_ready: cr, | ||
| channel, | ||
| negotiated_features: &context.negotiated_features, | ||
| revealed_pubkeys, | ||
| funding_confirmations, | ||
| revealed_scids, | ||
| })?; | ||
| record_recv_channel_ready( | ||
| channel_states, | ||
| revealed_pubkeys, | ||
| revealed_scids, | ||
| funding_scid, | ||
| cr, | ||
| ); | ||
| return Ok(msg); | ||
| } |
There was a problem hiding this comment.
The implicit handling is starting to bloat recv_non_ping into something its name doesn't accurately describe.
I think it would be good to move the implicit handling into a handle_implicit helper called from recv_bolt in a follow up.
| // Only the first `channel_ready` sets these. `ChannelReadyOracle` checks | ||
| // resends against its point, and a resend's point is stale once the | ||
| // commitment state advances, so it must not overwrite the next point. | ||
| if state.received_channel_ready_point.is_none() { |
There was a problem hiding this comment.
I think we could also simplify is_channel_ready_expected slightly by checking the new received_channel_ready_point. Fine as a follow up.
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
ebc13c9 to
42e3871
Compare
Depends-on: #212 and #259
Add
ChannelReadyOracleand consolidate all previously scattered checks relevant to thechannel_readynegotiation into it, including unknownchannel_id, cases where the target omits theshort_channel_id aliasrequired byoption_scid_alias, and cases where the target reuses a per-commitment point from an earlier negotiation.Deferred oracle checks
short_channel_idalias collisions: BOLT 2 requires aliases not to collide with any of the target's realshort_channel_ids. Checking this requires fetching theshort_channel_idfor all channels we have with the target over RPC. Bulk fetching adds RPC overhead that reduces fuzzing throughput, while lazy lookups can still miss collisions. Until this can be checked more efficiently, it is not worthwhile for this narrow surface.