From 8b514bb85ebfcb6847451982348526d6a1692592 Mon Sep 17 00:00:00 2001 From: zhoujunma Date: Mon, 5 Feb 2024 16:00:05 -0800 Subject: [PATCH] jwk #9: smoke tests (#11862) * jwk types update * update * update * jwk txn and execution * consensus ensure jwk txns are expected * update * jwk consensus network type defs * update cargo.toml * update * update * update * lint * jwk update quorum certification * jwk observation * the main jwk consensus state machine * jwk consensus epoch manager * update * jwk consensus wired into node * update * update * jwk consensus smoke tests * update --- Cargo.lock | 1 + testsuite/forge/src/backend/local/cargo.rs | 4 +- testsuite/smoke-test/Cargo.toml | 1 + .../smoke-test/src/jwks/dummy_provider/mod.rs | 109 +++++++++++++ .../jwks/dummy_provider/request_handler.rs | 118 ++++++++++++++ .../src/jwks/jwk_consensus_basic.rs | 147 ++++++++++++++++++ .../src/jwks/jwk_consensus_per_issuer.rs | 94 +++++++++++ .../jwk_consensus_provider_change_mind.rs | 104 +++++++++++++ .../smoke-test/src/{jwks.rs => jwks/mod.rs} | 52 ++++++- 9 files changed, 625 insertions(+), 5 deletions(-) create mode 100644 testsuite/smoke-test/src/jwks/dummy_provider/mod.rs create mode 100644 testsuite/smoke-test/src/jwks/dummy_provider/request_handler.rs create mode 100644 testsuite/smoke-test/src/jwks/jwk_consensus_basic.rs create mode 100644 testsuite/smoke-test/src/jwks/jwk_consensus_per_issuer.rs create mode 100644 testsuite/smoke-test/src/jwks/jwk_consensus_provider_change_mind.rs rename testsuite/smoke-test/src/{jwks.rs => jwks/mod.rs} (61%) diff --git a/Cargo.lock b/Cargo.lock index fe5b054e7744d..0d2f8010dfc39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14936,6 +14936,7 @@ dependencies = [ "diesel", "futures", "hex", + "hyper", "move-core-types", "num_cpus", "once_cell", diff --git a/testsuite/forge/src/backend/local/cargo.rs b/testsuite/forge/src/backend/local/cargo.rs index cdb97b0a53dbc..93af06ac2d8c7 100644 --- a/testsuite/forge/src/backend/local/cargo.rs +++ b/testsuite/forge/src/backend/local/cargo.rs @@ -171,9 +171,9 @@ pub fn git_merge_base>(rev: R) -> Result { pub fn cargo_build_common_args() -> Vec<&'static str> { let mut args = if build_aptos_node_without_indexer() { - vec!["build", "--features=failpoints"] + vec!["build", "--features=failpoints,smoke-test"] } else { - vec!["build", "--features=failpoints,indexer"] + vec!["build", "--features=failpoints,indexer,smoke-test"] }; if build_consensus_only_node() { args.push("--features=consensus-only-perf-test"); diff --git a/testsuite/smoke-test/Cargo.toml b/testsuite/smoke-test/Cargo.toml index 3f9cb5338d7e0..acbb13ca84256 100644 --- a/testsuite/smoke-test/Cargo.toml +++ b/testsuite/smoke-test/Cargo.toml @@ -50,6 +50,7 @@ diesel = { workspace = true, features = [ "serde_json", ] } hex = { workspace = true } +hyper = { workspace = true } move-core-types = { workspace = true } proptest = { workspace = true } reqwest = { workspace = true } diff --git a/testsuite/smoke-test/src/jwks/dummy_provider/mod.rs b/testsuite/smoke-test/src/jwks/dummy_provider/mod.rs new file mode 100644 index 0000000000000..f82a6b9bee95c --- /dev/null +++ b/testsuite/smoke-test/src/jwks/dummy_provider/mod.rs @@ -0,0 +1,109 @@ +// Copyright © Aptos Foundation + +use aptos_infallible::RwLock; +use hyper::{ + service::{make_service_fn, service_fn}, + Body, Request, Response, Server, +}; +use request_handler::RequestHandler; +use std::{convert::Infallible, mem, net::SocketAddr, sync::Arc}; +use tokio::{ + sync::{ + oneshot, + oneshot::{Receiver, Sender}, + }, + task::JoinHandle, +}; + +pub(crate) mod request_handler; + +/// A dummy OIDC provider. +pub struct DummyProvider { + close_tx: Sender<()>, + open_id_config_url: String, + handler_holder: Arc>>>, + server_join_handle: JoinHandle<()>, +} + +impl DummyProvider { + pub(crate) async fn spawn() -> Self { + let addr = SocketAddr::from(([127, 0, 0, 1], 0)); + let handler_holder = Arc::new(RwLock::new(None)); + let (port_tx, port_rx) = oneshot::channel::(); + let (close_tx, close_rx) = oneshot::channel::<()>(); + let server_join_handle = tokio::spawn(Self::run_server( + addr, + handler_holder.clone(), + port_tx, + close_rx, + )); + let actual_port = port_rx.await.unwrap(); + let open_id_config_url = format!("http://127.0.0.1:{}", actual_port); + Self { + close_tx, + open_id_config_url, + handler_holder, + server_join_handle, + } + } + + pub fn open_id_config_url(&self) -> String { + self.open_id_config_url.clone() + } + + pub fn update_request_handler( + &self, + handler: Option>, + ) -> Option> { + mem::replace(&mut *self.handler_holder.write(), handler) + } + + pub async fn shutdown(self) { + let DummyProvider { + close_tx, + server_join_handle, + .. + } = self; + close_tx.send(()).unwrap(); + server_join_handle.await.unwrap(); + } +} + +// Private functions. +impl DummyProvider { + async fn run_server( + addr: SocketAddr, + handler_holder: Arc>>>, + port_tx: Sender, + close_rx: Receiver<()>, + ) { + let make_svc = make_service_fn(move |_| { + let handler_holder_clone = handler_holder.clone(); + async move { + Ok::<_, Infallible>(service_fn(move |req| { + Self::handle_request(req, handler_holder_clone.clone()) + })) + } + }); + + let server = Server::bind(&addr).serve(make_svc); + let actual_addr = server.local_addr(); + port_tx.send(actual_addr.port()).unwrap(); + + // Graceful shutdown + let graceful = server.with_graceful_shutdown(async { + close_rx.await.unwrap(); + }); + + graceful.await.unwrap(); + } + + async fn handle_request( + request: Request, + handler_holder: Arc>>>, + ) -> Result, Infallible> { + let handler = handler_holder.write(); + let raw_response = handler.as_ref().unwrap().handle(request); + Ok(Response::new(Body::from(raw_response))) + } +} diff --git a/testsuite/smoke-test/src/jwks/dummy_provider/request_handler.rs b/testsuite/smoke-test/src/jwks/dummy_provider/request_handler.rs new file mode 100644 index 0000000000000..5310931ef1af7 --- /dev/null +++ b/testsuite/smoke-test/src/jwks/dummy_provider/request_handler.rs @@ -0,0 +1,118 @@ +// Copyright © Aptos Foundation + +use aptos_infallible::Mutex; +use hyper::{Body, Request}; +use move_core_types::account_address::AccountAddress; +use std::{collections::HashSet, str::FromStr}; + +/// A handler that handles JWK requests from a validator, +/// assuming the validator account address is written as the COOKIE. +pub trait RequestHandler: Send + Sync { + fn handle(&self, request: Request) -> Vec; +} + +pub struct StaticContentServer { + content: Vec, +} + +impl StaticContentServer { + pub fn new(content: Vec) -> Self { + Self { content } + } + + pub fn new_str(content: &str) -> Self { + Self::new(content.as_bytes().to_vec()) + } +} + +impl RequestHandler for StaticContentServer { + fn handle(&self, _origin: Request) -> Vec { + self.content.clone() + } +} + +fn origin_from_cookie(request: &Request) -> AccountAddress { + let cookie = request + .headers() + .get(hyper::header::COOKIE) + .unwrap() + .to_str() + .unwrap(); + AccountAddress::from_str(cookie).unwrap() +} + +/// The first `k` requesters will get content A forever, the rest will get content B forever. +pub struct EquivocatingServer { + content_a: Vec, + content_b: Vec, + k: usize, + requesters_observed: Mutex>, +} + +impl EquivocatingServer { + pub fn new(content_a: Vec, content_b: Vec, k: usize) -> Self { + Self { + content_a, + content_b, + k, + requesters_observed: Mutex::new(HashSet::new()), + } + } +} + +impl RequestHandler for EquivocatingServer { + fn handle(&self, request: Request) -> Vec { + let mut requesters_observed = self.requesters_observed.lock(); + let origin = origin_from_cookie(&request); + if requesters_observed.len() < self.k { + requesters_observed.insert(origin); + } + + if requesters_observed.contains(&origin) { + self.content_a.clone() + } else { + self.content_b.clone() + } + } +} + +/// This server first replies with `initial_thoughts`. +/// After enough audience receives it for at least once, it switches its reply to `second_thoughts`. +/// +/// This behavior simulates the situation where a provider performs a 2nd key rotation right after the 1st. +pub struct MindChangingServer { + initial_thoughts: Vec, + second_thoughts: Vec, + change_mind_threshold: usize, + requesters_observed: Mutex>, +} + +impl MindChangingServer { + pub fn new( + initial_thoughts: Vec, + second_thoughts: Vec, + change_mind_threshold: usize, + ) -> Self { + Self { + initial_thoughts, + second_thoughts, + change_mind_threshold, + requesters_observed: Mutex::new(HashSet::new()), + } + } +} + +impl RequestHandler for MindChangingServer { + fn handle(&self, request: Request) -> Vec { + let mut requesters_observed = self.requesters_observed.lock(); + let origin = origin_from_cookie(&request); + if requesters_observed.contains(&origin) + || requesters_observed.len() >= self.change_mind_threshold + { + self.second_thoughts.clone() + } else { + requesters_observed.insert(origin); + self.initial_thoughts.clone() + } + } +} diff --git a/testsuite/smoke-test/src/jwks/jwk_consensus_basic.rs b/testsuite/smoke-test/src/jwks/jwk_consensus_basic.rs new file mode 100644 index 0000000000000..0df449657d22b --- /dev/null +++ b/testsuite/smoke-test/src/jwks/jwk_consensus_basic.rs @@ -0,0 +1,147 @@ +// Copyright © Aptos Foundation + +use crate::{ + jwks::{ + dummy_provider::{ + request_handler::{EquivocatingServer, StaticContentServer}, + DummyProvider, + }, + get_patched_jwks, put_provider_on_chain, + }, + smoke_test_environment::SwarmBuilder, +}; +use aptos_forge::{NodeExt, Swarm, SwarmExt}; +use aptos_logger::{debug, info}; +use aptos_types::jwks::{ + jwk::JWK, rsa::RSA_JWK, unsupported::UnsupportedJWK, AllProvidersJWKs, OIDCProvider, + ProviderJWKs, +}; +use std::{sync::Arc, time::Duration}; +use tokio::time::sleep; + +/// The validators should agree on the JWK after provider set is changed/JWK is rotated. +#[tokio::test] +async fn jwk_consensus_basic() { + let epoch_duration_secs = 30; + + let (mut swarm, mut cli, _faucet) = SwarmBuilder::new_local(4) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + })) + .build_with_cli(0) + .await; + let client = swarm.validators().next().unwrap().rest_client(); + let root_idx = cli.add_account_with_address_to_cli( + swarm.root_key(), + swarm.chain_info().root_account().address(), + ); + swarm + .wait_for_all_nodes_to_catchup_to_epoch(2, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Epoch 2 taking too long to arrive!"); + + info!("Initially the provider set is empty. So should be the JWK map."); + + sleep(Duration::from_secs(10)).await; + let patched_jwks = get_patched_jwks(&client).await; + debug!("patched_jwks={:?}", patched_jwks); + assert!(patched_jwks.jwks.entries.is_empty()); + + info!("Adding some providers."); + let (provider_alice, provider_bob) = + tokio::join!(DummyProvider::spawn(), DummyProvider::spawn()); + + provider_alice.update_request_handler(Some(Arc::new(StaticContentServer::new_str( + r#" +{ + "keys": [ + {"kid":"kid1", "kty":"RSA", "e":"AQAB", "n":"n1", "alg":"RS384", "use":"sig"}, + {"n":"n0", "kty":"RSA", "use":"sig", "alg":"RS256", "e":"AQAB", "kid":"kid0"} + ] +} +"#, + )))); + provider_bob.update_request_handler(Some(Arc::new(StaticContentServer::new( + r#"{"keys": ["BOB_JWK_V0"]}"#.as_bytes().to_vec(), + )))); + let providers = vec![ + OIDCProvider { + name: b"https://alice.io".to_vec(), + config_url: provider_alice.open_id_config_url().into_bytes(), + }, + OIDCProvider { + name: b"https://bob.dev".to_vec(), + config_url: provider_bob.open_id_config_url().into_bytes(), + }, + ]; + let txn_summary = put_provider_on_chain(cli, root_idx, providers).await; + debug!("txn_summary={:?}", txn_summary); + + info!("Waiting for an on-chain update. 10 sec should be enough."); + sleep(Duration::from_secs(10)).await; + let patched_jwks = get_patched_jwks(&client).await; + debug!("patched_jwks={:?}", patched_jwks); + assert_eq!( + AllProvidersJWKs { + entries: vec![ + ProviderJWKs { + issuer: b"https://alice.io".to_vec(), + version: 1, + jwks: vec![ + JWK::RSA(RSA_JWK::new_256_aqab("kid0", "n0")).into(), + JWK::RSA(RSA_JWK::new_from_strs("kid1", "RSA", "RS384", "AQAB", "n1")) + .into(), + ], + }, + ProviderJWKs { + issuer: b"https://bob.dev".to_vec(), + version: 1, + jwks: vec![JWK::Unsupported(UnsupportedJWK::new_with_payload( + "\"BOB_JWK_V0\"" + )) + .into()], + }, + ] + }, + patched_jwks.jwks + ); + + info!("Rotating Alice keys. Also making https://alice.io gently equivocate."); + provider_alice.update_request_handler(Some(Arc::new(EquivocatingServer::new( + r#"{"keys": ["ALICE_JWK_V1A"]}"#.as_bytes().to_vec(), + r#"{"keys": ["ALICE_JWK_V1B"]}"#.as_bytes().to_vec(), + 1, + )))); + + info!("Waiting for an on-chain update. 30 sec should be enough."); + sleep(Duration::from_secs(30)).await; + let patched_jwks = get_patched_jwks(&client).await; + debug!("patched_jwks={:?}", patched_jwks); + assert_eq!( + AllProvidersJWKs { + entries: vec![ + ProviderJWKs { + issuer: b"https://alice.io".to_vec(), + version: 2, + jwks: vec![JWK::Unsupported(UnsupportedJWK::new_with_payload( + "\"ALICE_JWK_V1B\"" + )) + .into()], + }, + ProviderJWKs { + issuer: b"https://bob.dev".to_vec(), + version: 1, + jwks: vec![JWK::Unsupported(UnsupportedJWK::new_with_payload( + "\"BOB_JWK_V0\"" + )) + .into()], + }, + ] + }, + patched_jwks.jwks + ); + + info!("Tear down."); + provider_alice.shutdown().await; +} diff --git a/testsuite/smoke-test/src/jwks/jwk_consensus_per_issuer.rs b/testsuite/smoke-test/src/jwks/jwk_consensus_per_issuer.rs new file mode 100644 index 0000000000000..e271c2bb9cd67 --- /dev/null +++ b/testsuite/smoke-test/src/jwks/jwk_consensus_per_issuer.rs @@ -0,0 +1,94 @@ +// Copyright © Aptos Foundation + +use crate::{ + jwks::{ + dummy_provider::{ + request_handler::{EquivocatingServer, StaticContentServer}, + DummyProvider, + }, + get_patched_jwks, put_provider_on_chain, + }, + smoke_test_environment::SwarmBuilder, +}; +use aptos_forge::{NodeExt, Swarm, SwarmExt}; +use aptos_logger::{debug, info}; +use aptos_types::jwks::{ + jwk::JWK, unsupported::UnsupportedJWK, AllProvidersJWKs, OIDCProvider, ProviderJWKs, +}; +use std::{sync::Arc, time::Duration}; +use tokio::time::sleep; + +/// The validators should do JWK consensus per issuer: +/// one problematic issuer should not block valid updates of other issuers. +#[tokio::test] +async fn jwk_consensus_per_issuer() { + let epoch_duration_secs = 30; + + let (mut swarm, mut cli, _faucet) = SwarmBuilder::new_local(4) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + })) + .build_with_cli(0) + .await; + let client = swarm.validators().next().unwrap().rest_client(); + let root_idx = cli.add_account_with_address_to_cli( + swarm.root_key(), + swarm.chain_info().root_account().address(), + ); + swarm + .wait_for_all_nodes_to_catchup_to_epoch(2, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Epoch 2 taking too long to arrive!"); + + info!("Initially the provider set is empty. So should be the JWK map."); + + sleep(Duration::from_secs(10)).await; + let patched_jwks = get_patched_jwks(&client).await; + debug!("patched_jwks={:?}", patched_jwks); + assert!(patched_jwks.jwks.entries.is_empty()); + + info!("Adding some providers, one seriously equivocating, the other well behaving."); + let (provider_alice, provider_bob) = + tokio::join!(DummyProvider::spawn(), DummyProvider::spawn()); + provider_alice.update_request_handler(Some(Arc::new(EquivocatingServer::new( + r#"{"keys": ["ALICE_JWK_V1A"]}"#.as_bytes().to_vec(), + r#"{"keys": ["ALICE_JWK_V1B"]}"#.as_bytes().to_vec(), + 2, + )))); + provider_bob.update_request_handler(Some(Arc::new(StaticContentServer::new( + r#"{"keys": ["BOB_JWK_V0"]}"#.as_bytes().to_vec(), + )))); + let providers = vec![ + OIDCProvider { + name: b"https://alice.io".to_vec(), + config_url: provider_alice.open_id_config_url().into_bytes(), + }, + OIDCProvider { + name: b"https://bob.dev".to_vec(), + config_url: provider_bob.open_id_config_url().into_bytes(), + }, + ]; + let txn_summary = put_provider_on_chain(cli, root_idx, providers).await; + debug!("txn_summary={:?}", txn_summary); + + info!("Wait for 60 secs and there should only update for Bob, not Alice."); + sleep(Duration::from_secs(60)).await; + let patched_jwks = get_patched_jwks(&client).await; + debug!("patched_jwks={:?}", patched_jwks); + assert_eq!( + AllProvidersJWKs { + entries: vec![ProviderJWKs { + issuer: b"https://bob.dev".to_vec(), + version: 1, + jwks: vec![ + JWK::Unsupported(UnsupportedJWK::new_with_payload("\"BOB_JWK_V0\"")).into() + ], + }] + }, + patched_jwks.jwks + ); + + info!("Tear down."); + provider_alice.shutdown().await; +} diff --git a/testsuite/smoke-test/src/jwks/jwk_consensus_provider_change_mind.rs b/testsuite/smoke-test/src/jwks/jwk_consensus_provider_change_mind.rs new file mode 100644 index 0000000000000..7fad520c48ce1 --- /dev/null +++ b/testsuite/smoke-test/src/jwks/jwk_consensus_provider_change_mind.rs @@ -0,0 +1,104 @@ +// Copyright © Aptos Foundation + +use crate::{ + jwks::{ + dummy_provider::{ + request_handler::{MindChangingServer, StaticContentServer}, + DummyProvider, + }, + get_patched_jwks, put_provider_on_chain, + }, + smoke_test_environment::SwarmBuilder, +}; +use aptos_forge::{NodeExt, Swarm, SwarmExt}; +use aptos_logger::{debug, info}; +use aptos_types::jwks::{ + jwk::JWK, unsupported::UnsupportedJWK, AllProvidersJWKs, OIDCProvider, ProviderJWKs, +}; +use std::{sync::Arc, time::Duration}; +use tokio::time::sleep; + +/// The validators should be able to reach JWK consensus +/// even if a provider double-rotates its key in a very short period of time. +/// First rotation may have been observed by some validators. +#[tokio::test] +async fn jwk_consensus_provider_change_mind() { + // Big epoch duration to ensure epoch change does not help reset validators if they are stuck. + let epoch_duration_secs = 1800; + + let (mut swarm, mut cli, _faucet) = SwarmBuilder::new_local(4) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + })) + .build_with_cli(0) + .await; + let client = swarm.validators().next().unwrap().rest_client(); + let root_idx = cli.add_account_with_address_to_cli( + swarm.root_key(), + swarm.chain_info().root_account().address(), + ); + swarm + .wait_for_all_nodes_to_catchup_to_epoch(2, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Epoch 2 taking too long to arrive!"); + + info!("Initially the provider set is empty. So should be the ObservedJWKs."); + + sleep(Duration::from_secs(10)).await; + let patched_jwks = get_patched_jwks(&client).await; + debug!("patched_jwks={:?}", patched_jwks); + assert!(patched_jwks.jwks.entries.is_empty()); + + info!("Adding some providers."); + let (provider_alice, provider_bob) = + tokio::join!(DummyProvider::spawn(), DummyProvider::spawn()); + provider_alice.update_request_handler(Some(Arc::new(StaticContentServer::new( + r#"{"keys": ["ALICE_JWK_V0"]}"#.as_bytes().to_vec(), + )))); + provider_bob.update_request_handler(Some(Arc::new(MindChangingServer::new( + r#"{"keys": ["BOB_JWK_V0"]}"#.as_bytes().to_vec(), + r#"{"keys": ["BOB_JWK_V0_1"]}"#.as_bytes().to_vec(), + 2, + )))); + let providers = vec![ + OIDCProvider { + name: b"https://alice.io".to_vec(), + config_url: provider_alice.open_id_config_url().into_bytes(), + }, + OIDCProvider { + name: b"https://bob.dev".to_vec(), + config_url: provider_bob.open_id_config_url().into_bytes(), + }, + ]; + let txn_summary = put_provider_on_chain(cli, root_idx, providers).await; + debug!("txn_summary={:?}", txn_summary); + + info!("Waiting for an on-chain update. 30 secs should be enough."); + sleep(Duration::from_secs(30)).await; + let patched_jwks = get_patched_jwks(&client).await; + debug!("patched_jwks={:?}", patched_jwks); + assert_eq!( + AllProvidersJWKs { + entries: vec![ + ProviderJWKs { + issuer: b"https://alice.io".to_vec(), + version: 1, + jwks: vec![JWK::Unsupported(UnsupportedJWK::new_with_payload( + "\"ALICE_JWK_V0\"" + )) + .into()], + }, + ProviderJWKs { + issuer: b"https://bob.dev".to_vec(), + version: 1, + jwks: vec![JWK::Unsupported(UnsupportedJWK::new_with_payload( + "\"BOB_JWK_V0_1\"" + )) + .into()], + }, + ] + }, + patched_jwks.jwks + ); +} diff --git a/testsuite/smoke-test/src/jwks.rs b/testsuite/smoke-test/src/jwks/mod.rs similarity index 61% rename from testsuite/smoke-test/src/jwks.rs rename to testsuite/smoke-test/src/jwks/mod.rs index 6509a35b1d32c..2740ef1fc7df9 100644 --- a/testsuite/smoke-test/src/jwks.rs +++ b/testsuite/smoke-test/src/jwks/mod.rs @@ -1,18 +1,64 @@ // Copyright © Aptos Foundation +mod dummy_provider; +mod jwk_consensus_basic; +mod jwk_consensus_per_issuer; +mod jwk_consensus_provider_change_mind; + use crate::smoke_test_environment::SwarmBuilder; +use aptos::{common::types::TransactionSummary, test::CliTestFramework}; use aptos_forge::{NodeExt, Swarm, SwarmExt}; use aptos_logger::{debug, info}; use aptos_rest_client::Client; use aptos_types::jwks::{ jwk::{JWKMoveStruct, JWK}, unsupported::UnsupportedJWK, - AllProvidersJWKs, PatchedJWKs, ProviderJWKs, + AllProvidersJWKs, OIDCProvider, PatchedJWKs, ProviderJWKs, }; use move_core_types::account_address::AccountAddress; use std::time::Duration; -async fn get_latest_jwkset(rest_client: &Client) -> PatchedJWKs { +pub async fn put_provider_on_chain( + cli: CliTestFramework, + account_idx: usize, + providers: Vec, +) -> TransactionSummary { + let implementation = providers + .into_iter() + .map(|provider| { + let OIDCProvider { name, config_url } = provider; + format!( + r#" + let issuer = b"{}"; + let config_url = b"{}"; + jwks::upsert_oidc_provider(&framework_signer, issuer, config_url); +"#, + String::from_utf8(name).unwrap(), + String::from_utf8(config_url).unwrap(), + ) + }) + .collect::>() + .join(""); + + let add_dummy_provider_script = format!( + r#" +script {{ + use aptos_framework::aptos_governance; + use aptos_framework::jwks; + fun main(core_resources: &signer) {{ + let framework_signer = aptos_governance::get_signer_testnet_only(core_resources, @0000000000000000000000000000000000000000000000000000000000000001); + {implementation} + aptos_governance::reconfigure(&framework_signer); + }} +}} +"#, + ); + cli.run_script(account_idx, &add_dummy_provider_script) + .await + .unwrap() +} + +async fn get_patched_jwks(rest_client: &Client) -> PatchedJWKs { let maybe_response = rest_client .get_account_resource_bcs::(AccountAddress::ONE, "0x1::jwks::PatchedJWKs") .await; @@ -58,7 +104,7 @@ script { debug!("txn_summary={:?}", txn_summary); info!("Use resource API to check the patch result."); - let patched_jwks = get_latest_jwkset(&client).await; + let patched_jwks = get_patched_jwks(&client).await; debug!("patched_jwks={:?}", patched_jwks); let expected_providers_jwks = AllProvidersJWKs {