mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-07 23:03:04 +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;
|
||||
|
||||
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.WalletTransaction.Pool;
|
||||
import com.google.bitcoin.crypto.KeyCrypter;
|
||||
import com.google.bitcoin.crypto.KeyCrypterException;
|
||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||
import com.google.bitcoin.store.WalletProtobufSerializer;
|
||||
import com.google.bitcoin.utils.Locks;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.*;
|
||||
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.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
@ -249,6 +244,9 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
private int version;
|
||||
// User-provided description that may help people keep track of what a wallet is for.
|
||||
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,
|
||||
@ -270,6 +268,7 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
pending = new HashMap<Sha256Hash, Transaction>();
|
||||
dead = new HashMap<Sha256Hash, Transaction>();
|
||||
eventListeners = new CopyOnWriteArrayList<WalletEventListener>();
|
||||
extensions = new HashMap<String, WalletExtension>();
|
||||
createTransientState();
|
||||
}
|
||||
|
||||
@ -2847,6 +2846,37 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
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.
|
||||
|
@ -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;
|
||||
|
||||
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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@ -24,31 +37,6 @@ import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -75,19 +63,9 @@ public class WalletProtobufSerializer {
|
||||
|
||||
// Used for de-serialization
|
||||
protected Map<ByteString, Transaction> txMap;
|
||||
protected WalletExtensionSerializer helper;
|
||||
|
||||
public WalletProtobufSerializer() {
|
||||
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.
|
||||
walletBuilder.setVersion(wallet.getVersion());
|
||||
|
||||
Collection<Protos.Extension> extensions = helper.getExtensionsToWrite(wallet);
|
||||
for(Protos.Extension ext : extensions) {
|
||||
walletBuilder.addExtension(ext);
|
||||
}
|
||||
|
||||
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();
|
||||
Protos.Transaction.Builder txBuilder = Protos.Transaction.newBuilder();
|
||||
|
||||
@ -257,7 +242,7 @@ public class WalletProtobufSerializer {
|
||||
return txBuilder.build();
|
||||
}
|
||||
|
||||
protected static void writeConfidence(Protos.Transaction.Builder txBuilder,
|
||||
private static void writeConfidence(Protos.Transaction.Builder txBuilder,
|
||||
TransactionConfidence confidence,
|
||||
Protos.TransactionConfidence.Builder confidenceBuilder) {
|
||||
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
|
||||
* {@link Protos.Wallet} object.<p>
|
||||
*
|
||||
* @throws IOException if there is a problem reading the stream.
|
||||
* @throws IllegalArgumentException if the wallet is corrupt.
|
||||
* Parses a wallet from the given stream, using the provided Wallet instance to load data into. This is primarily
|
||||
* used when you want to register extensions. Data in the proto will be added into the wallet where applicable and
|
||||
* overwrite where not.
|
||||
*/
|
||||
public Wallet readWallet(InputStream input) throws IOException {
|
||||
// TODO: This method should throw more specific exception types than IllegalArgumentException.
|
||||
Protos.Wallet walletProto = parseToProto(input);
|
||||
|
||||
// System.out.println(TextFormat.printToString(walletProto));
|
||||
|
||||
// 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;
|
||||
if (walletProto.hasEncryptionParameters()) {
|
||||
Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters();
|
||||
@ -329,8 +312,21 @@ public class WalletProtobufSerializer {
|
||||
}
|
||||
|
||||
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()) {
|
||||
wallet.setDescription(walletProto.getDescription());
|
||||
}
|
||||
@ -352,6 +348,7 @@ public class WalletProtobufSerializer {
|
||||
byte[] pubKey = keyProto.hasPublicKey() ? keyProto.getPublicKey().toByteArray() : null;
|
||||
|
||||
ECKey ecKey;
|
||||
final KeyCrypter keyCrypter = wallet.getKeyCrypter();
|
||||
if (keyCrypter != null && keyCrypter.getUnderstoodEncryptionType() != EncryptionType.UNENCRYPTED) {
|
||||
// If the key is encrypted construct an ECKey using the encrypted private key bytes.
|
||||
ecKey = new ECKey(encryptedPrivateKey, pubKey, keyCrypter);
|
||||
@ -365,7 +362,7 @@ public class WalletProtobufSerializer {
|
||||
|
||||
// Read all transactions and insert into the txMap.
|
||||
for (Protos.Transaction txProto : walletProto.getTransactionList()) {
|
||||
readTransaction(txProto, params);
|
||||
readTransaction(txProto, wallet.getParams());
|
||||
}
|
||||
|
||||
// Update transaction outputs to point to inputs that spend them
|
||||
@ -386,9 +383,7 @@ public class WalletProtobufSerializer {
|
||||
wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight());
|
||||
}
|
||||
|
||||
for (Protos.Extension extProto : walletProto.getExtensionList()) {
|
||||
helper.readExtension(wallet, extProto);
|
||||
}
|
||||
loadExtensions(wallet, walletProto);
|
||||
|
||||
if (walletProto.hasVersion()) {
|
||||
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.
|
||||
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);
|
||||
}
|
||||
|
||||
protected void readTransaction(Protos.Transaction txProto, NetworkParameters params) {
|
||||
private void readTransaction(Protos.Transaction txProto, NetworkParameters params) {
|
||||
Transaction tx = new Transaction(params);
|
||||
if (txProto.hasUpdatedAt()) {
|
||||
tx.setUpdateTime(new Date(txProto.getUpdatedAt()));
|
||||
@ -452,7 +461,7 @@ public class WalletProtobufSerializer {
|
||||
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());
|
||||
WalletTransaction.Pool pool = WalletTransaction.Pool.valueOf(txProto.getPool().getNumber());
|
||||
if (pool == WalletTransaction.Pool.INACTIVE || pool == WalletTransaction.Pool.PENDING_INACTIVE) {
|
||||
@ -486,7 +495,7 @@ public class WalletProtobufSerializer {
|
||||
return new WalletTransaction(pool, tx);
|
||||
}
|
||||
|
||||
protected void readConfidence(Transaction tx, Protos.TransactionConfidence confidenceProto,
|
||||
private void readConfidence(Transaction tx, Protos.TransactionConfidence confidenceProto,
|
||||
TransactionConfidence confidence) {
|
||||
// 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.
|
||||
|
@ -6,22 +6,20 @@ import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
||||
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
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 org.junit.Assert.*;
|
||||
|
||||
import com.google.bitcoin.crypto.KeyCrypter;
|
||||
|
||||
public class WalletProtobufSerializerTest {
|
||||
static final NetworkParameters params = NetworkParameters.unitTests();
|
||||
private ECKey myKey;
|
||||
@ -227,7 +225,7 @@ public class WalletProtobufSerializerTest {
|
||||
assertEquals(work2, rebornConfidence1.getWorkDone());
|
||||
}
|
||||
|
||||
private Wallet roundTrip(Wallet wallet) throws Exception {
|
||||
private static Wallet roundTrip(Wallet wallet) throws Exception {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
//System.out.println(WalletProtobufSerializer.walletToText(wallet));
|
||||
new WalletProtobufSerializer().writeWallet(wallet, output);
|
||||
@ -237,7 +235,7 @@ public class WalletProtobufSerializerTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testSerializedExtensionNormalWallet() throws Exception {
|
||||
public void testRoundTripNormalWallet() throws Exception {
|
||||
Wallet wallet1 = roundTrip(myWallet);
|
||||
assertEquals(0, wallet1.getTransactions(true).size());
|
||||
assertEquals(BigInteger.ZERO, wallet1.getBalance());
|
||||
@ -251,99 +249,62 @@ public class WalletProtobufSerializerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializedExtensionFancyWallet() throws Exception {
|
||||
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 = roundTripExtension(wallet1);
|
||||
assertTrue("Wallet2 is not an instance of WalletExtension. It is a " + wallet2.getClass().getCanonicalName(), wallet2 instanceof WalletExtension);
|
||||
|
||||
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]);
|
||||
public void testExtensions() throws Exception {
|
||||
myWallet.addExtension(new SomeFooExtension("com.whatever.required", true));
|
||||
Protos.Wallet proto = new WalletProtobufSerializer().walletToProto(myWallet);
|
||||
Wallet wallet2 = new Wallet(params);
|
||||
// Initial extension is mandatory: try to read it back into a wallet that doesn't know about it.
|
||||
try {
|
||||
new WalletProtobufSerializer().readWallet(proto, wallet2);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected.
|
||||
}
|
||||
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
|
||||
public void testSerializedExtensionFancyWalletRegularTrip() throws Exception {
|
||||
Random rnd = new Random();
|
||||
WalletExtension wallet1 = new WalletExtension(params);
|
||||
wallet1.addKey(myKey);
|
||||
wallet1.random_bytes=new byte[100];
|
||||
rnd.nextBytes(wallet1.random_bytes);
|
||||
private static class SomeFooExtension implements WalletExtension {
|
||||
private final byte[] data = new byte[]{1, 2, 3};
|
||||
|
||||
Wallet wallet2 = roundTrip(myWallet);
|
||||
assertFalse(wallet2 instanceof WalletExtension);
|
||||
private final boolean isMandatory;
|
||||
private final String 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;
|
||||
public SomeFooExtension(String id, boolean isMandatory) {
|
||||
this.isMandatory = isMandatory;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wallet newWallet(NetworkParameters params) {
|
||||
return new WalletExtension(params);
|
||||
public String getWalletExtensionID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wallet newWallet(NetworkParameters params, KeyCrypter keyCrypter) {
|
||||
// Ignore encryption.
|
||||
return new WalletExtension(params);
|
||||
public boolean isWalletExtensionMandatory() {
|
||||
return isMandatory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExtension(Wallet wallet, Protos.Extension extProto) {
|
||||
if (wallet instanceof WalletExtension) {
|
||||
WalletExtension walletExt = (WalletExtension) wallet;
|
||||
if (extProto.getId().equals("WalletExtension.random_bytes")) {
|
||||
walletExt.random_bytes = extProto.getData().toByteArray();
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.readExtension(wallet, extProto);
|
||||
public byte[] serializeWalletExtension() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeWalletExtension(byte[] data) {
|
||||
assertArrayEquals(this.data, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user