3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-30 23:02:15 +00:00

Label transactions with their source (network, wallet, other, etc).

This will be used to allow spending of unconfirmed change.
This commit is contained in:
Mike Hearn 2013-02-04 15:34:33 +01:00
parent 3b7a494a37
commit 988641a5f7
10 changed files with 288 additions and 71 deletions

View File

@ -18,7 +18,8 @@
*/
/* Notes:
* - Endianness: All byte arrays that represent numbers (such as hashes and private keys) are Big Endian (a.k.a. network order)
* - Endianness: All byte arrays that represent numbers (such as hashes and private keys) are Big Endian
* - To regenerate after editing, run mvn clean package -DupdateProtobuf
*/
package wallet;
@ -115,6 +116,18 @@ message TransactionConfidence {
optional int64 work_done = 5;
repeated PeerAddress broadcast_by = 6;
// Where did we get this transaction from? Knowing the source may help us to risk analyze pending transactions.
enum Source {
SOURCE_UNKNOWN = 0; // We don't know where it came from, or this is a wallet from the future.
SOURCE_NETWORK = 1; // We received it from a network broadcast. This is the normal way to get payments.
SOURCE_SELF = 2; // We made it ourselves, so we know it should be valid.
// In future:
// - direct from trusted counterparty, eg via bluetooth/wifi direct
// - direct from untrusted counterparty
// - from a wallet that uses trusted computing/secure hardware that won't create double spends
}
optional Source source = 7;
}
/** A bitcoin transaction */

View File

