From b58fe0c90f0872407e4a78c6c32d4caa05f9157a Mon Sep 17 00:00:00 2001 From: raph Date: Thu, 6 Jun 2024 19:44:52 +0200 Subject: [PATCH] Make recursive endpoints proxiable (#3797) --- docs/po/zh.po | 4 +- docs/src/guides/testing.md | 4 +- src/subcommand/env.rs | 10 +- src/subcommand/server.rs | 299 +++++++++++++++++++++++-- src/subcommand/server/server_config.rs | 2 +- 5 files changed, 286 insertions(+), 33 deletions(-) diff --git a/docs/po/zh.po b/docs/po/zh.po index 05e20a5a21..f8f70d6d82 100644 --- a/docs/po/zh.po +++ b/docs/po/zh.po @@ -7642,7 +7642,7 @@ msgid "" "of the mainnet inscriptions." msgstr "" "为了避免在测试时必须将依赖铭文ID更改为主网铭文ID,你可以在测试时使用内容代" -"理。`ord server`接受一个`--content-proxy`选项,它需要另一个`ord server`实例的" +"理。`ord server`接受一个`--proxy`选项,它需要另一个`ord server`实例的" "URL。当设置了内容代理并且铭文未找到时,向`/content/`发出请" "求,`ord server`将会将请求转发给内容代理。这允许你运行一个带有主网内容代理的" "测试`ord server`实例。然后你可以在测试铭文中使用主网铭文ID,这将返回主网铭文" @@ -7651,7 +7651,7 @@ msgstr "" #: src/guides/testing.md:155 msgid "" "```\n" -"ord --regtest server --content-proxy https://ordinals.com\n" +"ord --regtest server --proxy https://ordinals.com\n" "```" msgstr "" diff --git a/docs/src/guides/testing.md b/docs/src/guides/testing.md index 26a13272b4..09765b2866 100644 --- a/docs/src/guides/testing.md +++ b/docs/src/guides/testing.md @@ -145,7 +145,7 @@ bitcoin-cli generatetoaddress 6 To avoid having to change dependency inscription IDs to mainnet inscription IDs, you may utilize a content proxy when testing. `ord server` accepts a -`--content-proxy` option, which takes the URL of a another `ord server` +`--proxy` option, which takes the URL of a another `ord server` instance. When making a request to `/content/` when a content proxy is set and the inscription is not found, `ord server` will forward the request to the content proxy. This allows you to run a test `ord server` @@ -154,5 +154,5 @@ in your test inscription, which will then return the content of the mainnet inscriptions. ``` -ord --regtest server --content-proxy https://ordinals.com +ord --regtest server --proxy https://ordinals.com ``` diff --git a/src/subcommand/env.rs b/src/subcommand/env.rs index ed3340b2f4..1e38a8a576 100644 --- a/src/subcommand/env.rs +++ b/src/subcommand/env.rs @@ -23,9 +23,9 @@ pub(crate) struct Env { pub(crate) decompress: bool, #[arg( long, - help = "Proxy `/content/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." + help = "Proxy `/content/INSCRIPTION_ID` and other recursive endpoints to `` if the inscription is not present on current chain." )] - pub(crate) content_proxy: Option, + pub(crate) proxy: Option, } #[derive(Serialize)] @@ -137,7 +137,7 @@ rpcport={bitcoind_port} let ord = std::env::current_exe()?; let decompress = self.decompress; - let content_proxy = self.content_proxy.map(|url| url.to_string()); + let proxy = self.proxy.map(|url| url.to_string()); let mut command = Command::new(&ord); let ord_server = command @@ -152,8 +152,8 @@ rpcport={bitcoind_port} ord_server.arg("--decompress"); } - if let Some(content_proxy) = content_proxy { - ord_server.arg("--content-proxy").arg(content_proxy); + if let Some(proxy) = proxy { + ord_server.arg("--proxy").arg(proxy); } let _ord = KillOnDrop(ord_server.spawn()?); diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 9995c66bf3..dc0e79b96c 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -128,9 +128,9 @@ pub struct Server { pub(crate) no_sync: bool, #[arg( long, - help = "Proxy `/content/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." + help = "Proxy `/content/INSCRIPTION_ID` and other recursive endpoints to `` if the inscription is not present on current chain." )] - pub(crate) content_proxy: Option, + pub(crate) proxy: Option, #[arg( long, default_value = "5s", @@ -170,7 +170,7 @@ impl Server { let server_config = Arc::new(ServerConfig { chain: settings.chain(), - content_proxy: self.content_proxy.clone(), + proxy: self.proxy.clone(), csp_origin: self.csp_origin.clone(), decompress: self.decompress, domain: acme_domains.first().cloned(), @@ -1005,27 +1005,45 @@ impl Server { async fn metadata( Extension(index): Extension>, + Extension(server_config): Extension>, Path(inscription_id): Path, - ) -> ServerResult> { + ) -> ServerResult { task::block_in_place(|| { - let metadata = index - .get_inscription_by_id(inscription_id)? - .ok_or_not_found(|| format!("inscription {inscription_id}"))? + let Some(inscription) = index.get_inscription_by_id(inscription_id)? else { + return if let Some(proxy) = server_config.proxy.as_ref() { + Self::proxy(proxy, &format!("r/metadata/{}", inscription_id)) + } else { + Err(ServerError::NotFound(format!( + "inscription {} not found", + inscription_id + ))) + }; + }; + + let metadata = inscription .metadata .ok_or_not_found(|| format!("inscription {inscription_id} metadata"))?; - Ok(Json(hex::encode(metadata))) + Ok(Json(hex::encode(metadata)).into_response()) }) } async fn inscription_recursive( Extension(index): Extension>, + Extension(server_config): Extension>, Path(inscription_id): Path, ) -> ServerResult { task::block_in_place(|| { - let inscription = index - .get_inscription_by_id(inscription_id)? - .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + let Some(inscription) = index.get_inscription_by_id(inscription_id)? else { + return if let Some(proxy) = server_config.proxy.as_ref() { + Self::proxy(proxy, &format!("r/inscription/{}", inscription_id)) + } else { + Err(ServerError::NotFound(format!( + "inscription {} not found", + inscription_id + ))) + }; + }; let entry = index .get_inscription_entry(inscription_id) @@ -1386,9 +1404,9 @@ impl Server { Redirect::to("https://docs.ordinals.com/bounty/") } - fn proxy_content(proxy: &Url, inscription_id: InscriptionId) -> ServerResult { + fn proxy(proxy: &Url, path: &String) -> ServerResult { let response = reqwest::blocking::Client::new() - .get(format!("{}content/{}", proxy, inscription_id)) + .get(format!("{}{}", proxy, path)) .send() .map_err(|err| anyhow!(err))?; @@ -1425,11 +1443,11 @@ impl Server { } let Some(mut inscription) = index.get_inscription_by_id(inscription_id)? else { - return if let Some(proxy) = server_config.content_proxy.as_ref() { - Self::proxy_content(proxy, inscription_id) + return if let Some(proxy) = server_config.proxy.as_ref() { + Self::proxy(proxy, &format!("content/{}", inscription_id)) } else { Err(ServerError::NotFound(format!( - "{} not found", + "inscription {} not found", inscription_id ))) }; @@ -1774,20 +1792,35 @@ impl Server { async fn children_recursive( Extension(index): Extension>, + Extension(server_config): Extension>, Path(inscription_id): Path, ) -> ServerResult { - Self::children_recursive_paginated(Extension(index), Path((inscription_id, 0))).await + Self::children_recursive_paginated( + Extension(index), + Extension(server_config), + Path((inscription_id, 0)), + ) + .await } async fn children_recursive_paginated( Extension(index): Extension>, + Extension(server_config): Extension>, Path((parent, page)): Path<(InscriptionId, usize)>, ) -> ServerResult { task::block_in_place(|| { - let parent_sequence_number = index - .get_inscription_entry(parent)? - .ok_or_not_found(|| format!("inscription {parent}"))? - .sequence_number; + let Some(parent) = index.get_inscription_entry(parent)? else { + return if let Some(proxy) = server_config.proxy.as_ref() { + Self::proxy(proxy, &format!("r/children/{}/{}", parent, page)) + } else { + Err(ServerError::NotFound(format!( + "inscription {} not found", + parent + ))) + }; + }; + + let parent_sequence_number = parent.sequence_number; let (ids, more) = index.get_children_by_sequence_number_paginated(parent_sequence_number, 100, page)?; @@ -6551,7 +6584,7 @@ next } #[test] - fn proxy() { + fn content_proxy() { let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); @@ -6575,7 +6608,7 @@ next let server_with_proxy = TestServer::builder() .chain(Chain::Regtest) - .server_option("--content-proxy", server.url.as_ref()) + .server_option("--proxy", server.url.as_ref()) .build(); server_with_proxy.mine_blocks(1); @@ -6584,6 +6617,226 @@ next server_with_proxy.assert_response(format!("/content/{id}"), StatusCode::OK, "foo"); } + #[test] + fn metadata_proxy() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + + server.mine_blocks(1); + + let mut metadata = Vec::new(); + ciborium::into_writer("bar", &mut metadata).unwrap(); + + let inscription = Inscription { + content_type: Some("text/html".into()), + body: Some("foo".into()), + metadata: Some(metadata.clone()), + ..default() + }; + + let txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription.to_witness())], + ..default() + }); + + server.mine_blocks(1); + + let id = InscriptionId { txid, index: 0 }; + + server.assert_response( + format!("/r/metadata/{id}"), + StatusCode::OK, + &format!("\"{}\"", hex::encode(metadata.clone())), + ); + + let server_with_proxy = TestServer::builder() + .chain(Chain::Regtest) + .server_option("--proxy", server.url.as_ref()) + .build(); + + server_with_proxy.mine_blocks(1); + + server.assert_response( + format!("/r/metadata/{id}"), + StatusCode::OK, + &format!("\"{}\"", hex::encode(metadata.clone())), + ); + + server_with_proxy.assert_response( + format!("/r/metadata/{id}"), + StatusCode::OK, + &format!("\"{}\"", hex::encode(metadata.clone())), + ); + } + + #[test] + fn children_proxy() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + + server.mine_blocks(1); + + let parent_txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..default() + }); + + let parent_id = InscriptionId { + txid: parent_txid, + index: 0, + }; + + server.assert_response( + format!("/r/children/{parent_id}"), + StatusCode::NOT_FOUND, + &format!("inscription {parent_id} not found"), + ); + + server.mine_blocks(1); + + let children = server.get_json::(format!("/r/children/{parent_id}")); + + assert_eq!(children.ids.len(), 0); + + let mut builder = script::Builder::new(); + for _ in 0..11 { + builder = Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: vec![parent_id.value()], + unrecognized_even_field: false, + ..default() + } + .append_reveal_script_to_builder(builder); + } + + let witness = Witness::from_slice(&[builder.into_bytes(), Vec::new()]); + + let txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, witness), (2, 1, 0, Default::default())], + ..default() + }); + + server.mine_blocks(1); + + let first_child_id = InscriptionId { txid, index: 0 }; + + let children = server.get_json::(format!("/r/children/{parent_id}")); + + assert_eq!(children.ids.len(), 11); + assert_eq!(first_child_id, children.ids[0]); + + let server_with_proxy = TestServer::builder() + .chain(Chain::Regtest) + .server_option("--proxy", server.url.as_ref()) + .build(); + + server_with_proxy.mine_blocks(1); + + let children = server.get_json::(format!("/r/children/{parent_id}")); + + assert_eq!(children.ids.len(), 11); + assert_eq!(first_child_id, children.ids[0]); + + let children = server_with_proxy.get_json::(format!("/r/children/{parent_id}")); + + assert_eq!(children.ids.len(), 11); + assert_eq!(first_child_id, children.ids[0]); + } + + #[test] + fn inscription_proxy() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + + server.mine_blocks(1); + + let inscription = Inscription { + content_type: Some("text/html".into()), + body: Some("foo".into()), + ..default() + }; + + let txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription.to_witness())], + ..default() + }); + + server.mine_blocks(1); + + let id = InscriptionId { txid, index: 0 }; + + pretty_assert_eq!( + server.get_json::(format!("/r/inscription/{id}")), + api::InscriptionRecursive { + charms: Vec::new(), + content_type: Some("text/html".into()), + content_length: Some(3), + delegate: None, + fee: 0, + height: 2, + id, + number: 0, + output: OutPoint { txid, vout: 0 }, + sat: None, + satpoint: SatPoint { + outpoint: OutPoint { txid, vout: 0 }, + offset: 0 + }, + timestamp: 2, + value: Some(50 * COIN_VALUE), + } + ); + + let server_with_proxy = TestServer::builder() + .chain(Chain::Regtest) + .server_option("--proxy", server.url.as_ref()) + .build(); + + server_with_proxy.mine_blocks(1); + + pretty_assert_eq!( + server.get_json::(format!("/r/inscription/{id}")), + api::InscriptionRecursive { + charms: Vec::new(), + content_type: Some("text/html".into()), + content_length: Some(3), + delegate: None, + fee: 0, + height: 2, + id, + number: 0, + output: OutPoint { txid, vout: 0 }, + sat: None, + satpoint: SatPoint { + outpoint: OutPoint { txid, vout: 0 }, + offset: 0 + }, + timestamp: 2, + value: Some(50 * COIN_VALUE), + } + ); + + assert_eq!( + server_with_proxy.get_json::(format!("/r/inscription/{id}")), + api::InscriptionRecursive { + charms: Vec::new(), + content_type: Some("text/html".into()), + content_length: Some(3), + delegate: None, + fee: 0, + height: 2, + id, + number: 0, + output: OutPoint { txid, vout: 0 }, + sat: None, + satpoint: SatPoint { + outpoint: OutPoint { txid, vout: 0 }, + offset: 0 + }, + timestamp: 2, + value: Some(50 * COIN_VALUE), + } + ); + } + #[test] fn block_info() { let server = TestServer::new(); diff --git a/src/subcommand/server/server_config.rs b/src/subcommand/server/server_config.rs index 79f141ebdd..ffa9c02ca8 100644 --- a/src/subcommand/server/server_config.rs +++ b/src/subcommand/server/server_config.rs @@ -3,7 +3,7 @@ use {super::*, axum::http::HeaderName}; #[derive(Default)] pub(crate) struct ServerConfig { pub(crate) chain: Chain, - pub(crate) content_proxy: Option, + pub(crate) proxy: Option, pub(crate) csp_origin: Option, pub(crate) decompress: bool, pub(crate) domain: Option,