mirror of
				https://github.com/Qortal/altcoinj.git
				synced 2025-11-03 22:17:17 +00:00 
			
		
		
		
	Support CLTV micropayment channels
Also extend WalletTool to send via, settle and refund these channels.
This commit is contained in:
		
				
					committed by
					
						
						Andreas Schildbach
					
				
			
			
				
	
			
			
			
						parent
						
							25db735b3a
						
					
				
				
					commit
					c9cce47962
				
			@@ -33,7 +33,6 @@ import org.bitcoinj.core.listeners.WalletChangeEventListener;
 | 
			
		||||
import org.bitcoinj.core.listeners.WalletCoinEventListener;
 | 
			
		||||
import org.bitcoinj.core.TransactionConfidence.*;
 | 
			
		||||
import org.bitcoinj.crypto.*;
 | 
			
		||||
import org.bitcoinj.params.*;
 | 
			
		||||
import org.bitcoinj.script.*;
 | 
			
		||||
import org.bitcoinj.signers.*;
 | 
			
		||||
import org.bitcoinj.store.*;
 | 
			
		||||
@@ -3643,6 +3642,24 @@ public class Wallet extends BaseTaggableObject
 | 
			
		||||
            return req;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static SendRequest toCLTVPaymentChannel(NetworkParameters params, Date releaseTime, ECKey from, ECKey to, Coin value) {
 | 
			
		||||
            long time = releaseTime.getTime() / 1000L;
 | 
			
		||||
            checkArgument(time >= Transaction.LOCKTIME_THRESHOLD, "Release time was too small");
 | 
			
		||||
            return toCLTVPaymentChannel(params, BigInteger.valueOf(time), from, to, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static SendRequest toCLTVPaymentChannel(NetworkParameters params, long lockTime, ECKey from, ECKey to, Coin value) {
 | 
			
		||||
            return toCLTVPaymentChannel(params, BigInteger.valueOf(lockTime), from, to, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static SendRequest toCLTVPaymentChannel(NetworkParameters params, BigInteger time, ECKey from, ECKey to, Coin value) {
 | 
			
		||||
            SendRequest req = new SendRequest();
 | 
			
		||||
            Script output = ScriptBuilder.createCLTVPaymentChannelOutput(time, from, to);
 | 
			
		||||
            req.tx = new Transaction(params);
 | 
			
		||||
            req.tx.addOutput(value, output);
 | 
			
		||||
            return req;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** Copy data from payment request. */
 | 
			
		||||
        public SendRequest fromPaymentDetails(PaymentDetails paymentDetails) {
 | 
			
		||||
            if (paymentDetails.hasMemo())
 | 
			
		||||
@@ -4120,6 +4137,19 @@ public class Wallet extends BaseTaggableObject
 | 
			
		||||
                if (key != null && (key.isEncrypted() || key.hasPrivKey()))
 | 
			
		||||
                    return true;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (script.isSentToCLTVPaymentChannel()) {
 | 
			
		||||
            // Any script for which we are the recipient or sender counts.
 | 
			
		||||
            byte[] sender = script.getCLTVPaymentChannelSenderPubKey();
 | 
			
		||||
            ECKey senderKey = findKeyFromPubKey(sender);
 | 
			
		||||
            if (senderKey != null && (senderKey.isEncrypted() || senderKey.hasPrivKey())) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            byte[] recipient = script.getCLTVPaymentChannelRecipientPubKey();
 | 
			
		||||
            ECKey recipientKey = findKeyFromPubKey(sender);
 | 
			
		||||
            if (recipientKey != null && (recipientKey.isEncrypted() || recipientKey.hasPrivKey())) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -302,6 +302,37 @@ public class Script {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the sender public key from a LOCKTIMEVERIFY transaction
 | 
			
		||||
     * @return
 | 
			
		||||
     * @throws ScriptException
 | 
			
		||||
     */
 | 
			
		||||
    public byte[] getCLTVPaymentChannelSenderPubKey() throws ScriptException {
 | 
			
		||||
        if (!isSentToCLTVPaymentChannel()) {
 | 
			
		||||
            throw new ScriptException("Script not a standard CHECKLOCKTIMVERIFY transaction: " + this);
 | 
			
		||||
        }
 | 
			
		||||
        return chunks.get(8).data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the recipient public key from a LOCKTIMEVERIFY transaction
 | 
			
		||||
     * @return
 | 
			
		||||
     * @throws ScriptException
 | 
			
		||||
     */
 | 
			
		||||
    public byte[] getCLTVPaymentChannelRecipientPubKey() throws ScriptException {
 | 
			
		||||
        if (!isSentToCLTVPaymentChannel()) {
 | 
			
		||||
            throw new ScriptException("Script not a standard CHECKLOCKTIMVERIFY transaction: " + this);
 | 
			
		||||
        }
 | 
			
		||||
        return chunks.get(1).data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BigInteger getCLTVPaymentChannelExpiry() {
 | 
			
		||||
        if (!isSentToCLTVPaymentChannel()) {
 | 
			
		||||
            throw new ScriptException("Script not a standard CHECKLOCKTIMEVERIFY transaction: " + this);
 | 
			
		||||
        }
 | 
			
		||||
        return castToBigInteger(chunks.get(4).data, 5);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * For 2-element [input] scripts assumes that the paid-to-address can be derived from the public key.
 | 
			
		||||
     * The concept of a "from address" isn't well defined in Bitcoin and you should not assume the sender of a
 | 
			
		||||
@@ -686,6 +717,22 @@ public class Script {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isSentToCLTVPaymentChannel() {
 | 
			
		||||
        if (chunks.size() != 10) return false;
 | 
			
		||||
        // Check that opcodes match the pre-determined format.
 | 
			
		||||
        if (!chunks.get(0).equalsOpCode(OP_IF)) return false;
 | 
			
		||||
        // chunk[1] = recipient pubkey
 | 
			
		||||
        if (!chunks.get(2).equalsOpCode(OP_CHECKSIGVERIFY)) return false;
 | 
			
		||||
        if (!chunks.get(3).equalsOpCode(OP_ELSE)) return false;
 | 
			
		||||
        // chunk[4] = locktime
 | 
			
		||||
        if (!chunks.get(5).equalsOpCode(OP_CHECKLOCKTIMEVERIFY)) return false;
 | 
			
		||||
        if (!chunks.get(6).equalsOpCode(OP_DROP)) return false;
 | 
			
		||||
        if (!chunks.get(7).equalsOpCode(OP_ENDIF)) return false;
 | 
			
		||||
        // chunk[8] = sender pubkey
 | 
			
		||||
        if (!chunks.get(9).equalsOpCode(OP_CHECKSIG)) return false;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static boolean equalsRange(byte[] a, int start, byte[] b) {
 | 
			
		||||
        if (start + b.length > a.length)
 | 
			
		||||
            return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import org.bitcoinj.core.Utils;
 | 
			
		||||
import org.bitcoinj.crypto.TransactionSignature;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.math.BigInteger;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
@@ -435,4 +436,32 @@ public class ScriptBuilder {
 | 
			
		||||
        checkArgument(data.length <= 40);
 | 
			
		||||
        return new ScriptBuilder().op(OP_RETURN).data(data).build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Script createCLTVPaymentChannelOutput(BigInteger time, ECKey from, ECKey to) {
 | 
			
		||||
        byte[] timeBytes = Utils.reverseBytes(Utils.encodeMPI(time, false));
 | 
			
		||||
        if (timeBytes.length > 5) {
 | 
			
		||||
            throw new RuntimeException("Time too large to encode as 5-byte int");
 | 
			
		||||
        }
 | 
			
		||||
        return new ScriptBuilder().op(OP_IF)
 | 
			
		||||
                .data(to.getPubKey()).op(OP_CHECKSIGVERIFY)
 | 
			
		||||
                .op(OP_ELSE)
 | 
			
		||||
                .data(timeBytes).op(OP_CHECKLOCKTIMEVERIFY).op(OP_DROP)
 | 
			
		||||
                .op(OP_ENDIF)
 | 
			
		||||
                .data(from.getPubKey()).op(OP_CHECKSIG).build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Script createCLTVPaymentChannelRefund(TransactionSignature signature) {
 | 
			
		||||
        ScriptBuilder builder = new ScriptBuilder();
 | 
			
		||||
        builder.data(signature.encodeToBitcoin());
 | 
			
		||||
        builder.data(new byte[] { 0 }); // Use the CHECKLOCKTIMEVERIFY if branch
 | 
			
		||||
        return builder.build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Script createCLTVPaymentChannelInput(TransactionSignature from, TransactionSignature to) {
 | 
			
		||||
        ScriptBuilder builder = new ScriptBuilder();
 | 
			
		||||
        builder.data(from.encodeToBitcoin());
 | 
			
		||||
        builder.data(to.encodeToBitcoin());
 | 
			
		||||
        builder.smallNum(1); // Use the CHECKLOCKTIMEVERIFY if branch
 | 
			
		||||
        return builder.build();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
package org.bitcoinj.core;
 | 
			
		||||
 | 
			
		||||
import org.bitcoinj.core.TransactionConfidence.*;
 | 
			
		||||
import org.bitcoinj.crypto.TransactionSignature;
 | 
			
		||||
import org.bitcoinj.params.*;
 | 
			
		||||
import org.bitcoinj.script.*;
 | 
			
		||||
import org.bitcoinj.testing.*;
 | 
			
		||||
import org.easymock.*;
 | 
			
		||||
import org.junit.*;
 | 
			
		||||
 | 
			
		||||
import java.math.BigInteger;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import static org.bitcoinj.core.BlockTest.params;
 | 
			
		||||
import static org.bitcoinj.core.Utils.HEX;
 | 
			
		||||
@@ -205,6 +207,86 @@ public class TransactionTest {
 | 
			
		||||
        assertEquals(tx.isMature(), false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testCLTVPaymentChannelTransactionSpending() {
 | 
			
		||||
        BigInteger time = BigInteger.valueOf(20);
 | 
			
		||||
 | 
			
		||||
        ECKey from = new ECKey(), to = new ECKey(), incorrect = new ECKey();
 | 
			
		||||
        Script outputScript = ScriptBuilder.createCLTVPaymentChannelOutput(time, from, to);
 | 
			
		||||
 | 
			
		||||
        Transaction tx = new Transaction(PARAMS);
 | 
			
		||||
        tx.addInput(new TransactionInput(PARAMS, tx, new byte[] {}));
 | 
			
		||||
        tx.getInput(0).setSequenceNumber(0);
 | 
			
		||||
        tx.setLockTime(time.subtract(BigInteger.ONE).longValue());
 | 
			
		||||
        TransactionSignature fromSig =
 | 
			
		||||
                tx.calculateSignature(0, from, outputScript, Transaction.SigHash.SINGLE, false);
 | 
			
		||||
        TransactionSignature toSig =
 | 
			
		||||
                tx.calculateSignature(0, to, outputScript, Transaction.SigHash.SINGLE, false);
 | 
			
		||||
        TransactionSignature incorrectSig =
 | 
			
		||||
                tx.calculateSignature(0, incorrect, outputScript, Transaction.SigHash.SINGLE, false);
 | 
			
		||||
        Script scriptSig =
 | 
			
		||||
                ScriptBuilder.createCLTVPaymentChannelInput(fromSig, toSig);
 | 
			
		||||
        Script refundSig =
 | 
			
		||||
                ScriptBuilder.createCLTVPaymentChannelRefund(fromSig);
 | 
			
		||||
        Script invalidScriptSig1 =
 | 
			
		||||
                ScriptBuilder.createCLTVPaymentChannelInput(fromSig, incorrectSig);
 | 
			
		||||
        Script invalidScriptSig2 =
 | 
			
		||||
                ScriptBuilder.createCLTVPaymentChannelInput(incorrectSig, toSig);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            scriptSig.correctlySpends(tx, 0, outputScript, Script.ALL_VERIFY_FLAGS);
 | 
			
		||||
        } catch (ScriptException e) {
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
            fail("Settle transaction failed to correctly spend the payment channel");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            refundSig.correctlySpends(tx, 0, outputScript, Script.ALL_VERIFY_FLAGS);
 | 
			
		||||
            fail("Refund passed before expiry");
 | 
			
		||||
        } catch (ScriptException e) { }
 | 
			
		||||
        try {
 | 
			
		||||
            invalidScriptSig1.correctlySpends(tx, 0, outputScript, Script.ALL_VERIFY_FLAGS);
 | 
			
		||||
            fail("Invalid sig 1 passed");
 | 
			
		||||
        } catch (ScriptException e) { }
 | 
			
		||||
        try {
 | 
			
		||||
            invalidScriptSig2.correctlySpends(tx, 0, outputScript, Script.ALL_VERIFY_FLAGS);
 | 
			
		||||
            fail("Invalid sig 2 passed");
 | 
			
		||||
        } catch (ScriptException e) { }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testCLTVPaymentChannelTransactionRefund() {
 | 
			
		||||
        BigInteger time = BigInteger.valueOf(20);
 | 
			
		||||
 | 
			
		||||
        ECKey from = new ECKey(), to = new ECKey(), incorrect = new ECKey();
 | 
			
		||||
        Script outputScript = ScriptBuilder.createCLTVPaymentChannelOutput(time, from, to);
 | 
			
		||||
 | 
			
		||||
        Transaction tx = new Transaction(PARAMS);
 | 
			
		||||
        tx.addInput(new TransactionInput(PARAMS, tx, new byte[] {}));
 | 
			
		||||
        tx.getInput(0).setSequenceNumber(0);
 | 
			
		||||
        tx.setLockTime(time.add(BigInteger.ONE).longValue());
 | 
			
		||||
        TransactionSignature fromSig =
 | 
			
		||||
                tx.calculateSignature(0, from, outputScript, Transaction.SigHash.SINGLE, false);
 | 
			
		||||
        TransactionSignature incorrectSig =
 | 
			
		||||
                tx.calculateSignature(0, incorrect, outputScript, Transaction.SigHash.SINGLE, false);
 | 
			
		||||
        Script scriptSig =
 | 
			
		||||
                ScriptBuilder.createCLTVPaymentChannelRefund(fromSig);
 | 
			
		||||
        Script invalidScriptSig =
 | 
			
		||||
                ScriptBuilder.createCLTVPaymentChannelRefund(incorrectSig);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            scriptSig.correctlySpends(tx, 0, outputScript, Script.ALL_VERIFY_FLAGS);
 | 
			
		||||
        } catch (ScriptException e) {
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
            fail("Refund failed to correctly spend the payment channel");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            invalidScriptSig.correctlySpends(tx, 0, outputScript, Script.ALL_VERIFY_FLAGS);
 | 
			
		||||
            fail("Invalid sig passed");
 | 
			
		||||
        } catch (ScriptException e) { }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testToStringWhenLockTimeIsSpecifiedInBlockHeight() {
 | 
			
		||||
        Transaction tx = newTransaction();
 | 
			
		||||
 
 | 
			
		||||
@@ -406,6 +406,12 @@ public class ScriptTest {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testCLTVPaymentChannelOutput() {
 | 
			
		||||
        Script script = ScriptBuilder.createCLTVPaymentChannelOutput(BigInteger.valueOf(20), new ECKey(), new ECKey());
 | 
			
		||||
        assertTrue("script is locktime-verify", script.isSentToCLTVPaymentChannel());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void getToAddress() throws Exception {
 | 
			
		||||
        // pay to pubkey
 | 
			
		||||
 
 | 
			
		||||
@@ -19,10 +19,7 @@ package org.bitcoinj.tools;
 | 
			
		||||
 | 
			
		||||
import org.bitcoinj.core.*;
 | 
			
		||||
import org.bitcoinj.core.Wallet.BalanceType;
 | 
			
		||||
import org.bitcoinj.crypto.DeterministicKey;
 | 
			
		||||
import org.bitcoinj.crypto.KeyCrypterException;
 | 
			
		||||
import org.bitcoinj.crypto.MnemonicCode;
 | 
			
		||||
import org.bitcoinj.crypto.MnemonicException;
 | 
			
		||||
import org.bitcoinj.crypto.*;
 | 
			
		||||
import org.bitcoinj.net.discovery.DnsDiscovery;
 | 
			
		||||
import org.bitcoinj.params.MainNetParams;
 | 
			
		||||
import org.bitcoinj.params.RegTestParams;
 | 
			
		||||
@@ -30,6 +27,7 @@ import org.bitcoinj.params.TestNet3Params;
 | 
			
		||||
import org.bitcoinj.protocols.payments.PaymentProtocol;
 | 
			
		||||
import org.bitcoinj.protocols.payments.PaymentProtocolException;
 | 
			
		||||
import org.bitcoinj.protocols.payments.PaymentSession;
 | 
			
		||||
import org.bitcoinj.script.ScriptBuilder;
 | 
			
		||||
import org.bitcoinj.store.*;
 | 
			
		||||
import org.bitcoinj.uri.BitcoinURI;
 | 
			
		||||
import org.bitcoinj.uri.BitcoinURIParseException;
 | 
			
		||||
@@ -173,6 +171,9 @@ public class WalletTool {
 | 
			
		||||
        SYNC,
 | 
			
		||||
        RESET,
 | 
			
		||||
        SEND,
 | 
			
		||||
        SEND_CLTVPAYMENTCHANNEL,
 | 
			
		||||
        SETTLE_CLTVPAYMENTCHANNEL,
 | 
			
		||||
        REFUND_CLTVPAYMENTCHANNEL,
 | 
			
		||||
        ENCRYPT,
 | 
			
		||||
        DECRYPT,
 | 
			
		||||
        MARRY,
 | 
			
		||||
@@ -228,6 +229,8 @@ public class WalletTool {
 | 
			
		||||
        parser.accepts("no-pki");
 | 
			
		||||
        parser.accepts("tor");
 | 
			
		||||
        parser.accepts("dump-privkeys");
 | 
			
		||||
        OptionSpec<String> refundFlag = parser.accepts("refund-to").withRequiredArg();
 | 
			
		||||
        OptionSpec<String> txHashFlag = parser.accepts("txhash").withRequiredArg();
 | 
			
		||||
        options = parser.parse(args);
 | 
			
		||||
 | 
			
		||||
        if (args.length == 0 || options.has("help") ||
 | 
			
		||||
@@ -366,6 +369,59 @@ public class WalletTool {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case SEND_CLTVPAYMENTCHANNEL: {
 | 
			
		||||
                if (!options.has(outputFlag)) {
 | 
			
		||||
                    System.err.println("You must specify a --output=addr:value");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                Coin fee = Coin.ZERO;
 | 
			
		||||
                if (options.has("fee")) {
 | 
			
		||||
                    fee = parseCoin((String) options.valueOf("fee"));
 | 
			
		||||
                }
 | 
			
		||||
                if (!options.has("locktime")) {
 | 
			
		||||
                    System.err.println("You must specify a --locktime");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                String lockTime = (String) options.valueOf("locktime");
 | 
			
		||||
                boolean allowUnconfirmed = options.has("allow-unconfirmed");
 | 
			
		||||
                if (!options.has(refundFlag)) {
 | 
			
		||||
                    System.err.println("You must specify an address to refund money to after expiry: --refund-to=addr");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                sendCLTVPaymentChannel(refundFlag.value(options), outputFlag.value(options), fee, lockTime, allowUnconfirmed);
 | 
			
		||||
                } break;
 | 
			
		||||
            case SETTLE_CLTVPAYMENTCHANNEL: {
 | 
			
		||||
                if (!options.has(outputFlag)) {
 | 
			
		||||
                    System.err.println("You must specify a --output=addr:value");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                Coin fee = Coin.ZERO;
 | 
			
		||||
                if (options.has("fee")) {
 | 
			
		||||
                    fee = parseCoin((String) options.valueOf("fee"));
 | 
			
		||||
                }
 | 
			
		||||
                boolean allowUnconfirmed = options.has("allow-unconfirmed");
 | 
			
		||||
                if (!options.has(txHashFlag)) {
 | 
			
		||||
                    System.err.println("You must specify the transaction to spend: --txhash=tx-hash");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                settleCLTVPaymentChannel(txHashFlag.value(options), outputFlag.value(options), fee, allowUnconfirmed);
 | 
			
		||||
                } break;
 | 
			
		||||
            case REFUND_CLTVPAYMENTCHANNEL: {
 | 
			
		||||
                if (!options.has(outputFlag)) {
 | 
			
		||||
                    System.err.println("You must specify a --output=addr:value");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                Coin fee = Coin.ZERO;
 | 
			
		||||
                if (options.has("fee")) {
 | 
			
		||||
                    fee = parseCoin((String) options.valueOf("fee"));
 | 
			
		||||
                }
 | 
			
		||||
                boolean allowUnconfirmed = options.has("allow-unconfirmed");
 | 
			
		||||
                if (!options.has(txHashFlag)) {
 | 
			
		||||
                    System.err.println("You must specify the transaction to spend: --txhash=tx-hash");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                refundCLTVPaymentChannel(txHashFlag.value(options), outputFlag.value(options), fee, allowUnconfirmed);
 | 
			
		||||
            } break;
 | 
			
		||||
            case ENCRYPT: encrypt(); break;
 | 
			
		||||
            case DECRYPT: decrypt(); break;
 | 
			
		||||
            case MARRY: marry(); break;
 | 
			
		||||
@@ -513,36 +569,25 @@ public class WalletTool {
 | 
			
		||||
            // Convert the input strings to outputs.
 | 
			
		||||
            Transaction t = new Transaction(params);
 | 
			
		||||
            for (String spec : outputs) {
 | 
			
		||||
                String[] parts = spec.split(":");
 | 
			
		||||
                if (parts.length != 2) {
 | 
			
		||||
                    System.err.println("Malformed output specification, must have two parts separated by :");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                String destination = parts[0];
 | 
			
		||||
                try {
 | 
			
		||||
                    Coin value;
 | 
			
		||||
                    if ("ALL".equalsIgnoreCase(parts[1]))
 | 
			
		||||
                        value = wallet.getBalance(BalanceType.ESTIMATED);
 | 
			
		||||
                    else
 | 
			
		||||
                        value = parseCoin(parts[1]);
 | 
			
		||||
                    if (destination.startsWith("0")) {
 | 
			
		||||
                        // Treat as a raw public key.
 | 
			
		||||
                        byte[] pubKey = new BigInteger(destination, 16).toByteArray();
 | 
			
		||||
                        ECKey key = ECKey.fromPublicOnly(pubKey);
 | 
			
		||||
                        t.addOutput(value, key);
 | 
			
		||||
                    OutputSpec outputSpec = new OutputSpec(spec);
 | 
			
		||||
                    if (outputSpec.isAddress()) {
 | 
			
		||||
                        t.addOutput(outputSpec.value, outputSpec.addr);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Treat as an address.
 | 
			
		||||
                        Address addr = Address.fromBase58(params, destination);
 | 
			
		||||
                        t.addOutput(value, addr);
 | 
			
		||||
                        t.addOutput(outputSpec.value, outputSpec.key);
 | 
			
		||||
                    }
 | 
			
		||||
                } catch (WrongNetworkException e) {
 | 
			
		||||
                    System.err.println("Malformed output specification, address is for a different network: " + parts[0]);
 | 
			
		||||
                    System.err.println("Malformed output specification, address is for a different network: " + spec);
 | 
			
		||||
                    return;
 | 
			
		||||
                } catch (AddressFormatException e) {
 | 
			
		||||
                    System.err.println("Malformed output specification, could not parse as address: " + parts[0]);
 | 
			
		||||
                    System.err.println("Malformed output specification, could not parse as address: " + spec);
 | 
			
		||||
                    return;
 | 
			
		||||
                } catch (NumberFormatException e) {
 | 
			
		||||
                    System.err.println("Malformed output specification, could not parse as value: " + parts[1]);
 | 
			
		||||
                    System.err.println("Malformed output specification, could not parse as value: " + spec);
 | 
			
		||||
                    return;
 | 
			
		||||
                } catch (IllegalArgumentException e) {
 | 
			
		||||
                    System.err.println(e.getMessage());
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Wallet.SendRequest req = Wallet.SendRequest.forTx(t);
 | 
			
		||||
@@ -605,6 +650,335 @@ public class WalletTool {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static class OutputSpec {
 | 
			
		||||
        public final Coin value;
 | 
			
		||||
        public final Address addr;
 | 
			
		||||
        public final ECKey key;
 | 
			
		||||
 | 
			
		||||
        public OutputSpec(String spec) throws IllegalArgumentException {
 | 
			
		||||
            String[] parts = spec.split(":");
 | 
			
		||||
            if (parts.length != 2) {
 | 
			
		||||
                throw new IllegalArgumentException("Malformed output specification, must have two parts separated by :");
 | 
			
		||||
            }
 | 
			
		||||
            String destination = parts[0];
 | 
			
		||||
            if ("ALL".equalsIgnoreCase(parts[1]))
 | 
			
		||||
                value = wallet.getBalance(BalanceType.ESTIMATED);
 | 
			
		||||
            else
 | 
			
		||||
                value = parseCoin(parts[1]);
 | 
			
		||||
            if (destination.startsWith("0")) {
 | 
			
		||||
                // Treat as a raw public key.
 | 
			
		||||
                byte[] pubKey = new BigInteger(destination, 16).toByteArray();
 | 
			
		||||
                key = ECKey.fromPublicOnly(pubKey);
 | 
			
		||||
                addr = null;
 | 
			
		||||
            } else {
 | 
			
		||||
                // Treat as an address.
 | 
			
		||||
                addr = Address.fromBase58(params, destination);
 | 
			
		||||
                key = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public boolean isAddress() {
 | 
			
		||||
            return addr != null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void sendCLTVPaymentChannel(String refund, String output, Coin fee, String lockTimeStr, boolean allowUnconfirmed) throws VerificationException {
 | 
			
		||||
        try {
 | 
			
		||||
            // Convert the input strings to outputs.
 | 
			
		||||
            ECKey outputKey, refundKey;
 | 
			
		||||
            Coin value;
 | 
			
		||||
            try {
 | 
			
		||||
                OutputSpec outputSpec = new OutputSpec(output);
 | 
			
		||||
                if (outputSpec.isAddress()) {
 | 
			
		||||
                    System.err.println("Output specification must be a public key");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                outputKey = outputSpec.key;
 | 
			
		||||
                value = outputSpec.value;
 | 
			
		||||
                byte[] refundPubKey = new BigInteger(refund, 16).toByteArray();
 | 
			
		||||
                refundKey = ECKey.fromPublicOnly(refundPubKey);
 | 
			
		||||
            } catch (WrongNetworkException e) {
 | 
			
		||||
                System.err.println("Malformed output specification, address is for a different network.");
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (AddressFormatException e) {
 | 
			
		||||
                System.err.println("Malformed output specification, could not parse as address.");
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (NumberFormatException e) {
 | 
			
		||||
                System.err.println("Malformed output specification, could not parse as value.");
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (IllegalArgumentException e) {
 | 
			
		||||
                System.err.println(e.getMessage());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            long lockTime;
 | 
			
		||||
            try {
 | 
			
		||||
                lockTime = parseLockTimeStr(lockTimeStr);
 | 
			
		||||
            } catch (ParseException e) {
 | 
			
		||||
                System.err.println("Could not understand --locktime of " + lockTimeStr);
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (ScriptException e) {
 | 
			
		||||
                throw new RuntimeException(e);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Wallet.SendRequest req = Wallet.SendRequest.toCLTVPaymentChannel(params, lockTime, refundKey, outputKey, value);
 | 
			
		||||
            if (req.tx.getOutputs().size() == 1 && req.tx.getOutput(0).getValue().equals(wallet.getBalance())) {
 | 
			
		||||
                log.info("Emptying out wallet, recipient may get less than what you expect");
 | 
			
		||||
                req.emptyWallet = true;
 | 
			
		||||
            }
 | 
			
		||||
            req.fee = fee;
 | 
			
		||||
            if (allowUnconfirmed) {
 | 
			
		||||
                wallet.allowSpendingUnconfirmedTransactions();
 | 
			
		||||
            }
 | 
			
		||||
            if (password != null) {
 | 
			
		||||
                req.aesKey = passwordToKey(true);
 | 
			
		||||
                if (req.aesKey == null)
 | 
			
		||||
                    return;  // Error message already printed.
 | 
			
		||||
            }
 | 
			
		||||
            wallet.completeTx(req);
 | 
			
		||||
 | 
			
		||||
            System.out.println(req.tx.getHashAsString());
 | 
			
		||||
            if (options.has("offline")) {
 | 
			
		||||
                wallet.commitTx(req.tx);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setup();
 | 
			
		||||
            peers.start();
 | 
			
		||||
            // Wait for peers to connect, the tx to be sent to one of them and for it to be propagated across the
 | 
			
		||||
            // network. Once propagation is complete and we heard the transaction back from all our peers, it will
 | 
			
		||||
            // be committed to the wallet.
 | 
			
		||||
            peers.broadcastTransaction(req.tx).future().get();
 | 
			
		||||
            // Hack for regtest/single peer mode, as we're about to shut down and won't get an ACK from the remote end.
 | 
			
		||||
            List<Peer> peerList = peers.getConnectedPeers();
 | 
			
		||||
            if (peerList.size() == 1)
 | 
			
		||||
                peerList.get(0).ping().get();
 | 
			
		||||
        } catch (BlockStoreException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (KeyCrypterException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (InterruptedException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (ExecutionException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (InsufficientMoneyException e) {
 | 
			
		||||
            System.err.println("Insufficient funds: have " + wallet.getBalance().toFriendlyString());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Settles a CLTV payment channel transaction given that we own both private keys (ie. for testing).
 | 
			
		||||
     * @param txHash
 | 
			
		||||
     * @param output
 | 
			
		||||
     * @param fee
 | 
			
		||||
     * @param allowUnconfirmed
 | 
			
		||||
     */
 | 
			
		||||
    private static void settleCLTVPaymentChannel(String txHash, String output, Coin fee, boolean allowUnconfirmed) {
 | 
			
		||||
        try {
 | 
			
		||||
            OutputSpec outputSpec;
 | 
			
		||||
            Coin value;
 | 
			
		||||
            try {
 | 
			
		||||
                outputSpec = new OutputSpec(output);
 | 
			
		||||
                value = outputSpec.value;
 | 
			
		||||
            } catch (WrongNetworkException e) {
 | 
			
		||||
                System.err.println("Malformed output specification, address is for a different network.");
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (AddressFormatException e) {
 | 
			
		||||
                System.err.println("Malformed output specification, could not parse as address.");
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (NumberFormatException e) {
 | 
			
		||||
                System.err.println("Malformed output specification, could not parse as value.");
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (IllegalArgumentException e) {
 | 
			
		||||
                System.err.println(e.getMessage());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Wallet.SendRequest req = outputSpec.isAddress() ?
 | 
			
		||||
                    Wallet.SendRequest.to(outputSpec.addr, value) :
 | 
			
		||||
                    Wallet.SendRequest.to(params, outputSpec.key, value);
 | 
			
		||||
            req.fee = fee;
 | 
			
		||||
 | 
			
		||||
            Transaction lockTimeVerify = wallet.getTransaction(Sha256Hash.wrap(txHash));
 | 
			
		||||
            if (lockTimeVerify == null) {
 | 
			
		||||
                System.err.println("Couldn't find transaction with given hash");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            TransactionOutput lockTimeVerifyOutput = null;
 | 
			
		||||
            for (TransactionOutput out : lockTimeVerify.getOutputs()) {
 | 
			
		||||
                if (out.getScriptPubKey().isSentToCLTVPaymentChannel()) {
 | 
			
		||||
                    lockTimeVerifyOutput = out;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (lockTimeVerifyOutput == null) {
 | 
			
		||||
                System.err.println("TX to spend wasn't sent to LockTimeVerify");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!req.fee.add(value).equals(lockTimeVerifyOutput.getValue())) {
 | 
			
		||||
                System.err.println("You must spend all the money in the input transaction");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (allowUnconfirmed) {
 | 
			
		||||
                wallet.allowSpendingUnconfirmedTransactions();
 | 
			
		||||
            }
 | 
			
		||||
            if (password != null) {
 | 
			
		||||
                req.aesKey = passwordToKey(true);
 | 
			
		||||
                if (req.aesKey == null)
 | 
			
		||||
                    return;  // Error message already printed.
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ECKey key1 = wallet.findKeyFromPubKey(
 | 
			
		||||
                    lockTimeVerifyOutput.getScriptPubKey().getCLTVPaymentChannelSenderPubKey());
 | 
			
		||||
            ECKey key2 = wallet.findKeyFromPubKey(
 | 
			
		||||
                    lockTimeVerifyOutput.getScriptPubKey().getCLTVPaymentChannelRecipientPubKey());
 | 
			
		||||
            if (key1 == null || key2 == null) {
 | 
			
		||||
                System.err.println("Don't own private keys for both pubkeys");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            TransactionInput input = new TransactionInput(
 | 
			
		||||
                    params, req.tx, new byte[] {}, lockTimeVerifyOutput.getOutPointFor());
 | 
			
		||||
            req.tx.addInput(input);
 | 
			
		||||
            TransactionSignature sig1 =
 | 
			
		||||
                    req.tx.calculateSignature(0, key1, lockTimeVerifyOutput.getScriptPubKey(), Transaction.SigHash.SINGLE, false);
 | 
			
		||||
            TransactionSignature sig2 =
 | 
			
		||||
                    req.tx.calculateSignature(0, key2, lockTimeVerifyOutput.getScriptPubKey(), Transaction.SigHash.SINGLE, false);
 | 
			
		||||
            input.setScriptSig(ScriptBuilder.createCLTVPaymentChannelInput(sig1, sig2));
 | 
			
		||||
 | 
			
		||||
            System.out.println(req.tx.getHashAsString());
 | 
			
		||||
            if (options.has("offline")) {
 | 
			
		||||
                wallet.commitTx(req.tx);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setup();
 | 
			
		||||
            peers.start();
 | 
			
		||||
            // Wait for peers to connect, the tx to be sent to one of them and for it to be propagated across the
 | 
			
		||||
            // network. Once propagation is complete and we heard the transaction back from all our peers, it will
 | 
			
		||||
            // be committed to the wallet.
 | 
			
		||||
            peers.broadcastTransaction(req.tx).future().get();
 | 
			
		||||
            // Hack for regtest/single peer mode, as we're about to shut down and won't get an ACK from the remote end.
 | 
			
		||||
            List<Peer> peerList = peers.getConnectedPeers();
 | 
			
		||||
            if (peerList.size() == 1)
 | 
			
		||||
                peerList.get(0).ping().get();
 | 
			
		||||
        } catch (BlockStoreException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (KeyCrypterException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (InterruptedException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (ExecutionException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refunds a CLTV payment channel transaction after the lock time has expired.
 | 
			
		||||
     * @param txHash
 | 
			
		||||
     * @param output
 | 
			
		||||
     * @param fee
 | 
			
		||||
     * @param allowUnconfirmed
 | 
			
		||||
     */
 | 
			
		||||
    private static void refundCLTVPaymentChannel(String txHash, String output, Coin fee, boolean allowUnconfirmed) {
 | 
			
		||||
        try {
 | 
			
		||||
            OutputSpec outputSpec;
 | 
			
		||||
            Coin value;
 | 
			
		||||
            try {
 | 
			
		||||
                outputSpec = new OutputSpec(output);
 | 
			
		||||
                value = outputSpec.value;
 | 
			
		||||
            } catch (WrongNetworkException e) {
 | 
			
		||||
                System.err.println("Malformed output specification, address is for a different network.");
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (AddressFormatException e) {
 | 
			
		||||
                System.err.println("Malformed output specification, could not parse as address.");
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (NumberFormatException e) {
 | 
			
		||||
                System.err.println("Malformed output specification, could not parse as value.");
 | 
			
		||||
                return;
 | 
			
		||||
            } catch (IllegalArgumentException e) {
 | 
			
		||||
                System.err.println(e.getMessage());
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Wallet.SendRequest req = outputSpec.isAddress() ?
 | 
			
		||||
                    Wallet.SendRequest.to(outputSpec.addr, value) :
 | 
			
		||||
                    Wallet.SendRequest.to(params, outputSpec.key, value);
 | 
			
		||||
            req.fee = fee;
 | 
			
		||||
 | 
			
		||||
            Transaction lockTimeVerify = wallet.getTransaction(Sha256Hash.wrap(txHash));
 | 
			
		||||
            if (lockTimeVerify == null) {
 | 
			
		||||
                System.err.println("Couldn't find transaction with given hash");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            TransactionOutput lockTimeVerifyOutput = null;
 | 
			
		||||
            for (TransactionOutput out : lockTimeVerify.getOutputs()) {
 | 
			
		||||
                if (out.getScriptPubKey().isSentToCLTVPaymentChannel()) {
 | 
			
		||||
                    lockTimeVerifyOutput = out;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (lockTimeVerifyOutput == null) {
 | 
			
		||||
                System.err.println("TX to spend wasn't sent to LockTimeVerify");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            req.tx.setLockTime(lockTimeVerifyOutput.getScriptPubKey().getCLTVPaymentChannelExpiry().longValue());
 | 
			
		||||
 | 
			
		||||
            if (!req.fee.add(value).equals(lockTimeVerifyOutput.getValue())) {
 | 
			
		||||
                System.err.println("You must spend all the money in the input transaction");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (allowUnconfirmed) {
 | 
			
		||||
                wallet.allowSpendingUnconfirmedTransactions();
 | 
			
		||||
            }
 | 
			
		||||
            if (password != null) {
 | 
			
		||||
                req.aesKey = passwordToKey(true);
 | 
			
		||||
                if (req.aesKey == null)
 | 
			
		||||
                    return;  // Error message already printed.
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ECKey key = wallet.findKeyFromPubKey(
 | 
			
		||||
                    lockTimeVerifyOutput.getScriptPubKey().getCLTVPaymentChannelSenderPubKey());
 | 
			
		||||
            if (key == null) {
 | 
			
		||||
                System.err.println("Don't own private key for pubkey");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            TransactionInput input = new TransactionInput(
 | 
			
		||||
                    params, req.tx, new byte[] {}, lockTimeVerifyOutput.getOutPointFor());
 | 
			
		||||
            input.setSequenceNumber(0);
 | 
			
		||||
            req.tx.addInput(input);
 | 
			
		||||
            TransactionSignature sig =
 | 
			
		||||
                    req.tx.calculateSignature(0, key, lockTimeVerifyOutput.getScriptPubKey(), Transaction.SigHash.SINGLE, false);
 | 
			
		||||
            input.setScriptSig(ScriptBuilder.createCLTVPaymentChannelRefund(sig));
 | 
			
		||||
 | 
			
		||||
            System.out.println(req.tx.getHashAsString());
 | 
			
		||||
            if (options.has("offline")) {
 | 
			
		||||
                wallet.commitTx(req.tx);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setup();
 | 
			
		||||
            peers.start();
 | 
			
		||||
            // Wait for peers to connect, the tx to be sent to one of them and for it to be propagated across the
 | 
			
		||||
            // network. Once propagation is complete and we heard the transaction back from all our peers, it will
 | 
			
		||||
            // be committed to the wallet.
 | 
			
		||||
            peers.broadcastTransaction(req.tx).future().get();
 | 
			
		||||
            // Hack for regtest/single peer mode, as we're about to shut down and won't get an ACK from the remote end.
 | 
			
		||||
            List<Peer> peerList = peers.getConnectedPeers();
 | 
			
		||||
            if (peerList.size() == 1)
 | 
			
		||||
                peerList.get(0).ping().get();
 | 
			
		||||
        } catch (BlockStoreException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (KeyCrypterException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (InterruptedException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        } catch (ExecutionException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Parses the string either as a whole number of blocks, or if it contains slashes as a YYYY/MM/DD format date
 | 
			
		||||
     * and returns the lock time in wire format.
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,30 @@ Usage: wallet-tool --flags action-name
 | 
			
		||||
                       If --date is specified, that's the creation date.
 | 
			
		||||
                       If --unixtime is specified, that's the creation time and it overrides --date.
 | 
			
		||||
                       If you omit both options, the creation time is being cleared (set to 0).
 | 
			
		||||
  send-cltvpaymentchannel
 | 
			
		||||
                       Creates and broadcasts a transaction paying to a CHECKLOCKTIMEVERIFY micropayment channel.
 | 
			
		||||
                       Requires a public key for the money recipient, public key to create the transactions (the
 | 
			
		||||
                       "return" address) and an expiry time.
 | 
			
		||||
                       Options:
 | 
			
		||||
                          --output=pubkey:value sets the amount to lock and the recipient
 | 
			
		||||
                          --refund-to=pubkey sets "our" public key
 | 
			
		||||
                          --fee=value sets the mining fee
 | 
			
		||||
                          --locktime=YYYY/MM/DD sets the expiry time for the channel
 | 
			
		||||
  settle-cltvpaymentchannel
 | 
			
		||||
                       Creates and broadcasts a transaction settling a previous micropayment channel.
 | 
			
		||||
                       This tool, for testing, requires the presence of both private keys.
 | 
			
		||||
                       Options:
 | 
			
		||||
                          --output=pubkey:value sets the destination for the money
 | 
			
		||||
                          --fee=value sets the mining fee
 | 
			
		||||
                          --txhash=hash sets the transaction to spend
 | 
			
		||||
  refund-cltvpaymentchannel
 | 
			
		||||
                       Creates and broadcasts a transaction refunding a previous micropayment channel.
 | 
			
		||||
                       This command can only be called once the expiry for the micropayment channel has passed -
 | 
			
		||||
                       the created transaction won't be accepted into the mempool until that point.
 | 
			
		||||
                       Options:
 | 
			
		||||
                          --output=pubkey:value sets the destination for the money
 | 
			
		||||
                          --fee=value sets the mining fee
 | 
			
		||||
                          --txhash=hash sets the transaction to spend
 | 
			
		||||
 | 
			
		||||
>>> GENERAL OPTIONS
 | 
			
		||||
  --debuglog           Enables logging from the core library.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user