From cb85305a63815f10df19a7141af007610e909f04 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sat, 11 Apr 2026 08:17:46 -0700 Subject: [PATCH 1/4] Fix fee calculation in bitcoind client --- crates/esploda/src/bitcoind/tx.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/esploda/src/bitcoind/tx.rs b/crates/esploda/src/bitcoind/tx.rs index ee889a2..4db1e4a 100644 --- a/crates/esploda/src/bitcoind/tx.rs +++ b/crates/esploda/src/bitcoind/tx.rs @@ -200,7 +200,9 @@ impl Transaction { /// /// # Panics /// - /// Asserts that `previous_outputs` and `self.inputs` have the same number of non-coinbase TXOs. + /// - Asserts that the computed `fee` is not negative. + /// - Asserts that `previous_outputs` and `self.inputs` have the same number of non-coinbase + /// TXOs. /// /// [`esplora::Transaction`]: crate::esplora::Transaction pub fn into_esplora( @@ -208,7 +210,11 @@ impl Transaction { block_height: u32, previous_outputs: Vec, ) -> crate::esplora::Transaction { - let fee = previous_outputs.iter().map(|txo| txo.value).sum(); + let fee = previous_outputs.iter().map(|txo| txo.value).sum() + - self.outputs.iter().map(|txo| txo.value).sum(); + + assert!(!fee.is_negative(), "Fee must never be negative"); + let mut previous_outputs = previous_outputs.into_iter(); let inputs = self .inputs From 9e0533aff8846ce69f4cbdf541b7c18bae8dde62 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sat, 11 Apr 2026 08:26:44 -0700 Subject: [PATCH 2/4] Fix type inference fail and deprecation warning --- crates/esploda/src/bitcoind/tx.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/esploda/src/bitcoind/tx.rs b/crates/esploda/src/bitcoind/tx.rs index 4db1e4a..467de88 100644 --- a/crates/esploda/src/bitcoind/tx.rs +++ b/crates/esploda/src/bitcoind/tx.rs @@ -1,9 +1,9 @@ use crate::esplora::{TxIn, TxOut}; -use bitcoin::{hash_types::TxMerkleNode, locktime::absolute::LockTime, pow::CompactTarget}; use bitcoin::{BlockHash, ScriptBuf, Sequence, Txid, Witness}; +use bitcoin::{hash_types::TxMerkleNode, locktime::absolute::LockTime, pow::CompactTarget}; use chrono::{DateTime, Utc}; use data_encoding::HEXLOWER; -use rust_decimal::{prelude::FromPrimitive as _, Decimal}; +use rust_decimal::{Decimal, prelude::FromPrimitive as _}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::str::FromStr; @@ -210,10 +210,10 @@ impl Transaction { block_height: u32, previous_outputs: Vec, ) -> crate::esplora::Transaction { - let fee = previous_outputs.iter().map(|txo| txo.value).sum() + let fee: Decimal = previous_outputs.iter().map(|txo| txo.value).sum() - self.outputs.iter().map(|txo| txo.value).sum(); - assert!(!fee.is_negative(), "Fee must never be negative"); + assert!(!fee.is_sign_negative(), "Fee must never be negative"); let mut previous_outputs = previous_outputs.into_iter(); let inputs = self From 5f7c65b1de925ded69fe53e61ea239f67f29bd64 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sat, 11 Apr 2026 08:30:37 -0700 Subject: [PATCH 3/4] Revert formatting changes --- crates/esploda/src/bitcoind/tx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/esploda/src/bitcoind/tx.rs b/crates/esploda/src/bitcoind/tx.rs index 467de88..ef775e0 100644 --- a/crates/esploda/src/bitcoind/tx.rs +++ b/crates/esploda/src/bitcoind/tx.rs @@ -1,9 +1,9 @@ use crate::esplora::{TxIn, TxOut}; -use bitcoin::{BlockHash, ScriptBuf, Sequence, Txid, Witness}; use bitcoin::{hash_types::TxMerkleNode, locktime::absolute::LockTime, pow::CompactTarget}; +use bitcoin::{BlockHash, ScriptBuf, Sequence, Txid, Witness}; use chrono::{DateTime, Utc}; use data_encoding::HEXLOWER; -use rust_decimal::{Decimal, prelude::FromPrimitive as _}; +use rust_decimal::{prelude::FromPrimitive as _, Decimal}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::str::FromStr; From 7cb0ad660b375c495e7c166c8eeea567a0ac8efb Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sat, 11 Apr 2026 08:37:34 -0700 Subject: [PATCH 4/4] Type inference is pretty bad when composing traits The issue is that the `Sub` and `Sum` traits cannot have their generic parameter types unified automatically. Both types must be specified. Alternatively, specifying the `Sum` parameter types on separate bindings allows the compiler to correctly infer the `Sub` parameter type. --- crates/esploda/src/bitcoind/tx.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/esploda/src/bitcoind/tx.rs b/crates/esploda/src/bitcoind/tx.rs index ef775e0..9f126e0 100644 --- a/crates/esploda/src/bitcoind/tx.rs +++ b/crates/esploda/src/bitcoind/tx.rs @@ -210,9 +210,10 @@ impl Transaction { block_height: u32, previous_outputs: Vec, ) -> crate::esplora::Transaction { - let fee: Decimal = previous_outputs.iter().map(|txo| txo.value).sum() - - self.outputs.iter().map(|txo| txo.value).sum(); + let prev_outputs: Decimal = previous_outputs.iter().map(|txo| txo.value).sum(); + let outputs: Decimal = self.outputs.iter().map(|txo| txo.value).sum(); + let fee = prev_outputs - outputs; assert!(!fee.is_sign_negative(), "Fee must never be negative"); let mut previous_outputs = previous_outputs.into_iter();