use np2wsh for loop in htlc

pull/34/head
Joost Jager 5 years ago
parent e765baa1f9
commit 30c7d71c57
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7

@ -276,15 +276,9 @@ func (s *Client) LoopOut(globalCtx context.Context,
// Post swap to the main loop.
s.executor.initiateSwap(globalCtx, swap)
// Retrieve htlc address.
htlcAddress, err := swap.htlc.Address(s.lndServices.ChainParams)
if err != nil {
return nil, nil, err
}
// Return hash so that the caller can identify this swap in the updates
// stream.
return &swap.hash, htlcAddress, nil
return &swap.hash, swap.htlc.Address, nil
}
// LoopOutQuote takes a LoopOut amount and returns a break down of estimated
@ -313,7 +307,7 @@ func (s *Client) LoopOutQuote(ctx context.Context,
)
minerFee, err := s.sweeper.GetSweepFee(
ctx, swap.QuoteHtlc.MaxSuccessWitnessSize,
ctx, swap.QuoteHtlc.AddSuccessToEstimator,
request.SweepConfTarget,
)
if err != nil {
@ -381,15 +375,9 @@ func (s *Client) LoopIn(globalCtx context.Context,
// Post swap to the main loop.
s.executor.initiateSwap(globalCtx, swap)
// Retrieve htlc address.
htlcAddress, err := swap.htlc.Address(s.lndServices.ChainParams)
if err != nil {
return nil, nil, err
}
// Return hash so that the caller can identify this swap in the updates
// stream.
return &swap.hash, htlcAddress, nil
return &swap.hash, swap.htlc.Address, nil
}
// LoopInQuote takes an amount and returns a break down of estimated

@ -13,7 +13,6 @@ import (
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/looprpc"
@ -104,19 +103,6 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
state = looprpc.SwapState_FAILED
}
htlc, err := swap.NewHtlc(
loopSwap.CltvExpiry, loopSwap.SenderKey, loopSwap.ReceiverKey,
loopSwap.SwapHash,
)
if err != nil {
return nil, err
}
address, err := htlc.Address(s.lnd.ChainParams)
if err != nil {
return nil, err
}
var swapType looprpc.SwapType
switch loopSwap.SwapType {
case loop.TypeIn:
@ -133,7 +119,7 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
State: state,
InitiationTime: loopSwap.InitiationTime.UnixNano(),
LastUpdateTime: loopSwap.LastUpdate.UnixNano(),
HtlcAddress: address.EncodeAddress(),
HtlcAddress: loopSwap.HtlcAddress.EncodeAddress(),
Type: swapType,
}, nil
}

