3
0
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:
Mike Hearn 2013-05-06 15:40:36 +02:00
parent 3eb3dbcf89
commit c64453f835
5 changed files with 197 additions and 207 deletions

View File

@ -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.

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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.

View File

@ -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);
} }
} }
} }