Skip to content

Commit 3c76532

Browse files
committed
Refactor to avoid code duplication. Add javadocs to methods and remove comments
1 parent 2e959d0 commit 3c76532

File tree

2 files changed

+57
-34
lines changed

2 files changed

+57
-34
lines changed

src/main/java/co/rsk/bitcoinj/wallet/Wallet.java

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -654,25 +654,22 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException {
654654
if (req.shuffleOutputs)
655655
req.tx.shuffleOutputs();
656656

657-
// Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
657+
// Now sign the legacy inputs, thus proving that we are entitled to redeem the connected outputs.
658658
if (req.signInputs)
659-
signTransaction(req);
659+
signLegacyTransaction(req);
660660

661661
// Check virtual size.
662662
int baseSize = req.tx.unsafeBitcoinSerialize().length;
663-
if (req.isSegwitCompatible) {
664-
// in segwit-compatible, the scriptSig is a 36-bytes-fixed-size hash,
665-
// so we should count its bytes manually.
666-
int segwitCompatibleScriptSigSize = 36;
667-
baseSize += req.tx.getInputs().size() * segwitCompatibleScriptSigSize;
668-
}
669663
int totalSize = baseSize;
664+
// if the tx was signed, there's nothing else to consider
670665
if (!req.signInputs) {
671-
if (!req.isSegwitCompatible) {
672-
baseSize += estimateBytesForSigning(bestCoinSelection);
666+
if (req.isSegwitCompatible) {
667+
baseSize += calculateSegwitScriptSigSize(req.tx);
673668
totalSize = baseSize;
674-
} else {
675669
totalSize += estimateBytesForSigning(bestCoinSelection);
670+
} else {
671+
baseSize += estimateBytesForSigning(bestCoinSelection);
672+
totalSize = baseSize;
676673
}
677674
}
678675

@@ -693,12 +690,12 @@ public void completeTx(SendRequest req) throws InsufficientMoneyException {
693690
}
694691

695692
/**
696-
* <p>Given a send request containing transaction, attempts to sign it's inputs. This method expects transaction
693+
* <p>Given a send request containing a legacy transaction, attempts to sign it's inputs. This method expects transaction
697694
* to have all necessary inputs connected or they will be ignored.</p>
698695
* <p>Actual signing is done by pluggable {@link #signers} and it's not guaranteed that
699696
* transaction will be complete in the end.</p>
700697
*/
701-
public void signTransaction(SendRequest req) {
698+
public void signLegacyTransaction(SendRequest req) {
702699
try {
703700
BtcTransaction tx = req.tx;
704701
List<TransactionInput> inputs = tx.getInputs();
@@ -748,7 +745,7 @@ private boolean adjustOutputDownwardsForFee(
748745
Coin feePerKb,
749746
boolean ensureMinRequiredFee
750747
) {
751-
final int size = calculateTxSizeBeforeSigning(tx, isSegwit, coinSelection);
748+
final int size = calculateTxSizeForFees(tx, isSegwit, coinSelection);
752749
Coin fee = feePerKb.multiply(size).divide(1000);
753750
if (ensureMinRequiredFee && fee.compareTo(BtcTransaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
754751
fee = BtcTransaction.REFERENCE_DEFAULT_MIN_TX_FEE;
@@ -1024,7 +1021,7 @@ private FeeCalculation calculateFee(
10241021
checkState(input.getScriptBytes().length == 0);
10251022
}
10261023

1027-
int size = calculateTxSizeBeforeSigning(tx, req.isSegwitCompatible, selection);
1024+
int size = calculateTxSizeForFees(tx, req.isSegwitCompatible, selection);
10281025

10291026
Coin feePerKb = req.feePerKb;
10301027
if (needAtLeastReferenceFee && feePerKb.compareTo(BtcTransaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) {
@@ -1043,26 +1040,51 @@ private FeeCalculation calculateFee(
10431040
return result;
10441041
}
10451042

1046-
private int calculateTxSizeBeforeSigning(BtcTransaction tx, boolean isSegwitCompatible, CoinSelection bestCoinSelection) {
1043+
/**
1044+
* Calculates the virtual size of a Bitcoin transaction for fee estimation purposes.
1045+
* When estimating fees, we assume the transaction is not yet signed, so we need to consider
1046+
* the expected size of the signatures.
1047+
* - If the transaction is legacy (non-SegWit), signature data is part of the base size,
1048+
* and the total size is equal to the base size.
1049+
* - If the transaction is SegWit-compatible, the scriptSig is a 36-byte fixed-size hash
1050+
* located in the input, so its part of the base size. Signatures are located in the
1051+
* witness, so they are just part of the total size.
1052+
* @param tx the unsigned Bitcoin transaction
1053+
* @param isSegwitCompatible whether the transaction is SegWit-compatible
1054+
* @param bestCoinSelection the selected UTXOs for the transaction
1055+
* @return the estimated virtual size of the transaction
1056+
*/
1057+
private int calculateTxSizeForFees(BtcTransaction tx, boolean isSegwitCompatible, CoinSelection bestCoinSelection) {
10471058
int baseSize = tx.unsafeBitcoinSerialize().length;
1048-
if (isSegwitCompatible) {
1049-
// in segwit-compatible, the scriptSig is a 36-bytes-fixed-size hash,
1050-
// so we should count its bytes manually.
1051-
int segwitCompatibleScriptSigSize = 36;
1052-
baseSize += tx.getInputs().size() * segwitCompatibleScriptSigSize;
1053-
}
1054-
int totalSize = baseSize;
1059+
int totalSize;
10551060
if (!isSegwitCompatible) {
10561061
baseSize += estimateBytesForSigning(bestCoinSelection);
10571062
totalSize = baseSize;
10581063
} else {
1064+
baseSize += calculateSegwitScriptSigSize(tx);
1065+
totalSize = baseSize;
10591066
totalSize += estimateBytesForSigning(bestCoinSelection);
10601067
}
10611068
return calculateVirtualSize(baseSize, totalSize);
10621069
}
10631070

1071+
private int calculateSegwitScriptSigSize(BtcTransaction tx) {
1072+
int segwitCompatibleScriptSigSize = 36;
1073+
return tx.getInputs().size() * segwitCompatibleScriptSigSize;
1074+
}
1075+
1076+
/**
1077+
* Calculates the virtual size of a Bitcoin transaction as defined in BIP141.
1078+
* The virtual size is a weighted size used to properly account for the discount SegWit
1079+
* provides on witness data.
1080+
* - For legacy transactions, {@code baseSize == totalSize}, so the virtual size equals both.
1081+
* - For SegWit transactions, {@code baseSize} excludes witness data, and {@code totalSize} includes it.
1082+
*
1083+
* @param baseSize the size of the transaction excluding witness data
1084+
* @param totalSize the full size of the transaction including witness data
1085+
* @return the virtual size in vbytes
1086+
*/
10641087
private int calculateVirtualSize(int baseSize, int totalSize) {
1065-
// As described in BIP141
10661088
int txWeight = totalSize + (3 * baseSize);
10671089
return txWeight / 4;
10681090
}

src/test/java/co/rsk/bitcoinj/wallet/WalletTest.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
import java.nio.charset.StandardCharsets;
1010
import java.util.*;
1111

12-
import static org.junit.Assert.assertFalse;
13-
import static org.junit.Assert.assertTrue;
14-
import static org.junit.Assert.assertThrows;
12+
import static org.junit.Assert.*;
1513

1614
public class WalletTest {
1715
// data from tx https://mempool.space/testnet/tx/1744459aeaf7369aadc9fc40de9ab2bf575b14e35029b35a7ee4bbd3de65af7f
@@ -123,11 +121,12 @@ public void completeTx_legacy() throws InsufficientMoneyException {
123121
wallet.completeTx(sr);
124122

125123
// assert
126-
double expectedSize = 375;
124+
double realSize = 375;
125+
int expectedCalculatedSize = 340;
127126
// for legacy tx, the calculation has a 10% diff approx,
128127
// but we have to keep the same implementation for backwards compatibility
129128
double allowedPercentageError = 10.0;
130-
assertCalculatedSizeIsCloseToExpectedSize(expectedSize, allowedPercentageError);
129+
assertCalculatedSizeIsCloseToExpectedSize(realSize, allowedPercentageError, expectedCalculatedSize);
131130
}
132131

133132
@Test
@@ -148,10 +147,11 @@ public void completeTx_segwit() throws InsufficientMoneyException {
148147
wallet.completeTx(sr);
149148

150149
// assert
151-
double expectedSize = 183.75;
150+
double realSize = 183.75;
151+
int expectedCalculatedSize = 184;
152152
// for segwit, the calculation seems to be pretty accurate
153153
double allowedPercentageError = 1.0;
154-
assertCalculatedSizeIsCloseToExpectedSize(expectedSize, allowedPercentageError);
154+
assertCalculatedSizeIsCloseToExpectedSize(realSize, allowedPercentageError, expectedCalculatedSize);
155155
}
156156

157157
@Test
@@ -176,17 +176,18 @@ public void completeTx_forTxThatExceedsMaximumStandardSize_throwsExceededMaxTran
176176
assertThrows(Wallet.ExceededMaxTransactionSize.class, () -> wallet.completeTx(sr));
177177
}
178178

179-
private void assertCalculatedSizeIsCloseToExpectedSize(double expectedSize, double allowedPercentageError) {
180-
double allowedError = Math.ceil(expectedSize * allowedPercentageError / 100);
179+
private void assertCalculatedSizeIsCloseToExpectedSize(double realSize, double allowedPercentageError, int expectedCalculatedSize) {
180+
double allowedError = Math.ceil(realSize * allowedPercentageError / 100);
181181

182182
Coin availableAmount = AMOUNT_IN_EACH_UTXO.multiply(AMOUNT_OF_UTXOS_TO_USE);
183183
Coin actualValueSent = tx.getOutputs().get(0).getValue();
184184
Coin fee = availableAmount.minus(actualValueSent);
185185
// fee = (tx size * feePerKb) / 1000 => fee = (tx size * 10_000) / 1000
186186
// => fee = tx size * 10 => tx size = fee / 10
187187
long size = fee.divide(10L).getValue();
188+
assertEquals(expectedCalculatedSize, size); // to make the test deterministic
188189

189-
double diff = Math.abs(expectedSize - size);
190+
double diff = Math.abs(realSize - size);
190191
assertTrue(diff <= allowedError);
191192
}
192193
}

0 commit comments

Comments
 (0)