From c64453f835eeb8213ad905942081058a75198f72 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 6 May 2013 15:40:36 +0200 Subject: [PATCH] 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. --- .../java/com/google/bitcoin/core/Wallet.java | 50 +++++-- .../google/bitcoin/core/WalletExtension.java | 44 ++++++ .../store/WalletExtensionSerializer.java | 54 ------- .../store/WalletProtobufSerializer.java | 123 ++++++++-------- .../store/WalletProtobufSerializerTest.java | 133 +++++++----------- 5 files changed, 197 insertions(+), 207 deletions(-) create mode 100644 core/src/main/java/com/google/bitcoin/core/WalletExtension.java delete mode 100644 core/src/main/java/com/google/bitcoin/store/WalletExtensionSerializer.java diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index 24407e96..8e35f9f5 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -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 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(); dead = new HashMap(); eventListeners = new CopyOnWriteArrayList(); + extensions = new HashMap(); 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 getExtensions() { + lock.lock(); + try { + return ImmutableMap.copyOf(extensions); + } finally { + lock.unlock(); + } + } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Boilerplate for running event listeners - unlocks the wallet, runs, re-locks. diff --git a/core/src/main/java/com/google/bitcoin/core/WalletExtension.java b/core/src/main/java/com/google/bitcoin/core/WalletExtension.java new file mode 100644 index 00000000..8722d6f1 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/core/WalletExtension.java @@ -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; + +/** + *

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

+ * + *

Note that extensions are singletons - you cannot add two objects that provide the same ID to the same wallet.

+ */ +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); +} diff --git a/core/src/main/java/com/google/bitcoin/store/WalletExtensionSerializer.java b/core/src/main/java/com/google/bitcoin/store/WalletExtensionSerializer.java deleted file mode 100644 index 822f294d..00000000 --- a/core/src/main/java/com/google/bitcoin/store/WalletExtensionSerializer.java +++ /dev/null @@ -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 getExtensionsToWrite(Wallet wallet) { - return Collections.emptyList(); - } -} diff --git a/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java b/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java index 5e75ac86..89184b18 100644 --- a/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java +++ b/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java @@ -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 txMap; - protected WalletExtensionSerializer helper; public WalletProtobufSerializer() { txMap = new HashMap(); - 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 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.

- * - * @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 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. diff --git a/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java b/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java index 470b63e5..c39b6692 100644 --- a/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java +++ b/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java @@ -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 getExtensionsToWrite(Wallet wallet) { - List lst = new LinkedList(); - 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); } } }