Skip to content

feat(contract): API to submit foreign chain config without RPC url consensus#2784

Merged
DSharifi merged 8 commits intomainfrom
dsharifi/add-rpc-url-to-config-on-chain-contract-only
Apr 13, 2026
Merged

feat(contract): API to submit foreign chain config without RPC url consensus#2784
DSharifi merged 8 commits intomainfrom
dsharifi/add-rpc-url-to-config-on-chain-contract-only

Conversation

@DSharifi
Copy link
Copy Markdown
Contributor

@DSharifi DSharifi commented Apr 9, 2026

closes #2783

This PR allows nodes to submit their RPC configuration on chain, and exposes a new API to get supported chains which doesn't require all nodes to use same RPC urls. Instead a chain is considered "supported" as long as all nodes have a configuration of it, regardless of their RPC urls.

The previous API to submit "policies" will be deprecated in a follow up PR.

For all the changes that will be done see #2779

@DSharifi DSharifi marked this pull request as ready for review April 9, 2026 14:25
@claude
Copy link
Copy Markdown

claude bot commented Apr 9, 2026

Code Review

The PR adds a new ForeignChainSupport data model allowing nodes to independently register their RPC configurations, with get_supported_foreign_chains returning the intersection (chains supported by all active participants). The migration from the old policy model is well handled. Good test coverage across unit, and sandbox tests.

One concern (non-blocking):

  • Migration panics on non-Running state (v3_8_1_state.rs:46): The From<MpcContract> migration panics if the contract isn't in Running state. This is fine as long as upgrades are always gated on Running state, which appears to be the convention. Just flagging for awareness — if an upgrade were ever triggered during Resharing/Initializing, it would brick the migration.

No critical issues found. The logic for unanimous support checking is correct (is_superset against active participants), the backward-compat bridge in vote_foreign_chain_policy properly populates the new data model, and the TODO for cleaning stale votes after resharing is appropriately tracked.

✅ Approved

@DSharifi
Copy link
Copy Markdown
Contributor Author

DSharifi commented Apr 9, 2026

One concern (non-blocking):

Migration panics on non-Running state** (v3_8_1_state.rs:46): The From<MpcContract> migration panics if the contract isn't in Running state.

Migrations always happen in running state. We always use this pattern in the From implementation for previous states.

Copy link
Copy Markdown
Collaborator

@netrome netrome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looks good to me but I find the votes_per_chain name misleading which made me confused when I initially read this.

Comment thread crates/contract/src/lib.rs
Comment thread crates/contract/src/lib.rs Outdated
@DSharifi DSharifi requested a review from netrome April 10, 2026 13:31
Comment thread crates/contract/tests/sandbox/foreign_chain_policy.rs
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is quite a bit of code duplication in this file.
It would be nice to introduce helper methods for registering and chain configurations and fetching supported chains, such that we reduce boilerplate and make it easier to write or extend tests.


// Then: only Bitcoin is in the supported set (Starknet is not unanimous)
let supported: Vec<String> = contract
.view("get_supported_foreign_chains")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets replace this string with GET_SUPPORTED_FOREIGN_CHAINS from the contract interface crate.

Comment on lines +126 to +181
fn test_migration_derives_supported_foreign_chains_from_policy() {
let policy = make_policy(vec![
dtos::ForeignChain::Bitcoin,
dtos::ForeignChain::Ethereum,
]);

let supported: dtos::SupportedForeignChains = policy
.chains
.keys()
.copied()
.collect::<BTreeSet<_>>()
.into();

assert!(supported.contains(&dtos::ForeignChain::Bitcoin));
assert!(supported.contains(&dtos::ForeignChain::Ethereum));
assert_eq!(supported.len(), 2);
}

#[test]
fn test_migration_derives_empty_supported_foreign_chains_from_empty_policy() {
let policy = dtos::ForeignChainPolicy {
chains: BTreeMap::new(),
};

let supported: dtos::SupportedForeignChains = policy
.chains
.keys()
.cloned()
.collect::<BTreeSet<_>>()
.into();

assert!(supported.is_empty());
}

#[test]
fn test_migration_supported_foreign_chains_preserves_all_chain_keys() {
let all_chains = vec![
dtos::ForeignChain::Solana,
dtos::ForeignChain::Bitcoin,
dtos::ForeignChain::Ethereum,
dtos::ForeignChain::Base,
dtos::ForeignChain::Bnb,
dtos::ForeignChain::Arbitrum,
];
let policy = make_policy(all_chains.clone());

let supported: dtos::SupportedForeignChains = policy
.chains
.keys()
.cloned()
.collect::<BTreeSet<_>>()
.into();

assert_eq!(supported.len(), all_chains.len());
for chain in &all_chains {
assert!(supported.contains(chain));
Copy link
Copy Markdown
Contributor

@kevindeforth kevindeforth Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must be missing something. We are not testing actual migration logic here, but verifying properties of the output of make_policy, which is a function defined in this test module.

If we want to test the actual migration logic, we need to construct an instance of MpcContract and test the migration method above, or break the relevant logic out to a function and test that in isolation.

Comment thread crates/contract/src/lib.rs
self.foreign_chain_policy_votes.to_dto()
}

pub fn get_supported_foreign_chains(&self) -> dtos::SupportedForeignChains {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have benchmarks for the gas costs of this method?
Did we consider alternative data structures for foreign_chain_configuration_by_node?

It's currently IterableMap<dtos::AccountId, dtos::ForeignChainConfiguration>, which is a nested map:
Participant <-> { ForeignChain <-> Set<RpcProvider>> }, which essentially is: AccountId <-> {Enum <-> Set<String> }

It seems to me this is optimizing for insertion, but not for lookup.
Right now, we only use it to check whether a specific foreign chain is supported by all participants, which would be more suitable for IterableMap<ForeginChain, BTreeSet<AccountId>>.

Copy link
Copy Markdown
Contributor Author

@DSharifi DSharifi Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a read method. We don't use gas for reads

Edit: It's used in the verify foreign transaction function

Copy link
Copy Markdown
Contributor

@kevindeforth kevindeforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some comments, but nothing blocking.

Copy link
Copy Markdown
Collaborator

@netrome netrome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating!

@DSharifi DSharifi added this pull request to the merge queue Apr 13, 2026
Merged via the queue into main with commit ac040a0 Apr 13, 2026
27 checks passed
@DSharifi DSharifi deleted the dsharifi/add-rpc-url-to-config-on-chain-contract-only branch April 13, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

contract allows nodes to submit configuration without requiring consensus on RPC providers

3 participants