forked from Qortal/qortal
GetTransaction test app to demo fetching any bitcoin transaction using bitcoinj. Plus some AT-API work
This commit is contained in:
parent
2c4bad6455
commit
8844cc0076
153
src/main/java/org/qora/at/BlockchainAPI.java
Normal file
153
src/main/java/org/qora/at/BlockchainAPI.java
Normal file
@ -0,0 +1,153 @@
|
||||
package org.qora.at;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.Timestamp;
|
||||
import org.qora.account.Account;
|
||||
import org.qora.block.Block;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.data.transaction.ATTransactionData;
|
||||
import org.qora.data.transaction.PaymentTransactionData;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.repository.BlockRepository;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.transaction.Transaction;
|
||||
|
||||
public enum BlockchainAPI {
|
||||
|
||||
QORA(0) {
|
||||
@Override
|
||||
public void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state) {
|
||||
int height = timestamp.blockHeight;
|
||||
int sequence = timestamp.transactionSequence + 1;
|
||||
|
||||
QoraATAPI api = (QoraATAPI) state.getAPI();
|
||||
BlockRepository blockRepository = api.getRepository().getBlockRepository();
|
||||
|
||||
try {
|
||||
Account recipientAccount = new Account(api.getRepository(), recipient);
|
||||
|
||||
while (height <= blockRepository.getBlockchainHeight()) {
|
||||
BlockData blockData = blockRepository.fromHeight(height);
|
||||
|
||||
if (blockData == null)
|
||||
throw new DataException("Unable to fetch block " + height + " from repository?");
|
||||
|
||||
Block block = new Block(api.getRepository(), blockData);
|
||||
|
||||
List<Transaction> transactions = block.getTransactions();
|
||||
|
||||
// No more transactions in this block? Try next block
|
||||
if (sequence >= transactions.size()) {
|
||||
++height;
|
||||
sequence = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
Transaction transaction = transactions.get(sequence);
|
||||
|
||||
// Transaction needs to be sent to specified recipient
|
||||
if (transaction.getRecipientAccounts().contains(recipientAccount)) {
|
||||
// Found a transaction
|
||||
|
||||
api.setA1(state, new Timestamp(height, timestamp.blockchainId, sequence).longValue());
|
||||
|
||||
// Hash transaction's signature into other three A fields for future verification that it's the same transaction
|
||||
byte[] hash = QoraATAPI.sha192(transaction.getTransactionData().getSignature());
|
||||
|
||||
api.setA2(state, QoraATAPI.fromBytes(hash, 0));
|
||||
api.setA3(state, QoraATAPI.fromBytes(hash, 8));
|
||||
api.setA4(state, QoraATAPI.fromBytes(hash, 16));
|
||||
return;
|
||||
}
|
||||
|
||||
// Transaction wasn't for us - keep going
|
||||
++sequence;
|
||||
}
|
||||
|
||||
// No more transactions - zero A and exit
|
||||
api.zeroA(state);
|
||||
} catch (DataException e) {
|
||||
throw new RuntimeException("AT API unable to fetch next transaction?", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAmountFromTransactionInA(Timestamp timestamp, MachineState state) {
|
||||
QoraATAPI api = (QoraATAPI) state.getAPI();
|
||||
TransactionData transactionData = api.fetchTransaction(state);
|
||||
|
||||
switch (transactionData.getType()) {
|
||||
case PAYMENT:
|
||||
return ((PaymentTransactionData) transactionData).getAmount().unscaledValue().longValue();
|
||||
|
||||
case AT:
|
||||
BigDecimal amount = ((ATTransactionData) transactionData).getAmount();
|
||||
|
||||
if (amount != null)
|
||||
return amount.unscaledValue().longValue();
|
||||
else
|
||||
return 0xffffffffffffffffL;
|
||||
|
||||
default:
|
||||
return 0xffffffffffffffffL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionOutput getIndexedOutputFromTransactionInA(MachineState state, int outputIndex) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
},
|
||||
BTC(1) {
|
||||
@Override
|
||||
public void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state) {
|
||||
// TODO BTC transaction support for ATv2
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAmountFromTransactionInA(Timestamp timestamp, MachineState state) {
|
||||
// TODO BTC transaction support for ATv2
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionOutput getIndexedOutputFromTransactionInA(MachineState state, int outputIndex) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
public static class TransactionOutput {
|
||||
byte[] recipient;
|
||||
long amount;
|
||||
}
|
||||
|
||||
public final int value;
|
||||
|
||||
private static final Map<Integer, BlockchainAPI> map = stream(BlockchainAPI.values()).collect(toMap(type -> type.value, type -> type));
|
||||
|
||||
BlockchainAPI(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static BlockchainAPI valueOf(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
|
||||
// Blockchain-specific API methods
|
||||
|
||||
public abstract void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state);
|
||||
|
||||
public abstract long getAmountFromTransactionInA(Timestamp timestamp, MachineState state);
|
||||
|
||||
public abstract TransactionOutput getIndexedOutputFromTransactionInA(MachineState state, int outputIndex);
|
||||
|
||||
}
|
@ -10,6 +10,8 @@ import org.ciyam.at.FunctionData;
|
||||
import org.ciyam.at.IllegalFunctionCodeException;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.Timestamp;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
/**
|
||||
* Qortal-specific CIYAM-AT Functions.
|
||||
@ -20,7 +22,7 @@ import org.ciyam.at.Timestamp;
|
||||
public enum QortalFunctionCode {
|
||||
/**
|
||||
* <tt>0x0500</tt><br>
|
||||
* Returns current BTC block's "timestamp"
|
||||
* Returns current BTC block's "timestamp".
|
||||
*/
|
||||
GET_BTC_BLOCK_TIMESTAMP(0x0500, 0, true) {
|
||||
@Override
|
||||
@ -30,7 +32,7 @@ public enum QortalFunctionCode {
|
||||
},
|
||||
/**
|
||||
* <tt>0x0501</tt><br>
|
||||
* Put transaction from specific recipient after timestamp in A, or zero if none<br>
|
||||
* Put transaction from specific recipient after timestamp in A, or zero if none.
|
||||
*/
|
||||
PUT_TX_FROM_B_RECIPIENT_AFTER_TIMESTAMP_IN_A(0x0501, 1, false) {
|
||||
@Override
|
||||
@ -42,13 +44,74 @@ public enum QortalFunctionCode {
|
||||
BlockchainAPI blockchainAPI = BlockchainAPI.valueOf(timestamp.blockchainId);
|
||||
blockchainAPI.putTransactionFromRecipientAfterTimestampInA(recipient, timestamp, state);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* <tt>0x0502</tt><br>
|
||||
* Get output, using transaction in A and passed index, putting address in B and returning amount.<br>
|
||||
* Return -1 if no such output;
|
||||
*/
|
||||
GET_INDEXED_OUTPUT(0x0502, 1, true) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
int outputIndex = (int) (functionData.value1 & 0xffffffffL);
|
||||
|
||||
BlockchainAPI.TransactionOutput output = BlockchainAPI.BTC.getIndexedOutputFromTransactionInA(state, outputIndex);
|
||||
|
||||
if (output == null) {
|
||||
functionData.returnValue = -1L;
|
||||
return;
|
||||
}
|
||||
|
||||
state.getAPI().setB(state, output.recipient);
|
||||
functionData.returnValue = output.amount;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* <tt>0x0510</tt><br>
|
||||
* Convert address in B to 20-byte value in LSB of B1, and all of B2 & B3.
|
||||
*/
|
||||
CONVERT_B_TO_PKH(0x0510, 0, false) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
// Needs to be 'B' sized
|
||||
byte[] pkh = new byte[32];
|
||||
|
||||
// Copy PKH part of B to last 20 bytes
|
||||
System.arraycopy(state.getB(), 32 - 20 - 4, pkh, 32 - 20, 20);
|
||||
|
||||
state.getAPI().setB(state, pkh);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* <tt>0x0511</tt><br>
|
||||
* Convert 20-byte value in LSB of B1, and all of B2 & B3 to P2SH.<br>
|
||||
* P2SH stored in lower 25 bytes of B.
|
||||
*/
|
||||
CONVERT_B_TO_P2SH(0x0511, 0, false) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
byte addressPrefix = Settings.getInstance().useBitcoinTestNet() ? (byte) 0xc4 : 0x05;
|
||||
|
||||
convertAddressInB(addressPrefix, state);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* <tt>0x0512</tt><br>
|
||||
* Convert 20-byte value in LSB of B1, and all of B2 & B3 to Qortal address.<br>
|
||||
* Qortal address stored in lower 25 bytes of B.
|
||||
*/
|
||||
CONVERT_B_TO_QORTAL(0x0512, 0, false) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
convertAddressInB(Crypto.ADDRESS_VERSION, state);
|
||||
}
|
||||
};
|
||||
|
||||
public final short value;
|
||||
public final int paramCount;
|
||||
public final boolean returnsValue;
|
||||
|
||||
private final static Map<Short, QortalFunctionCode> map = Arrays.stream(QortalFunctionCode.values())
|
||||
private static final Map<Short, QortalFunctionCode> map = Arrays.stream(QortalFunctionCode.values())
|
||||
.collect(Collectors.toMap(functionCode -> functionCode.value, functionCode -> functionCode));
|
||||
|
||||
private QortalFunctionCode(int value, int paramCount, boolean returnsValue) {
|
||||
@ -100,4 +163,19 @@ public enum QortalFunctionCode {
|
||||
/** Actually execute function */
|
||||
protected abstract void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException;
|
||||
|
||||
private static void convertAddressInB(byte addressPrefix, MachineState state) {
|
||||
byte[] addressNoChecksum = new byte[1 + 20];
|
||||
addressNoChecksum[0] = addressPrefix;
|
||||
System.arraycopy(state.getB(), 0, addressNoChecksum, 1, 20);
|
||||
|
||||
byte[] checksum = Crypto.doubleDigest(addressNoChecksum);
|
||||
|
||||
// Needs to be 'B' sized
|
||||
byte[] address = new byte[32];
|
||||
System.arraycopy(addressNoChecksum, 0, address, 32 - 1 - 20 - 4, addressNoChecksum.length);
|
||||
System.arraycopy(checksum, 0, address, 32 - 4, 4);
|
||||
|
||||
state.getAPI().setB(state, address);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.DigestOutputStream;
|
||||
@ -21,6 +20,7 @@ import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -28,22 +28,28 @@ import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.BlockChain;
|
||||
import org.bitcoinj.core.CheckpointManager;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.PeerAddress;
|
||||
import org.bitcoinj.core.PeerGroup;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.StoredBlock;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.core.listeners.BlocksDownloadedEventListener;
|
||||
import org.bitcoinj.core.listeners.NewBestBlockListener;
|
||||
import org.bitcoinj.net.discovery.DnsDiscovery;
|
||||
import org.bitcoinj.params.MainNetParams;
|
||||
import org.bitcoinj.params.RegTestParams;
|
||||
import org.bitcoinj.params.TestNet3Params;
|
||||
import org.bitcoinj.script.Script.ScriptType;
|
||||
import org.bitcoinj.store.BlockStore;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.bitcoinj.store.MemoryBlockStore;
|
||||
import org.bitcoinj.utils.Threading;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
import org.bitcoinj.wallet.WalletTransaction;
|
||||
import org.bitcoinj.wallet.WalletTransaction.Pool;
|
||||
import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener;
|
||||
import org.bitcoinj.wallet.listeners.WalletCoinsSentEventListener;
|
||||
import org.qortal.settings.Settings;
|
||||
@ -168,10 +174,12 @@ public class BTC {
|
||||
|
||||
private BTC() {
|
||||
if (Settings.getInstance().useBitcoinTestNet()) {
|
||||
/*
|
||||
this.params = RegTestParams.get();
|
||||
this.checkpointsFileName = "checkpoints-regtest.txt";
|
||||
// TestNet3Params.get();
|
||||
// this.checkpointsFileName = "checkpoints-testnet.txt";
|
||||
*/
|
||||
this.params = TestNet3Params.get();
|
||||
this.checkpointsFileName = "checkpoints-testnet.txt";
|
||||
} else {
|
||||
this.params = MainNetParams.get();
|
||||
this.checkpointsFileName = "checkpoints.txt";
|
||||
@ -256,7 +264,25 @@ public class BTC {
|
||||
return Wallet.createBasic(this.params);
|
||||
}
|
||||
|
||||
private void replayChain(long startTime, Wallet wallet) throws BlockStoreException {
|
||||
private class ReplayHooks {
|
||||
private Runnable preReplay;
|
||||
private Runnable postReplay;
|
||||
|
||||
public ReplayHooks(Runnable preReplay, Runnable postReplay) {
|
||||
this.preReplay = preReplay;
|
||||
this.postReplay = postReplay;
|
||||
}
|
||||
|
||||
public void preReplay() {
|
||||
this.preReplay.run();
|
||||
}
|
||||
|
||||
public void postReplay() {
|
||||
this.postReplay.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void replayChain(long startTime, Wallet wallet, ReplayHooks replayHooks) throws BlockStoreException {
|
||||
this.start(startTime);
|
||||
|
||||
final WalletCoinsReceivedEventListener coinsReceivedListener = (someWallet, tx, prevBalance, newBalance) -> {
|
||||
@ -277,12 +303,18 @@ public class BTC {
|
||||
}
|
||||
|
||||
try {
|
||||
if (replayHooks != null)
|
||||
replayHooks.preReplay();
|
||||
|
||||
// Sync blockchain using peerGroup, skipping as much as we can before startTime
|
||||
this.peerGroup.setFastCatchupTimeSecs(startTime);
|
||||
this.chain.addNewBestBlockListener(Threading.SAME_THREAD, this.manager);
|
||||
this.peerGroup.downloadBlockChain();
|
||||
} finally {
|
||||
// Clean up
|
||||
if (replayHooks != null)
|
||||
replayHooks.postReplay();
|
||||
|
||||
if (wallet != null) {
|
||||
wallet.removeCoinsReceivedEventListener(coinsReceivedListener);
|
||||
wallet.removeCoinsSentEventListener(coinsSentListener);
|
||||
@ -296,7 +328,7 @@ public class BTC {
|
||||
}
|
||||
|
||||
private void replayChain(long startTime) throws BlockStoreException {
|
||||
this.replayChain(startTime, null);
|
||||
this.replayChain(startTime, null, null);
|
||||
}
|
||||
|
||||
// Actual useful methods for use by other classes
|
||||
@ -334,7 +366,7 @@ public class BTC {
|
||||
wallet.addWatchedAddress(address, startTime);
|
||||
|
||||
try {
|
||||
replayChain(startTime, wallet);
|
||||
replayChain(startTime, wallet, null);
|
||||
|
||||
// Now that blockchain is up-to-date, return current balance
|
||||
return wallet.getBalance();
|
||||
@ -344,13 +376,13 @@ public class BTC {
|
||||
}
|
||||
}
|
||||
|
||||
public List<TransactionOutput> getUnspentOutputs(String base58Address, long startTime) {
|
||||
public List<TransactionOutput> getOutputs(String base58Address, long startTime) {
|
||||
Wallet wallet = createEmptyWallet();
|
||||
Address address = Address.fromString(this.params, base58Address);
|
||||
wallet.addWatchedAddress(address, startTime);
|
||||
|
||||
try {
|
||||
replayChain(startTime, wallet);
|
||||
replayChain(startTime, wallet, null);
|
||||
|
||||
// Now that blockchain is up-to-date, return outputs
|
||||
return wallet.getWatchedOutputs(true);
|
||||
@ -360,4 +392,54 @@ public class BTC {
|
||||
}
|
||||
}
|
||||
|
||||
private static class TransactionStorage {
|
||||
private Transaction transaction;
|
||||
|
||||
public void store(Transaction transaction) {
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
public Transaction getTransaction() {
|
||||
return this.transaction;
|
||||
}
|
||||
}
|
||||
|
||||
public List<TransactionOutput> getOutputs(byte[] txId, long startTime) {
|
||||
Wallet wallet = createEmptyWallet();
|
||||
|
||||
// Add random address to wallet
|
||||
ECKey fakeKey = new ECKey();
|
||||
wallet.addWatchedAddress(Address.fromKey(this.params, fakeKey, ScriptType.P2PKH), startTime);
|
||||
|
||||
final Sha256Hash txHash = Sha256Hash.wrap(txId);
|
||||
|
||||
final TransactionStorage transactionStorage = new TransactionStorage();
|
||||
|
||||
final BlocksDownloadedEventListener listener = (peer, block, filteredBlock, blocksLeft) -> {
|
||||
List<Transaction> transactions = block.getTransactions();
|
||||
|
||||
if (transactions == null)
|
||||
return;
|
||||
|
||||
for (Transaction transaction : transactions)
|
||||
if (transaction.getTxId().equals(txHash)) {
|
||||
System.out.println(String.format("We downloaded block containing tx!"));
|
||||
transactionStorage.store(transaction);
|
||||
}
|
||||
};
|
||||
|
||||
ReplayHooks replayHooks = new ReplayHooks(() -> this.peerGroup.addBlocksDownloadedEventListener(listener), () -> this.peerGroup.removeBlocksDownloadedEventListener(listener));
|
||||
|
||||
// Replay chain in the hope it will download transactionId as a dependency
|
||||
try {
|
||||
replayChain(startTime, wallet, replayHooks);
|
||||
|
||||
Transaction realTx = transactionStorage.getTransaction();
|
||||
return realTx.getOutputs();
|
||||
} catch (BlockStoreException e) {
|
||||
LOGGER.error(String.format("BTC blockstore issue: %s", e.getMessage()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
64
src/test/java/org/qora/test/btcacct/GetTransaction.java
Normal file
64
src/test/java/org/qora/test/btcacct/GetTransaction.java
Normal file
@ -0,0 +1,64 @@
|
||||
package org.qora.test.btcacct;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.List;
|
||||
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.qora.crosschain.BTC;
|
||||
import org.qora.settings.Settings;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
public class GetTransaction {
|
||||
|
||||
static {
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
}
|
||||
|
||||
private static void usage(String error) {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: GetTransaction <bitcoin-tx>"));
|
||||
System.err.println(String.format("example: GetTransaction 816407e79568f165f13e09e9912c5f2243e0a23a007cec425acedc2e89284660 (mainnet)"));
|
||||
System.err.println(String.format("example: GetTransaction 3bfd17a492a4e3d6cb7204e17e20aca6c1ab82e1828bd1106eefbaf086fb8a4e (testnet)"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 1 || args.length > 1)
|
||||
usage(null);
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Settings.fileInstance("settings-test.json");
|
||||
|
||||
byte[] transactionId = null;
|
||||
|
||||
try {
|
||||
int argIndex = 0;
|
||||
|
||||
transactionId = HashCode.fromString(args[argIndex++]).asBytes();
|
||||
} catch (NumberFormatException | AddressFormatException e) {
|
||||
usage(String.format("Argument format exception: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
// Chain replay start time
|
||||
long startTime = (System.currentTimeMillis() / 1000L) - 14 * 24 * 60 * 60; // 14 days before now, in seconds
|
||||
|
||||
// Grab all outputs from transaction
|
||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getOutputs(transactionId, startTime);
|
||||
if (fundingOutputs == null) {
|
||||
System.out.println(String.format("Transaction not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Found %d output%s", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
|
||||
|
||||
for (TransactionOutput fundingOutput : fundingOutputs)
|
||||
System.out.println(String.format("Output %d: %s", fundingOutput.getIndex(), fundingOutput.getValue().toPlainString()));
|
||||
}
|
||||
|
||||
}
|
@ -167,7 +167,7 @@ public class Redeem {
|
||||
System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString()));
|
||||
|
||||
// Grab all P2SH funding transactions (just in case there are more than one)
|
||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT);
|
||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT);
|
||||
System.out.println(String.format("Found %d unspent output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
|
||||
|
||||
if (fundingOutputs.isEmpty()) {
|
||||
|
@ -171,7 +171,7 @@ public class Refund {
|
||||
System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString()));
|
||||
|
||||
// Grab all P2SH funding transactions (just in case there are more than one)
|
||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT);
|
||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT);
|
||||
System.out.println(String.format("Found %d unspent output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : "")));
|
||||
|
||||
if (fundingOutputs.isEmpty()) {
|
||||
|
Loading…
Reference in New Issue
Block a user