@ -185,6 +185,8 @@ public class Block extends Message {
transactions = new ArrayList<Transaction>(numTransactions);
for (int i = 0; i < numTransactions; i++) {
Transaction tx = new Transaction(params, bytes, cursor, this, parseLazy, parseRetain, UNKNOWN_LENGTH);
// Label the transaction as coming from the P2P network, so code that cares where we first saw it knows.
tx.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
transactions.add(tx);
cursor += tx.getMessageSize();
optimalEncodingMessageSize += tx.getOptimalEncodingMessageSize();

View File

@ -416,6 +416,9 @@ public class Peer {
tx = memoryPool.seen(tx, getAddress());
}
final Transaction fTx = tx;
// Label the transaction as coming in from the P2P network (as opposed to being created by us, direct import,
// etc). This helps the wallet decide how to risk analyze it later.
fTx.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
if (maybeHandleRequestedData(fTx)) {
return;
}

View File

@ -67,43 +67,11 @@ public class TransactionConfidence implements Serializable {
// Lazily created listeners array.
private transient ArrayList<Listener> listeners;
/**
* The depth of the transaction on the best chain in blocks. An unconfirmed block has depth 0, after one confirmation
* its depth is 1.
*/
// The depth of the transaction on the best chain in blocks. An unconfirmed block has depth 0.
private int depth;
/**
* The cumulative work done for the blocks that bury this transaction. BigInteger.ZERO if the transaction is not
* on the best chain.
*/
// The cumulative work done for the blocks that bury this transaction.
private BigInteger workDone = BigInteger.ZERO;
/**
* <p>Adds an event listener that will be run when this confidence object is updated. The listener will be locked and
* is likely to be invoked on a peer thread.</p>
*
* <p>Note that this is NOT called when every block arrives. Instead it is called when the transaction
* transitions between confidence states, ie, from not being seen in the chain to being seen (not necessarily in
* the best chain). If you want to know when the transaction gets buried under another block, implement a
* {@link BlockChainListener}, attach it to a {@link BlockChain} and then use the getters on the
* confidence object to determine the new depth.</p>
*/
public synchronized void addEventListener(Listener listener) {
Preconditions.checkNotNull(listener);
if (listeners == null)
listeners = new ArrayList<Listener>(2);
// Dedupe registrations. This makes the wallet code simpler.
if (!listeners.contains(listener))
listeners.add(listener);
}
public synchronized void removeEventListener(Listener listener) {
Preconditions.checkNotNull(listener);
Preconditions.checkNotNull(listeners);
listeners.remove(listener);
}
/** Describes the state of the transaction in general terms. Properties can be read to learn specifics. */
public enum ConfidenceType {
/** If BUILDING, then the transaction is included in the best chain and your confidence in it is increasing. */
@ -161,6 +129,32 @@ public class TransactionConfidence implements Serializable {
};
private ConfidenceType confidenceType = ConfidenceType.UNKNOWN;
private int appearedAtChainHeight = -1;
// The transaction that double spent this one, if any.
private Transaction overridingTransaction;
/**
* Information about where the transaction was first seen (network, sent direct from peer, created by ourselves).
* Useful for risk analyzing pending transactions. Probably not that useful after a tx is included in the chain,
* unless re-org double spends start happening frequently.
*/
public enum Source {
/** We don't know where the transaction came from. */
UNKNOWN,
/** We got this transaction from a network peer. */
NETWORK,
/** This transaction was created by our own wallet, so we know it's not a double spend. */
SELF
}
private Source source = Source.UNKNOWN;
public TransactionConfidence(Transaction tx) {
// Assume a default number of peers for our set.
broadcastBy = new CopyOnWriteArrayList<PeerAddress>();
transaction = tx;
}
/**
* <p>A confidence listener is informed when the level of {@link TransactionConfidence} is updated by something, like
* for example a {@link Wallet}. You can add listeners to update your user interface or manage your order tracking
@ -174,14 +168,29 @@ public class TransactionConfidence implements Serializable {
public void onConfidenceChanged(Transaction tx);
};
private ConfidenceType confidenceType = ConfidenceType.UNKNOWN;
private int appearedAtChainHeight = -1;
private Transaction overridingTransaction;
/**
* <p>Adds an event listener that will be run when this confidence object is updated. The listener will be locked and
* is likely to be invoked on a peer thread.</p>
*
* <p>Note that this is NOT called when every block arrives. Instead it is called when the transaction
* transitions between confidence states, ie, from not being seen in the chain to being seen (not necessarily in
* the best chain). If you want to know when the transaction gets buried under another block, implement a
* {@link BlockChainListener}, attach it to a {@link BlockChain} and then use the getters on the
* confidence object to determine the new depth.</p>
*/
public synchronized void addEventListener(Listener listener) {
Preconditions.checkNotNull(listener);
if (listeners == null)
listeners = new ArrayList<Listener>(2);
// Dedupe registrations. This makes the wallet code simpler.
if (!listeners.contains(listener))
listeners.add(listener);
}
public TransactionConfidence(Transaction tx) {
// Assume a default number of peers for our set.
broadcastBy = new CopyOnWriteArrayList<PeerAddress>();
transaction = tx;
public synchronized void removeEventListener(Listener listener) {
Preconditions.checkNotNull(listener);
Preconditions.checkNotNull(listeners);
listeners.remove(listener);
}
/**
@ -403,4 +412,24 @@ public class TransactionConfidence implements Serializable {
}
});
}
/**
* The source of a transaction tries to identify where it came from originally. For instance, did we download it
* from the peer to peer network, or make it ourselves, or receive it via Bluetooth, or import it from another app,
* and so on. This information is useful for {@link Wallet.CoinSelector} implementations to risk analyze
* transactions and decide when to spend them.
*/
public synchronized Source getSource() {
return source;
}
/**
* The source of a transaction tries to identify where it came from originally. For instance, did we download it
* from the peer to peer network, or make it ourselves, or receive it via Bluetooth, or import it from another app,
* and so on. This information is useful for {@link Wallet.CoinSelector} implementations to risk analyze
* transactions and decide when to spend them.
*/
public synchronized void setSource(Source source) {
this.source = source;
}
}

View File

@ -705,6 +705,9 @@ public class Wallet implements Serializable, BlockChainListener {
" and sends us %s BTC", tx.getHashAsString(), Utils.bitcoinValueToFriendlyString(valueSentFromMe),
Utils.bitcoinValueToFriendlyString(valueSentToMe)));
}
if (tx.getConfidence().getSource().equals(TransactionConfidence.Source.UNKNOWN)) {
log.warn("Wallet received transaction with an unknown source. Consider tagging tx!");
}
// Mark the tx as having been seen but is not yet in the chain. This will normally have been done already by
// the Peer before we got to this point, but in some cases (unit tests, other sources of transactions) it may
// have been missed out.
@ -1662,6 +1665,11 @@ public class Wallet implements Serializable, BlockChainListener {
// happen, if it does it means the wallet has got into an inconsistent state.
throw new RuntimeException(e);
}
// Label the transaction as being self created. We can use this later to spend its change output even before
// the transaction is confirmed.
req.tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
req.completed = true;
log.info(" completed {}", req.tx.getHashAsString());
return true;

View File

@ -213,6 +213,15 @@ public class WalletProtobufSerializer {
Sha256Hash overridingHash = confidence.getOverridingTransaction().getHash();
confidenceBuilder.setOverridingTransaction(hashToByteString(overridingHash));
}
TransactionConfidence.Source source = confidence.getSource();
switch (source) {
case SELF: confidenceBuilder.setSource(Protos.TransactionConfidence.Source.SOURCE_SELF); break;
case NETWORK: confidenceBuilder.setSource(Protos.TransactionConfidence.Source.SOURCE_NETWORK); break;
case UNKNOWN:
// Fall through.
default:
confidenceBuilder.setSource(Protos.TransactionConfidence.Source.SOURCE_UNKNOWN); break;
}
}
for (ListIterator<PeerAddress> it = confidence.getBroadcastBy(); it.hasNext();) {
PeerAddress address = it.next();
@ -423,5 +432,12 @@ public class WalletProtobufSerializer {
address.setServices(BigInteger.valueOf(proto.getServices()));
confidence.markBroadcastBy(address);
}
switch (confidenceProto.getSource()) {
case SOURCE_SELF: confidence.setSource(TransactionConfidence.Source.SELF); break;
case SOURCE_NETWORK: confidence.setSource(TransactionConfidence.Source.NETWORK); break;
case SOURCE_UNKNOWN:
// Fall through.
default: confidence.setSource(TransactionConfidence.Source.UNKNOWN); break;
}
}
}

