3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-31 07:12:17 +00:00

coinbase-tx phase1 complete (add blockheight to wallet and protobuf)

This commit is contained in:
Jim Burton 2012-04-03 18:43:46 +01:00
parent 23a960e0f4
commit 6c31abd698
7 changed files with 177 additions and 18 deletions

View File

@ -144,6 +144,11 @@ public class Wallet implements Serializable {
// this field) then we need to migrate. // this field) then we need to migrate.
private boolean hasTransactionConfidences; private boolean hasTransactionConfidences;
/**
* The hash of the last block seen on the best chain
*/
private Sha256Hash lastBlockSeenHash;
transient private ArrayList<WalletEventListener> eventListeners; transient private ArrayList<WalletEventListener> eventListeners;
/** /**
@ -533,6 +538,18 @@ public class Wallet implements Serializable {
log.info("Balance is now: " + bitcoinValueToFriendlyString(getBalance())); log.info("Balance is now: " + bitcoinValueToFriendlyString(getBalance()));
// Store the block hash
if (bestChain) {
if (block != null && block.getHeader() != null) {
// Check to see if this block has been seen before
Sha256Hash newBlockHash = block.getHeader().getHash();
if (!newBlockHash.equals(getLastBlockSeenHash())) {
// new hash
setLastBlockSeenHash(newBlockHash);
}
}
}
// WARNING: The code beyond this point can trigger event listeners on transaction confidence objects, which are // WARNING: The code beyond this point can trigger event listeners on transaction confidence objects, which are
// in turn allowed to re-enter the Wallet. This means we cannot assume anything about the state of the wallet // in turn allowed to re-enter the Wallet. This means we cannot assume anything about the state of the wallet
// from now on. The balance just received may already be spent. // from now on. The balance just received may already be spent.
@ -1640,4 +1657,12 @@ public class Wallet implements Serializable {
} }
return peerEventListener; return peerEventListener;
} }
public Sha256Hash getLastBlockSeenHash() {
return lastBlockSeenHash;
}
public void setLastBlockSeenHash(Sha256Hash lastBlockSeenHash) {
this.lastBlockSeenHash = lastBlockSeenHash;
}
} }

View File

@ -90,9 +90,7 @@ public class WalletProtobufSerializer {
*/ */
public static Protos.Wallet walletToProto(Wallet wallet) { public static Protos.Wallet walletToProto(Wallet wallet) {
Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder(); Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder();
walletBuilder.setNetworkIdentifier(wallet.getNetworkParameters().getId()) walletBuilder.setNetworkIdentifier(wallet.getNetworkParameters().getId());
//.setLastSeenBlockHash(null) // TODO
;
for (WalletTransaction wtx : wallet.getWalletTransactions()) { for (WalletTransaction wtx : wallet.getWalletTransactions()) {
Protos.Transaction txProto = makeTxProto(wtx); Protos.Transaction txProto = makeTxProto(wtx);
walletBuilder.addTransaction(txProto); walletBuilder.addTransaction(txProto);
@ -110,6 +108,12 @@ public class WalletProtobufSerializer {
buf.setPublicKey(ByteString.copyFrom(key.getPubKey())); buf.setPublicKey(ByteString.copyFrom(key.getPubKey()));
walletBuilder.addKey(buf); walletBuilder.addKey(buf);
} }
Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash();
if (lastSeenBlockHash != null) {
walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash));
}
return walletBuilder.build(); return walletBuilder.build();
} }
@ -238,6 +242,13 @@ public class WalletProtobufSerializer {
wallet.addWalletTransaction(wtx); wallet.addWalletTransaction(wtx);
} }
// Update the lastBlockSeenHash.
if (!walletProto.hasLastSeenBlockHash()) {
wallet.setLastBlockSeenHash(null);
} else {
wallet.setLastBlockSeenHash(byteStringToHash(walletProto.getLastSeenBlockHash()));
}
for (Protos.Extension extProto : walletProto.getExtensionList()) { for (Protos.Extension extProto : walletProto.getExtensionList()) {
if (extProto.getMandatory()) { if (extProto.getMandatory()) {
throw new IllegalArgumentException("Did not understand a mandatory extension in the wallet"); throw new IllegalArgumentException("Did not understand a mandatory extension in the wallet");

View File

@ -2323,7 +2323,7 @@ public final class Protos {
boolean hasHash(); boolean hasHash();
com.google.protobuf.ByteString getHash(); com.google.protobuf.ByteString getHash();
// required .wallet.Transaction.Pool pool = 3; // optional .wallet.Transaction.Pool pool = 3;
boolean hasPool(); boolean hasPool();
org.bitcoinj.wallet.Protos.Transaction.Pool getPool(); org.bitcoinj.wallet.Protos.Transaction.Pool getPool();
@ -2495,7 +2495,7 @@ public final class Protos {
return hash_; return hash_;
} }
// required .wallet.Transaction.Pool pool = 3; // optional .wallet.Transaction.Pool pool = 3;
public static final int POOL_FIELD_NUMBER = 3; public static final int POOL_FIELD_NUMBER = 3;
private org.bitcoinj.wallet.Protos.Transaction.Pool pool_; private org.bitcoinj.wallet.Protos.Transaction.Pool pool_;
public boolean hasPool() { public boolean hasPool() {
@ -2618,10 +2618,6 @@ public final class Protos {
memoizedIsInitialized = 0; memoizedIsInitialized = 0;
return false; return false;
} }
if (!hasPool()) {
memoizedIsInitialized = 0;
return false;
}
for (int i = 0; i < getTransactionInputCount(); i++) { for (int i = 0; i < getTransactionInputCount(); i++) {
if (!getTransactionInput(i).isInitialized()) { if (!getTransactionInput(i).isInitialized()) {
memoizedIsInitialized = 0; memoizedIsInitialized = 0;
@ -3073,10 +3069,6 @@ public final class Protos {
return false; return false;
} }
if (!hasPool()) {
return false;
}
for (int i = 0; i < getTransactionInputCount(); i++) { for (int i = 0; i < getTransactionInputCount(); i++) {
if (!getTransactionInput(i).isInitialized()) { if (!getTransactionInput(i).isInitialized()) {
@ -3223,7 +3215,7 @@ public final class Protos {
return this; return this;
} }
// required .wallet.Transaction.Pool pool = 3; // optional .wallet.Transaction.Pool pool = 3;
private org.bitcoinj.wallet.Protos.Transaction.Pool pool_ = org.bitcoinj.wallet.Protos.Transaction.Pool.UNSPENT; private org.bitcoinj.wallet.Protos.Transaction.Pool pool_ = org.bitcoinj.wallet.Protos.Transaction.Pool.UNSPENT;
public boolean hasPool() { public boolean hasPool() {
return ((bitField0_ & 0x00000004) == 0x00000004); return ((bitField0_ & 0x00000004) == 0x00000004);
@ -5689,7 +5681,7 @@ public final class Protos {
"G\020\001\022\025\n\021NOT_SEEN_IN_CHAIN\020\002\022\025\n\021NOT_IN_BES" + "G\020\001\022\025\n\021NOT_SEEN_IN_CHAIN\020\002\022\025\n\021NOT_IN_BES" +
"T_CHAIN\020\003\022\036\n\032OVERRIDDEN_BY_DOUBLE_SPEND\020" + "T_CHAIN\020\003\022\036\n\032OVERRIDDEN_BY_DOUBLE_SPEND\020" +
"\004\"\211\003\n\013Transaction\022\017\n\007version\030\001 \002(\005\022\014\n\004ha" + "\004\"\211\003\n\013Transaction\022\017\n\007version\030\001 \002(\005\022\014\n\004ha" +
"sh\030\002 \002(\014\022&\n\004pool\030\003 \002(\0162\030.wallet.Transact" + "sh\030\002 \002(\014\022&\n\004pool\030\003 \001(\0162\030.wallet.Transact" +
"ion.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdated_a", "ion.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdated_a",
"t\030\005 \001(\003\0223\n\021transaction_input\030\006 \003(\0132\030.wal" + "t\030\005 \001(\003\0223\n\021transaction_input\030\006 \003(\0132\030.wal" +
"let.TransactionInput\0225\n\022transaction_outp" + "let.TransactionInput\0225\n\022transaction_outp" +

View File

@ -31,7 +31,7 @@ import static org.junit.Assert.*;
public class BlockTest { public class BlockTest {
static final NetworkParameters params = NetworkParameters.testNet(); static final NetworkParameters params = NetworkParameters.testNet();
static final byte[] blockBytes; public static final byte[] blockBytes;
static { static {
// Block 00000000a6e5eb79dcec11897af55e90cd571a4335383a3ccfbc12ec81085935 // Block 00000000a6e5eb79dcec11897af55e90cd571a4335383a3ccfbc12ec81085935

View File

@ -19,10 +19,13 @@ package com.google.bitcoin.core;
import com.google.bitcoin.store.BlockStore; import com.google.bitcoin.store.BlockStore;
import com.google.bitcoin.store.BlockStoreException; import com.google.bitcoin.store.BlockStoreException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
public class TestUtils { public class TestUtils {
public static Transaction createFakeTx(NetworkParameters params, BigInteger nanocoins, Address to) { public static Transaction createFakeTx(NetworkParameters params, BigInteger nanocoins, Address to) throws IOException, ProtocolException {
// Create a fake TX of sufficient realism to exercise the unit tests. Two outputs, one to us, one to somewhere // Create a fake TX of sufficient realism to exercise the unit tests. Two outputs, one to us, one to somewhere
// else to simulate change. // else to simulate change.
Transaction t = new Transaction(params); Transaction t = new Transaction(params);
@ -38,7 +41,51 @@ public class TestUtils {
prevTx.addOutput(prevOut); prevTx.addOutput(prevOut);
// Connect it. // Connect it.
t.addInput(prevOut); t.addInput(prevOut);
return t;
// roundtrip tx
return roundTripTransaction(params, t);
}
/**
* @return Transaction[] Transaction[0] is a feeder transaction, supplying BTC to Transaction[1]
*/
public static Transaction[] createFakeTx(NetworkParameters params, BigInteger nanocoins, Address to, Address from) throws IOException, ProtocolException {
// Create fake TXes of sufficient realism to exercise the unit tests. This transaction send BTC from the from address, to the to address
// with to one to somewhere else to simulate change.
Transaction t = new Transaction(params);
TransactionOutput outputToMe = new TransactionOutput(params, t, nanocoins, to);
t.addOutput(outputToMe);
TransactionOutput change = new TransactionOutput(params, t, Utils.toNanoCoins(1, 11), new ECKey().toAddress(params));
t.addOutput(change);
// Make a feeder tx that sends to the from address specified. This feeder tx is not really valid but it doesn't
// matter for our purposes.
Transaction feederTx = new Transaction(params);
TransactionOutput feederOut = new TransactionOutput(params, feederTx, nanocoins, from);
feederTx.addOutput(feederOut);
// make a previous tx that sends from the feeder to the from address
Transaction prevTx = new Transaction(params);
TransactionOutput prevOut = new TransactionOutput(params, prevTx, nanocoins, to);
prevTx.addOutput(prevOut);
// Connect up the txes
prevTx.addInput(feederOut);
t.addInput(prevOut);
// roundtrip the tx so that they are just like they would be from the wire
return new Transaction[]{roundTripTransaction(params, prevTx), roundTripTransaction(params,t)};
}
/**
* Roundtrip a transaction so that it appears as if it has just come from the wire
*/
private static Transaction roundTripTransaction(NetworkParameters params, Transaction tx) throws IOException, ProtocolException {
BitcoinSerializer bs = new BitcoinSerializer(params, true, null);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bs.serialize(tx, bos);
return (Transaction)bs.deserialize(new ByteArrayInputStream(bos.toByteArray()));
} }
public static class DoubleSpends { public static class DoubleSpends {

View File

@ -656,5 +656,61 @@ public class WalletTest {
} }
@Test
public void spendToSameWallet() throws Exception {
// Test that a spend to the same wallet is dealt with correctly.
// It should appear in the wallet and confirm.
// This is a bit of a silly thing to do in the real world as all it does is burn a fee but it is perfectly valid.
BigInteger coin1 = Utils.toNanoCoins(1, 0);
BigInteger coinHalf = Utils.toNanoCoins(0, 50);
// Start by giving us 1 coin.
Transaction inbound1 = createFakeTx(params, coin1, myAddress);
wallet.receiveFromBlock(inbound1, null, BlockChain.NewBlockType.BEST_CHAIN);
// Send half to ourselves. We should then have a balance available to spend of zero.
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
Transaction outbound1 = wallet.createSend(myAddress, coinHalf);
wallet.commitTx(outbound1);
// We should have a zero available balance before the next block.
assertEquals(BigInteger.ZERO, wallet.getBalance());
wallet.receiveFromBlock(outbound1, null, BlockChain.NewBlockType.BEST_CHAIN);
// We should have a balance of 1 BTC after the block is received.
assertEquals(coin1, wallet.getBalance());
}
@Test
public void rememberLastBlockSeenHash() throws Exception {
BigInteger v1 = toNanoCoins(5, 0);
BigInteger v2 = toNanoCoins(0, 50);
BigInteger v3 = toNanoCoins(0, 25);
Transaction t1 = createFakeTx(params, v1, myAddress);
Transaction t2 = createFakeTx(params, v2, myAddress);
Transaction t3 = createFakeTx(params, v3, myAddress);
StoredBlock b1 = createFakeBlock(params, blockStore, t1).storedBlock;
// TODO for Mike - b2 gets sent to side chain but b1, b2, b3 are all chained together - unrealistic.
StoredBlock b2 = createFakeBlock(params, blockStore, t2).storedBlock;
StoredBlock b3 = createFakeBlock(params, blockStore, t3).storedBlock;
// Receive a block on the best chain - this should set the last block seen hash.
wallet.receiveFromBlock(t1, b1, BlockChain.NewBlockType.BEST_CHAIN);
assertEquals(b1.getHeader().getHash(), wallet.getLastBlockSeenHash());
// Receive a block on the side chain - this should not change the last block seen hash.
wallet.receiveFromBlock(t2, b2, BlockChain.NewBlockType.SIDE_CHAIN);
assertEquals(b1.getHeader().getHash(), wallet.getLastBlockSeenHash());
// Receive block 3 on the best chain - this should change the last block seen hash.
wallet.receiveFromBlock(t3, b3, BlockChain.NewBlockType.BEST_CHAIN);
assertEquals(b3.getHeader().getHash(), wallet.getLastBlockSeenHash());
}
// Support for offline spending is tested in PeerGroupTest // Support for offline spending is tested in PeerGroupTest
} }

View File

@ -4,6 +4,8 @@ package com.google.bitcoin.store;
import com.google.bitcoin.core.*; import com.google.bitcoin.core.*;
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType; import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
import com.google.bitcoin.utils.BriefLogFormatter; import com.google.bitcoin.utils.BriefLogFormatter;
import com.google.protobuf.ByteString;
import org.bitcoinj.wallet.Protos; import org.bitcoinj.wallet.Protos;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -108,6 +110,32 @@ public class WalletProtobufSerializerTest {
} }
} }
@Test
public void testLastBlockSeenHash() throws Exception {
// Test the lastBlockSeenHash field works.
// LastBlockSeenHash should be empty if never set.
wallet = new Wallet(params);
Protos.Wallet walletProto = WalletProtobufSerializer.walletToProto(wallet);
ByteString lastSeenBlockHash = walletProto.getLastSeenBlockHash();
assertTrue(lastSeenBlockHash.isEmpty());
// Create a block.
Block block = new Block(params, BlockTest.blockBytes);
Sha256Hash blockHash = block.getHash();
wallet.setLastBlockSeenHash(blockHash);
// Roundtrip the wallet and check it has stored te blockHash.
Wallet wallet1 = roundTrip(wallet);
assertEquals(blockHash, wallet1.getLastBlockSeenHash());
// Test the Satoshi genesis block (hash of all zeroes) is roundtripped ok.
Block genesisBlock = NetworkParameters.prodNet().genesisBlock;
wallet.setLastBlockSeenHash(genesisBlock.getHash());
Wallet wallet2 = roundTrip(wallet);
assertEquals(genesisBlock.getHash(), wallet2.getLastBlockSeenHash());
}
private Wallet roundTrip(Wallet wallet) throws IOException { private Wallet roundTrip(Wallet wallet) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();
//System.out.println(WalletProtobufSerializer.walletToText(wallet)); //System.out.println(WalletProtobufSerializer.walletToText(wallet));