3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 06:44:16 +00:00

Refactor some Stored* classes that are worthless...

Specifically, this moves StoredTransaction to
MemoryFullPrunedBlockStore and uses custom serialization for
StoredTransactionOutput.
This commit is contained in:
Matt Corallo 2012-10-18 18:57:12 -04:00 committed by Mike Hearn
parent 91cd289a5c
commit a036b68aa2
8 changed files with 222 additions and 161 deletions

View File

@ -84,10 +84,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
protected StoredBlock addToBlockStore(StoredBlock storedPrev, Block block)
throws BlockStoreException, VerificationException {
StoredBlock newBlock = storedPrev.build(block);
LinkedList<StoredTransaction> transactions = new LinkedList<StoredTransaction>();
for (Transaction tx : block.transactions)
transactions.add(new StoredTransaction(tx, newBlock.getHeight()));
blockStore.put(newBlock, new StoredUndoableBlock(newBlock.getHeader().getHash(), transactions));
blockStore.put(newBlock, new StoredUndoableBlock(newBlock.getHeader().getHash(), block.transactions));
return newBlock;
}
@ -287,14 +284,14 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
}
TransactionOutputChanges txOutChanges;
try {
List<StoredTransaction> transactions = block.getTransactions();
List<Transaction> transactions = block.getTransactions();
if (transactions != null) {
LinkedList<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>();
LinkedList<StoredTransactionOutput> txOutsCreated = new LinkedList<StoredTransactionOutput>();
long sigOps = 0;
final boolean enforcePayToScriptHash = newBlock.getHeader().getTimeSeconds() >= params.BIP16_ENFORCE_TIME;
if (!params.isCheckpoint(newBlock.getHeight())) {
for(StoredTransaction tx : transactions) {
for(Transaction tx : transactions) {
Sha256Hash hash = tx.getHash();
if (blockStore.hasUnspentOutputs(hash, tx.getOutputs().size()))
throw new VerificationException("Block failed BIP30 test!");
@ -306,7 +303,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
if (scriptVerificationExecutor.isShutdown())
scriptVerificationExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
List<Future<VerificationException>> listScriptVerificationResults = new ArrayList<Future<VerificationException>>(transactions.size());
for(final StoredTransaction tx : transactions) {
for(final Transaction tx : transactions) {
boolean isCoinBase = tx.isCoinBase();
BigInteger valueIn = BigInteger.ZERO;
BigInteger valueOut = BigInteger.ZERO;
@ -340,7 +337,6 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
// TODO: Find out the underlying issue and create a better work-around
// TODO: Thoroughly test that this fixes the issue like the non-StoredBlock version does
final int currentIndex = index;
final Transaction txCache = new Transaction(params, tx);
final Script scriptSig;
try {
scriptSig = in.getScriptSig();
@ -356,7 +352,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
FutureTask<VerificationException> future = new FutureTask<VerificationException>(new Callable<VerificationException>() {
public VerificationException call() {
try{
scriptSig.correctlySpends(txCache, currentIndex, scriptPubKey, enforcePayToScriptHash);
scriptSig.correctlySpends(tx, currentIndex, scriptPubKey, enforcePayToScriptHash);
} catch (ScriptException e) {
return new VerificationException("Error verifying script: " + e.getMessage());
} catch (VerificationException e) {
@ -372,7 +368,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
}
}
Sha256Hash hash = tx.getHash();
for (StoredTransactionOutput out : tx.getOutputs()) {
for (TransactionOutput out : tx.getOutputs()) {
valueOut = valueOut.add(out.getValue());
StoredTransactionOutput newOut = new StoredTransactionOutput(hash, out.getIndex(), out.getValue(),
newBlock.getHeight(), isCoinBase,

View File

@ -1,112 +0,0 @@
/**
* 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<StoredTransactionOutput> outputs;
private List<TransactionInput> inputs;
private long version;
private long lockTime;
private Sha256Hash hash;
public StoredTransaction(Transaction tx, int height) {
inputs = new LinkedList<TransactionInput>();
outputs = new LinkedList<StoredTransactionOutput>();
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<TransactionInput> getInputs() {
return inputs;
}
/**
* The lits of outputs in this transaction
* Note that the hashes of all of these are null
*/
public List<StoredTransactionOutput> 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());
}
}

View File

@ -16,6 +16,10 @@
package com.google.bitcoin.core;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.math.BigInteger;
@ -69,6 +73,35 @@ public class StoredTransactionOutput implements Serializable {
this.scriptBytes = out.getScriptBytes();
}
public StoredTransactionOutput(InputStream in) throws IOException {
byte[] valueBytes = new byte[8];
in.read(valueBytes, 0, 8);
value = BigInteger.valueOf(Utils.readInt64(valueBytes, 0));
int scriptBytesLength = ((in.read() & 0xFF) << 0) |
((in.read() & 0xFF) << 8) |
((in.read() & 0xFF) << 16) |
((in.read() & 0xFF) << 24);
scriptBytes = new byte[scriptBytesLength];
if (in.read(scriptBytes) != scriptBytesLength)
throw new EOFException();
byte[] hashBytes = new byte[32];
if (in.read(hashBytes) != 32)
throw new EOFException();
hash = new Sha256Hash(hashBytes);
byte[] indexBytes = new byte[4];
if (in.read(indexBytes) != 4)
throw new EOFException();
index = Utils.readUint32(indexBytes, 0);
height = ((in.read() & 0xFF) << 0) |
((in.read() & 0xFF) << 8) |
((in.read() & 0xFF) << 16) |
((in.read() & 0xFF) << 24);
}
/**
* The value which this Transaction output holds
* @return the value
@ -121,4 +154,22 @@ public class StoredTransactionOutput implements Serializable {
return ((StoredTransactionOutput) o).getIndex() == this.getIndex() &&
((StoredTransactionOutput) o).getHash().equals(this.getHash());
}
public void serializeToStream(OutputStream bos) throws IOException {
Utils.uint64ToByteStreamLE(value, bos);
bos.write((int) (0xFF & (scriptBytes.length >> 0)));
bos.write((int) (0xFF & (scriptBytes.length >> 8)));
bos.write((int) (0xFF & (scriptBytes.length >> 16)));
bos.write((int) (0xFF & (scriptBytes.length >> 24)));
bos.write(scriptBytes);
bos.write(hash.getBytes());
Utils.uint32ToByteStreamLE(index, bos);
bos.write((int) (0xFF & (height >> 0)));
bos.write((int) (0xFF & (height >> 8)));
bos.write((int) (0xFF & (height >> 16)));
bos.write((int) (0xFF & (height >> 24)));
}
}

View File

@ -34,7 +34,7 @@ public class StoredUndoableBlock implements Serializable {
// Only one of either txOutChanges or transactions will be set
private TransactionOutputChanges txOutChanges;
private List<StoredTransaction> transactions;
private List<Transaction> transactions;
public StoredUndoableBlock(Sha256Hash hash, TransactionOutputChanges txOutChanges) {
this.blockHash = hash;
@ -42,7 +42,7 @@ public class StoredUndoableBlock implements Serializable {
this.txOutChanges = txOutChanges;
}
public StoredUndoableBlock(Sha256Hash hash, List<StoredTransaction> transactions) {
public StoredUndoableBlock(Sha256Hash hash, List<Transaction> transactions) {
this.blockHash = hash;
this.txOutChanges = null;
this.transactions = transactions;
@ -60,7 +60,7 @@ public class StoredUndoableBlock implements Serializable {
* 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<StoredTransaction> getTransactions() {
public List<Transaction> getTransactions() {
return transactions;
}

View File

@ -139,22 +139,6 @@ 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<TransactionInput>(tx.getInputs());
this.outputs = new ArrayList<TransactionOutput>(tx.getOutputs().size());
for (StoredTransactionOutput output : tx.getOutputs()) {
this.outputs.add(new TransactionOutput(params, this, output.getValue(), output.getScriptBytes()));
}
}
/**
* Returns the transaction hash as you see them in the block explorer.

View File

@ -16,7 +16,10 @@
package com.google.bitcoin.core;
import java.io.Serializable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
/**
@ -25,9 +28,7 @@ import java.util.List;
* 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 class TransactionOutputChanges {
public final List<StoredTransactionOutput> txOutsCreated;
public final List<StoredTransactionOutput> txOutsSpent;
@ -35,4 +36,42 @@ public class TransactionOutputChanges implements Serializable {
this.txOutsCreated = txOutsCreated;
this.txOutsSpent = txOutsSpent;
}
public TransactionOutputChanges(InputStream in) throws IOException {
int numOutsCreated = ((in.read() & 0xFF) << 0) |
((in.read() & 0xFF) << 8) |
((in.read() & 0xFF) << 16) |
((in.read() & 0xFF) << 24);
txOutsCreated = new LinkedList<StoredTransactionOutput>();
for (int i = 0; i < numOutsCreated; i++)
txOutsCreated.add(new StoredTransactionOutput(in));
int numOutsSpent = ((in.read() & 0xFF) << 0) |
((in.read() & 0xFF) << 8) |
((in.read() & 0xFF) << 16) |
((in.read() & 0xFF) << 24);
txOutsSpent = new LinkedList<StoredTransactionOutput>();
for (int i = 0; i < numOutsSpent; i++)
txOutsSpent.add(new StoredTransactionOutput(in));
}
public void serializeToStream(OutputStream bos) throws IOException {
int numOutsCreated = txOutsCreated.size();
bos.write((int) (0xFF & (numOutsCreated >> 0)));
bos.write((int) (0xFF & (numOutsCreated >> 8)));
bos.write((int) (0xFF & (numOutsCreated >> 16)));
bos.write((int) (0xFF & (numOutsCreated >> 24)));
for (StoredTransactionOutput output : txOutsCreated) {
output.serializeToStream(bos);
}
int numOutsSpent = txOutsSpent.size();
bos.write((int) (0xFF & (numOutsSpent >> 0)));
bos.write((int) (0xFF & (numOutsSpent >> 8)));
bos.write((int) (0xFF & (numOutsSpent >> 16)));
bos.write((int) (0xFF & (numOutsSpent >> 24)));
for (StoredTransactionOutput output : txOutsSpent) {
output.serializeToStream(bos);
}
}
}

View File

@ -18,10 +18,13 @@ package com.google.bitcoin.store;
import com.google.bitcoin.core.*;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.sql.*;
import java.util.LinkedList;
@ -199,7 +202,7 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
StoredBlock storedGenesisHeader = new StoredBlock(params.genesisBlock.cloneAsHeader(), params.genesisBlock.getWork(), 0);
// The coinbase in the genesis block is not spendable. This is because of how the reference client inits
// its database - the genesis transaction isn't actually in the db so its spent flags can never be updated.
List<StoredTransaction> genesisTransactions = Lists.newLinkedList();
List<Transaction> genesisTransactions = Lists.newLinkedList();
StoredUndoableBlock storedGenesis = new StoredUndoableBlock(params.genesisBlock.getHash(), genesisTransactions);
put(storedGenesisHeader, storedGenesis);
setChainHead(storedGenesisHeader);
@ -339,15 +342,19 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
byte[] txOutChanges = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = new ObjectOutputStream(bos);
if (undoableBlock.getTxOutChanges() != null) {
out.writeObject(undoableBlock.getTxOutChanges());
undoableBlock.getTxOutChanges().serializeToStream(bos);
txOutChanges = bos.toByteArray();
} else {
out.writeObject(undoableBlock.getTransactions());
int numTxn = undoableBlock.getTransactions().size();
bos.write((int) (0xFF & (numTxn >> 0)));
bos.write((int) (0xFF & (numTxn >> 8)));
bos.write((int) (0xFF & (numTxn >> 16)));
bos.write((int) (0xFF & (numTxn >> 24)));
for (Transaction tx : undoableBlock.getTransactions())
tx.bitcoinSerialize(bos);
transactions = bos.toByteArray();
}
out.close();
bos.close();
} catch (IOException e) {
throw new BlockStoreException(e);
@ -436,7 +443,6 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
}
}
@SuppressWarnings("unchecked")
public StoredUndoableBlock getUndoBlock(Sha256Hash hash) throws BlockStoreException {
maybeConnect();
PreparedStatement s = null;
@ -456,14 +462,22 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
byte[] transactions = results.getBytes(2);
StoredUndoableBlock block;
if (txOutChanges == null) {
Object transactionsObject = new ObjectInputStream(new ByteArrayInputStream(transactions)).readObject();
if (!(((List<?>) transactionsObject).get(0) instanceof StoredTransaction))
throw new BlockStoreException("Corrupted StoredUndoableBlock");
block = new StoredUndoableBlock(hash, (List<StoredTransaction>) transactionsObject);
int offset = 0;
int numTxn = ((transactions[offset++] & 0xFF) << 0) |
((transactions[offset++] & 0xFF) << 8) |
((transactions[offset++] & 0xFF) << 16) |
((transactions[offset++] & 0xFF) << 24);
List<Transaction> transactionList = new LinkedList<Transaction>();
for (int i = 0; i < numTxn; i++) {
Transaction tx = new Transaction(params, transactions, offset);
transactionList.add(tx);
offset += tx.getMessageSize();
}
block = new StoredUndoableBlock(hash, transactionList);
} else {
ObjectInputStream obj = new ObjectInputStream(new ByteArrayInputStream(txOutChanges));
Object transactionsObject = obj.readObject();
block = new StoredUndoableBlock(hash, (TransactionOutputChanges) transactionsObject);
TransactionOutputChanges outChangesObject =
new TransactionOutputChanges(new ByteArrayInputStream(txOutChanges));
block = new StoredUndoableBlock(hash, outChangesObject);
}
return block;
} catch (SQLException ex) {
@ -474,7 +488,7 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
} catch (ClassCastException e) {
// Corrupted database.
throw new BlockStoreException(e);
} catch (ClassNotFoundException e) {
} catch (ProtocolException e) {
// Corrupted database.
throw new BlockStoreException(e);
} catch (IOException e) {

View File

@ -25,6 +25,95 @@ import java.io.Serializable;
import java.util.*;
/**
* 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.
*/
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<StoredTransactionOutput> outputs;
private List<TransactionInput> inputs;
private long version;
private long lockTime;
private Sha256Hash hash;
public StoredTransaction(NetworkParameters params, Transaction tx, int height) {
inputs = new LinkedList<TransactionInput>();
outputs = new LinkedList<StoredTransactionOutput>();
for (TransactionInput in : tx.getInputs())
inputs.add(new TransactionInput(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<TransactionInput> getInputs() {
return inputs;
}
/**
* The lits of outputs in this transaction
* Note that the hashes of all of these are null
*/
public List<StoredTransactionOutput> 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());
}
}
/**
* Used as a key for memory map (to avoid having to think about NetworkParameters,
* which is required for {@link TransactionOutPoint}
@ -240,7 +329,7 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
try {
StoredBlock storedGenesisHeader = new StoredBlock(params.genesisBlock.cloneAsHeader(), params.genesisBlock.getWork(), 0);
// The coinbase in the genesis block is not spendable
List<StoredTransaction> genesisTransactions = Lists.newLinkedList();
List<Transaction> genesisTransactions = Lists.newLinkedList();
StoredUndoableBlock storedGenesis = new StoredUndoableBlock(params.genesisBlock.getHash(), genesisTransactions);
put(storedGenesisHeader, storedGenesis);
setChainHead(storedGenesisHeader);