View File

@ -2281,6 +2281,10 @@ public final class Protos {
getBroadcastByOrBuilderList();
org.bitcoinj.wallet.Protos.PeerAddressOrBuilder getBroadcastByOrBuilder(
int index);
// optional .wallet.TransactionConfidence.Source source = 7;
boolean hasSource();
org.bitcoinj.wallet.Protos.TransactionConfidence.Source getSource();
}
public static final class TransactionConfidence extends
com.google.protobuf.GeneratedMessage
@ -2388,6 +2392,78 @@ public final class Protos {
// @@protoc_insertion_point(enum_scope:wallet.TransactionConfidence.Type)
}
public enum Source
implements com.google.protobuf.ProtocolMessageEnum {
SOURCE_UNKNOWN(0, 0),
SOURCE_NETWORK(1, 1),
SOURCE_SELF(2, 2),
;
public static final int SOURCE_UNKNOWN_VALUE = 0;
public static final int SOURCE_NETWORK_VALUE = 1;
public static final int SOURCE_SELF_VALUE = 2;
public final int getNumber() { return value; }
public static Source valueOf(int value) {
switch (value) {
case 0: return SOURCE_UNKNOWN;
case 1: return SOURCE_NETWORK;
case 2: return SOURCE_SELF;
default: return null;
}
}
public static com.google.protobuf.Internal.EnumLiteMap<Source>
internalGetValueMap() {
return internalValueMap;
}
private static com.google.protobuf.Internal.EnumLiteMap<Source>
internalValueMap =
new com.google.protobuf.Internal.EnumLiteMap<Source>() {
public Source findValueByNumber(int number) {
return Source.valueOf(number);
}
};
public final com.google.protobuf.Descriptors.EnumValueDescriptor
getValueDescriptor() {
return getDescriptor().getValues().get(index);
}
public final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptorForType() {
return getDescriptor();
}
public static final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptor() {
return org.bitcoinj.wallet.Protos.TransactionConfidence.getDescriptor().getEnumTypes().get(1);
}
private static final Source[] VALUES = {
SOURCE_UNKNOWN, SOURCE_NETWORK, SOURCE_SELF,
};
public static Source valueOf(
com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
if (desc.getType() != getDescriptor()) {
throw new java.lang.IllegalArgumentException(
"EnumValueDescriptor is not for this type.");
}
return VALUES[desc.getIndex()];
}
private final int index;
private final int value;
private Source(int index, int value) {
this.index = index;
this.value = value;
}
// @@protoc_insertion_point(enum_scope:wallet.TransactionConfidence.Source)
}
private int bitField0_;
// optional .wallet.TransactionConfidence.Type type = 1;
public static final int TYPE_FIELD_NUMBER = 1;
@ -2460,6 +2536,16 @@ public final class Protos {
return broadcastBy_.get(index);
}
// optional .wallet.TransactionConfidence.Source source = 7;
public static final int SOURCE_FIELD_NUMBER = 7;
private org.bitcoinj.wallet.Protos.TransactionConfidence.Source source_;
public boolean hasSource() {
return ((bitField0_ & 0x00000020) == 0x00000020);
}
public org.bitcoinj.wallet.Protos.TransactionConfidence.Source getSource() {
return source_;
}
private void initFields() {
type_ = org.bitcoinj.wallet.Protos.TransactionConfidence.Type.UNKNOWN;
appearedAtHeight_ = 0;
@ -2467,6 +2553,7 @@ public final class Protos {
depth_ = 0;
workDone_ = 0L;
broadcastBy_ = java.util.Collections.emptyList();
source_ = org.bitcoinj.wallet.Protos.TransactionConfidence.Source.SOURCE_UNKNOWN;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@ -2504,6 +2591,9 @@ public final class Protos {
for (int i = 0; i < broadcastBy_.size(); i++) {
output.writeMessage(6, broadcastBy_.get(i));
}
if (((bitField0_ & 0x00000020) == 0x00000020)) {
output.writeEnum(7, source_.getNumber());
}
getUnknownFields().writeTo(output);
}
@ -2537,6 +2627,10 @@ public final class Protos {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(6, broadcastBy_.get(i));
}
if (((bitField0_ & 0x00000020) == 0x00000020)) {
size += com.google.protobuf.CodedOutputStream
.computeEnumSize(7, source_.getNumber());
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@ -2678,6 +2772,8 @@ public final class Protos {
} else {
broadcastByBuilder_.clear();
}
source_ = org.bitcoinj.wallet.Protos.TransactionConfidence.Source.SOURCE_UNKNOWN;
bitField0_ = (bitField0_ & ~0x00000040);
return this;
}
@ -2745,6 +2841,10 @@ public final class Protos {
} else {
result.broadcastBy_ = broadcastByBuilder_.build();
}
if (((from_bitField0_ & 0x00000040) == 0x00000040)) {
to_bitField0_ |= 0x00000020;
}
result.source_ = source_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@ -2802,6 +2902,9 @@ public final class Protos {
}
}
}
if (other.hasSource()) {
setSource(other.getSource());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@ -2876,6 +2979,17 @@ public final class Protos {
addBroadcastBy(subBuilder.buildPartial());
break;
}
case 56: {
int rawValue = input.readEnum();
org.bitcoinj.wallet.Protos.TransactionConfidence.Source value = org.bitcoinj.wallet.Protos.TransactionConfidence.Source.valueOf(rawValue);
if (value == null) {
unknownFields.mergeVarintField(7, rawValue);
} else {
bitField0_ |= 0x00000040;
source_ = value;
}
break;
}
}
}
}
@ -3179,6 +3293,30 @@ public final class Protos {
return broadcastByBuilder_;
}
// optional .wallet.TransactionConfidence.Source source = 7;
private org.bitcoinj.wallet.Protos.TransactionConfidence.Source source_ = org.bitcoinj.wallet.Protos.TransactionConfidence.Source.SOURCE_UNKNOWN;
public boolean hasSource() {
return ((bitField0_ & 0x00000040) == 0x00000040);
}
public org.bitcoinj.wallet.Protos.TransactionConfidence.Source getSource() {
return source_;
}
public Builder setSource(org.bitcoinj.wallet.Protos.TransactionConfidence.Source value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000040;
source_ = value;
onChanged();
return this;
}
public Builder clearSource() {
bitField0_ = (bitField0_ & ~0x00000040);
source_ = org.bitcoinj.wallet.Protos.TransactionConfidence.Source.SOURCE_UNKNOWN;
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:wallet.TransactionConfidence)
}
@ -6570,31 +6708,34 @@ public final class Protos {
"ence\030\004 \001(\r\"\177\n\021TransactionOutput\022\r\n\005value",
"\030\001 \002(\003\022\024\n\014script_bytes\030\002 \002(\014\022!\n\031spent_by" +
"_transaction_hash\030\003 \001(\014\022\"\n\032spent_by_tran" +
"saction_index\030\004 \001(\005\"\255\002\n\025TransactionConfi" +
"saction_index\030\004 \001(\005\"\246\003\n\025TransactionConfi" +
"dence\0220\n\004type\030\001 \001(\0162\".wallet.Transaction" +
"Confidence.Type\022\032\n\022appeared_at_height\030\002 " +
"\001(\005\022\036\n\026overriding_transaction\030\003 \001(\014\022\r\n\005d" +
"epth\030\004 \001(\005\022\021\n\twork_done\030\005 \001(\003\022)\n\014broadca" +
"st_by\030\006 \003(\0132\023.wallet.PeerAddress\"Y\n\004Type" +
"\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDING\020\001\022\025\n\021NOT_SEEN_" +
"IN_CHAIN\020\002\022\025\n\021NOT_IN_BEST_CHAIN\020\003\022\010\n\004DEA",
"D\020\004\"\211\003\n\013Transaction\022\017\n\007version\030\001 \002(\005\022\014\n\004" +
"hash\030\002 \002(\014\022&\n\004pool\030\003 \001(\0162\030.wallet.Transa" +
"ction.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdated" +
"_at\030\005 \001(\003\0223\n\021transaction_input\030\006 \003(\0132\030.w" +
"allet.TransactionInput\0225\n\022transaction_ou" +
"tput\030\007 \003(\0132\031.wallet.TransactionOutput\022\022\n" +
"\nblock_hash\030\010 \003(\014\0221\n\nconfidence\030\t \001(\0132\035." +
"wallet.TransactionConfidence\"Y\n\004Pool\022\013\n\007" +
"UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIVE\020\002\022\010\n\004DE" +
"AD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_INACTIVE\020\022\"",
"8\n\tExtension\022\n\n\002id\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\022\021" +
"\n\tmandatory\030\003 \002(\010\"\254\001\n\006Wallet\022\032\n\022network_" +
"identifier\030\001 \002(\t\022\034\n\024last_seen_block_hash" +
"\030\002 \001(\014\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013tran" +
"saction\030\004 \003(\0132\023.wallet.Transaction\022$\n\tex" +
"tension\030\n \003(\0132\021.wallet.ExtensionB\035\n\023org." +
"bitcoinj.walletB\006Protos"
"st_by\030\006 \003(\0132\023.wallet.PeerAddress\0224\n\006sour" +
"ce\030\007 \001(\0162$.wallet.TransactionConfidence." +
"Source\"Y\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDING\020",
"\001\022\025\n\021NOT_SEEN_IN_CHAIN\020\002\022\025\n\021NOT_IN_BEST_" +
"CHAIN\020\003\022\010\n\004DEAD\020\004\"A\n\006Source\022\022\n\016SOURCE_UN" +
"KNOWN\020\000\022\022\n\016SOURCE_NETWORK\020\001\022\017\n\013SOURCE_SE" +
"LF\020\002\"\211\003\n\013Transaction\022\017\n\007version\030\001 \002(\005\022\014\n" +
"\004hash\030\002 \002(\014\022&\n\004pool\030\003 \001(\0162\030.wallet.Trans" +
"action.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdate" +
"d_at\030\005 \001(\003\0223\n\021transaction_input\030\006 \003(\0132\030." +
"wallet.TransactionInput\0225\n\022transaction_o" +
"utput\030\007 \003(\0132\031.wallet.TransactionOutput\022\022" +
"\n\nblock_hash\030\010 \003(\014\0221\n\nconfidence\030\t \001(\0132\035",
".wallet.TransactionConfidence\"Y\n\004Pool\022\013\n" +
"\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIVE\020\002\022\010\n\004D" +
"EAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_INACTIVE\020\022" +
"\"8\n\tExtension\022\n\n\002id\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\022" +
"\021\n\tmandatory\030\003 \002(\010\"\254\001\n\006Wallet\022\032\n\022network" +
"_identifier\030\001 \002(\t\022\034\n\024last_seen_block_has" +
"h\030\002 \001(\014\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013tra" +
"nsaction\030\004 \003(\0132\023.wallet.Transaction\022$\n\te" +
"xtension\030\n \003(\0132\021.wallet.ExtensionB\035\n\023org" +
".bitcoinj.walletB\006Protos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -6638,7 +6779,7 @@ public final class Protos {
internal_static_wallet_TransactionConfidence_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_wallet_TransactionConfidence_descriptor,
new java.lang.String[] { "Type", "AppearedAtHeight", "OverridingTransaction", "Depth", "WorkDone", "BroadcastBy", },
new java.lang.String[] { "Type", "AppearedAtHeight", "OverridingTransaction", "Depth", "WorkDone", "BroadcastBy", "Source", },
org.bitcoinj.wallet.Protos.TransactionConfidence.class,
org.bitcoinj.wallet.Protos.TransactionConfidence.Builder.class);
internal_static_wallet_Transaction_descriptor =

