bidirectional: target chain from event.caip2_id instead of dest#696
bidirectional: target chain from event.caip2_id instead of dest#696
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Bitcoin as a recognized chain across the chain-signatures stack and introduces CAIP-2/deprecated chain-id parsing helpers to consistently derive target chains for bidirectional events.
Changes:
- Extend
mpc_primitives::ChainwithBitcoinplus associated timing/checkpoint config. - Add chain-id metadata + parsing helpers (
from_caip2_chain_id,from_deprecated_chain_id) inmpc-cryptoKDF and expose equivalent constructors in primitives. - Switch bidirectional event target-chain derivation from
destparsing to CAIP-2 parsing; add “bitcoin unsupported” handling in node RPC.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| chain-signatures/primitives/src/lib.rs | Adds Chain::Bitcoin, timing/checkpoint defaults, and chain-id parsing constructors bridging to mpc_crypto::kdf::Chain. |
| chain-signatures/node/src/stream/ops.rs | Introduces SignBidirectionalEvent::target_chain() (CAIP-2 based) and uses it during bidirectional respond processing. |
| chain-signatures/node/src/rpc.rs | Adds Bitcoin match arms with “unsupported” behavior in publish flow and client selection. |
| chain-signatures/node/src/protocol/signature.rs | Updates logging to reference event.target_chain() for bidirectional requests. |
| chain-signatures/crypto/src/kdf.rs | Refactors chain-id metadata, adds CAIP-2/deprecated chain-id parsing helpers, and derives PartialEq/Eq for Chain. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
920b7a3 to
7361463
Compare
7361463 to
706c370
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
chain-signatures/crypto/src/kdf.rs
Outdated
| .find(|chain| chain.deprecated_chain_id() == chain_id) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
New parsing helpers from_caip2_chain_id / from_deprecated_chain_id were added but aren’t covered by the existing unit tests in this module. Adding tests for expected mappings (and a negative case) would help prevent accidental regressions in chain-id strings and ensure the ALL list stays in sync with the enum variants.
| #[cfg(test)] | |
| mod tests { | |
| use super::Chain; | |
| #[test] | |
| fn caip2_chain_id_mappings_are_correct() { | |
| // Positive cases: ensure that each known CAIP-2 chain id maps back to the expected Chain. | |
| assert_eq!(Chain::from_caip2_chain_id("near:mainnet"), Some(Chain::Near)); | |
| assert_eq!(Chain::from_caip2_chain_id("eip155:1"), Some(Chain::Ethereum)); | |
| assert_eq!( | |
| Chain::from_caip2_chain_id("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"), | |
| Some(Chain::Solana) | |
| ); | |
| assert_eq!( | |
| Chain::from_caip2_chain_id("bip122:000000000019d6689c085ae165831e93"), | |
| Some(Chain::Bitcoin) | |
| ); | |
| assert_eq!( | |
| Chain::from_caip2_chain_id("polkadot:2034"), | |
| Some(Chain::Hydration) | |
| ); | |
| // Negative case: unknown CAIP-2 chain id must not map to any Chain. | |
| assert_eq!(Chain::from_caip2_chain_id("unknown:chain"), None); | |
| } | |
| #[test] | |
| fn deprecated_chain_id_mappings_are_correct() { | |
| // Positive cases: ensure that each known deprecated chain id maps back to the expected Chain. | |
| assert_eq!(Chain::from_deprecated_chain_id("0x18d"), Some(Chain::Near)); | |
| assert_eq!(Chain::from_deprecated_chain_id("0x1"), Some(Chain::Ethereum)); | |
| assert_eq!( | |
| Chain::from_deprecated_chain_id("0x800001f5"), | |
| Some(Chain::Solana) | |
| ); | |
| assert_eq!( | |
| Chain::from_deprecated_chain_id("bip122:000000000019d6689c085ae165831e93"), | |
| Some(Chain::Bitcoin) | |
| ); | |
| assert_eq!( | |
| Chain::from_deprecated_chain_id("polkadot:2034"), | |
| Some(Chain::Hydration) | |
| ); | |
| // Negative case: unknown deprecated chain id must not map to any Chain. | |
| assert_eq!(Chain::from_deprecated_chain_id("0xdeadbeef"), None); | |
| } | |
| } |
| pub fn target_chain(&self) -> anyhow::Result<Chain> { | ||
| let event_caip2_id = self.caip2_id(); | ||
| Chain::from_caip2_chain_id(&event_caip2_id).ok_or_else(|| { | ||
| anyhow::anyhow!( | ||
| "unable to parse target chain from event caip2_id field: {event_caip2_id}" | ||
| ) | ||
| }) | ||
| } |
There was a problem hiding this comment.
SignBidirectionalEvent::target_chain currently clones the caip2_id String via self.caip2_id() just to parse it. This adds an avoidable allocation on a hot path (request processing). Consider returning a borrowed &str/Cow<'_, str> from caip2_id() (or matching on self in target_chain and borrowing the inner field) so parsing can operate on a reference.
volovyks
left a comment
There was a problem hiding this comment.
Let's not forget about docs. I was expecting that this PR would be mostly about documenting struct fields and flows.
| } | ||
| } | ||
|
|
||
| pub fn target_chain(&self) -> anyhow::Result<Chain> { |
There was a problem hiding this comment.
Returning Result<> here should not be required if we will parse string -> Cahin enum when constructing this Event.
It will reduce complexity (like event.target_chain()?;).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| pub fn target_chain(&self) -> Chain { | ||
| // we can directly unwrap because we've checked that the chain id is valid during event deserialization in the indexer | ||
| Chain::from_caip2_chain_id(&self.caip2_id()).unwrap() |
| pub fn from_caip2_chain_id(chain_id: &str) -> Result<Self, ChainFromError> { | ||
| Self::iter() | ||
| .into_iter() | ||
| .find(|chain| chain.caip2_chain_id() == chain_id) | ||
| .ok_or_else(|| ChainFromError::UnknownCaip2Id(chain_id.to_string())) | ||
| } | ||
|
|
||
| pub fn from_deprecated_chain_id(chain_id: &str) -> Result<Self, ChainFromError> { | ||
| Self::iter() | ||
| .into_iter() | ||
| .find(|chain| chain.deprecated_chain_id() == chain_id) | ||
| .ok_or_else(|| ChainFromError::UnknownDeprecatedId(chain_id.to_string())) | ||
| } |
| /// The program ID of the Solana program that emitted this event. | ||
| /// | ||
| /// Used by off-chain services (e.g., relayers, indexers) to filter and | ||
| /// verify events from the correct program. | ||
| /// | ||
| /// MUST be provided and MUST match the deployed program ID. |
volovyks
left a comment
There was a problem hiding this comment.
Maybe we could rely a bit less on Copilot and address the suggestions from it before requesting a review 🙂
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| pub fn target_chain(&self) -> Result<Chain, mpc_primitives::ChainFromError> { | ||
| // we can directly unwrap because we've checked that the chain id is valid during event deserialization in the indexer | ||
| Chain::from_caip2_chain_id(&self.caip2_id()) | ||
| } |
| pub fn from_caip2_chain_id(chain_id: &str) -> Result<Self, ChainFromError> { | ||
| Self::iter() | ||
| .into_iter() | ||
| .find(|chain| chain.caip2_chain_id() == chain_id) | ||
| .ok_or_else(|| ChainFromError::UnknownCaip2Id(chain_id.to_string())) | ||
| } |
| /// The program ID of the Solana program that emitted this event. | ||
| /// | ||
| /// Used by MPC service to filter and verify events from the correct program. | ||
| /// | ||
| /// MUST match the deployed program ID. |
No description provided.