From b4215e8b0130c3c0854b5618bbea6ca01f13f1cf Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 9 Jul 2012 04:23:27 +0200 Subject: [PATCH] Add a few Storage classes which avoid storing unnecessary data. Specifically, this adds: * StoredTransaction, which avoid having to store the entire transaction when we only need its inputs+outputs. * StoredTransactionOutput, which avoids having to store the entire parentTransaction just to get the hash and index. * TransactionOutputChanges, which is used to store two lists of StoredTransactionOutputs, one for the created set and one for the spent set. * StoredUndoableBlock, which can store either only TransactionOutputChanges or only StoredTransactions so that the block can be more easily connected/disconnected at will. --- .../java/com/google/bitcoin/core/Block.java | 3 +- .../bitcoin/core/NetworkParameters.java | 2 +- .../bitcoin/core/StoredTransaction.java | 112 ++++++++++++++++ .../bitcoin/core/StoredTransactionOutput.java | 124 ++++++++++++++++++ .../bitcoin/core/StoredUndoableBlock.java | 87 ++++++++++++ .../com/google/bitcoin/core/Transaction.java | 16 +++ .../bitcoin/core/TransactionOutput.java | 4 +- .../core/TransactionOutputChanges.java | 38 ++++++ 8 files changed, 382 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/com/google/bitcoin/core/StoredTransaction.java create mode 100644 core/src/main/java/com/google/bitcoin/core/StoredTransactionOutput.java create mode 100644 core/src/main/java/com/google/bitcoin/core/StoredUndoableBlock.java create mode 100644 core/src/main/java/com/google/bitcoin/core/TransactionOutputChanges.java diff --git a/core/src/main/java/com/google/bitcoin/core/Block.java b/core/src/main/java/com/google/bitcoin/core/Block.java index bcb66239..c5add4ce 100644 --- a/core/src/main/java/com/google/bitcoin/core/Block.java +++ b/core/src/main/java/com/google/bitcoin/core/Block.java @@ -840,7 +840,8 @@ public class Block extends Message { // Here we will do things a bit differently so a new address isn't needed every time. We'll put a simple // counter in the scriptSig so every transaction has a different hash. coinbase.addInput(new TransactionInput(params, coinbase, new byte[]{(byte) txCounter++})); - coinbase.addOutput(new TransactionOutput(params, coinbase, Script.createOutputScript(pubKeyTo))); + coinbase.addOutput(new TransactionOutput(params, coinbase, Script.createOutputScript(pubKeyTo), + Utils.toNanoCoins(50, 0))); transactions.add(coinbase); coinbase.setParent(this); coinbase.length = coinbase.bitcoinSerialize().length; diff --git a/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java b/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java index afa45536..68352f57 100644 --- a/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java +++ b/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java @@ -128,7 +128,7 @@ public class NetworkParameters implements Serializable { Script.writeBytes(scriptPubKeyBytes, Hex.decode ("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f")); scriptPubKeyBytes.write(Script.OP_CHECKSIG); - t.addOutput(new TransactionOutput(n, t, scriptPubKeyBytes.toByteArray())); + t.addOutput(new TransactionOutput(n, t, scriptPubKeyBytes.toByteArray(), Utils.toNanoCoins(50, 0))); } catch (Exception e) { // Cannot happen. throw new RuntimeException(e); diff --git a/core/src/main/java/com/google/bitcoin/core/StoredTransaction.java b/core/src/main/java/com/google/bitcoin/core/StoredTransaction.java new file mode 100644 index 00000000..08d78c20 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/core/StoredTransaction.java @@ -0,0 +1,112 @@ +/** + * Copyright 2012 Matt Corallo. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.bitcoin.core; + +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; + +//TODO: Move this to MemoryFullPrunedBlockStore and use a different method for on-disk storage (bitcoin serialization?) +/** + * A StoredTransaction message contains the information necessary to check a transaction later (ie after a reorg). + * It is used to avoid having to store the entire transaction when we only need its inputs+outputs. + * Its only really useful for MemoryFullPrunedBlockStore, and should probably be moved there + */ +public class StoredTransaction implements Serializable { + private static final long serialVersionUID = 6243881368122528323L; + + /** + * A transaction has some value and a script used for authenticating that the redeemer is allowed to spend + * this output. + */ + private List outputs; + private List inputs; + private long version; + private long lockTime; + private Sha256Hash hash; + + public StoredTransaction(Transaction tx, int height) { + inputs = new LinkedList(); + outputs = new LinkedList(); + for (TransactionInput in : tx.getInputs()) + inputs.add(new TransactionInput(in.params, null, in.getScriptBytes(), in.getOutpoint())); + for (TransactionOutput out : tx.getOutputs()) + outputs.add(new StoredTransactionOutput(null, out, height, tx.isCoinBase())); + this.version = tx.getVersion(); + this.lockTime = tx.getLockTime(); + this.hash = tx.getHash(); + } + + /** + * The lits of inputs in this transaction + */ + public List getInputs() { + return inputs; + } + + /** + * The lits of outputs in this transaction + * Note that the hashes of all of these are null + */ + public List getOutputs() { + return outputs; + } + + /** + * The hash of this stored transaction + */ + public Sha256Hash getHash() { + return hash; + } + + /** + * The lockTime of the stored transaction + */ + public long getLockTime() { + return lockTime; + } + + /** + * The version of the stored transaction + */ + public long getVersion() { + return version; + } + + /** + * A coinbase transaction is one that creates a new coin. They are the first transaction in each block and their + * value is determined by a formula that all implementations of BitCoin share. In 2011 the value of a coinbase + * transaction is 50 coins, but in future it will be less. A coinbase transaction is defined not only by its + * position in a block but by the data in the inputs. + */ + public boolean isCoinBase() { + return inputs.get(0).isCoinBase(); + } + + public String toString() { + return "Stored Transaction: " + hash.toString(); + } + + public int hashCode() { + return getHash().hashCode(); + } + + public boolean equals(Object o) { + if (!(o instanceof StoredTransaction)) return false; + return ((StoredTransaction) o).getHash().equals(this.getHash()); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/google/bitcoin/core/StoredTransactionOutput.java b/core/src/main/java/com/google/bitcoin/core/StoredTransactionOutput.java new file mode 100644 index 00000000..47f3c623 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/core/StoredTransactionOutput.java @@ -0,0 +1,124 @@ +/** + * Copyright 2012 Matt Corallo. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.bitcoin.core; + +import java.io.Serializable; +import java.math.BigInteger; + +/** + * A StoredTransactionOutput message contains the information necessary to check a spending transaction. + * It avoids having to store the entire parentTransaction just to get the hash and index. + * Its only really useful for MemoryFullPrunedBlockStore, and should probably be moved there + */ +public class StoredTransactionOutput implements Serializable { + private static final long serialVersionUID = -8744924157056340509L; + + /** + * A transaction output has some value and a script used for authenticating that the redeemer is allowed to spend + * this output. + */ + private BigInteger value; + private byte[] scriptBytes; + + /** Hash of the transaction to which we refer. */ + private Sha256Hash hash; + /** Which output of that transaction we are talking about. */ + private long index; + + /** arbitrary value lower than -{@link NetworkParameters.spendableCoinbaseDepth} + * (not too low to get overflows when we do blockHeight - NONCOINBASE_HEIGHT, though) */ + private static final int NONCOINBASE_HEIGHT = -200; + /** The height of the creating block (for coinbases, NONCOINBASE_HEIGHT otherwise) */ + private int height; + + /** + * Creates a stored transaction output + * @param hash the hash of the containing transaction + * @param index the outpoint + * @param value the value available + * @param height the height this output was created in + * @param scriptBytes + */ + public StoredTransactionOutput(Sha256Hash hash, long index, BigInteger value, int height, boolean isCoinbase, byte[] scriptBytes) { + this.hash = hash; + this.index = index; + this.value = value; + this.height = isCoinbase ? height : NONCOINBASE_HEIGHT; + this.scriptBytes = scriptBytes; + } + + public StoredTransactionOutput(Sha256Hash hash, TransactionOutput out, int height, boolean isCoinbase) { + this.hash = hash; + this.index = out.getIndex(); + this.value = out.getValue(); + this.height = isCoinbase ? height : NONCOINBASE_HEIGHT; + this.scriptBytes = out.getScriptBytes(); + } + + /** + * The value which this Transaction output holds + * @return the value + */ + public BigInteger getValue() { + return value; + } + + /** + * The backing script bytes which can be turned into a Script object. + * @return the scriptBytes + */ + public byte[] getScriptBytes() { + return scriptBytes; + } + + /** + * The hash of the transaction which holds this output + * @return the hash + */ + public Sha256Hash getHash() { + return hash; + } + + /** + * The index of this output in the transaction which holds it + * @return the index + */ + public long getIndex() { + return index; + } + + /** + * Gets the height of the block that created this output (or -1 if this output was not created by a coinbase) + */ + public int getHeight() { + return height; + } + + public String toString() { + return String.format("Stored TxOut of %s (%s:%l)", Utils.bitcoinValueToFriendlyString(value), hash.toString(), index); + } + + public int hashCode() { + return hash.hashCode() + (int)index; + } + + public boolean equals(Object o) { + if (!(o instanceof StoredTransactionOutput)) return false; + return ((StoredTransactionOutput) o).getIndex() == this.getIndex() && + ((StoredTransactionOutput) o).getHash().equals(this.getHash()); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/google/bitcoin/core/StoredUndoableBlock.java b/core/src/main/java/com/google/bitcoin/core/StoredUndoableBlock.java new file mode 100644 index 00000000..18fa9ca0 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/core/StoredUndoableBlock.java @@ -0,0 +1,87 @@ +/** + * Copyright 2011 Google Inc. + * Copyright 2012 Matt Corallo. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.bitcoin.core; + +import java.io.Serializable; +import java.util.List; + +/** + * Contains minimal data neccessary to disconnect/connect the transactions + * in the stored block at will. Can either store the full set of + * transactions (if the inputs for the block have not been tested to work) + * or the set of transaction outputs created/destroyed when the block is + * connected. + */ +public class StoredUndoableBlock implements Serializable { + private static final long serialVersionUID = 5127353027086786117L; + + Sha256Hash blockHash; + + // Only one of either txOutChanges or transactions will be set + private TransactionOutputChanges txOutChanges; + private List transactions; + + public StoredUndoableBlock(Sha256Hash hash, TransactionOutputChanges txOutChanges) { + this.blockHash = hash; + this.transactions = null; + this.txOutChanges = txOutChanges; + } + + public StoredUndoableBlock(Sha256Hash hash, List transactions) { + this.blockHash = hash; + this.txOutChanges = null; + this.transactions = transactions; + } + + /** + * Get the transaction output changes if they have been calculated, otherwise null. + * Only one of this and getTransactions() will return a non-null value. + */ + public TransactionOutputChanges getTxOutChanges() { + return txOutChanges; + } + + /** + * Get the full list of transactions if it is stored, otherwise null. + * Only one of this and getTxOutChanges() will return a non-null value. + */ + public List getTransactions() { + return transactions; + } + + /** + * Get the hash of the represented block + */ + public Sha256Hash getHash() { + return blockHash; + } + + public int hashCode() { + return blockHash.hashCode(); + } + + public boolean equals(Object o) { + if (!(o instanceof StoredUndoableBlock)) return false; + return ((StoredUndoableBlock)o).getHash().equals(this.getHash()); + } + + @Override + public String toString() { + return "Undoable Block " + blockHash.toString(); + } +} diff --git a/core/src/main/java/com/google/bitcoin/core/Transaction.java b/core/src/main/java/com/google/bitcoin/core/Transaction.java index 4e6647b5..9f3acbec 100644 --- a/core/src/main/java/com/google/bitcoin/core/Transaction.java +++ b/core/src/main/java/com/google/bitcoin/core/Transaction.java @@ -136,6 +136,22 @@ public class Transaction extends ChildMessage implements Serializable { throws ProtocolException { super(params, msg, 0, parent, parseLazy, parseRetain, length); } + + /** + * Creates a transaction from the given StoredTransaction + * This is unsafe in that editing some aspects of this transaction may edit the stored transaction. + * Thus, this should only be used if the resulting transaction will only be used in a read-only manner. + */ + Transaction(NetworkParameters params, StoredTransaction tx) { + super(params); + this.version = tx.getVersion(); + this.lockTime = tx.getLockTime(); + this.inputs = new ArrayList(tx.getInputs()); + this.outputs = new ArrayList(tx.getOutputs().size()); + for (StoredTransactionOutput output : tx.getOutputs()) { + this.outputs.add(new TransactionOutput(params, this, output.getScriptBytes(), output.getValue())); + } + } /** * Returns the transaction hash as you see them in the block explorer. diff --git a/core/src/main/java/com/google/bitcoin/core/TransactionOutput.java b/core/src/main/java/com/google/bitcoin/core/TransactionOutput.java index 89d80971..d101e838 100644 --- a/core/src/main/java/com/google/bitcoin/core/TransactionOutput.java +++ b/core/src/main/java/com/google/bitcoin/core/TransactionOutput.java @@ -114,10 +114,10 @@ public class TransactionOutput extends ChildMessage implements Serializable { /** * Used only in creation of the genesis blocks and in unit tests. */ - TransactionOutput(NetworkParameters params, Transaction parent, byte[] scriptBytes) { + TransactionOutput(NetworkParameters params, Transaction parent, byte[] scriptBytes, BigInteger value) { super(params); this.scriptBytes = scriptBytes; - this.value = Utils.toNanoCoins(50, 0); + this.value = value; parentTransaction = parent; availableForSpending = true; } diff --git a/core/src/main/java/com/google/bitcoin/core/TransactionOutputChanges.java b/core/src/main/java/com/google/bitcoin/core/TransactionOutputChanges.java new file mode 100644 index 00000000..df0cc570 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/core/TransactionOutputChanges.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012 Matt Corallo. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.bitcoin.core; + +import java.io.Serializable; +import java.util.List; + +/** + * TransactionOutputChanges is used as a return value for BlockChainBase.connectInputs. + * It contains the full list of transaction outputs created and spent in a block. + * It does contain outputs created that were spent later in the block, as those are needed for + * BIP30 (no duplicate txid creation if the previous one was not fully spent prior to this block) verification. + */ +public class TransactionOutputChanges implements Serializable { + private static final long serialVersionUID = -6169346729324181905L; + + public final List txOutsCreated; + public final List txOutsSpent; + + public TransactionOutputChanges(List txOutsCreated, List txOutsSpent) { + this.txOutsCreated = txOutsCreated; + this.txOutsSpent = txOutsSpent; + } +}