mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-16 20:37:50 +00:00
Protobuf serialization for Wallet
This commit is contained in:
committed by
Miron Cuperman
parent
0e7e583626
commit
6af16c863c
80
src/bitcoin.proto
Normal file
80
src/bitcoin.proto
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Author: Jim Burton
|
||||
*/
|
||||
|
||||
package wallet;
|
||||
|
||||
option java_package = "org.bitcoinj.wallet";
|
||||
option java_outer_classname = "Protos";
|
||||
|
||||
message Wallet {
|
||||
required string network_identifier = 1; // the network used by this wallet
|
||||
// org.bitcoin.production = production network (Satoshi genesis block)
|
||||
// org.bitcoin.test = test network (Andresen genesis block)
|
||||
|
||||
optional bytes last_seen_block_hash = 2; // the Sha256 hash of the block last seen by this wallet
|
||||
|
||||
message Key {
|
||||
required string private_key = 1; // base58 representation of private key
|
||||
optional string label = 2; // for presentation purposes
|
||||
optional int64 creation_timestamp = 3; // datetime stored as millis since epoch.
|
||||
}
|
||||
repeated Key key = 3;
|
||||
|
||||
|
||||
message Transaction {
|
||||
enum Pool {
|
||||
UNSPENT = 0;
|
||||
SPENT = 1;
|
||||
PENDING = 2;
|
||||
INACTIVE = 3;
|
||||
DEAD = 4;
|
||||
}
|
||||
|
||||
// See com.google.bitcoin.core.Wallet.java for detailed description of pool semantics
|
||||
required Pool pool = 1;
|
||||
|
||||
optional int64 updated_at = 2; // millis since epoch the transaction was last updated
|
||||
|
||||
message TransactionInput {
|
||||
required bytes transaction_out_point_hash = 1;
|
||||
// Sha256Hash of transaction output this input is using
|
||||
required int32 transaction_out_point_index = 2;
|
||||
// index of transaction output used by this input if in this wallet
|
||||
|
||||
required bytes script_bytes = 3; // script of transaction input
|
||||
}
|
||||
|
||||
repeated TransactionInput transaction_input = 3;
|
||||
|
||||
message TransactionOutput {
|
||||
required int64 value = 1;
|
||||
required bytes script_bytes = 2; // script of transaction output
|
||||
optional bytes spent_by_transaction_hash = 3; // if spent, the Sha256Hash of the transaction doing the spend
|
||||
optional int32 spent_by_transaction_index = 4;
|
||||
// if spent, the index of the transaction output of the transaction doing the spend
|
||||
}
|
||||
repeated TransactionOutput transaction_output = 4;
|
||||
|
||||
|
||||
repeated bytes block_hash = 5;
|
||||
// Sha256Hash of block in block chain in which this transaction appears
|
||||
}
|
||||
repeated Transaction transaction = 4;
|
||||
} // end of Wallet
|
||||
@@ -78,6 +78,8 @@ public class NetworkParameters implements Serializable {
|
||||
* signatures using it.
|
||||
*/
|
||||
public byte[] alertSigningKey;
|
||||
|
||||
public String id;
|
||||
|
||||
private static Block createGenesis(NetworkParameters n) {
|
||||
Block genesisBlock = new Block(n);
|
||||
@@ -122,6 +124,7 @@ public class NetworkParameters implements Serializable {
|
||||
n.genesisBlock.setTime(1296688602L);
|
||||
n.genesisBlock.setDifficultyTarget(0x1d07fff8L);
|
||||
n.genesisBlock.setNonce(384568319);
|
||||
n.id = "org.bitcoin.test";
|
||||
String genesisHash = n.genesisBlock.getHashAsString();
|
||||
assert genesisHash.equals("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008") : genesisHash;
|
||||
return n;
|
||||
@@ -148,6 +151,7 @@ public class NetworkParameters implements Serializable {
|
||||
n.genesisBlock.setDifficultyTarget(0x1d00ffffL);
|
||||
n.genesisBlock.setTime(1231006505L);
|
||||
n.genesisBlock.setNonce(2083236893);
|
||||
n.id = "org.bitcoin.production";
|
||||
String genesisHash = n.genesisBlock.getHashAsString();
|
||||
assert genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") : genesisHash;
|
||||
return n;
|
||||
@@ -162,6 +166,14 @@ public class NetworkParameters implements Serializable {
|
||||
n.genesisBlock.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET);
|
||||
n.interval = 10;
|
||||
n.targetTimespan = 200000000; // 6 years. Just a very big number.
|
||||
n.id = "com.google.bitcoin.unittest";
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* A java package style string acting as unique ID for these parameters
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
* Returns a set of blocks which contain the transaction, or null if this transaction doesn't have that data
|
||||
* because it's not stored in the wallet or because it has never appeared in a block.
|
||||
*/
|
||||
Set<StoredBlock> getAppearsIn() {
|
||||
public Set<StoredBlock> getAppearsIn() {
|
||||
return appearsIn;
|
||||
}
|
||||
|
||||
@@ -204,6 +204,9 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
* @param bestChain whether to set the updatedAt timestamp from the block header (only if not already set)
|
||||
*/
|
||||
void setBlockAppearance(StoredBlock block, boolean bestChain) {
|
||||
if (bestChain && updatedAt == null) {
|
||||
updatedAt = new Date(block.getHeader().getTimeSeconds() * 1000);
|
||||
}
|
||||
if (appearsIn == null) {
|
||||
appearsIn = new HashSet<StoredBlock>();
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
|
||||
/**
|
||||
* Returns the connected input.
|
||||
*/
|
||||
TransactionInput getSpentBy() {
|
||||
public TransactionInput getSpentBy() {
|
||||
return spentBy;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import com.google.bitcoin.core.WalletTransaction.Pool;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -150,6 +152,14 @@ public class Wallet implements Serializable {
|
||||
dead = new HashMap<Sha256Hash, Transaction>();
|
||||
eventListeners = new ArrayList<WalletEventListener>();
|
||||
}
|
||||
|
||||
public NetworkParameters getNetworkParameters() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public Iterable<ECKey> getKeys() {
|
||||
return keychain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses Java serialization to save the wallet to the given file.
|
||||
@@ -185,6 +195,12 @@ public class Wallet implements Serializable {
|
||||
public static Wallet loadFromFile(File f) throws IOException {
|
||||
return loadFromFileStream(new FileInputStream(f));
|
||||
}
|
||||
|
||||
private void checkInvariants() {
|
||||
if (getTransactions(true, true).size() !=
|
||||
unspent.size() + spent.size() + pending.size() + dead.size() + inactive.size())
|
||||
throw new RuntimeException("Invariant broken - a tx appears in more than one pool");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a wallet deserialized from the given file input stream.
|
||||
@@ -429,6 +445,8 @@ public class Wallet implements Serializable {
|
||||
if (!reorg && bestChain && valueDifference.compareTo(BigInteger.ZERO) > 0 && wtx == null) {
|
||||
invokeOnCoinsReceived(tx, prevBalance, getBalance());
|
||||
}
|
||||
|
||||
checkInvariants();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -604,6 +622,8 @@ public class Wallet implements Serializable {
|
||||
// Add to the pending pool. It'll be moved out once we receive this transaction on the best chain.
|
||||
log.info("->pending: {}", tx.getHashAsString());
|
||||
pending.put(tx.getHash(), tx);
|
||||
|
||||
checkInvariants();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,6 +644,48 @@ public class Wallet implements Serializable {
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of all WalletTransactions in the wallet.
|
||||
*/
|
||||
public Iterable<WalletTransaction> getWalletTransactions() {
|
||||
Set<WalletTransaction> all = new HashSet<WalletTransaction>();
|
||||
addWalletTransactionsToSet(all, Pool.UNSPENT, unspent);
|
||||
addWalletTransactionsToSet(all, Pool.SPENT, spent);
|
||||
addWalletTransactionsToSet(all, Pool.PENDING, pending);
|
||||
addWalletTransactionsToSet(all, Pool.DEAD, dead);
|
||||
addWalletTransactionsToSet(all, Pool.INACTIVE, inactive);
|
||||
return all;
|
||||
}
|
||||
|
||||
static private void addWalletTransactionsToSet(Set<WalletTransaction> txs,
|
||||
Pool poolType, Map<Sha256Hash, Transaction> pool) {
|
||||
for (Transaction tx : pool.values()) {
|
||||
txs.add(new WalletTransaction(poolType, tx));
|
||||
}
|
||||
}
|
||||
|
||||
public void addWalletTransaction(WalletTransaction wtx) {
|
||||
switch (wtx.getPool()) {
|
||||
case UNSPENT:
|
||||
unspent.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
||||
break;
|
||||
case SPENT:
|
||||
spent.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
||||
break;
|
||||
case PENDING:
|
||||
pending.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
||||
break;
|
||||
case DEAD:
|
||||
dead.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
||||
break;
|
||||
case INACTIVE:
|
||||
inactive.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unknown wallet transaction type " + wtx.getPool());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all non-dead, active transactions ordered by recency.
|
||||
*/
|
||||
@@ -642,7 +704,9 @@ public class Wallet implements Serializable {
|
||||
public List<Transaction> getRecentTransactions(int numTransactions, boolean includeDead) {
|
||||
assert numTransactions >= 0;
|
||||
// Firstly, put all transactions into an array.
|
||||
int size = getPoolSize(Pool.UNSPENT) + getPoolSize(Pool.SPENT) + getPoolSize(Pool.PENDING);
|
||||
int size = getPoolSize(WalletTransaction.Pool.UNSPENT) +
|
||||
getPoolSize(WalletTransaction.Pool.SPENT) +
|
||||
getPoolSize(WalletTransaction.Pool.PENDING);
|
||||
if (numTransactions > size || numTransactions == 0) {
|
||||
numTransactions = size;
|
||||
}
|
||||
@@ -695,16 +759,6 @@ public class Wallet implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
// This is used only for unit testing, it's an internal API.
|
||||
enum Pool {
|
||||
UNSPENT,
|
||||
SPENT,
|
||||
PENDING,
|
||||
INACTIVE,
|
||||
DEAD,
|
||||
ALL,
|
||||
}
|
||||
|
||||
EnumSet<Pool> getContainingPools(Transaction tx) {
|
||||
EnumSet<Pool> result = EnumSet.noneOf(Pool.class);
|
||||
Sha256Hash txHash = tx.getHash();
|
||||
@@ -726,7 +780,7 @@ public class Wallet implements Serializable {
|
||||
return result;
|
||||
}
|
||||
|
||||
int getPoolSize(Pool pool) {
|
||||
int getPoolSize(WalletTransaction.Pool pool) {
|
||||
switch (pool) {
|
||||
case UNSPENT:
|
||||
return unspent.size();
|
||||
@@ -1218,6 +1272,8 @@ public class Wallet implements Serializable {
|
||||
l.onReorganize(this);
|
||||
}
|
||||
}
|
||||
|
||||
checkInvariants();
|
||||
}
|
||||
|
||||
private void reprocessTxAfterReorg(Map<Sha256Hash, Transaction> pool, Transaction tx) {
|
||||
|
||||
58
src/com/google/bitcoin/core/WalletTransaction.java
Normal file
58
src/com/google/bitcoin/core/WalletTransaction.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.core;
|
||||
|
||||
/**
|
||||
* A Transaction in a Wallet - includes the pool ID
|
||||
*
|
||||
* @author Miron Cuperman
|
||||
*/
|
||||
public class WalletTransaction {
|
||||
public enum Pool {
|
||||
UNSPENT(0),
|
||||
SPENT(1),
|
||||
PENDING(2),
|
||||
INACTIVE(3),
|
||||
DEAD(4),
|
||||
ALL(-1);
|
||||
|
||||
private int value;
|
||||
Pool(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
private Transaction transaction;
|
||||
private Pool pool;
|
||||
|
||||
public WalletTransaction(Pool pool, Transaction transaction) {
|
||||
this.pool = pool;
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
public Transaction getTransaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public Pool getPool() {
|
||||
return pool;
|
||||
}
|
||||
}
|
||||
|
||||
102
src/com/google/bitcoin/store/WalletProtobufSerializer.java
Normal file
102
src/com/google/bitcoin/store/WalletProtobufSerializer.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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.ECKey;
|
||||
import com.google.bitcoin.core.StoredBlock;
|
||||
import com.google.bitcoin.core.Transaction;
|
||||
import com.google.bitcoin.core.TransactionInput;
|
||||
import com.google.bitcoin.core.TransactionOutput;
|
||||
import com.google.bitcoin.core.Wallet;
|
||||
import com.google.bitcoin.core.WalletTransaction;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Serialize and de-serialize a wallet to a protobuf stream.
|
||||
*
|
||||
* @author Miron Cuperman
|
||||
*/
|
||||
public class WalletProtobufSerializer {
|
||||
void writeWallet(Wallet wallet, OutputStream output) throws IOException {
|
||||
Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder();
|
||||
walletBuilder
|
||||
.setNetworkIdentifier(wallet.getNetworkParameters().getId())
|
||||
.setLastSeenBlockHash(null) // TODO
|
||||
;
|
||||
for (WalletTransaction wtx : wallet.getWalletTransactions()) {
|
||||
Protos.Wallet.Transaction txProto = makeTxProto(wtx);
|
||||
walletBuilder.addTransaction(txProto);
|
||||
}
|
||||
|
||||
for (ECKey key : wallet.getKeys()) {
|
||||
final String base58PrivateKey =
|
||||
key.getPrivateKeyEncoded(wallet.getNetworkParameters()).toString();
|
||||
walletBuilder.addKey(
|
||||
Protos.Wallet.Key.newBuilder()
|
||||
// .setCreationTimestamp() TODO
|
||||
// .setLabel() TODO
|
||||
.setPrivateKey(base58PrivateKey));
|
||||
}
|
||||
|
||||
walletBuilder.build().writeTo(output);
|
||||
}
|
||||
|
||||
private Protos.Wallet.Transaction makeTxProto(WalletTransaction wtx) {
|
||||
Transaction tx = wtx.getTransaction();
|
||||
Protos.Wallet.Transaction.Builder txBuilder = Protos.Wallet.Transaction.newBuilder();
|
||||
|
||||
txBuilder
|
||||
.setUpdatedAt(tx.getUpdateTime().getTime())
|
||||
.setPool(Protos.Wallet.Transaction.Pool.valueOf(wtx.getPool().getValue()));
|
||||
|
||||
// Handle inputs
|
||||
for (TransactionInput input : tx.getInputs()) {
|
||||
txBuilder.addTransactionInput(
|
||||
Protos.Wallet.Transaction.TransactionInput.newBuilder()
|
||||
.setScriptBytes(ByteString.copyFrom(input.getScriptBytes()))
|
||||
.setTransactionOutPointHash(ByteString.copyFrom(
|
||||
input.getOutpoint().getHash().getBytes()))
|
||||
.setTransactionOutPointIndex((int)input.getOutpoint().getIndex()) // FIXME
|
||||
);
|
||||
}
|
||||
|
||||
// Handle outputs
|
||||
for (TransactionOutput output : tx.getOutputs()) {
|
||||
final TransactionInput spentBy = output.getSpentBy();
|
||||
txBuilder.addTransactionOutput(
|
||||
Protos.Wallet.Transaction.TransactionOutput.newBuilder()
|
||||
.setScriptBytes(ByteString.copyFrom(output.getScriptBytes()))
|
||||
.setSpentByTransactionHash(ByteString.copyFrom(
|
||||
spentBy.getHash().getBytes()))
|
||||
.setSpentByTransactionIndex((int)spentBy.getOutpoint().getIndex()) // FIXME
|
||||
.setValue(output.getValue().longValue())
|
||||
);
|
||||
}
|
||||
|
||||
// Handle which blocks tx was seen in
|
||||
for (StoredBlock block : tx.getAppearsIn()) {
|
||||
txBuilder.addBlockHash(ByteString.copyFrom(block.getHeader().getHash().getBytes()));
|
||||
}
|
||||
|
||||
return txBuilder.build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user