diff --git a/coins/monero/src/ringct/bulletproofs/mod.rs b/coins/monero/src/ringct/bulletproofs/mod.rs index 866f9018..fdddca2a 100644 --- a/coins/monero/src/ringct/bulletproofs/mod.rs +++ b/coins/monero/src/ringct/bulletproofs/mod.rs @@ -37,6 +37,7 @@ impl Bulletproofs { pub(crate) fn fee_weight(plus: bool, outputs: usize) -> usize { let fields = if plus { 6 } else { 9 }; + // TODO: Shouldn't this use u32/u64? #[allow(non_snake_case)] let mut LR_len = usize::try_from(usize::BITS - (outputs - 1).leading_zeros()).unwrap(); let padded_outputs = 1 << LR_len; diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index 10704ec6..0a58cf94 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -124,6 +124,8 @@ pub enum TransactionError { TooManyOutputs, #[error("too much data")] TooMuchData, + #[error("too many inputs/too much arbitrary data")] + TooLargeTransaction, #[error("not enough funds (in {0}, out {1})")] NotEnoughFunds(u64, u64), #[error("wrong spend private key")] @@ -303,8 +305,6 @@ impl SignableTransaction { } } - // TODO TX MAX SIZE - // If we don't have two outputs, as required by Monero, error if (payments.len() == 1) && change_address.is_none() { Err(TransactionError::NoChange)?; @@ -316,8 +316,24 @@ impl SignableTransaction { // Calculate the extra length let extra = Extra::fee_weight(outputs, has_payment_id, data.as_ref()); + // This is a extremely heavy fee weight estimation which can only be trusted for two things + // 1) Ensuring we have enough for whatever fee we end up using + // 2) Ensuring we aren't over the max size + let estimated_tx_size = Transaction::fee_weight(protocol, inputs.len(), outputs, extra); + + // The actual limit is half the block size, and for the minimum block size of 300k, that'd be + // 150k + // wallet2 will only create transactions up to 100k bytes however + const MAX_TX_SIZE: usize = 100_000; + + // This uses the weight (estimated_tx_size) despite the BP clawback + // The clawback *increases* the weight, so this will over-estimate, yet it's still safe + if estimated_tx_size >= MAX_TX_SIZE { + Err(TransactionError::TooLargeTransaction)?; + } + // Calculate the fee. - let fee = fee_rate.calculate(Transaction::fee_weight(protocol, inputs.len(), outputs, extra)); + let fee = fee_rate.calculate(estimated_tx_size); // Make sure we have enough funds let in_amount = inputs.iter().map(|input| input.commitment().amount).sum::();