@@ -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,25 @@ 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_SCHEME : & str = "Bearer" ;
29+ const HEADER_AUTH_TOKEN : & str = "base-bearer-token" ;
30+ const QUERY_AUTH_PARAM : & str = "apikey" ;
31+ const QUERY_AUTH_TOKEN : & str = "bnb-query-token" ;
32+
2233struct ForeignTxTestEnv {
2334 cluster : e2e_tests:: MpcCluster ,
2435 foreign_tx_domain_id : DomainId ,
2536 _mock_servers : Vec < MockServer > ,
2637 /// Polygon is configured with multiple RPC providers so the test can verify
2738 /// that `FanOut` queries every one of them.
2839 polygon_mocks : Vec < MockServerExt > ,
40+ bitcoin_mock : MockServerExt ,
41+ base_mock : MockServerExt ,
42+ bnb_mock : MockServerExt ,
2943}
3044
3145struct MockServerUrls {
@@ -48,7 +62,12 @@ fn build_foreign_chains_config(urls: &MockServerUrls) -> ForeignChainsConfig {
4862 "mock" . to_string ( ) . into ( ) ,
4963 ForeignChainProviderConfig {
5064 rpc_url : urls. bitcoin . clone ( ) ,
51- auth : Default :: default ( ) ,
65+ auth : AuthConfig :: Path {
66+ placeholder : PATH_AUTH_PLACEHOLDER . to_string ( ) ,
67+ token : TokenConfig :: Val {
68+ val : PATH_AUTH_API_KEY . to_string ( ) ,
69+ } ,
70+ } ,
5271 } ,
5372 ) ,
5473 } ) ,
@@ -70,7 +89,12 @@ fn build_foreign_chains_config(urls: &MockServerUrls) -> ForeignChainsConfig {
7089 "mock" . to_string ( ) . into ( ) ,
7190 ForeignChainProviderConfig {
7291 rpc_url : urls. bnb . clone ( ) ,
73- auth : Default :: default ( ) ,
92+ auth : AuthConfig :: Query {
93+ name : QUERY_AUTH_PARAM . to_string ( ) ,
94+ token : TokenConfig :: Val {
95+ val : QUERY_AUTH_TOKEN . to_string ( ) ,
96+ } ,
97+ } ,
7498 } ,
7599 ) ,
76100 } ) ,
@@ -92,7 +116,13 @@ fn build_foreign_chains_config(urls: &MockServerUrls) -> ForeignChainsConfig {
92116 "mock" . to_string ( ) . into ( ) ,
93117 ForeignChainProviderConfig {
94118 rpc_url : urls. base . clone ( ) ,
95- auth : Default :: default ( ) ,
119+ auth : AuthConfig :: Header {
120+ name : "authorization" . parse ( ) . expect ( "valid header name" ) ,
121+ scheme : Some ( HEADER_AUTH_SCHEME . to_string ( ) ) ,
122+ token : TokenConfig :: Val {
123+ val : HEADER_AUTH_TOKEN . to_string ( ) ,
124+ } ,
125+ } ,
96126 } ,
97127 ) ,
98128 } ) ,
@@ -158,26 +188,45 @@ async fn setup_foreign_tx_cluster() -> anyhow::Result<ForeignTxTestEnv> {
158188 let arbitrum_server = MockServer :: start ( ) ;
159189 let hyper_evm_server = MockServer :: start ( ) ;
160190
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) ;
191+ let bitcoin_mock_id = setup_bitcoin_mock (
192+ & bitcoin_server,
193+ MockAuthExpectation :: ApiKeyInPath {
194+ key : PATH_AUTH_API_KEY . to_string ( ) ,
195+ } ,
196+ ) ;
197+ let base_mock_id = setup_evm_mock (
198+ & base_server,
199+ MockAuthExpectation :: Header {
200+ name : "authorization" . to_string ( ) ,
201+ value : format ! ( "{HEADER_AUTH_SCHEME} {HEADER_AUTH_TOKEN}" ) ,
202+ } ,
203+ ) ;
204+ let bnb_mock_id = setup_evm_mock (
205+ & bnb_server,
206+ MockAuthExpectation :: QueryParam {
207+ name : QUERY_AUTH_PARAM . to_string ( ) ,
208+ value : QUERY_AUTH_TOKEN . to_string ( ) ,
209+ } ,
210+ ) ;
211+ setup_evm_mock ( & abstract_server, MockAuthExpectation :: None ) ;
212+ setup_starknet_mock ( & starknet_server, MockAuthExpectation :: None ) ;
213+ setup_evm_mock ( & arbitrum_server, MockAuthExpectation :: None ) ;
214+ setup_evm_mock ( & hyper_evm_server, MockAuthExpectation :: None ) ;
168215
169216 // Polygon is configured with three RPC providers so the test can assert
170217 // that `FanOut` queries every one of them.
171218 let polygon_mocks: Vec < MockServerExt > = ( 0 ..3 )
172219 . map ( |_| {
173220 let server = MockServer :: start ( ) ;
174- let mock_id = setup_evm_mock ( & server) ;
221+ let mock_id = setup_evm_mock ( & server, MockAuthExpectation :: None ) ;
175222 MockServerExt :: new ( server, mock_id)
176223 } )
177224 . collect ( ) ;
178225
179226 let urls = MockServerUrls {
180- bitcoin : bitcoin_server. url ( "/" ) ,
227+ // The configured URL carries the literal placeholder; the node must
228+ // substitute the API key into it before any request can match the mock.
229+ bitcoin : bitcoin_server. url ( format ! ( "/{PATH_AUTH_PLACEHOLDER}" ) ) ,
181230 abstract_chain : abstract_server. url ( "/" ) ,
182231 bnb : bnb_server. url ( "/" ) ,
183232 starknet : starknet_server. url ( "/" ) ,
@@ -187,12 +236,12 @@ async fn setup_foreign_tx_cluster() -> anyhow::Result<ForeignTxTestEnv> {
187236 polygon : polygon_mocks. iter ( ) . map ( |m| m. server . url ( "/" ) ) . collect ( ) ,
188237 } ;
189238
239+ let bitcoin_mock = MockServerExt :: new ( bitcoin_server, bitcoin_mock_id) ;
240+ let base_mock = MockServerExt :: new ( base_server, base_mock_id) ;
241+ let bnb_mock = MockServerExt :: new ( bnb_server, bnb_mock_id) ;
190242 let mock_servers = vec ! [
191- bitcoin_server,
192243 abstract_server,
193- bnb_server,
194244 starknet_server,
195- base_server,
196245 arbitrum_server,
197246 hyper_evm_server,
198247 ] ;
@@ -272,6 +321,9 @@ async fn setup_foreign_tx_cluster() -> anyhow::Result<ForeignTxTestEnv> {
272321 foreign_tx_domain_id,
273322 _mock_servers : mock_servers,
274323 polygon_mocks,
324+ bitcoin_mock,
325+ base_mock,
326+ bnb_mock,
275327 } )
276328}
277329
@@ -443,6 +495,17 @@ async fn verify_hyper_evm(env: &ForeignTxTestEnv) -> anyhow::Result<()> {
443495 verify_foreign_tx_response ( & outcome)
444496}
445497
498+ /// A successful verification implies the credentialed mock answered,
499+ /// so this is a backstop against the mock setup being loosened to answer unauthenticated requests.
500+ fn assert_authenticated_provider_was_queried ( mock : & MockServerExt , provider : & str ) {
501+ let calls = mock. calls ( ) ;
502+ assert ! (
503+ calls > 0 ,
504+ "the {provider} mock was never hit with the expected credentials; \
505+ expected >= 1 matching RPC request, got {calls}"
506+ ) ;
507+ }
508+
446509/// Verifies that every Polygon RPC provider configured in the fan-out received
447510/// at least one HTTP request during the preceding `verify_polygon` call.
448511///
@@ -482,6 +545,10 @@ async fn verify_polygon(env: &ForeignTxTestEnv) -> anyhow::Result<()> {
482545/// BNB, Base, Starknet, Arbitrum, HyperEVM, and Polygon and verifies the MPC
483546/// nodes return valid signed responses. Also verifies rejection for unsupported
484547/// chains and non-existent domains.
548+ ///
549+ /// Bitcoin, Base and BNB providers require authentication (one per
550+ /// credential-carrying `AuthConfig` kind), so the test also proves the node
551+ /// applies configured RPC credentials end to end.
485552#[ tokio:: test]
486553#[ expect( non_snake_case) ]
487554async fn verify_foreign_transaction__should_sign_all_supported_chains ( ) {
@@ -495,11 +562,14 @@ async fn verify_foreign_transaction__should_sign_all_supported_chains() {
495562 verify_bitcoin ( & env)
496563 . await
497564 . expect ( "bitcoin verification failed" ) ;
565+ assert_authenticated_provider_was_queried ( & env. bitcoin_mock , "bitcoin (path auth)" ) ;
498566 verify_abstract ( & env)
499567 . await
500568 . expect ( "abstract verification failed" ) ;
501569 verify_bnb ( & env) . await . expect ( "bnb verification failed" ) ;
570+ assert_authenticated_provider_was_queried ( & env. bnb_mock , "bnb (query auth)" ) ;
502571 verify_base ( & env) . await . expect ( "base verification failed" ) ;
572+ assert_authenticated_provider_was_queried ( & env. base_mock , "base (header auth)" ) ;
503573 verify_starknet ( & env)
504574 . await
505575 . expect ( "starknet verification failed" ) ;
0 commit comments