View File

@ -153,8 +153,10 @@ public class TestUtils {
Address to = new ECKey().toAddress(chainHead.params);
Block b = chainHead.createNextBlock(to, timeSeconds);
// Coinbase tx was already added.
for (Transaction tx : transactions)
for (Transaction tx : transactions) {
tx.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
b.addTransaction(tx);
}
b.solve();
BlockPair pair = new BlockPair();
pair.block = b;

View File

@ -94,6 +94,7 @@ public class WalletTest {
Transaction t2 = req.tx;
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
assertEquals(TransactionConfidence.Source.SELF, t2.getConfidence().getSource());
// Do some basic sanity checks.
assertEquals(1, t2.getInputs().size());

View File

@ -56,13 +56,15 @@ public class WalletProtobufSerializerTest {
Transaction t1 = createFakeTx(params, v1, myAddress);
t1.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("1.2.3.4")));
t1.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("5.6.7.8")));
myWallet.receiveFromBlock(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
t1.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
myWallet.receivePending(t1, new ArrayList<Transaction>());
Wallet wallet1 = roundTrip(myWallet);
assertEquals(1, wallet1.getTransactions(true, true).size());
assertEquals(v1, wallet1.getBalance());
assertArrayEquals(t1.bitcoinSerialize(),
wallet1.getTransaction(t1.getHash()).bitcoinSerialize());
assertEquals(2, wallet1.getTransaction(t1.getHash()).getConfidence().numBroadcastPeers());
assertEquals(v1, wallet1.getBalance(Wallet.BalanceType.ESTIMATED));
Transaction t1copy = wallet1.getTransaction(t1.getHash());
assertArrayEquals(t1.bitcoinSerialize(), t1copy.bitcoinSerialize());
assertEquals(2, t1copy.getConfidence().numBroadcastPeers());
assertEquals(TransactionConfidence.Source.NETWORK, t1copy.getConfidence().getSource());
Protos.Wallet walletProto = new WalletProtobufSerializer().walletToProto(myWallet);
assertEquals(Protos.Key.Type.ORIGINAL, walletProto.getKey(0).getType());
@ -73,7 +75,7 @@ public class WalletProtobufSerializerTest {
Protos.Transaction t1p = walletProto.getTransaction(0);
assertEquals(0, t1p.getBlockHashCount());
assertArrayEquals(t1.getHash().getBytes(), t1p.getHash().toByteArray());
assertEquals(Protos.Transaction.Pool.UNSPENT, t1p.getPool());
assertEquals(Protos.Transaction.Pool.PENDING, t1p.getPool());
assertFalse(t1p.hasLockTime());
assertFalse(t1p.getTransactionInput(0).hasSequence());
assertArrayEquals(t1.getInputs().get(0).getOutpoint().getHash().getBytes(),