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

Read Wallet from protobuf stream

This commit is contained in:
Miron Cuperman 2012-01-06 16:31:43 -08:00 committed by Miron Cuperman
parent 6af16c863c
commit 319c52b2a6
7 changed files with 185 additions and 20 deletions

View File

@ -48,9 +48,10 @@ message Wallet {
} }
// See com.google.bitcoin.core.Wallet.java for detailed description of pool semantics // See com.google.bitcoin.core.Wallet.java for detailed description of pool semantics
required Pool pool = 1; required bytes hash = 1;
required Pool pool = 2;
optional int64 updated_at = 2; // millis since epoch the transaction was last updated optional int64 updated_at = 3; // millis since epoch the transaction was last updated
message TransactionInput { message TransactionInput {
required bytes transaction_out_point_hash = 1; required bytes transaction_out_point_hash = 1;
@ -61,7 +62,7 @@ message Wallet {
required bytes script_bytes = 3; // script of transaction input required bytes script_bytes = 3; // script of transaction input
} }
repeated TransactionInput transaction_input = 3; repeated TransactionInput transaction_input = 4;
message TransactionOutput { message TransactionOutput {
required int64 value = 1; required int64 value = 1;
@ -70,10 +71,10 @@ message Wallet {
optional int32 spent_by_transaction_index = 4; optional int32 spent_by_transaction_index = 4;
// if spent, the index of the transaction output of the transaction doing the spend // if spent, the index of the transaction output of the transaction doing the spend
} }
repeated TransactionOutput transaction_output = 4; repeated TransactionOutput transaction_output = 5;
repeated bytes block_hash = 5; repeated bytes block_hash = 6;
// Sha256Hash of block in block chain in which this transaction appears // Sha256Hash of block in block chain in which this transaction appears
} }
repeated Transaction transaction = 4; repeated Transaction transaction = 4;

View File

