Skip to content

Commit

Permalink
Add backlinks from /output and /transaction (ordinals#1235)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Jan 16, 2023
1 parent d67be7f commit 6aa3a2b
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 52 deletions.
22 changes: 17 additions & 5 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,17 +524,29 @@ impl Index {
}
}

pub(crate) fn get_transaction_blockhash(&self, txid: Txid) -> Result<Option<BlockHash>> {
Ok(
self
.client
.get_raw_transaction_info(&txid, None)
.into_option()?
.and_then(|info| {
if info.in_active_chain.unwrap_or_default() {
info.blockhash
} else {
None
}
}),
)
}

pub(crate) fn is_transaction_in_active_chain(&self, txid: Txid) -> Result<bool> {
Ok(
self
.client
.get_raw_transaction_info(&txid, None)
.into_option()?
.and_then(|transaction_info| {
transaction_info
.confirmations
.map(|confirmations| confirmations > 0)
})
.and_then(|info| info.in_active_chain)
.unwrap_or(false),
)
}
Expand Down
40 changes: 23 additions & 17 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,11 +469,14 @@ impl Server {
) -> ServerResult<PageHtml<TransactionHtml>> {
let inscription = index.get_inscription_by_id(txid.into())?;

let blockhash = index.get_transaction_blockhash(txid)?;

Ok(
TransactionHtml::new(
index
.get_transaction(txid)?
.ok_or_not_found(|| format!("transaction {txid}"))?,
blockhash,
inscription.map(|_| txid.into()),
chain,
)
Expand Down Expand Up @@ -1275,37 +1278,40 @@ mod tests {

#[test]
fn output_with_sat_index() {
let txid = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b";
TestServer::new_with_sat_index().assert_response_regex(
"/output/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0",
StatusCode::OK,
".*<title>Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</title>.*<h1>Output <span class=monospace>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</span></h1>
format!("/output/{txid}:0"),
StatusCode::OK,
format!(
".*<title>Output {txid}:0</title>.*<h1>Output <span class=monospace>{txid}:0</span></h1>
<dl>
<dt>value</dt><dd>5000000000</dd>
<dt>script pubkey</dt><dd class=data>OP_PUSHBYTES_65 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG</dd>
<dt>script pubkey</dt><dd class=data>OP_PUSHBYTES_65 [[:xdigit:]]{{130}} OP_CHECKSIG</dd>
<dt>transaction</dt><dd><a class=monospace href=/tx/{txid}>{txid}</a></dd>
</dl>
<h2>1 Sat Range</h2>
<ul class=monospace>
<li><a href=/range/0/5000000000 class=mythic>0–5000000000</a></li>
</ul>.*",
);
</ul>.*"
),
);
}

#[test]
fn output_without_sat_index() {
let txid = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b";
TestServer::new().assert_response_regex(
"/output/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0",
StatusCode::OK,
".*<title>Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</title>.*<h1>Output <span class=monospace>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</span></h1>
format!("/output/{txid}:0"),
StatusCode::OK,
format!(
".*<title>Output {txid}:0</title>.*<h1>Output <span class=monospace>{txid}:0</span></h1>
<dl>
<dt>value</dt><dd>5000000000</dd>
<dt>script pubkey</dt><dd class=data>OP_PUSHBYTES_65 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG</dd>
</dl>
</main>
</body>
</html>
",
);
<dt>script pubkey</dt><dd class=data>OP_PUSHBYTES_65 [[:xdigit:]]{{130}} OP_CHECKSIG</dd>
<dt>transaction</dt><dd><a class=monospace href=/tx/{txid}>{txid}</a></dd>
</dl>.*"
),
);
}

#[test]
Expand Down
37 changes: 16 additions & 21 deletions src/templates/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,24 @@ mod tests {

#[test]
fn unspent_output() {
pretty_assert_eq!(
assert_regex_match!(
OutputHtml {
inscriptions: Vec::new(),
outpoint: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0"
.parse()
.unwrap(),
outpoint: outpoint(1),
list: Some(List::Unspent(vec![(0, 1), (1, 3)])),
chain: Chain::Mainnet,
output: TxOut {
value: 3,
script_pubkey: Script::new_p2pkh(&PubkeyHash::all_zeros()),
},
}
.to_string(),
},
"
<h1>Output <span class=monospace>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</span></h1>
<h1>Output <span class=monospace>1{64}:1</span></h1>
<dl>
<dt>value</dt><dd>3</dd>
<dt>script pubkey</dt><dd class=data>OP_DUP OP_HASH160 OP_PUSHBYTES_20 0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG</dd>
<dt>script pubkey</dt><dd class=data>OP_DUP OP_HASH160 OP_PUSHBYTES_20 0{40} OP_EQUALVERIFY OP_CHECKSIG</dd>
<dt>address</dt><dd class=monospace>1111111111111111111114oLvT2</dd>
<dt>transaction</dt><dd><a class=monospace href=/tx/1{64}>1{64}</a></dd>
</dl>
<h2>2 Sat Ranges</h2>
<ul class=monospace>
Expand All @@ -57,25 +55,23 @@ mod tests {

#[test]
fn spent_output() {
pretty_assert_eq!(
assert_regex_match!(
OutputHtml {
inscriptions: Vec::new(),
outpoint: "0000000000000000000000000000000000000000000000000000000000000000:0"
.parse()
.unwrap(),
outpoint: outpoint(1),
list: Some(List::Spent),
chain: Chain::Mainnet,
output: TxOut {
value: 1,
script_pubkey: script::Builder::new().push_int(0).into_script(),
},
}
.to_string(),
},
"
<h1>Output <span class=monospace>0000000000000000000000000000000000000000000000000000000000000000:0</span></h1>
<h1>Output <span class=monospace>1{64}:1</span></h1>
<dl>
<dt>value</dt><dd>1</dd>
<dt>script pubkey</dt><dd class=data>OP_0</dd>
<dt>transaction</dt><dd><a class=monospace href=/tx/1{64}>1{64}</a></dd>
</dl>
<p>Output has been spent.</p>
"
Expand All @@ -85,12 +81,10 @@ mod tests {

#[test]
fn no_list() {
pretty_assert_eq!(
assert_regex_match!(
OutputHtml {
inscriptions: Vec::new(),
outpoint: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0"
.parse()
.unwrap(),
outpoint: outpoint(1),
list: None,
chain: Chain::Mainnet,
output: TxOut {
Expand All @@ -100,11 +94,12 @@ mod tests {
}
.to_string(),
"
<h1>Output <span class=monospace>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</span></h1>
<h1>Output <span class=monospace>1{64}:1</span></h1>
<dl>
<dt>value</dt><dd>3</dd>
<dt>script pubkey</dt><dd class=data>OP_DUP OP_HASH160 OP_PUSHBYTES_20 0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG</dd>
<dt>script pubkey</dt><dd class=data>OP_DUP OP_HASH160 OP_PUSHBYTES_20 0{40} OP_EQUALVERIFY OP_CHECKSIG</dd>
<dt>address</dt><dd class=monospace>1111111111111111111114oLvT2</dd>
<dt>transaction</dt><dd><a class=monospace href=/tx/1{64}>1{64}</a></dd>
</dl>
"
.unindent()
Expand Down
55 changes: 47 additions & 8 deletions src/templates/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::*;

#[derive(Boilerplate)]
pub(crate) struct TransactionHtml {
blockhash: Option<BlockHash>,
chain: Chain,
inscription: Option<InscriptionId>,
transaction: Transaction,
Expand All @@ -11,11 +12,13 @@ pub(crate) struct TransactionHtml {
impl TransactionHtml {
pub(crate) fn new(
transaction: Transaction,
blockhash: Option<BlockHash>,
inscription: Option<InscriptionId>,
chain: Chain,
) -> Self {
Self {
txid: transaction.txid(),
blockhash,
chain,
inscription,
transaction,
Expand All @@ -37,7 +40,7 @@ mod tests {
};

#[test]
fn transaction_html() {
fn html() {
let transaction = Transaction {
version: 0,
lock_time: PackedLockTime(0),
Expand All @@ -54,24 +57,27 @@ mod tests {
],
};

let txid = transaction.txid();

pretty_assert_eq!(
TransactionHtml::new(transaction, None, Chain::Mainnet).to_string(),
"
<h1>Transaction <span class=monospace>9c03542773bfbbf2a951a54e73e2955eeb0e070df07e753e1055de1ea54a74bb</span></h1>
TransactionHtml::new(transaction, None, None, Chain::Mainnet).to_string(),
format!(
"
<h1>Transaction <span class=monospace>{txid}</span></h1>
<h2>2 Outputs</h2>
<ul class=monospace>
<li>
<a href=/output/9c03542773bfbbf2a951a54e73e2955eeb0e070df07e753e1055de1ea54a74bb:0 class=monospace>
9c03542773bfbbf2a951a54e73e2955eeb0e070df07e753e1055de1ea54a74bb:0
<a href=/output/{txid}:0 class=monospace>
{txid}:0
</a>
<dl>
<dt>value</dt><dd>5000000000</dd>
<dt>script pubkey</dt><dd class=data>OP_0</dd>
</dl>
</li>
<li>
<a href=/output/9c03542773bfbbf2a951a54e73e2955eeb0e070df07e753e1055de1ea54a74bb:1 class=monospace>
9c03542773bfbbf2a951a54e73e2955eeb0e070df07e753e1055de1ea54a74bb:1
<a href=/output/{txid}:1 class=monospace>
{txid}:1
</a>
<dl>
<dt>value</dt><dd>5000000000</dd>
Expand All @@ -80,6 +86,39 @@ mod tests {
</li>
</ul>
"
)
.unindent()
);
}

#[test]
fn with_blockhash() {
let transaction = Transaction {
version: 0,
lock_time: PackedLockTime(0),
input: Vec::new(),
output: vec![
TxOut {
value: 50 * COIN_VALUE,
script_pubkey: script::Builder::new().push_int(0).into_script(),
},
TxOut {
value: 50 * COIN_VALUE,
script_pubkey: script::Builder::new().push_int(1).into_script(),
},
],
};

assert_regex_match!(
TransactionHtml::new(transaction, Some(blockhash(0)), None, Chain::Mainnet),
"
<h1>Transaction <span class=monospace>[[:xdigit:]]{64}</span></h1>
<dl>
<dt>block</dt>
<dd><a href=/block/0{64} class=monospace>0{64}</a></dd>
</dl>
.*
"
.unindent()
);
}
Expand Down
10 changes: 10 additions & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ macro_rules! assert_matches {
}
}

pub(crate) fn blockhash(n: u64) -> BlockHash {
let hex = format!("{n:x}");

if hex.is_empty() || hex.len() > 1 {
panic!();
}

hex.repeat(64).parse().unwrap()
}

pub(crate) fn txid(n: u64) -> Txid {
let hex = format!("{n:x}");

Expand Down
1 change: 1 addition & 0 deletions templates/output.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ <h1>Output <span class=monospace>{{self.outpoint}}</span></h1>
%% if let Ok(address) = self.chain.address_from_script(&self.output.script_pubkey ) {
<dt>address</dt><dd class=monospace>{{ address }}</dd>
%% }
<dt>transaction</dt><dd><a class=monospace href=/tx/{{ self.outpoint.txid }}>{{ self.outpoint.txid }}</a></dd>
</dl>
%% if let Some(list) = &self.list {
%% match list {
Expand Down
6 changes: 6 additions & 0 deletions templates/transaction.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ <h2>Inscription Geneses</h2>
{{ Iframe::thumbnail(id) }}
</div>
%% }
%% if let Some(blockhash) = self.blockhash {
<dl>
<dt>block</dt>
<dd><a href=/block/{{ blockhash }} class=monospace>{{ blockhash }}</a></dd>
</dl>
%% }
<h2>{{"Output".tally(self.transaction.output.len())}}</h2>
<ul class=monospace>
%% for (vout, output) in self.transaction.output.iter().enumerate() {
Expand Down
2 changes: 1 addition & 1 deletion test-bitcoincore-rpc/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ impl Api for Server {
match self.state().transactions.get(&txid) {
Some(_) => Ok(
serde_json::to_value(GetRawTransactionResult {
in_active_chain: None,
in_active_chain: Some(true),
hex: Vec::new(),
txid: Txid::all_zeros(),
hash: Wtxid::all_zeros(),
Expand Down

0 comments on commit 6aa3a2b

Please sign in to comment.