3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-12 02:05:53 +00:00

Migrate old wallets to use transaction confidences, as much as possible, and add a unit test for deserializing old wallets.

This commit is contained in:
Mike Hearn 2012-02-02 16:17:54 +01:00
parent be8d3c3896
commit 7f82613559
5 changed files with 83 additions and 5 deletions

View File

@ -56,7 +56,7 @@ public class Transaction extends ChildMessage implements Serializable {
private long lockTime;
// This is being migrated to appearsInHashes
// This is being migrated to appearsInHashes. It's set to null after migration.
Set<StoredBlock> appearsIn;
// Stored only in Java serialization. This is either the time the transaction was broadcast as measured from the

View File

@ -320,6 +320,9 @@ public class TransactionConfidence implements Serializable {
/**
* If this transaction has been overridden by a double spend (is dead), this call returns the overriding transaction.
* Note that this call <b>can return null</b> if you have migrated an old wallet, as pre-Jan 2012 wallets did not
* store this information.
*
* @return the transaction that double spent this one
* @throws IllegalStateException if confidence type is not OVERRIDDEN_BY_DOUBLE_SPEND.
*/

View File

@ -137,6 +137,11 @@ public class Wallet implements Serializable {
private final NetworkParameters params;
// Primitive kind of versioning protocol that does not break serializability. If this is true it means the
// Transaction objects in this wallet have confidence objects. If false (the default for old wallets missing
// this field) then we need to migrate.
private boolean hasTransactionConfidences;
transient private ArrayList<WalletEventListener> eventListeners;
/**
@ -152,6 +157,7 @@ public class Wallet implements Serializable {
pending = new HashMap<Sha256Hash, Transaction>();
dead = new HashMap<Sha256Hash, Transaction>();
eventListeners = new ArrayList<WalletEventListener>();
hasTransactionConfidences = true;
}
public NetworkParameters getNetworkParameters() {
@ -178,7 +184,7 @@ public class Wallet implements Serializable {
/**
* Uses Java serialization to save the wallet to the given file stream.
*/
public synchronized void saveToFileStream(FileOutputStream f) throws IOException {
public synchronized void saveToFileStream(OutputStream f) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(f);
oos.writeObject(this);
oos.close();
@ -208,9 +214,9 @@ public class Wallet implements Serializable {
}
/**
* Returns a wallet deserialized from the given file input stream.
* Returns a wallet deserialized from the given input stream.
*/
public static Wallet loadFromFileStream(FileInputStream f) throws IOException {
public static Wallet loadFromFileStream(InputStream f) throws IOException {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(f);
@ -225,6 +231,44 @@ public class Wallet implements Serializable {
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
eventListeners = new ArrayList<WalletEventListener>();
maybeMigrateToTransactionConfidences();
}
/** Migrate old wallets that don't have any tx confidences, filling out whatever information we can. */
private void maybeMigrateToTransactionConfidences() {
if (hasTransactionConfidences) return;
// We can't fill out tx confidence objects exactly, we don't have enough data to do that. But we do the
// best we can.
List<Transaction> transactions = new LinkedList<Transaction>();
transactions.addAll(unspent.values());
transactions.addAll(spent.values());
for (Transaction tx : transactions) {
TransactionConfidence confidence = tx.getConfidence();
confidence.setConfidenceType(TransactionConfidence.ConfidenceType.BUILDING);
Set<StoredBlock> appearsIn = tx.appearsIn;
// appearsIn is being migrated away from, in favor of just storing the hashes instead of full blocks.
// TODO: Clear this code out once old wallets fade away.
if (appearsIn != null) {
int minHeight = Integer.MAX_VALUE;
for (StoredBlock block : appearsIn) {
minHeight = Math.min(minHeight, block.getHeight());
}
confidence.setAppearedAtChainHeight(minHeight);
}
}
for (Transaction tx : pending.values()) {
tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
}
for (Transaction tx : inactive.values()) {
tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_IN_BEST_CHAIN);
}
for (Transaction tx : dead.values()) {
tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.OVERRIDDEN_BY_DOUBLE_SPEND);
// We'd ideally like to set overridingTransaction here, but old wallets don't have that data.
// Dead transactions in the wallet should be rare, so API users will just have to handle this
// edge case until old wallets have gone away.
}
hasTransactionConfidences = true;
}
/**

View File

@ -22,9 +22,13 @@ import com.google.bitcoin.utils.BriefLogFormatter;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.google.bitcoin.core.TestUtils.createFakeBlock;
import static com.google.bitcoin.core.TestUtils.createFakeTx;
@ -520,5 +524,32 @@ public class WalletTest {
assertNull(tx1.appearsIn);
}
@Test
public void oldWalletsDeserialize() throws Exception {
// Check that the Wallet class fills out tx confidences as best it can when loading old wallets. The new
// API provides a superset of the info that used to be available so it's impossible to do a complete
// migration but we can do some.
//
// TODO: This test does not check migration of dead or pending transactions.
InputStream stream = getClass().getResourceAsStream("old1.wallet");
wallet = Wallet.loadFromFileStream(stream);
Set<Transaction> transactions = wallet.getTransactions(true, true);
assertEquals(91, transactions.size());
Transaction tx = wallet.unspent.get(new Sha256Hash("5649c63ad55002ce2f39d1d4744996ebaccc1d15e0491c9e8d60eb3720dabebd"));
assertEquals(tx.getAppearsInHashes().iterator().next(), new Sha256Hash("00000000019380f5aef28393827737f55a1cf8abb51a36d46ab6f2db0a5b9cb8"));
assertEquals(TransactionConfidence.ConfidenceType.BUILDING, tx.getConfidence().getConfidenceType());
assertEquals(42814, tx.getConfidence().getAppearedAtChainHeight());
// Re-serialize the wallet. Make sure it's all still there.
ByteArrayOutputStream bios = new ByteArrayOutputStream();
wallet.saveToFileStream(bios);
wallet = Wallet.loadFromFileStream(new ByteArrayInputStream(bios.toByteArray()));
assertEquals(91, transactions.size());
tx = wallet.unspent.get(new Sha256Hash("5649c63ad55002ce2f39d1d4744996ebaccc1d15e0491c9e8d60eb3720dabebd"));
assertEquals(tx.getAppearsInHashes().iterator().next(), new Sha256Hash("00000000019380f5aef28393827737f55a1cf8abb51a36d46ab6f2db0a5b9cb8"));
assertEquals(TransactionConfidence.ConfidenceType.BUILDING, tx.getConfidence().getConfidenceType());
assertEquals(42814, tx.getConfidence().getAppearedAtChainHeight());
}
// Support for offline spending is tested in PeerGroupTest
}

Binary file not shown.