3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-11 17:55:53 +00:00

Makes Wallet easy to extend.

Adds WalletProtobufHelper, which is an easy class to extend
to add extensions to the wallet serialization.

Modified WalletProtobufSerializer to use that helper.
This commit is contained in:
Fireduck 2012-05-04 22:57:21 -07:00 committed by Mike Hearn
parent db67db5943
commit 61fba05d3d
6 changed files with 206 additions and 31 deletions

View File

@ -350,7 +350,7 @@ public class PeerGroup {
* than the current chain head, the relevant parts of the chain won't be redownloaded for you.</p>
*/
public synchronized void addWallet(Wallet wallet) {
Preconditions.checkNotNull(wallet);
Preconditions.checkNotNull(wallet);
wallets.add(wallet);
addEventListener(wallet.getPeerEventListener());
announcePendingWalletTransactions(Collections.singletonList(wallet), peers);

View File

@ -210,7 +210,7 @@ public class Wallet implements Serializable {
* {@link WalletProtobufSerializer}.
*/
public synchronized void saveToFileStream(OutputStream f) throws IOException {
WalletProtobufSerializer.writeWallet(this, f);
new WalletProtobufSerializer().writeWallet(this, f);
}
/** Returns the parameters this wallet was created with. */
@ -293,7 +293,7 @@ public class Wallet implements Serializable {
if (ois != null) ois.close();
}
} else {
wallet = WalletProtobufSerializer.readWallet(stream);
wallet = new WalletProtobufSerializer().readWallet(stream);
}
if (!wallet.isConsistent()) {

View File

@ -0,0 +1,48 @@
/*
* 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 com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Wallet;
import org.bitcoinj.wallet.Protos;
import java.util.Collection;
import java.util.Collections;
/**
* 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 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.<Protos.Extension>emptyList();
}
}

View File

@ -29,6 +29,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@ -57,17 +58,28 @@ public class WalletProtobufSerializer {
// Used for de-serialization
private Map<ByteString, Transaction> txMap;
private WalletExtensionSerializer helper;
private WalletProtobufSerializer() {
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;
}
/**
* Formats the given wallet (transactions and keys) to the given output stream in protocol buffer format.<p>
*
* Equivalent to <tt>walletToProto(wallet).writeTo(output);</tt>
*/
public static void writeWallet(Wallet wallet, OutputStream output) throws IOException {
public void writeWallet(Wallet wallet, OutputStream output) throws IOException {
Protos.Wallet walletProto = walletToProto(wallet);
walletProto.writeTo(output);
}
@ -79,7 +91,7 @@ public class WalletProtobufSerializer {
* structures anyway, consisting as they do of keys (large random numbers) and {@link Transaction}s which also
* mostly contain keys and hashes.
*/
public static String walletToText(Wallet wallet) {
public String walletToText(Wallet wallet) {
Protos.Wallet walletProto = walletToProto(wallet);
return TextFormat.printToString(walletProto);
}
@ -88,7 +100,7 @@ public class WalletProtobufSerializer {
* Converts the given wallet to the object representation of the protocol buffers. This can be modified, or
* additional data fields set, before serialization takes place.
*/
public static Protos.Wallet walletToProto(Wallet wallet) {
public Protos.Wallet walletToProto(Wallet wallet) {
Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder();
walletBuilder.setNetworkIdentifier(wallet.getNetworkParameters().getId());
for (WalletTransaction wtx : wallet.getWalletTransactions()) {
@ -114,8 +126,15 @@ public class WalletProtobufSerializer {
walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash));
}
Collection<Protos.Extension> extensions = helper.getExtensionsToWrite(wallet);
for(Protos.Extension ext : extensions) {
walletBuilder.addExtension(ext);
}
return walletBuilder.build();
}
private static Protos.Transaction makeTxProto(WalletTransaction wtx) {
Transaction tx = wtx.getTransaction();
@ -198,6 +217,7 @@ public class WalletProtobufSerializer {
return new Sha256Hash(bs.toByteArray());
}
/**
* Parses a wallet from the given stream. The stream is expected to contain a binary serialization of a
* {@link Protos.Wallet} object.<p>
@ -206,7 +226,7 @@ public class WalletProtobufSerializer {
* {@link IllegalArgumentException} is thrown.
*
*/
public static Wallet readWallet(InputStream input) throws IOException {
public Wallet readWallet(InputStream input) throws IOException {
// TODO: This method should throw more specific exception types than IllegalArgumentException.
WalletProtobufSerializer serializer = new WalletProtobufSerializer();
Protos.Wallet walletProto = Protos.Wallet.parseFrom(input);
@ -214,7 +234,7 @@ public class WalletProtobufSerializer {
// System.out.println(TextFormat.printToString(walletProto));
NetworkParameters params = NetworkParameters.fromID(walletProto.getNetworkIdentifier());
Wallet wallet = new Wallet(params);
Wallet wallet = helper.newWallet(params);
// Read all keys
for (Protos.Key keyProto : walletProto.getKeyList()) {
@ -250,9 +270,7 @@ public class WalletProtobufSerializer {
}
for (Protos.Extension extProto : walletProto.getExtensionList()) {
if (extProto.getMandatory()) {
throw new IllegalArgumentException("Did not understand a mandatory extension in the wallet");
}
helper.readExtension(wallet, extProto);
}
return wallet;

View File

@ -652,7 +652,7 @@ public class WalletTest {
// Now check we can serialize old wallets to protocol buffers. Covers bug 134.
bios.reset();
WalletProtobufSerializer.writeWallet(wallet, bios);
new WalletProtobufSerializer().writeWallet(wallet, bios);
}

View File

@ -5,7 +5,6 @@ import com.google.bitcoin.core.*;
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.junit.Before;
import org.junit.Test;
@ -14,6 +13,10 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import static com.google.bitcoin.core.TestUtils.createFakeTx;
import static org.junit.Assert.*;
@ -22,7 +25,7 @@ public class WalletProtobufSerializerTest {
static final NetworkParameters params = NetworkParameters.unitTests();
private ECKey myKey;
private Address myAddress;
private Wallet wallet;
private Wallet myWallet;
@Before
public void setUp() throws Exception {
@ -30,14 +33,14 @@ public class WalletProtobufSerializerTest {
myKey = new ECKey();
myKey.setCreationTimeSeconds(123456789L);
myAddress = myKey.toAddress(params);
wallet = new Wallet(params);
wallet.addKey(myKey);
myWallet = new Wallet(params);
myWallet.addKey(myKey);
}
@Test
public void empty() throws Exception {
// Check the base case of a wallet with one key and no transactions.
Wallet wallet1 = roundTrip(wallet);
Wallet wallet1 = roundTrip(myWallet);
assertEquals(0, wallet1.getTransactions(true, true).size());
assertEquals(BigInteger.ZERO, wallet1.getBalance());
assertArrayEquals(myKey.getPubKey(),
@ -54,14 +57,14 @@ public class WalletProtobufSerializerTest {
BigInteger v1 = Utils.toNanoCoins(1, 0);
Transaction t1 = createFakeTx(params, v1, myAddress);
wallet.receiveFromBlock(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
Wallet wallet1 = roundTrip(wallet);
myWallet.receiveFromBlock(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
Wallet wallet1 = roundTrip(myWallet);
assertEquals(1, wallet1.getTransactions(true, true).size());
assertEquals(v1, wallet1.getBalance());
assertArrayEquals(t1.bitcoinSerialize(),
wallet1.getTransaction(t1.getHash()).bitcoinSerialize());
Protos.Wallet walletProto = WalletProtobufSerializer.walletToProto(wallet);
Protos.Wallet walletProto = new WalletProtobufSerializer().walletToProto(myWallet);
assertEquals(Protos.Key.Type.ORIGINAL, walletProto.getKey(0).getType());
assertEquals(0, walletProto.getExtensionCount());
assertEquals(1, walletProto.getTransactionCount());
@ -84,10 +87,10 @@ public class WalletProtobufSerializerTest {
// Check that we can serialize double spends correctly, as this is a slightly tricky case.
TestUtils.DoubleSpends doubleSpends = TestUtils.createFakeDoubleSpendTxns(params, myAddress);
// t1 spends to our wallet.
wallet.receivePending(doubleSpends.t1);
myWallet.receivePending(doubleSpends.t1);
// t2 rolls back t1 and spends somewhere else.
wallet.receiveFromBlock(doubleSpends.t2, null, BlockChain.NewBlockType.BEST_CHAIN);
Wallet wallet1 = roundTrip(wallet);
myWallet.receiveFromBlock(doubleSpends.t2, null, BlockChain.NewBlockType.BEST_CHAIN);
Wallet wallet1 = roundTrip(myWallet);
assertEquals(1, wallet1.getTransactions(true, true).size());
Transaction t1 = wallet1.getTransaction(doubleSpends.t1.getHash());
assertEquals(ConfidenceType.OVERRIDDEN_BY_DOUBLE_SPEND, t1.getConfidence().getConfidenceType());
@ -102,9 +105,9 @@ public class WalletProtobufSerializerTest {
for (int i = 0 ; i < 20 ; i++) {
myKey = new ECKey();
myAddress = myKey.toAddress(params);
wallet = new Wallet(params);
wallet.addKey(myKey);
Wallet wallet1 = roundTrip(wallet);
myWallet = new Wallet(params);
myWallet.addKey(myKey);
Wallet wallet1 = roundTrip(myWallet);
assertArrayEquals(myKey.getPubKey(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
}
@ -115,8 +118,8 @@ public class WalletProtobufSerializerTest {
// Test the lastBlockSeenHash field works.
// LastBlockSeenHash should be empty if never set.
wallet = new Wallet(params);
Protos.Wallet walletProto = WalletProtobufSerializer.walletToProto(wallet);
Wallet wallet = new Wallet(params);
Protos.Wallet walletProto = new WalletProtobufSerializer().walletToProto(wallet);
ByteString lastSeenBlockHash = walletProto.getLastSeenBlockHash();
assertTrue(lastSeenBlockHash.isEmpty());
@ -139,8 +142,114 @@ public class WalletProtobufSerializerTest {
private Wallet roundTrip(Wallet wallet) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
//System.out.println(WalletProtobufSerializer.walletToText(wallet));
WalletProtobufSerializer.writeWallet(wallet, output);
new WalletProtobufSerializer().writeWallet(wallet, output);
ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
return WalletProtobufSerializer.readWallet(input);
return new WalletProtobufSerializer().readWallet(input);
}
@Test
public void testSerializedExtensionNormalWallet() throws Exception {
Wallet wallet1 = roundTrip(myWallet);
assertEquals(0, wallet1.getTransactions(true, true).size());
assertEquals(BigInteger.ZERO, wallet1.getBalance());
assertArrayEquals(myKey.getPubKey(),
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey());
assertArrayEquals(myKey.getPrivKeyBytes(),
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
assertEquals(myKey.getCreationTimeSeconds(),
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
}
@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 instanceof WalletExtension);
WalletExtension wallet2ext = (WalletExtension)wallet2;
assertNotNull(wallet2ext.random_bytes);
for (int i = 0; i < 100; i++) {
assertEquals(wallet1.random_bytes[i], wallet2ext.random_bytes[i]);
}
}
@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);
Wallet wallet2 = roundTrip(myWallet);
assertFalse(wallet2 instanceof WalletExtension);
}
private Wallet roundTripExtension(Wallet wallet) throws IOException {
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 class WalletExtension extends Wallet {
public byte[] random_bytes;
public WalletExtension(NetworkParameters params) {
super(params);
}
}
public 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
public Wallet newWallet(NetworkParameters params) {
return new WalletExtension(params);
}
@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);
}
}
}