@@ -6,10 +6,12 @@ use anyhow::{Context, bail};
66use backon:: { ConstantBuilder , Retryable } ;
77use e2e_tests:: CLUSTER_WAIT_TIMEOUT ;
88use e2e_tests:: foreign_chain_mock:: {
9- MockServerExt , setup_bitcoin_mock, setup_evm_mock, setup_starknet_mock,
9+ MockAuthExpectation , MockServerExt , setup_bitcoin_mock, setup_evm_mock, setup_starknet_mock,
1010} ;
1111use httpmock:: prelude:: * ;
12- use mpc_node_config:: { ForeignChainConfig , ForeignChainProviderConfig , ForeignChainsConfig } ;
12+ use mpc_node_config:: {
13+ AuthConfig , ForeignChainConfig , ForeignChainProviderConfig , ForeignChainsConfig , TokenConfig ,
14+ } ;
1315use near_mpc_bounded_collections:: NonEmptyBTreeMap ;
1416use near_mpc_contract_interface:: types:: {
1517 BitcoinExtractor , BitcoinRpcRequest , BitcoinTxId , BlockConfirmations , DomainConfig , DomainId ,
@@ -19,13 +21,26 @@ use near_mpc_contract_interface::types::{
1921 VerifyForeignTransactionRequestArgs ,
2022} ;
2123
24+ /// One chain per credential-carrying [`AuthConfig`] kind: Bitcoin uses `path`,
25+ /// Base `header`, BNB `query`; the remaining chains use `None`.
26+ const PATH_AUTH_PLACEHOLDER : & str = "{api_key}" ;
27+ const PATH_AUTH_API_KEY : & str = "bitcoin-path-api-key" ;
28+ const HEADER_AUTH_NAME : & str = "authorization" ;
29+ const HEADER_AUTH_SCHEME : & str = "Bearer" ;
30+ const HEADER_AUTH_TOKEN : & str = "base-bearer-token" ;
31+ const QUERY_AUTH_PARAM : & str = "apikey" ;
32+ const QUERY_AUTH_TOKEN : & str = "bnb-query-token" ;
33+
2234struct ForeignTxTestEnv {
2335 cluster : e2e_tests:: MpcCluster ,
2436 foreign_tx_domain_id : DomainId ,
2537 _mock_servers : Vec < MockServer > ,
2638 /// Polygon is configured with multiple RPC providers so the test can verify
2739 /// that `FanOut` queries every one of them.
2840 polygon_mocks : Vec < MockServerExt > ,
41+ bitcoin_mock : MockServerExt ,
42+ base_mock : MockServerExt ,
43+ bnb_mock : MockServerExt ,
2944}
3045
3146struct MockServerUrls {
@@ -48,7 +63,12 @@ fn build_foreign_chains_config(urls: &MockServerUrls) -> ForeignChainsConfig {
4863 "mock" . to_string ( ) . into ( ) ,
4964 ForeignChainProviderConfig {
5065 rpc_url : urls. bitcoin . clone ( ) ,
51- auth : Default :: default ( ) ,
66+ auth : AuthConfig :: Path {
67+ placeholder : PATH_AUTH_PLACEHOLDER . to_string ( ) ,
68+ token : TokenConfig :: Val {
69+ val : PATH_AUTH_API_KEY . to_string ( ) ,
70+ } ,
71+ } ,
5272 } ,
5373 ) ,
5474 } ) ,
@@ -70,7 +90,12 @@ fn build_foreign_chains_config(urls: &MockServerUrls) -> ForeignChainsConfig {
7090 "mock" . to_string ( ) . into ( ) ,
7191 ForeignChainProviderConfig {
7292 rpc_url : urls. bnb . clone ( ) ,
73- auth : Default :: default ( ) ,
93+ auth : AuthConfig :: Query {
94+ name : QUERY_AUTH_PARAM . to_string ( ) ,
95+ token : TokenConfig :: Val {
96+ val : QUERY_AUTH_TOKEN . to_string ( ) ,
97+ } ,
98+ } ,
7499 } ,
75100 ) ,
76101 } ) ,
@@ -92,7 +117,13 @@ fn build_foreign_chains_config(urls: &MockServerUrls) -> ForeignChainsConfig {
92117 "mock" . to_string ( ) . into ( ) ,
93118 ForeignChainProviderConfig {
94119 rpc_url : urls. base . clone ( ) ,
95- auth : Default :: default ( ) ,
120+ auth : AuthConfig :: Header {
121+ name : HEADER_AUTH_NAME . parse ( ) . expect ( "valid header name" ) ,
122+ scheme : Some ( HEADER_AUTH_SCHEME . to_string ( ) ) ,
123+ token : TokenConfig :: Val {
124+ val : HEADER_AUTH_TOKEN . to_string ( ) ,
125+ } ,
126+ } ,
96127 } ,
97128 ) ,
98129 } ) ,
@@ -158,26 +189,45 @@ async fn setup_foreign_tx_cluster() -> anyhow::Result<ForeignTxTestEnv> {
158189 let arbitrum_server = MockServer :: start ( ) ;
159190 let hyper_evm_server = MockServer :: start ( ) ;
160191
161- setup_bitcoin_mock ( & bitcoin_server) ;
162- setup_evm_mock ( & abstract_server) ;
163- setup_evm_mock ( & bnb_server) ;
164- setup_starknet_mock ( & starknet_server) ;
165- setup_evm_mock ( & base_server) ;
166- setup_evm_mock ( & arbitrum_server) ;
167- setup_evm_mock ( & hyper_evm_server) ;
192+ let bitcoin_mock_id = setup_bitcoin_mock (
193+ & bitcoin_server,
194+ MockAuthExpectation :: ApiKeyInPath {
195+ key : PATH_AUTH_API_KEY . to_string ( ) ,
196+ } ,
197+ ) ;
198+ let base_mock_id = setup_evm_mock (
199+ & base_server,
200+ MockAuthExpectation :: Header {
201+ name : HEADER_AUTH_NAME . to_string ( ) ,
202+ value : format ! ( "{HEADER_AUTH_SCHEME} {HEADER_AUTH_TOKEN}" ) ,
203+ } ,
204+ ) ;
205+ let bnb_mock_id = setup_evm_mock (
206+ & bnb_server,
207+ MockAuthExpectation :: QueryParam {
208+ name : QUERY_AUTH_PARAM . to_string ( ) ,
209+ value : QUERY_AUTH_TOKEN . to_string ( ) ,
210+ } ,
211+ ) ;
212+ setup_evm_mock ( & abstract_server, MockAuthExpectation :: None ) ;
213+ setup_starknet_mock ( & starknet_server, MockAuthExpectation :: None ) ;
214+ setup_evm_mock ( & arbitrum_server, MockAuthExpectation :: None ) ;
215+ setup_evm_mock ( & hyper_evm_server, MockAuthExpectation :: None ) ;
168216
169217 // Polygon is configured with three RPC providers so the test can assert
170218 // that `FanOut` queries every one of them.
171219 let polygon_mocks: Vec < MockServerExt > = ( 0 ..3 )
172220 . map ( |_| {
173221 let server = MockServer :: start ( ) ;
174- let mock_id = setup_evm_mock ( & server) ;
222+ let mock_id = setup_evm_mock ( & server, MockAuthExpectation :: None ) ;
175223 MockServerExt :: new ( server, mock_id)
176224 } )
177225 . collect ( ) ;
178226
179227 let urls = MockServerUrls {
180- bitcoin : bitcoin_server. url ( "/" ) ,
228+ // The configured URL carries the literal placeholder; the node must
229+ // substitute the API key into it before any request can match the mock.
230+ bitcoin : bitcoin_server. url ( format ! ( "/{PATH_AUTH_PLACEHOLDER}" ) ) ,
181231 abstract_chain : abstract_server. url ( "/" ) ,
182232 bnb : bnb_server. url ( "/" ) ,
183233 starknet : starknet_server. url ( "/" ) ,
@@ -187,12 +237,12 @@ async fn setup_foreign_tx_cluster() -> anyhow::Result<ForeignTxTestEnv> {
187237 polygon : polygon_mocks. iter ( ) . map ( |m| m. server . url ( "/" ) ) . collect ( ) ,
188238 } ;
189239
240+ let bitcoin_mock = MockServerExt :: new ( bitcoin_server, bitcoin_mock_id) ;
241+ let base_mock = MockServerExt :: new ( base_server, base_mock_id) ;
242+ let bnb_mock = MockServerExt :: new ( bnb_server, bnb_mock_id) ;
190243 let mock_servers = vec ! [
191- bitcoin_server,
192244 abstract_server,
193- bnb_server,
194245 starknet_server,
195- base_server,
196246 arbitrum_server,
197247 hyper_evm_server,
198248 ] ;
@@ -272,6 +322,9 @@ async fn setup_foreign_tx_cluster() -> anyhow::Result<ForeignTxTestEnv> {
272322 foreign_tx_domain_id,
273323 _mock_servers : mock_servers,
274324 polygon_mocks,
325+ bitcoin_mock,
326+ base_mock,
327+ bnb_mock,
275328 } )
276329}
277330
@@ -443,6 +496,17 @@ async fn verify_hyper_evm(env: &ForeignTxTestEnv) -> anyhow::Result<()> {
443496 verify_foreign_tx_response ( & outcome)
444497}
445498
499+ /// A successful verification implies the credentialed mock answered,
500+ /// so this is a backstop against the mock setup being loosened to answer unauthenticated requests.
501+ fn assert_authenticated_provider_was_queried ( mock : & MockServerExt , provider : & str ) {
502+ let calls = mock. calls ( ) ;
503+ assert ! (
504+ calls > 0 ,
505+ "the {provider} mock was never hit with the expected credentials; \
506+ expected >= 1 matching RPC request, got {calls}"
507+ ) ;
508+ }
509+
446510/// Verifies that every Polygon RPC provider configured in the fan-out received
447511/// at least one HTTP request during the preceding `verify_polygon` call.
448512///
@@ -477,11 +541,10 @@ async fn verify_polygon(env: &ForeignTxTestEnv) -> anyhow::Result<()> {
477541 verify_foreign_tx_response ( & outcome)
478542}
479543
480- /// Sets up a single 2-node cluster with mock RPC servers for all chains,
481- /// then submits verify_foreign_transaction requests for Bitcoin, Abstract,
482- /// BNB, Base, Starknet, Arbitrum, HyperEVM, and Polygon and verifies the MPC
483- /// nodes return valid signed responses. Also verifies rejection for unsupported
484- /// chains and non-existent domains.
544+ /// Verifies all supported chains sign, and unsupported chains and non-existent
545+ /// domains are rejected. Bitcoin, Base and BNB require authentication (one per
546+ /// credential-carrying [`AuthConfig`] kind), proving the node applies configured
547+ /// RPC credentials end to end.
485548#[ tokio:: test]
486549#[ expect( non_snake_case) ]
487550async fn verify_foreign_transaction__should_sign_all_supported_chains ( ) {
@@ -495,11 +558,14 @@ async fn verify_foreign_transaction__should_sign_all_supported_chains() {
495558 verify_bitcoin ( & env)
496559 . await
497560 . expect ( "bitcoin verification failed" ) ;
561+ assert_authenticated_provider_was_queried ( & env. bitcoin_mock , "bitcoin (path auth)" ) ;
498562 verify_abstract ( & env)
499563 . await
500564 . expect ( "abstract verification failed" ) ;
501565 verify_bnb ( & env) . await . expect ( "bnb verification failed" ) ;
566+ assert_authenticated_provider_was_queried ( & env. bnb_mock , "bnb (query auth)" ) ;
502567 verify_base ( & env) . await . expect ( "base verification failed" ) ;
568+ assert_authenticated_provider_was_queried ( & env. base_mock , "base (header auth)" ) ;
503569 verify_starknet ( & env)
504570 . await
505571 . expect ( "starknet verification failed" ) ;
0 commit comments