Message: fold unsafeBitcoinSerialize() into bitcoinSerialize()

This commit is contained in:
Andreas Schildbach 2023-03-22 02:17:23 +01:00
parent e882322505
commit 7fba2d8c6b
17 changed files with 43 additions and 69 deletions

View file

@ -905,7 +905,7 @@ public class Block extends Message {
ScriptBuilder.createP2PKOutputScript(ECKey.fromPublicOnly(pubKeyTo)).getProgram()));
transactions.add(coinbase);
coinbase.setParent(this);
coinbase.length = coinbase.unsafeBitcoinSerialize().length;
coinbase.length = coinbase.bitcoinSerialize().length;
adjustLength(transactions.size(), coinbase.length);
}

View file

@ -256,7 +256,7 @@ public class BloomFilter extends Message {
/** Inserts the given transaction outpoint. */
public synchronized void insert(TransactionOutPoint outpoint) {
insert(outpoint.unsafeBitcoinSerialize());
insert(outpoint.bitcoinSerialize());
}
/**
@ -358,7 +358,7 @@ public class BloomFilter extends Message {
}
if (found) return true;
for (TransactionInput input : tx.getInputs()) {
if (contains(input.getOutpoint().unsafeBitcoinSerialize())) {
if (contains(input.getOutpoint().bitcoinSerialize())) {
return true;
}
for (ScriptChunk chunk : input.getScriptSig().getChunks()) {

View file

@ -138,38 +138,12 @@ public abstract class Message {
}
}
/**
* Returns a copy of the array returned by {@link Message#unsafeBitcoinSerialize()}, which is safe to mutate.
* If you need extra performance and can guarantee you won't write to the array, you can use the unsafe version.
*
* @return a freshly allocated serialized byte array
*/
public byte[] bitcoinSerialize() {
byte[] bytes = unsafeBitcoinSerialize();
byte[] copy = new byte[bytes.length];
System.arraycopy(bytes, 0, copy, 0, bytes.length);
return copy;
}
/**
* <p>Serialize this message to a byte array that conforms to the bitcoin wire protocol.</p>
*
* <p>This method may return the original byte array used to construct this message if the
* following conditions are met:</p>
*
* <ol>
* <li>1) The message was parsed from a byte array with parseRetain = true</li>
* <li>2) The message has not been modified</li>
* <li>3) The array had an offset of 0 and no surplus bytes</li>
* </ol>
*
* <p>If condition 3 is not met then an copy of the relevant portion of the array will be returned.
* Otherwise a full serialize will occur. For this reason you should only use this API if you can guarantee you
* will treat the resulting array as read only.</p>
*
* @return a byte array owned by this object, do NOT mutate it.
* @return a byte array
*/
public byte[] unsafeBitcoinSerialize() {
public final byte[] bitcoinSerialize() {
// No cached array available so serialize parts by stream.
ByteArrayOutputStream stream = new ByteArrayOutputStream(length < 32 ? 32 : length + 32);
try {

View file

@ -136,7 +136,7 @@ public class StoredBlock {
buffer.putInt(getHeight());
// Using unsafeBitcoinSerialize here can give us direct access to the same bytes we read off the wire,
// avoiding serialization round-trips.
byte[] bytes = getHeader().unsafeBitcoinSerialize();
byte[] bytes = getHeader().bitcoinSerialize();
buffer.put(bytes, 0, Block.HEADER_SIZE); // Trim the trailing 00 byte (zero transactions).
}

View file

@ -823,7 +823,7 @@ public class Transaction extends ChildMessage {
s.append(", wtxid ").append(wTxId);
s.append('\n');
int weight = getWeight();
int size = unsafeBitcoinSerialize().length;
int size = bitcoinSerialize().length;
int vsize = getVsize();
s.append(indent).append("weight: ").append(weight).append(" wu, ");
if (size != vsize)
@ -951,7 +951,7 @@ public class Transaction extends ChildMessage {
* @return raw transaction in hex format
*/
public String toHexString() {
return ByteUtils.formatHex(unsafeBitcoinSerialize());
return ByteUtils.formatHex(bitcoinSerialize());
}
/**
@ -965,7 +965,7 @@ public class Transaction extends ChildMessage {
}
inputs.clear();
// You wanted to reserialize, right?
this.length = this.unsafeBitcoinSerialize().length;
this.length = this.bitcoinSerialize().length;
}
/**
@ -1130,7 +1130,7 @@ public class Transaction extends ChildMessage {
}
outputs.clear();
// You wanted to reserialize, right?
this.length = this.unsafeBitcoinSerialize().length;
this.length = this.bitcoinSerialize().length;
}
/**
@ -1303,7 +1303,7 @@ public class Transaction extends ChildMessage {
try {
// Create a copy of this transaction to operate upon because we need make changes to the inputs and outputs.
// It would not be thread-safe to change the attributes of the transaction object itself.
Transaction tx = this.params.getDefaultSerializer().makeTransaction(ByteBuffer.wrap(this.bitcoinSerialize()));
Transaction tx = this.params.getDefaultSerializer().makeTransaction(ByteBuffer.wrap(bitcoinSerialize()));
// Clear input scripts in preparation for signing. If we're signing a fresh
// transaction that step isn't very helpful, but it doesn't add much cost relative to the actual

View file

@ -212,7 +212,7 @@ public class TransactionOutput extends ChildMessage {
// so dust is a spendable txout less than
// 98*dustRelayFee/1000 (in satoshis).
// 294 satoshis at the default rate of 3000 sat/kB.
long size = this.unsafeBitcoinSerialize().length;
long size = this.bitcoinSerialize().length;
final Script script = getScriptPubKey();
if (ScriptPattern.isP2PKH(script) || ScriptPattern.isP2PK(script) || ScriptPattern.isP2SH(script))
size += 32 + 4 + 1 + 107 + 4; // 148

View file

@ -346,7 +346,7 @@ public class PaymentProtocol {
Protos.Payment.Builder builder = Protos.Payment.newBuilder();
for (Transaction transaction : transactions) {
transaction.verify();
builder.addTransactions(ByteString.copyFrom(transaction.unsafeBitcoinSerialize()));
builder.addTransactions(ByteString.copyFrom(transaction.bitcoinSerialize()));
}
if (refundOutputs != null) {
for (Protos.Output output : refundOutputs)

View file

@ -4417,7 +4417,7 @@ public class Wallet extends BaseTaggableObject
signTransaction(req);
// Check size.
final int size = req.tx.unsafeBitcoinSerialize().length;
final int size = req.tx.bitcoinSerialize().length;
if (size > Transaction.MAX_STANDARD_TX_SIZE)
throw new ExceededMaxTransactionSize();
@ -5626,7 +5626,7 @@ public class Wallet extends BaseTaggableObject
if (sign)
signTransaction(req);
// KeyTimeCoinSelector should never select enough inputs to push us oversize.
checkState(rekeyTx.unsafeBitcoinSerialize().length < Transaction.MAX_STANDARD_TX_SIZE);
checkState(rekeyTx.bitcoinSerialize().length < Transaction.MAX_STANDARD_TX_SIZE);
return rekeyTx;
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.

View file

@ -172,16 +172,16 @@ public class BlockTest {
tx.addInput(new TransactionInput(TESTNET, null, new byte[] {(byte) ScriptOpCodes.OP_FALSE},
new TransactionOutPoint(TESTNET, 0, Sha256Hash.of(new byte[] { 1 }))));
int origTxLength = 8 + 2 + 8 + 1 + 10 + 40 + 1 + 1;
assertEquals(tx.unsafeBitcoinSerialize().length, tx.length);
assertEquals(tx.bitcoinSerialize().length, tx.length);
assertEquals(origTxLength, tx.length);
block.addTransaction(tx);
assertEquals(block.unsafeBitcoinSerialize().length, block.length);
assertEquals(block.bitcoinSerialize().length, block.length);
assertEquals(origBlockLen + tx.length, block.length);
block.getTransactions().get(1).getInputs().get(0).setScriptBytes(new byte[] {(byte) ScriptOpCodes.OP_FALSE, (byte) ScriptOpCodes.OP_FALSE});
assertEquals(block.length, origBlockLen + tx.length);
assertEquals(tx.length, origTxLength + 1);
block.getTransactions().get(1).getInputs().get(0).clearScriptBytes();
assertEquals(block.length, block.unsafeBitcoinSerialize().length);
assertEquals(block.length, block.bitcoinSerialize().length);
assertEquals(block.length, origBlockLen + tx.length);
assertEquals(tx.length, origTxLength - 1);
block.getTransactions().get(1).addInput(new TransactionInput(TESTNET, null, new byte[] {(byte) ScriptOpCodes.OP_FALSE},

View file

@ -54,7 +54,7 @@ public class BloomFilterTest {
assertTrue(filter.contains(ByteUtils.parseHex("b9300670b4c5366e95b2699e8b18bc75e5f729c5")));
// Value generated by Bitcoin Core
assertEquals("03614e9b050000000000000001", ByteUtils.formatHex(filter.unsafeBitcoinSerialize()));
assertEquals("03614e9b050000000000000001", ByteUtils.formatHex(filter.bitcoinSerialize()));
}
@Test
@ -73,7 +73,7 @@ public class BloomFilterTest {
assertTrue(filter.contains(ByteUtils.parseHex("b9300670b4c5366e95b2699e8b18bc75e5f729c5")));
// Value generated by Bitcoin Core
assertEquals("03ce4299050000000100008002", ByteUtils.formatHex(filter.unsafeBitcoinSerialize()));
assertEquals("03ce4299050000000100008002", ByteUtils.formatHex(filter.bitcoinSerialize()));
}
@Test
@ -97,6 +97,6 @@ public class BloomFilterTest {
BloomFilter filter = wallet.getBloomFilter(wallet.getBloomFilterElementCount(), 0.001, 0);
// Value generated by Bitcoin Core
assertEquals("082ae5edc8e51d4a03080000000000000002", ByteUtils.formatHex(filter.unsafeBitcoinSerialize()));
assertEquals("082ae5edc8e51d4a03080000000000000002", ByteUtils.formatHex(filter.bitcoinSerialize()));
}
}

View file

@ -193,7 +193,7 @@ public class FullBlockTestGenerator {
if (outStream != null && element instanceof BlockAndValidity) {
try {
ByteUtils.writeUint32BE(params.getPacketMagic(), outStream);
byte[] block = ((BlockAndValidity)element).block.bitcoinSerialize();
byte[] block = ((BlockAndValidity) element).block.bitcoinSerialize();
byte[] length = new byte[4];
ByteUtils.writeUint32BE(block.length, length, 0);
outStream.write(ByteUtils.reverseBytes(length));

View file

@ -147,7 +147,7 @@ public class WalletProtobufSerializerTest {
assertEquals(1, wallet1.getTransactions(true).size());
assertEquals(v1, wallet1.getBalance(Wallet.BalanceType.ESTIMATED));
Transaction t1copy = wallet1.getTransaction(t1.getTxId());
assertArrayEquals(t1.unsafeBitcoinSerialize(), t1copy.unsafeBitcoinSerialize());
assertArrayEquals(t1.bitcoinSerialize(), t1copy.bitcoinSerialize());
assertEquals(2, t1copy.getConfidence().numBroadcastPeers());
assertNotNull(t1copy.getConfidence().lastBroadcastTime());
assertEquals(TransactionConfidence.Source.NETWORK, t1copy.getConfidence().getSource());

View file

@ -1679,10 +1679,10 @@ public class WalletTest extends TestWithWallet {
wallet.addWatchedAddress(watchedAddress);
// Note that this has a 1e-12 chance of failing this unit test due to a false positive
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.unsafeBitcoinSerialize()));
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
sendMoneyToWallet(BlockChain.NewBlockType.BEST_CHAIN, t1);
assertTrue(wallet.getBloomFilter(1e-12).contains(outPoint.unsafeBitcoinSerialize()));
assertTrue(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
}
@Test
@ -1732,10 +1732,10 @@ public class WalletTest extends TestWithWallet {
TransactionOutPoint outPoint = new TransactionOutPoint(TESTNET, 0, t1);
// Note that this has a 1e-12 chance of failing this unit test due to a false positive
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.unsafeBitcoinSerialize()));
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
sendMoneyToWallet(BlockChain.NewBlockType.BEST_CHAIN, t1);
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.unsafeBitcoinSerialize()));
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
}
}
@ -1750,10 +1750,10 @@ public class WalletTest extends TestWithWallet {
Transaction t1 = createFakeTx(TESTNET, CENT, address);
TransactionOutPoint outPoint = new TransactionOutPoint(TESTNET, 0, t1);
assertFalse(wallet.getBloomFilter(falsePositiveRate).contains(outPoint.unsafeBitcoinSerialize()));
assertFalse(wallet.getBloomFilter(falsePositiveRate).contains(outPoint.bitcoinSerialize()));
sendMoneyToWallet(BlockChain.NewBlockType.BEST_CHAIN, t1);
assertTrue(wallet.getBloomFilter(falsePositiveRate).contains(outPoint.unsafeBitcoinSerialize()));
assertTrue(wallet.getBloomFilter(falsePositiveRate).contains(outPoint.bitcoinSerialize()));
}
@Test
@ -2344,7 +2344,7 @@ public class WalletTest extends TestWithWallet {
SendRequest request15 = SendRequest.to(OTHER_ADDRESS, CENT);
for (int i = 0; i < 29; i++)
request15.tx.addOutput(CENT, OTHER_ADDRESS);
assertTrue(request15.tx.unsafeBitcoinSerialize().length > 1000);
assertTrue(request15.tx.bitcoinSerialize().length > 1000);
request15.feePerKb = Transaction.DEFAULT_TX_FEE;
request15.ensureMinRequiredFee = true;
wallet.completeTx(request15);
@ -2361,7 +2361,7 @@ public class WalletTest extends TestWithWallet {
request16.ensureMinRequiredFee = true;
for (int i = 0; i < 29; i++)
request16.tx.addOutput(CENT, OTHER_ADDRESS);
assertTrue(request16.tx.unsafeBitcoinSerialize().length > 1000);
assertTrue(request16.tx.bitcoinSerialize().length > 1000);
wallet.completeTx(request16);
// Just the reference fee should be added if feePerKb == 0
// Hardcoded tx length because actual length may vary depending on actual signature length
@ -2383,14 +2383,14 @@ public class WalletTest extends TestWithWallet {
assertEquals(Coin.valueOf(99900), request17.tx.getFee());
assertEquals(1, request17.tx.getInputs().size());
// Calculate its max length to make sure it is indeed 999
int theoreticalMaxLength17 = request17.tx.unsafeBitcoinSerialize().length + myKey.getPubKey().length + 75;
int theoreticalMaxLength17 = request17.tx.bitcoinSerialize().length + myKey.getPubKey().length + 75;
for (TransactionInput in : request17.tx.getInputs())
theoreticalMaxLength17 -= in.getScriptBytes().length;
assertEquals(999, theoreticalMaxLength17);
Transaction spend17 = request17.tx;
{
// Its actual size must be between 996 and 999 (inclusive) as signatures have a 3-byte size range (almost always)
final int length = spend17.unsafeBitcoinSerialize().length;
final int length = spend17.bitcoinSerialize().length;
assertTrue(Integer.toString(length), length >= 996 && length <= 999);
}
// Now check that it got a fee of 1 since its max size is 999 (1kb).
@ -2411,13 +2411,13 @@ public class WalletTest extends TestWithWallet {
assertEquals(1, request18.tx.getInputs().size());
// Calculate its max length to make sure it is indeed 1001
Transaction spend18 = request18.tx;
int theoreticalMaxLength18 = spend18.unsafeBitcoinSerialize().length + myKey.getPubKey().length + 75;
int theoreticalMaxLength18 = spend18.bitcoinSerialize().length + myKey.getPubKey().length + 75;
for (TransactionInput in : spend18.getInputs())
theoreticalMaxLength18 -= in.getScriptBytes().length;
assertEquals(1001, theoreticalMaxLength18);
// Its actual size must be between 998 and 1000 (inclusive) as signatures have a 3-byte size range (almost always)
assertTrue(spend18.unsafeBitcoinSerialize().length >= 998);
assertTrue(spend18.unsafeBitcoinSerialize().length <= 1001);
assertTrue(spend18.bitcoinSerialize().length >= 998);
assertTrue(spend18.bitcoinSerialize().length <= 1001);
// Now check that it did get a fee since its max size is 1000
assertEquals(25, spend18.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one
@ -2523,7 +2523,7 @@ public class WalletTest extends TestWithWallet {
Coin dustThresholdMinusOne = new TransactionOutput(TESTNET, null, Coin.COIN, OTHER_ADDRESS).getMinNonDustValue().subtract(SATOSHI);
request26.tx.addOutput(CENT.subtract(fee.add(dustThresholdMinusOne)),
OTHER_ADDRESS);
assertTrue(request26.tx.unsafeBitcoinSerialize().length > 1000);
assertTrue(request26.tx.bitcoinSerialize().length > 1000);
request26.feePerKb = SATOSHI;
request26.ensureMinRequiredFee = true;
wallet.completeTx(request26);

View file

@ -181,7 +181,7 @@ public class FilteredBlockAndPartialMerkleTreeTest extends TestWithPeerGroup {
BloomFilter filter = wallet.getBloomFilter(wallet.getKeyChainGroupSize()*2, 0.001, 0xDEADBEEF);
// Compare the serialized bloom filter to a known-good value
assertArrayEquals(ByteUtils.parseHex("0e1b091ca195e45a9164889b6bc46a09000000efbeadde02"), filter.unsafeBitcoinSerialize());
assertArrayEquals(ByteUtils.parseHex("0e1b091ca195e45a9164889b6bc46a09000000efbeadde02"), filter.bitcoinSerialize());
// Create a peer.
peerGroup.start();

View file

@ -652,7 +652,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
InboundMessageQueuer p3 = connectPeer(3);
assertTrue(p3.lastReceivedFilter.contains(key.getPubKey()));
assertTrue(p3.lastReceivedFilter.contains(key.getPubKeyHash()));
assertTrue(p3.lastReceivedFilter.contains(outpoint.unsafeBitcoinSerialize()));
assertTrue(p3.lastReceivedFilter.contains(outpoint.bitcoinSerialize()));
}
@Test

View file

@ -854,9 +854,9 @@ public class PeerTest extends TestWithNetworkConnections {
@Override
public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
// Add some hashes.
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[]{1})));
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[]{2})));
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[]{3})));
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[] { 1 })));
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[] { 2 })));
addItem(new InventoryItem(InventoryItem.Type.TRANSACTION, Sha256Hash.of(new byte[] { 3 })));
// Write out a copy that's truncated in the middle.
ByteArrayOutputStream bos = new ByteArrayOutputStream();

View file

@ -86,7 +86,7 @@ public class TestFeeLevel {
request.feePerKb = feeRateToTest;
request.ensureMinRequiredFee = false;
kit.wallet().completeTx(request);
System.out.println("Size in bytes is " + request.tx.unsafeBitcoinSerialize().length);
System.out.println("Size in bytes is " + request.tx.bitcoinSerialize().length);
System.out.println("TX is " + request.tx);
System.out.println("Waiting for " + kit.peerGroup().getMaxConnections() + " connected peers");
kit.peerGroup().addDisconnectedEventListener((peer, peerCount) -> System.out.println(peerCount +