mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-12 10:15:52 +00:00
Wallet: change the extensions API.
The old API was very limiting. Rather than have extensions be a single object that creates subclasses of the Wallet class, allow arbitrary objects to be attached to Wallets. Objects know if they are mandatory and how to serialize themselves. A wallet can have as many extensions as wanted in this way.
This commit is contained in:
parent
3eb3dbcf89
commit
c64453f835
@ -16,27 +16,22 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
|
||||||
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
|
||||||
import org.spongycastle.crypto.params.KeyParameter;
|
|
||||||
|
|
||||||
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
||||||
import com.google.bitcoin.core.WalletTransaction.Pool;
|
import com.google.bitcoin.core.WalletTransaction.Pool;
|
||||||
import com.google.bitcoin.crypto.KeyCrypter;
|
import com.google.bitcoin.crypto.KeyCrypter;
|
||||||
import com.google.bitcoin.crypto.KeyCrypterException;
|
import com.google.bitcoin.crypto.KeyCrypterException;
|
||||||
|
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||||
import com.google.bitcoin.store.WalletProtobufSerializer;
|
import com.google.bitcoin.store.WalletProtobufSerializer;
|
||||||
import com.google.bitcoin.utils.Locks;
|
import com.google.bitcoin.utils.Locks;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.*;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
@ -249,6 +244,9 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||||||
private int version;
|
private int version;
|
||||||
// User-provided description that may help people keep track of what a wallet is for.
|
// User-provided description that may help people keep track of what a wallet is for.
|
||||||
private String description;
|
private String description;
|
||||||
|
// Stores objects that know how to serialize/unserialize themselves to byte streams and whether they're mandatory
|
||||||
|
// or not. The string key comes from the extension itself.
|
||||||
|
private final HashMap<String, WalletExtension> extensions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead,
|
* Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead,
|
||||||
@ -270,6 +268,7 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||||||
pending = new HashMap<Sha256Hash, Transaction>();
|
pending = new HashMap<Sha256Hash, Transaction>();
|
||||||
dead = new HashMap<Sha256Hash, Transaction>();
|
dead = new HashMap<Sha256Hash, Transaction>();
|
||||||
eventListeners = new CopyOnWriteArrayList<WalletEventListener>();
|
eventListeners = new CopyOnWriteArrayList<WalletEventListener>();
|
||||||
|
extensions = new HashMap<String, WalletExtension>();
|
||||||
createTransientState();
|
createTransientState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2847,6 +2846,37 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Extensions to the wallet format.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By providing an object implementing the {@link WalletExtension} interface, you can save and load arbitrary
|
||||||
|
* additional data that will be stored with the wallet. Each extension is identified by an ID, so attempting to
|
||||||
|
* add the same extension twice (or two different objects that use the same ID) will throw an IllegalStateException.
|
||||||
|
*/
|
||||||
|
public void addExtension(WalletExtension extension) {
|
||||||
|
String id = checkNotNull(extension).getWalletExtensionID();
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (extensions.containsKey(id))
|
||||||
|
throw new IllegalStateException("Cannot add two extensions with the same ID: " + id);
|
||||||
|
extensions.put(id, extension);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a snapshot of all registered extension objects. The extensions themselves are not copied. */
|
||||||
|
public Map<String, WalletExtension> getExtensions() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
return ImmutableMap.copyOf(extensions);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Boilerplate for running event listeners - unlocks the wallet, runs, re-locks.
|
// Boilerplate for running event listeners - unlocks the wallet, runs, re-locks.
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>An object implementing this interface can be added to a {@link Wallet} and provide arbitrary byte arrays that will
|
||||||
|
* be serialized alongside the wallet. Extensions can be mandatory, in which case applications that don't know how to
|
||||||
|
* read the given data will refuse to load the wallet at all. Extensions identify themselves with a string ID that
|
||||||
|
* should use a Java-style reverse DNS identifier to avoid being mixed up with other kinds of extension. To use an
|
||||||
|
* extension, add an object that implements this interface to the wallet using {@link Wallet#addExtension(WalletExtension)}
|
||||||
|
* before you load it (to read existing data) and ensure it's present when the wallet is save (to write the data).</p>
|
||||||
|
*
|
||||||
|
* <p>Note that extensions are singletons - you cannot add two objects that provide the same ID to the same wallet.</p>
|
||||||
|
*/
|
||||||
|
public interface WalletExtension {
|
||||||
|
/** Returns a Java package/class style name used to disambiguate this extension from others. */
|
||||||
|
public String getWalletExtensionID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this returns true, the mandatory flag is set when the wallet is serialized and attempts to load it without
|
||||||
|
* the extension being in the wallet will throw an exception. This method should not change its result during
|
||||||
|
* the objects lifetime.
|
||||||
|
*/
|
||||||
|
public boolean isWalletExtensionMandatory();
|
||||||
|
|
||||||
|
/** Returns bytes that will be saved in the wallet. */
|
||||||
|
public byte[] serializeWalletExtension();
|
||||||
|
/** Loads the contents of this object from the wallet. */
|
||||||
|
public void deserializeWalletExtension(byte[] data);
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* 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.store;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import org.bitcoinj.wallet.Protos;
|
|
||||||
|
|
||||||
import com.google.bitcoin.core.NetworkParameters;
|
|
||||||
import com.google.bitcoin.core.Wallet;
|
|
||||||
import com.google.bitcoin.crypto.KeyCrypter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional helper for WalletProtobufSerializer that allows for serialization and deserialization of Wallet objects
|
|
||||||
* with extensions and corresponding extended Wallet classes. If you want to store proprietary data into the wallet,
|
|
||||||
* this is how to do it.
|
|
||||||
*/
|
|
||||||
public class WalletExtensionSerializer {
|
|
||||||
public Wallet newWallet(NetworkParameters params) {
|
|
||||||
return new Wallet(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Wallet newWallet(NetworkParameters params, KeyCrypter keyCrypter) {
|
|
||||||
return new Wallet(params, keyCrypter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void readExtension(Wallet wallet, Protos.Extension extProto) {
|
|
||||||
if (extProto.getMandatory()) {
|
|
||||||
throw new IllegalArgumentException("Unknown mandatory extension in the wallet: " + extProto.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get collection of extensions to add, should be overridden by any class adding wallet extensions.
|
|
||||||
*/
|
|
||||||
public Collection<Protos.Extension> getExtensionsToWrite(Wallet wallet) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,6 +16,19 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.store;
|
package com.google.bitcoin.store;
|
||||||
|
|
||||||
|
import com.google.bitcoin.core.*;
|
||||||
|
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
||||||
|
import com.google.bitcoin.crypto.EncryptedPrivateKey;
|
||||||
|
import com.google.bitcoin.crypto.KeyCrypter;
|
||||||
|
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.TextFormat;
|
||||||
|
import org.bitcoinj.wallet.Protos;
|
||||||
|
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@ -24,31 +37,6 @@ import java.net.InetAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.bitcoinj.wallet.Protos;
|
|
||||||
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
|
||||||
|
|
||||||
import com.google.bitcoin.crypto.EncryptedPrivateKey;
|
|
||||||
import com.google.bitcoin.crypto.KeyCrypter;
|
|
||||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.bitcoin.core.ECKey;
|
|
||||||
import com.google.bitcoin.core.NetworkParameters;
|
|
||||||
import com.google.bitcoin.core.PeerAddress;
|
|
||||||
import com.google.bitcoin.core.Sha256Hash;
|
|
||||||
import com.google.bitcoin.core.Transaction;
|
|
||||||
import com.google.bitcoin.core.TransactionConfidence;
|
|
||||||
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
|
||||||
import com.google.bitcoin.core.TransactionInput;
|
|
||||||
import com.google.bitcoin.core.TransactionOutPoint;
|
|
||||||
import com.google.bitcoin.core.TransactionOutput;
|
|
||||||
import com.google.bitcoin.core.Wallet;
|
|
||||||
import com.google.bitcoin.core.WalletTransaction;
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import com.google.protobuf.TextFormat;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,19 +63,9 @@ public class WalletProtobufSerializer {
|
|||||||
|
|
||||||
// Used for de-serialization
|
// Used for de-serialization
|
||||||
protected Map<ByteString, Transaction> txMap;
|
protected Map<ByteString, Transaction> txMap;
|
||||||
protected WalletExtensionSerializer helper;
|
|
||||||
|
|
||||||
public WalletProtobufSerializer() {
|
public WalletProtobufSerializer() {
|
||||||
txMap = new HashMap<ByteString, Transaction>();
|
txMap = new HashMap<ByteString, Transaction>();
|
||||||
helper = new WalletExtensionSerializer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the WalletExtensionSerializer used to create new wallet objects
|
|
||||||
* and handle extensions
|
|
||||||
*/
|
|
||||||
public void setWalletExtensionSerializer(WalletExtensionSerializer h) {
|
|
||||||
this.helper = h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,18 +165,25 @@ public class WalletProtobufSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
populateExtensions(wallet, walletBuilder);
|
||||||
|
|
||||||
// Populate the wallet version.
|
// Populate the wallet version.
|
||||||
walletBuilder.setVersion(wallet.getVersion());
|
walletBuilder.setVersion(wallet.getVersion());
|
||||||
|
|
||||||
Collection<Protos.Extension> extensions = helper.getExtensionsToWrite(wallet);
|
|
||||||
for(Protos.Extension ext : extensions) {
|
|
||||||
walletBuilder.addExtension(ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
return walletBuilder.build();
|
return walletBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Protos.Transaction makeTxProto(WalletTransaction wtx) {
|
private static void populateExtensions(Wallet wallet, Protos.Wallet.Builder walletBuilder) {
|
||||||
|
for (WalletExtension extension : wallet.getExtensions().values()) {
|
||||||
|
Protos.Extension.Builder proto = Protos.Extension.newBuilder();
|
||||||
|
proto.setId(extension.getWalletExtensionID());
|
||||||
|
proto.setMandatory(extension.isWalletExtensionMandatory());
|
||||||
|
proto.setData(ByteString.copyFrom(extension.serializeWalletExtension()));
|
||||||
|
walletBuilder.addExtension(proto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Protos.Transaction makeTxProto(WalletTransaction wtx) {
|
||||||
Transaction tx = wtx.getTransaction();
|
Transaction tx = wtx.getTransaction();
|
||||||
Protos.Transaction.Builder txBuilder = Protos.Transaction.newBuilder();
|
Protos.Transaction.Builder txBuilder = Protos.Transaction.newBuilder();
|
||||||
|
|
||||||
@ -257,7 +242,7 @@ public class WalletProtobufSerializer {
|
|||||||
return txBuilder.build();
|
return txBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void writeConfidence(Protos.Transaction.Builder txBuilder,
|
private static void writeConfidence(Protos.Transaction.Builder txBuilder,
|
||||||
TransactionConfidence confidence,
|
TransactionConfidence confidence,
|
||||||
Protos.TransactionConfidence.Builder confidenceBuilder) {
|
Protos.TransactionConfidence.Builder confidenceBuilder) {
|
||||||
synchronized (confidence) {
|
synchronized (confidence) {
|
||||||
@ -309,19 +294,17 @@ public class WalletProtobufSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a wallet from the given stream. The stream is expected to contain a binary serialization of a
|
* Parses a wallet from the given stream, using the provided Wallet instance to load data into. This is primarily
|
||||||
* {@link Protos.Wallet} object.<p>
|
* used when you want to register extensions. Data in the proto will be added into the wallet where applicable and
|
||||||
*
|
* overwrite where not.
|
||||||
* @throws IOException if there is a problem reading the stream.
|
|
||||||
* @throws IllegalArgumentException if the wallet is corrupt.
|
|
||||||
*/
|
*/
|
||||||
public Wallet readWallet(InputStream input) throws IOException {
|
public Wallet readWallet(InputStream input) throws IOException {
|
||||||
// TODO: This method should throw more specific exception types than IllegalArgumentException.
|
|
||||||
Protos.Wallet walletProto = parseToProto(input);
|
Protos.Wallet walletProto = parseToProto(input);
|
||||||
|
|
||||||
// System.out.println(TextFormat.printToString(walletProto));
|
// System.out.println(TextFormat.printToString(walletProto));
|
||||||
|
|
||||||
// Read the scrypt parameters that specify how encryption and decryption is performed.
|
// Read the scrypt parameters that specify how encryption and decryption is performed.
|
||||||
|
// TODO: Why is the key crypter special? This should just be added to the wallet after construction as well.
|
||||||
KeyCrypter keyCrypter = null;
|
KeyCrypter keyCrypter = null;
|
||||||
if (walletProto.hasEncryptionParameters()) {
|
if (walletProto.hasEncryptionParameters()) {
|
||||||
Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters();
|
Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters();
|
||||||
@ -329,8 +312,21 @@ public class WalletProtobufSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NetworkParameters params = NetworkParameters.fromID(walletProto.getNetworkIdentifier());
|
NetworkParameters params = NetworkParameters.fromID(walletProto.getNetworkIdentifier());
|
||||||
Wallet wallet = helper.newWallet(params, keyCrypter);
|
Wallet wallet = new Wallet(params, keyCrypter);
|
||||||
|
readWallet(walletProto, wallet);
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads wallet data from the given protocol buffer and inserts it into the given Wallet object. This is primarily
|
||||||
|
* useful when you wish to pre-register extension objects. Note that if loading fails the provided Wallet object
|
||||||
|
* may be in an indeterminate state and should be thrown away.
|
||||||
|
*
|
||||||
|
* @throws IOException if there is a problem reading the stream.
|
||||||
|
* @throws IllegalArgumentException if the wallet is corrupt.
|
||||||
|
*/
|
||||||
|
public void readWallet(Protos.Wallet walletProto, Wallet wallet) throws IOException {
|
||||||
|
// TODO: This method should throw more specific exception types than IllegalArgumentException.
|
||||||
if (walletProto.hasDescription()) {
|
if (walletProto.hasDescription()) {
|
||||||
wallet.setDescription(walletProto.getDescription());
|
wallet.setDescription(walletProto.getDescription());
|
||||||
}
|
}
|
||||||
@ -352,6 +348,7 @@ public class WalletProtobufSerializer {
|
|||||||
byte[] pubKey = keyProto.hasPublicKey() ? keyProto.getPublicKey().toByteArray() : null;
|
byte[] pubKey = keyProto.hasPublicKey() ? keyProto.getPublicKey().toByteArray() : null;
|
||||||
|
|
||||||
ECKey ecKey;
|
ECKey ecKey;
|
||||||
|
final KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
||||||
if (keyCrypter != null && keyCrypter.getUnderstoodEncryptionType() != EncryptionType.UNENCRYPTED) {
|
if (keyCrypter != null && keyCrypter.getUnderstoodEncryptionType() != EncryptionType.UNENCRYPTED) {
|
||||||
// If the key is encrypted construct an ECKey using the encrypted private key bytes.
|
// If the key is encrypted construct an ECKey using the encrypted private key bytes.
|
||||||
ecKey = new ECKey(encryptedPrivateKey, pubKey, keyCrypter);
|
ecKey = new ECKey(encryptedPrivateKey, pubKey, keyCrypter);
|
||||||
@ -365,7 +362,7 @@ public class WalletProtobufSerializer {
|
|||||||
|
|
||||||
// Read all transactions and insert into the txMap.
|
// Read all transactions and insert into the txMap.
|
||||||
for (Protos.Transaction txProto : walletProto.getTransactionList()) {
|
for (Protos.Transaction txProto : walletProto.getTransactionList()) {
|
||||||
readTransaction(txProto, params);
|
readTransaction(txProto, wallet.getParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update transaction outputs to point to inputs that spend them
|
// Update transaction outputs to point to inputs that spend them
|
||||||
@ -386,9 +383,7 @@ public class WalletProtobufSerializer {
|
|||||||
wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight());
|
wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Protos.Extension extProto : walletProto.getExtensionList()) {
|
loadExtensions(wallet, walletProto);
|
||||||
helper.readExtension(wallet, extProto);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (walletProto.hasVersion()) {
|
if (walletProto.hasVersion()) {
|
||||||
wallet.setVersion(walletProto.getVersion());
|
wallet.setVersion(walletProto.getVersion());
|
||||||
@ -396,8 +391,22 @@ public class WalletProtobufSerializer {
|
|||||||
|
|
||||||
// Make sure the object can be re-used to read another wallet without corruption.
|
// Make sure the object can be re-used to read another wallet without corruption.
|
||||||
txMap.clear();
|
txMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
return wallet;
|
private static void loadExtensions(Wallet wallet, Protos.Wallet walletProto) {
|
||||||
|
final Map<String, WalletExtension> extensions = wallet.getExtensions();
|
||||||
|
for (Protos.Extension extProto : walletProto.getExtensionList()) {
|
||||||
|
String id = extProto.getId();
|
||||||
|
WalletExtension extension = extensions.get(id);
|
||||||
|
if (extension == null) {
|
||||||
|
if (extProto.getMandatory()) {
|
||||||
|
throw new IllegalArgumentException("Unknown mandatory extension in wallet: " + id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.info("Loading wallet extension {}", id);
|
||||||
|
extension.deserializeWalletExtension(extProto.getData().toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -409,7 +418,7 @@ public class WalletProtobufSerializer {
|
|||||||
return Protos.Wallet.parseFrom(input);
|
return Protos.Wallet.parseFrom(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void readTransaction(Protos.Transaction txProto, NetworkParameters params) {
|
private void readTransaction(Protos.Transaction txProto, NetworkParameters params) {
|
||||||
Transaction tx = new Transaction(params);
|
Transaction tx = new Transaction(params);
|
||||||
if (txProto.hasUpdatedAt()) {
|
if (txProto.hasUpdatedAt()) {
|
||||||
tx.setUpdateTime(new Date(txProto.getUpdatedAt()));
|
tx.setUpdateTime(new Date(txProto.getUpdatedAt()));
|
||||||
@ -452,7 +461,7 @@ public class WalletProtobufSerializer {
|
|||||||
txMap.put(txProto.getHash(), tx);
|
txMap.put(txProto.getHash(), tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WalletTransaction connectTransactionOutputs(org.bitcoinj.wallet.Protos.Transaction txProto) {
|
private WalletTransaction connectTransactionOutputs(org.bitcoinj.wallet.Protos.Transaction txProto) {
|
||||||
Transaction tx = txMap.get(txProto.getHash());
|
Transaction tx = txMap.get(txProto.getHash());
|
||||||
WalletTransaction.Pool pool = WalletTransaction.Pool.valueOf(txProto.getPool().getNumber());
|
WalletTransaction.Pool pool = WalletTransaction.Pool.valueOf(txProto.getPool().getNumber());
|
||||||
if (pool == WalletTransaction.Pool.INACTIVE || pool == WalletTransaction.Pool.PENDING_INACTIVE) {
|
if (pool == WalletTransaction.Pool.INACTIVE || pool == WalletTransaction.Pool.PENDING_INACTIVE) {
|
||||||
@ -486,7 +495,7 @@ public class WalletProtobufSerializer {
|
|||||||
return new WalletTransaction(pool, tx);
|
return new WalletTransaction(pool, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void readConfidence(Transaction tx, Protos.TransactionConfidence confidenceProto,
|
private void readConfidence(Transaction tx, Protos.TransactionConfidence confidenceProto,
|
||||||
TransactionConfidence confidence) {
|
TransactionConfidence confidence) {
|
||||||
// We are lenient here because tx confidence is not an essential part of the wallet.
|
// We are lenient here because tx confidence is not an essential part of the wallet.
|
||||||
// If the tx has an unknown type of confidence, ignore.
|
// If the tx has an unknown type of confidence, ignore.
|
||||||
|
@ -6,22 +6,20 @@ 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 com.google.protobuf.ByteString;
|
||||||
import org.bitcoinj.wallet.Protos;
|
import org.bitcoinj.wallet.Protos;
|
||||||
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.google.bitcoin.core.TestUtils.createFakeTx;
|
import static com.google.bitcoin.core.TestUtils.createFakeTx;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import com.google.bitcoin.crypto.KeyCrypter;
|
|
||||||
|
|
||||||
public class WalletProtobufSerializerTest {
|
public class WalletProtobufSerializerTest {
|
||||||
static final NetworkParameters params = NetworkParameters.unitTests();
|
static final NetworkParameters params = NetworkParameters.unitTests();
|
||||||
private ECKey myKey;
|
private ECKey myKey;
|
||||||
@ -227,7 +225,7 @@ public class WalletProtobufSerializerTest {
|
|||||||
assertEquals(work2, rebornConfidence1.getWorkDone());
|
assertEquals(work2, rebornConfidence1.getWorkDone());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Wallet roundTrip(Wallet wallet) throws Exception {
|
private static Wallet roundTrip(Wallet wallet) throws Exception {
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
//System.out.println(WalletProtobufSerializer.walletToText(wallet));
|
//System.out.println(WalletProtobufSerializer.walletToText(wallet));
|
||||||
new WalletProtobufSerializer().writeWallet(wallet, output);
|
new WalletProtobufSerializer().writeWallet(wallet, output);
|
||||||
@ -237,7 +235,7 @@ public class WalletProtobufSerializerTest {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSerializedExtensionNormalWallet() throws Exception {
|
public void testRoundTripNormalWallet() throws Exception {
|
||||||
Wallet wallet1 = roundTrip(myWallet);
|
Wallet wallet1 = roundTrip(myWallet);
|
||||||
assertEquals(0, wallet1.getTransactions(true).size());
|
assertEquals(0, wallet1.getTransactions(true).size());
|
||||||
assertEquals(BigInteger.ZERO, wallet1.getBalance());
|
assertEquals(BigInteger.ZERO, wallet1.getBalance());
|
||||||
@ -251,99 +249,62 @@ public class WalletProtobufSerializerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSerializedExtensionFancyWallet() throws Exception {
|
public void testExtensions() throws Exception {
|
||||||
Random rnd = new Random();
|
myWallet.addExtension(new SomeFooExtension("com.whatever.required", true));
|
||||||
WalletExtension wallet1 = new WalletExtension(params);
|
Protos.Wallet proto = new WalletProtobufSerializer().walletToProto(myWallet);
|
||||||
wallet1.addKey(myKey);
|
Wallet wallet2 = new Wallet(params);
|
||||||
wallet1.random_bytes = new byte[100];
|
// Initial extension is mandatory: try to read it back into a wallet that doesn't know about it.
|
||||||
rnd.nextBytes(wallet1.random_bytes);
|
try {
|
||||||
|
new WalletProtobufSerializer().readWallet(proto, wallet2);
|
||||||
Wallet wallet2 = roundTripExtension(wallet1);
|
fail();
|
||||||
assertTrue("Wallet2 is not an instance of WalletExtension. It is a " + wallet2.getClass().getCanonicalName(), wallet2 instanceof WalletExtension);
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Expected.
|
||||||
WalletExtension wallet2ext = (WalletExtension)wallet2;
|
|
||||||
|
|
||||||
assertNotNull("Wallet2s random bytes were null", wallet2ext.random_bytes);
|
|
||||||
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
assertEquals("Wallet extension byte different at byte " + i, wallet1.random_bytes[i], wallet2ext.random_bytes[i]);
|
|
||||||
}
|
}
|
||||||
|
Wallet wallet3 = new Wallet(params);
|
||||||
|
// This time it works.
|
||||||
|
wallet3.addExtension(new SomeFooExtension("com.whatever.required", true));
|
||||||
|
new WalletProtobufSerializer().readWallet(proto, wallet3);
|
||||||
|
assertTrue(wallet3.getExtensions().containsKey("com.whatever.required"));
|
||||||
|
|
||||||
|
|
||||||
|
// Non-mandatory extensions are ignored if the wallet doesn't know how to read them.
|
||||||
|
Wallet wallet4 = new Wallet(params);
|
||||||
|
wallet4.addExtension(new SomeFooExtension("com.whatever.optional", false));
|
||||||
|
Protos.Wallet proto4 = new WalletProtobufSerializer().walletToProto(wallet4);
|
||||||
|
Wallet wallet5 = new Wallet(params);
|
||||||
|
new WalletProtobufSerializer().readWallet(proto4, wallet5);
|
||||||
|
assertEquals(0, wallet5.getExtensions().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private static class SomeFooExtension implements WalletExtension {
|
||||||
public void testSerializedExtensionFancyWalletRegularTrip() throws Exception {
|
private final byte[] data = new byte[]{1, 2, 3};
|
||||||
Random rnd = new Random();
|
|
||||||
WalletExtension wallet1 = new WalletExtension(params);
|
|
||||||
wallet1.addKey(myKey);
|
|
||||||
wallet1.random_bytes=new byte[100];
|
|
||||||
rnd.nextBytes(wallet1.random_bytes);
|
|
||||||
|
|
||||||
Wallet wallet2 = roundTrip(myWallet);
|
private final boolean isMandatory;
|
||||||
assertFalse(wallet2 instanceof WalletExtension);
|
private final String id;
|
||||||
|
|
||||||
}
|
public SomeFooExtension(String id, boolean isMandatory) {
|
||||||
|
this.isMandatory = isMandatory;
|
||||||
|
this.id = id;
|
||||||
private Wallet roundTripExtension(Wallet wallet) throws Exception {
|
|
||||||
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
WalletProtobufSerializer serializer = new WalletProtobufSerializer();
|
|
||||||
serializer.setWalletExtensionSerializer(new WalletExtensionSerializerRandom());
|
|
||||||
serializer.writeWallet(wallet, output);
|
|
||||||
|
|
||||||
ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
|
|
||||||
return serializer.readWallet(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An extension of a wallet that stores a number.
|
|
||||||
*/
|
|
||||||
public static class WalletExtension extends Wallet {
|
|
||||||
public byte[] random_bytes;
|
|
||||||
|
|
||||||
public WalletExtension(NetworkParameters params) {
|
|
||||||
super(params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class WalletExtensionSerializerRandom extends WalletExtensionSerializer {
|
|
||||||
@Override
|
|
||||||
public Collection<Protos.Extension> getExtensionsToWrite(Wallet wallet) {
|
|
||||||
List<Protos.Extension> lst = new LinkedList<Protos.Extension>();
|
|
||||||
if (wallet instanceof WalletExtension) {
|
|
||||||
WalletExtension walletExt = (WalletExtension) wallet;
|
|
||||||
Protos.Extension.Builder e = Protos.Extension.newBuilder();
|
|
||||||
e.setId("WalletExtension.random_bytes");
|
|
||||||
e.setMandatory(false);
|
|
||||||
e.setData(ByteString.copyFrom(walletExt.random_bytes));
|
|
||||||
lst.add(e.build());
|
|
||||||
}
|
|
||||||
lst.addAll(super.getExtensionsToWrite(wallet));
|
|
||||||
return lst;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Wallet newWallet(NetworkParameters params) {
|
public String getWalletExtensionID() {
|
||||||
return new WalletExtension(params);
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Wallet newWallet(NetworkParameters params, KeyCrypter keyCrypter) {
|
public boolean isWalletExtensionMandatory() {
|
||||||
// Ignore encryption.
|
return isMandatory;
|
||||||
return new WalletExtension(params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readExtension(Wallet wallet, Protos.Extension extProto) {
|
public byte[] serializeWalletExtension() {
|
||||||
if (wallet instanceof WalletExtension) {
|
return data;
|
||||||
WalletExtension walletExt = (WalletExtension) wallet;
|
}
|
||||||
if (extProto.getId().equals("WalletExtension.random_bytes")) {
|
|
||||||
walletExt.random_bytes = extProto.getData().toByteArray();
|
@Override
|
||||||
return;
|
public void deserializeWalletExtension(byte[] data) {
|
||||||
}
|
assertArrayEquals(this.data, data);
|
||||||
}
|
|
||||||
super.readExtension(wallet, extProto);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user