@ -78,7 +78,7 @@ public class Transaction extends ChildMessage implements Serializable {
// Data about how confirmed this tx is. Serialized, may be null. // Data about how confirmed this tx is. Serialized, may be null.
private TransactionConfidence confidence; private TransactionConfidence confidence;
Transaction(NetworkParameters params) { public Transaction(NetworkParameters params) {
super(params); super(params);
version = 1; version = 1;
inputs = new ArrayList<TransactionInput>(); inputs = new ArrayList<TransactionInput>();
@ -87,6 +87,16 @@ public class Transaction extends ChildMessage implements Serializable {
length = 10; // 8 for std fields + 1 for each 0 varint length = 10; // 8 for std fields + 1 for each 0 varint
} }
public Transaction(NetworkParameters params, Sha256Hash hash) {
super(params);
version = 1;
inputs = new ArrayList<TransactionInput>();
outputs = new ArrayList<TransactionOutput>();
this.hash = hash;
// We don't initialize appearsIn deliberately as it's only useful for transactions stored in the wallet.
length = 10; //8 for std fields + 1 for each 0 varint
}
/** /**
* Creates a transaction from the given serialized bytes, eg, from a block or a tx network message. * Creates a transaction from the given serialized bytes, eg, from a block or a tx network message.
*/ */
@ -338,6 +348,10 @@ public class Transaction extends ChildMessage implements Serializable {
return updatedAt; return updatedAt;
} }
public void setUpdateTime(Date updatedAt) {
this.updatedAt = updatedAt;
}
/** /**
* These constants are a part of a scriptSig signature on the inputs. They define the details of how a * These constants are a part of a scriptSig signature on the inputs. They define the details of how a
* transaction can be redeemed, specifically, they control how the hash of the transaction is calculated. * transaction can be redeemed, specifically, they control how the hash of the transaction is calculated.

View File

@ -61,6 +61,18 @@ public class TransactionInput extends ChildMessage implements Serializable {
length = 40 + (scriptBytes == null ? 1 : VarInt.sizeOf(scriptBytes.length) + scriptBytes.length); length = 40 + (scriptBytes == null ? 1 : VarInt.sizeOf(scriptBytes.length) + scriptBytes.length);
} }
public TransactionInput(NetworkParameters params, Transaction parentTransaction,
byte[] scriptBytes,
TransactionOutPoint outpoint) {
super(params);
this.scriptBytes = scriptBytes;
this.outpoint = outpoint;
this.sequence = 0xFFFFFFFFL;
this.parentTransaction = parentTransaction;
length = 40 + (scriptBytes == null ? 1 : VarInt.sizeOf(scriptBytes.length) + scriptBytes.length);
}
/** /**
* Creates an UNSIGNED input that links to the given output * Creates an UNSIGNED input that links to the given output
*/ */

View File

@ -38,7 +38,7 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
// It points to the connected transaction. // It points to the connected transaction.
Transaction fromTx; Transaction fromTx;
TransactionOutPoint(NetworkParameters params, long index, Transaction fromTx) { public TransactionOutPoint(NetworkParameters params, long index, Transaction fromTx) {
super(params); super(params);
this.index = index; this.index = index;
if (fromTx != null) { if (fromTx != null) {

View File

@ -92,6 +92,15 @@ public class TransactionOutput extends ChildMessage implements Serializable {
length = 8 + VarInt.sizeOf(scriptBytes.length) + scriptBytes.length; length = 8 + VarInt.sizeOf(scriptBytes.length) + scriptBytes.length;
} }
public TransactionOutput(NetworkParameters params, Transaction parent, BigInteger value, byte[] scriptBytes) {
super(params);
this.value = value;
this.scriptBytes = scriptBytes;
parentTransaction = parent;
availableForSpending = true;
length = 8 + VarInt.sizeOf(scriptBytes.length) + scriptBytes.length;
}
/** /**
* Used only in creation of the genesis blocks and in unit tests. * Used only in creation of the genesis blocks and in unit tests.
*/ */
@ -153,7 +162,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
* Sets this objects availableToSpend flag to false and the spentBy pointer to the given input. * Sets this objects availableToSpend flag to false and the spentBy pointer to the given input.
* If the input is null, it means this output was signed over to somebody else rather than one of our own keys. * If the input is null, it means this output was signed over to somebody else rather than one of our own keys.
*/ */
void markAsSpent(TransactionInput input) { public void markAsSpent(TransactionInput input) {
assert availableForSpending; assert availableForSpending;
availableForSpending = false; availableForSpending = false;
spentBy = input; spentBy = input;

View File

@ -16,6 +16,7 @@
package com.google.bitcoin.core; package com.google.bitcoin.core;
/** /**
* A Transaction in a Wallet - includes the pool ID * A Transaction in a Wallet - includes the pool ID
* *
@ -38,6 +39,17 @@ public class WalletTransaction {
public int getValue() { public int getValue() {
return value; return value;
} }
public static Pool valueOf(int value) {
switch (value) {
case 0: return UNSPENT;
case 1: return SPENT;
case 2: return PENDING;
case 3: return INACTIVE;
case 4: return DEAD;
default: return null;
}
}
} }
private Transaction transaction; private Transaction transaction;
private Pool pool; private Pool pool;

View File

@ -16,10 +16,15 @@
package com.google.bitcoin.store; package com.google.bitcoin.store;
import com.google.bitcoin.core.AddressFormatException;
import com.google.bitcoin.core.DumpedPrivateKey;
import com.google.bitcoin.core.ECKey; import com.google.bitcoin.core.ECKey;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.core.StoredBlock; import com.google.bitcoin.core.StoredBlock;
import com.google.bitcoin.core.Transaction; import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionInput; import com.google.bitcoin.core.TransactionInput;
import com.google.bitcoin.core.TransactionOutPoint;
import com.google.bitcoin.core.TransactionOutput; import com.google.bitcoin.core.TransactionOutput;
import com.google.bitcoin.core.Wallet; import com.google.bitcoin.core.Wallet;
import com.google.bitcoin.core.WalletTransaction; import com.google.bitcoin.core.WalletTransaction;
@ -28,7 +33,12 @@ import com.google.protobuf.ByteString;
import org.bitcoinj.wallet.Protos; import org.bitcoinj.wallet.Protos;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** /**
* Serialize and de-serialize a wallet to a protobuf stream. * Serialize and de-serialize a wallet to a protobuf stream.
@ -36,7 +46,14 @@ import java.io.OutputStream;
* @author Miron Cuperman * @author Miron Cuperman
*/ */
public class WalletProtobufSerializer { public class WalletProtobufSerializer {
void writeWallet(Wallet wallet, OutputStream output) throws IOException { // Used for de-serialization
private Map<ByteString, Transaction> txMap;
public WalletProtobufSerializer() {
txMap = new HashMap<ByteString, Transaction>();
}
static public void writeWallet(Wallet wallet, OutputStream output) throws IOException {
Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder(); Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder();
walletBuilder walletBuilder
.setNetworkIdentifier(wallet.getNetworkParameters().getId()) .setNetworkIdentifier(wallet.getNetworkParameters().getId())
@ -60,13 +77,15 @@ public class WalletProtobufSerializer {
walletBuilder.build().writeTo(output); walletBuilder.build().writeTo(output);
} }
private Protos.Wallet.Transaction makeTxProto(WalletTransaction wtx) { private static Protos.Wallet.Transaction makeTxProto(WalletTransaction wtx) {
Transaction tx = wtx.getTransaction(); Transaction tx = wtx.getTransaction();
Protos.Wallet.Transaction.Builder txBuilder = Protos.Wallet.Transaction.newBuilder(); Protos.Wallet.Transaction.Builder txBuilder = Protos.Wallet.Transaction.newBuilder();
txBuilder txBuilder
.setUpdatedAt(tx.getUpdateTime().getTime()) .setUpdatedAt(tx.getUpdateTime().getTime())
.setPool(Protos.Wallet.Transaction.Pool.valueOf(wtx.getPool().getValue())); .setPool(Protos.Wallet.Transaction.Pool.valueOf(wtx.getPool().getValue()))
.setHash(ByteString.copyFrom(tx.getHash().getBytes()))
;
// Handle inputs // Handle inputs
for (TransactionInput input : tx.getInputs()) { for (TransactionInput input : tx.getInputs()) {
@ -81,15 +100,17 @@ public class WalletProtobufSerializer {
// Handle outputs // Handle outputs
for (TransactionOutput output : tx.getOutputs()) { for (TransactionOutput output : tx.getOutputs()) {
final TransactionInput spentBy = output.getSpentBy(); Protos.Wallet.Transaction.TransactionOutput.Builder outputBuilder =
txBuilder.addTransactionOutput(
Protos.Wallet.Transaction.TransactionOutput.newBuilder() Protos.Wallet.Transaction.TransactionOutput.newBuilder()
.setScriptBytes(ByteString.copyFrom(output.getScriptBytes())) .setScriptBytes(ByteString.copyFrom(output.getScriptBytes()))
.setSpentByTransactionHash(ByteString.copyFrom( .setValue(output.getValue().longValue());
spentBy.getHash().getBytes())) final TransactionInput spentBy = output.getSpentBy();
.setSpentByTransactionIndex((int)spentBy.getOutpoint().getIndex()) // FIXME if (spentBy != null) {
.setValue(output.getValue().longValue()) outputBuilder
); .setSpentByTransactionHash(ByteString.copyFrom(spentBy.getHash().getBytes()))
.setSpentByTransactionIndex((int)spentBy.getOutpoint().getIndex()); // FIXME
}
txBuilder.addTransactionOutput(outputBuilder);
} }
// Handle which blocks tx was seen in // Handle which blocks tx was seen in
@ -99,4 +120,100 @@ public class WalletProtobufSerializer {
return txBuilder.build(); return txBuilder.build();
} }
static public Wallet readWallet(InputStream input, NetworkParameters params, BlockStore store)
throws IOException, AddressFormatException, BlockStoreException {
WalletProtobufSerializer serializer = new WalletProtobufSerializer();
Protos.Wallet walletProto = Protos.Wallet.parseFrom(input);
if (!params.getId().equals(walletProto.getNetworkIdentifier()))
throw new IllegalArgumentException(
"Trying to read a wallet with a different network id " +
walletProto.getNetworkIdentifier());
Wallet wallet = new Wallet(params);
// Read all keys
for (Protos.Wallet.Key keyProto : walletProto.getKeyList()) {
wallet.addKey(new DumpedPrivateKey(params, keyProto.getPrivateKey()).getKey());
}
// Read all transactions and create outputs
for (Protos.Wallet.Transaction txProto : walletProto.getTransactionList()) {
serializer.readTransaction(txProto, params, store);
}
// Create transactions inputs pointing to transactions
for (Protos.Wallet.Transaction txProto : walletProto.getTransactionList()) {
serializer.connectTransactionInputs(txProto, params);
}
// Update transaction outputs to point to inputs that spend them
for (Protos.Wallet.Transaction txProto : walletProto.getTransactionList()) {
WalletTransaction wtx = serializer.connectTransactionOutputs(txProto, params);
wallet.addWalletTransaction(wtx);
}
return wallet;
}
private void readTransaction(Protos.Wallet.Transaction txProto,
NetworkParameters params, BlockStore store) throws BlockStoreException {
Transaction tx = new Transaction(params, new Sha256Hash(txProto.getHash().toByteArray()));
if (txProto.hasUpdatedAt())
tx.setUpdateTime(new Date(txProto.getUpdatedAt()));
for (Protos.Wallet.Transaction.TransactionOutput outputProto :
txProto.getTransactionOutputList()) {
TransactionOutput output = new TransactionOutput(params, tx,
BigInteger.valueOf(outputProto.getValue()),
outputProto.getScriptBytes().toByteArray());
tx.addOutput(output);
}
if (txMap.containsKey(tx.getHash())) {
throw new RuntimeException("Transaction " + tx.getHashAsString() + " appears twice");
}
for (ByteString blockHash : txProto.getBlockHashList()) {
tx.addBlockAppearance(store.get(new Sha256Hash(blockHash.toByteArray())), false);
}
txMap.put(txProto.getHash(), tx);
}
private void connectTransactionInputs(Protos.Wallet.Transaction txProto, NetworkParameters params) {
Transaction tx = txMap.get(txProto.getHash());
for (Protos.Wallet.Transaction.TransactionInput transactionInput : txProto.getTransactionInputList()) {
TransactionInput input =
new TransactionInput(params, tx,
transactionInput.getScriptBytes().toByteArray(),
new TransactionOutPoint(params,
transactionInput.getTransactionOutPointIndex(),
txMap.get(transactionInput.getTransactionOutPointHash())
)
);
tx.addInput(input);
}
}
private WalletTransaction connectTransactionOutputs(
org.bitcoinj.wallet.Protos.Wallet.Transaction txProto, NetworkParameters params) {
Transaction tx = txMap.get(txProto.getHash());
WalletTransaction.Pool pool =
WalletTransaction.Pool.valueOf(txProto.getPool().getNumber());
for (int i = 0 ; i < tx.getOutputs().size() ; i++) {
TransactionOutput output = tx.getOutputs().get(i);
final Protos.Wallet.Transaction.TransactionOutput transactionOutput =
txProto.getTransactionOutput(i);
if (transactionOutput.hasSpentByTransactionHash()) {
Transaction spendingTx =
txMap.get(transactionOutput.getSpentByTransactionHash());
final int spendingIndex = transactionOutput.getSpentByTransactionIndex();
output.markAsSpent(spendingTx.getInputs().get(spendingIndex));
}
}
return new WalletTransaction(pool, tx);
}
} }