@ -4,6 +4,8 @@ import (
"fmt"
"strconv"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/swap"
)
@ -28,13 +30,21 @@ func view(config *config) error {
}
defer cleanup()
swaps, err := swapClient.FetchLoopOutSwaps()
if err != nil {
if err := viewOut(swapClient, chainParams); err != nil {
return err
}
if err := viewIn(swapClient, chainParams); err != nil {
return err
}
if len(swaps) == 0 {
fmt.Printf("No swaps\n")
return nil
}
func viewOut(swapClient *loop.Client, chainParams *chaincfg.Params) error {
swaps, err := swapClient.FetchLoopOutSwaps()
if err != nil {
return err
}
for _, s := range swaps {
@ -42,23 +52,18 @@ func view(config *config) error {
s.Contract.CltvExpiry,
s.Contract.SenderKey,
s.Contract.ReceiverKey,
s.Hash,
s.Hash, swap.HtlcP2WSH, chainParams,
)
if err != nil {
return err
}
htlcAddress, err := htlc.Address(chainParams)
if err != nil {
return err
}
fmt.Printf("%v\n", s.Hash)
fmt.Printf("OUT %v\n", s.Hash)
fmt.Printf(" Created: %v (height %v)\n",
s.Contract.InitiationTime, s.Contract.InitiationHeight,
)
fmt.Printf(" Preimage: %v\n", s.Contract.Preimage)
fmt.Printf(" Htlc address: %v\n", htlcAddress)
fmt.Printf(" Htlc address: %v\n", htlc.Address)
unchargeChannel := "any"
if s.Contract.UnchargeChannel != nil {
@ -81,3 +86,40 @@ func view(config *config) error {
return nil
}
func viewIn(swapClient *loop.Client, chainParams *chaincfg.Params) error {
swaps, err := swapClient.FetchLoopInSwaps()
if err != nil {
return err
}
for _, s := range swaps {
htlc, err := swap.NewHtlc(
s.Contract.CltvExpiry,
s.Contract.SenderKey,
s.Contract.ReceiverKey,
s.Hash, swap.HtlcNP2WSH, chainParams,
)
if err != nil {
return err
}
fmt.Printf("IN %v\n", s.Hash)
fmt.Printf(" Created: %v (height %v)\n",
s.Contract.InitiationTime, s.Contract.InitiationHeight,
)
fmt.Printf(" Preimage: %v\n", s.Contract.Preimage)
fmt.Printf(" Htlc address: %v\n", htlc.Address)
fmt.Printf(" Amt: %v, Expiry: %v\n",
s.Contract.AmountRequested, s.Contract.CltvExpiry,
)
for i, e := range s.Events {
fmt.Printf(" Update %v, Time %v, State: %v\n",
i, e.Time, e.State,
)
}
fmt.Println()
}
return nil
}

@ -289,6 +289,8 @@ type SwapInfo struct {
SwapType Type
loopdb.SwapContract
HtlcAddress btcutil.Address
}
// LastUpdate returns the last update time of the swap

@ -151,7 +151,7 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
}
swapKit, err := newSwapKit(
swapHash, TypeIn, cfg, &contract.SwapContract,
swapHash, TypeIn, cfg, &contract.SwapContract, swap.HtlcNP2WSH,
)
if err != nil {
return nil, err
@ -184,7 +184,7 @@ func resumeLoopInSwap(reqContext context.Context, cfg *swapConfig,
logger.Infof("Resuming loop in swap %v", hash)
swapKit, err := newSwapKit(
hash, TypeIn, cfg, &pend.Contract.SwapContract,
hash, TypeIn, cfg, &pend.Contract.SwapContract, swap.HtlcNP2WSH,
)
if err != nil {
return nil, err
@ -307,7 +307,7 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
// Determine the htlc outpoint by inspecting the htlc tx.
htlcOutpoint, htlcValue, err := swap.GetScriptOutput(
conf.Tx, s.htlc.ScriptHash,
conf.Tx, s.htlc.PkScript,
)
if err != nil {
return err
@ -340,7 +340,7 @@ func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) (
ctx, cancel := context.WithCancel(globalCtx)
defer cancel()
confChan, confErr, err := s.lnd.ChainNotifier.RegisterConfirmationsNtfn(
ctx, nil, s.htlc.ScriptHash, 1, s.InitiationHeight,
ctx, nil, s.htlc.PkScript, 1, s.InitiationHeight,
)
if err != nil {
return nil, err
@ -400,7 +400,7 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
s.log.Infof("Publishing on chain HTLC with fee rate %v", feeRate)
tx, err := s.lnd.WalletKit.SendOutputs(ctx,
[]*wire.TxOut{{
PkScript: s.htlc.ScriptHash,
PkScript: s.htlc.PkScript,
Value: int64(s.LoopInContract.AmountRequested),
}},
feeRate,
@ -424,7 +424,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
rpcCtx, cancel := context.WithCancel(ctx)
defer cancel()
spendChan, spendErr, err := s.lnd.ChainNotifier.RegisterSpendNtfn(
rpcCtx, nil, s.htlc.ScriptHash, s.InitiationHeight,
rpcCtx, nil, s.htlc.PkScript, s.InitiationHeight,
)
if err != nil {
return fmt.Errorf("register spend ntfn: %v", err)
@ -583,7 +583,7 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
// Calculate sweep tx fee
fee, err := s.sweeper.GetSweepFee(
ctx, s.htlc.MaxTimeoutWitnessSize, TimeoutTxConfTarget,
ctx, s.htlc.AddTimeoutToEstimator, TimeoutTxConfTarget,
)
if err != nil {
return err

@ -257,7 +257,7 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool) {
htlc, err := swap.NewHtlc(
contract.CltvExpiry, contract.SenderKey, contract.ReceiverKey,
testPreimage.Hash(),
testPreimage.Hash(), swap.HtlcNP2WSH, cfg.lnd.ChainParams,
)
if err != nil {
t.Fatal(err)
@ -329,7 +329,7 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool) {
ctx.assertState(loopdb.StateHtlcPublished)
htlcTx.AddTxOut(&wire.TxOut{
PkScript: htlc.ScriptHash,
PkScript: htlc.PkScript,
})
}

@ -105,7 +105,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
}
swapKit, err := newSwapKit(
swapHash, TypeOut, cfg, &contract.SwapContract,
swapHash, TypeOut, cfg, &contract.SwapContract, swap.HtlcP2WSH,
)
if err != nil {
return nil, err
@ -138,7 +138,7 @@ func resumeLoopOutSwap(reqContext context.Context, cfg *swapConfig,
logger.Infof("Resuming loop out swap %v", hash)
swapKit, err := newSwapKit(
hash, TypeOut, cfg, &pend.Contract.SwapContract,
hash, TypeOut, cfg, &pend.Contract.SwapContract, swap.HtlcP2WSH,
)
if err != nil {
return nil, err
@ -168,6 +168,7 @@ func (s *loopOutSwap) execute(mainCtx context.Context,
s.executeConfig = *cfg
s.height = height
// Execute swap.
err := s.executeAndFinalize(mainCtx)
// If an unexpected error happened, report a temporary failure.
@ -189,6 +190,7 @@ func (s *loopOutSwap) execute(mainCtx context.Context,
// executeAndFinalize executes a swap and awaits the definitive outcome of the
// offchain payments. When this method returns, the swap outcome is final.
func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error {
// Announce swap by sending out an initial update.
err := s.sendUpdate(globalCtx)
if err != nil {
@ -281,7 +283,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
// Retrieve outpoint for sweep.
htlcOutpoint, htlcValue, err := swap.GetScriptOutput(
txConf.Tx, s.htlc.ScriptHash,
txConf.Tx, s.htlc.PkScript,
)
if err != nil {
return err
@ -383,7 +385,7 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
defer cancel()
htlcConfChan, htlcErrChan, err :=
s.lnd.ChainNotifier.RegisterConfirmationsNtfn(
ctx, nil, s.htlc.ScriptHash, 1,
ctx, nil, s.htlc.PkScript, 1,
s.InitiationHeight,
)
if err != nil {
@ -514,7 +516,7 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
ctx, cancel := context.WithCancel(globalCtx)
defer cancel()
spendChan, spendErr, err := s.lnd.ChainNotifier.RegisterSpendNtfn(
ctx, nil, s.htlc.ScriptHash, s.InitiationHeight,
ctx, nil, s.htlc.PkScript, s.InitiationHeight,
)
if err != nil {
return nil, fmt.Errorf("register spend ntfn: %v", err)
@ -571,7 +573,7 @@ func (s *loopOutSwap) sweep(ctx context.Context,
// Calculate sweep tx fee
fee, err := s.sweeper.GetSweepFee(
ctx, s.htlc.MaxSuccessWitnessSize,
ctx, s.htlc.AddSuccessToEstimator,
s.SweepConfTarget,
)
if err != nil {

@ -29,29 +29,26 @@ type swapKit struct {
}
func newSwapKit(hash lntypes.Hash, swapType Type, cfg *swapConfig,
contract *loopdb.SwapContract) (*swapKit, error) {
contract *loopdb.SwapContract, outputType swap.HtlcOutputType) (
*swapKit, error) {
// Compose expected on-chain swap script
htlc, err := swap.NewHtlc(
contract.CltvExpiry, contract.SenderKey,
contract.ReceiverKey, hash,
contract.ReceiverKey, hash, outputType,
cfg.lnd.ChainParams,
)
if err != nil {
return nil, err
}
// Log htlc address for debugging.
htlcAddress, err := htlc.Address(cfg.lnd.ChainParams)
if err != nil {
return nil, err
}
log := &SwapLog{
Hash: hash,
Logger: logger,
}
log.Infof("Htlc address: %v", htlcAddress)
// Log htlc address for debugging.
log.Infof("Htlc address: %v", htlc.Address)
return &swapKit{
swapConfig: *cfg,
@ -72,6 +69,7 @@ func (s *swapKit) sendUpdate(ctx context.Context) error {
SwapType: s.swapType,
LastUpdate: s.lastUpdateTime,
State: s.state,
HtlcAddress: s.htlc.Address,
}
s.log.Infof("state %v", info.State)

@ -2,6 +2,7 @@ package swap
import (
"bytes"
"crypto/sha256"
"errors"
"github.com/btcsuite/btcd/chaincfg"
@ -12,13 +13,27 @@ import (
"github.com/lightningnetwork/lnd/lntypes"
)
// HtlcOutputType defines the output type of the htlc that is published.
type HtlcOutputType uint8
const (
// HtlcP2WSH is a pay-to-witness-script-hash output (segwit only)
HtlcP2WSH HtlcOutputType = iota
// HtlcNP2WSH is a nested pay-to-witness-script-hash output that can be
// paid to be legacy wallets.
HtlcNP2WSH
)
// Htlc contains relevant htlc information from the receiver perspective.
type Htlc struct {
Script []byte
ScriptHash []byte
Hash lntypes.Hash
MaxSuccessWitnessSize int
MaxTimeoutWitnessSize int
Script []byte
PkScript []byte
Hash lntypes.Hash
OutputType HtlcOutputType
ChainParams *chaincfg.Params
Address btcutil.Address
SigScript []byte
}
var (
@ -30,13 +45,15 @@ var (
// the maximum value for cltv expiry to get the maximum (worst case)
// script size.
QuoteHtlc, _ = NewHtlc(
^int32(0), quoteKey, quoteKey, quoteHash,
^int32(0), quoteKey, quoteKey, quoteHash, HtlcP2WSH,
&chaincfg.MainNetParams,
)
)
// NewHtlc returns a new instance.
func NewHtlc(cltvExpiry int32, senderKey, receiverKey [33]byte,
hash lntypes.Hash) (*Htlc, error) {
hash lntypes.Hash, outputType HtlcOutputType,
chainParams *chaincfg.Params) (*Htlc, error) {
script, err := swapHTLCScript(
cltvExpiry, senderKey, receiverKey, hash,
@ -45,39 +62,73 @@ func NewHtlc(cltvExpiry int32, senderKey, receiverKey [33]byte,
return nil, err
}
scriptHash, err := input.WitnessScriptHash(script)
p2wshPkScript, err := input.WitnessScriptHash(script)
if err != nil {
return nil, err
}
// Calculate maximum success witness size
//
// - number_of_witness_elements: 1 byte
// - receiver_sig_length: 1 byte
// - receiver_sig: 73 bytes
// - preimage_length: 1 byte
// - preimage: 33 bytes
// - witness_script_length: 1 byte
// - witness_script: len(script) bytes
maxSuccessWitnessSize := 1 + 1 + 73 + 1 + 33 + 1 + len(script)
// Calculate maximum timeout witness size
//
// - number_of_witness_elements: 1 byte
// - sender_sig_length: 1 byte
// - sender_sig: 73 bytes
// - zero_length: 1 byte
// - zero: 1 byte
// - witness_script_length: 1 byte
// - witness_script: len(script) bytes
maxTimeoutWitnessSize := 1 + 1 + 73 + 1 + 1 + 1 + len(script)
p2wshPkScriptHash := sha256.Sum256(p2wshPkScript)
var pkScript, sigScript []byte
var address btcutil.Address
switch outputType {
case HtlcNP2WSH:
// Generate p2sh script for p2wsh (nested).
hash160 := input.Ripemd160H(p2wshPkScriptHash[:])
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_HASH160)
builder.AddData(hash160)
builder.AddOp(txscript.OP_EQUAL)
pkScript, err = builder.Script()
if err != nil {
return nil, err
}
// Generate a valid sigScript that will allow us to spend the
// p2sh output. The sigScript will contain only a single push of
// the p2wsh witness program corresponding to the matching
// public key of this address.
sigScript, err = txscript.NewScriptBuilder().
AddData(p2wshPkScript).
Script()
if err != nil {
return nil, err
}
address, err = btcutil.NewAddressScriptHash(
p2wshPkScript, chainParams,
)
if err != nil {
return nil, err
}
case HtlcP2WSH:
pkScript = p2wshPkScript
address, err = btcutil.NewAddressWitnessScriptHash(
p2wshPkScriptHash[:],
chainParams,
)
if err != nil {
return nil, err
}
default:
return nil, errors.New("unknown output type")
}
return &Htlc{
Hash: hash,
Script: script,
ScriptHash: scriptHash,
MaxSuccessWitnessSize: maxSuccessWitnessSize,
MaxTimeoutWitnessSize: maxTimeoutWitnessSize,
Hash: hash,
Script: script,
PkScript: pkScript,
OutputType: outputType,
ChainParams: chainParams,
Address: address,
SigScript: sigScript,
}, nil
}
@ -127,17 +178,6 @@ func swapHTLCScript(cltvExpiry int32, senderHtlcKey,
return builder.Script()
}
// Address returns the p2wsh address of the htlc.
func (h *Htlc) Address(chainParams *chaincfg.Params) (
btcutil.Address, error) {
// Skip OP_0 and data length.
return btcutil.NewAddressWitnessScriptHash(
h.ScriptHash[2:],
chainParams,
)
}
// GenSuccessWitness returns the success script to spend this htlc with the
// preimage.
func (h *Htlc) GenSuccessWitness(receiverSig []byte,
@ -178,3 +218,47 @@ func (h *Htlc) GenTimeoutWitness(senderSig []byte) (wire.TxWitness, error) {
return witnessStack, nil
}
// AddSuccessToEstimator adds a successful spend to a weight estimator.
func (h *Htlc) AddSuccessToEstimator(estimator *input.TxWeightEstimator) {
// Calculate maximum success witness size
//
// - number_of_witness_elements: 1 byte
// - receiver_sig_length: 1 byte
// - receiver_sig: 73 bytes
// - preimage_length: 1 byte
// - preimage: 33 bytes
// - witness_script_length: 1 byte
// - witness_script: len(script) bytes
maxSuccessWitnessSize := 1 + 1 + 73 + 1 + 33 + 1 + len(h.Script)
switch h.OutputType {
case HtlcP2WSH:
estimator.AddWitnessInput(maxSuccessWitnessSize)
case HtlcNP2WSH:
estimator.AddNestedP2WSHInput(maxSuccessWitnessSize)
}
}
// AddTimeoutToEstimator adds a timeout spend to a weight estimator.
func (h *Htlc) AddTimeoutToEstimator(estimator *input.TxWeightEstimator) {
// Calculate maximum timeout witness size
//
// - number_of_witness_elements: 1 byte
// - sender_sig_length: 1 byte
// - sender_sig: 73 bytes
// - zero_length: 1 byte
// - zero: 1 byte
// - witness_script_length: 1 byte
// - witness_script: len(script) bytes
maxTimeoutWitnessSize := 1 + 1 + 73 + 1 + 1 + 1 + len(h.Script)
switch h.OutputType {
case HtlcP2WSH:
estimator.AddWitnessInput(maxTimeoutWitnessSize)
case HtlcNP2WSH:
estimator.AddNestedP2WSHInput(maxTimeoutWitnessSize)
}
}

@ -36,6 +36,7 @@ func (s *Sweeper) CreateSweepTx(
// Add HTLC input.
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: htlcOutpoint,
SignatureScript: htlc.SigScript,
})
// Add output for the destination address.
@ -85,9 +86,12 @@ func (s *Sweeper) CreateSweepTx(
return sweepTx, nil
}
// GetSweepFee calculates the required tx fee.
// GetSweepFee calculates the required tx fee to spend to P2WKH. It takes a
// function that is expected to add the weight of the input to the weight
// estimator.
func (s *Sweeper) GetSweepFee(ctx context.Context,
htlcSuccessWitnessSize int, sweepConfTarget int32) (
addInputEstimate func(*input.TxWeightEstimator),
sweepConfTarget int32) (
btcutil.Amount, error) {
// Get fee estimate from lnd.
@ -99,7 +103,7 @@ func (s *Sweeper) GetSweepFee(ctx context.Context,
// Calculate weight for this tx.
var weightEstimate input.TxWeightEstimator
weightEstimate.AddP2WKHOutput()
weightEstimate.AddWitnessInput(htlcSuccessWitnessSize)
addInputEstimate(&weightEstimate)
weight := weightEstimate.Weight()
return feeRate.FeeForWeight(int64(weight)), nil

Loading…
Cancel
Save