Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
eb8d22e
lnwallet/channel: properly set SignDesc.Output
halseth Dec 9, 2020
1e68cdc
input+lnwallet+contractcourt: define SignDetails for HTLC resolutions
halseth Dec 9, 2020
6150995
sweep/txgenerator: log in case of no change output
halseth Dec 9, 2020
83f9aae
sweeper/tx_input_set: add logging for input set construction
halseth Dec 9, 2020
09f2307
itest: increase htlc amt for local force close tests
halseth Dec 9, 2020
241e21a
itest: use hex encoded hash in error message
halseth Dec 9, 2020
8eb6d7c
input/size: define witness constants needed
halseth Dec 9, 2020
65e50f6
input/witnessgen: define witness type for HTLC 2nd level inputs
halseth Dec 9, 2020
5f61314
contractcourt: decouple waitForHeight from commit sweep resolver
halseth Dec 9, 2020
4da2b29
contractcourt/succes+timeout resolver: extract waitForSpend logic
halseth Dec 9, 2020
9b08ef6
contractcourt/[incoming|outgoing]_contest_resolver: make inner resolv…
halseth Dec 9, 2020
9d33b00
contractcourt/success_resolver: extract remoteHTLC sweep into resolve…
halseth Dec 9, 2020
0b84d5f
contractcourt/success_resolver: extract HTLC success handling into br…
halseth Dec 9, 2020
7142a30
contractcourt/success_resolver: remove sweep tx checkpoint
halseth Dec 9, 2020
d02b486
contractcourt: revamp HTLC success unit test
halseth Dec 9, 2020
85ea181
contraccourt+input: create HtlcSecondLevelAnchorInput and resolver for
halseth Dec 9, 2020
aabba32
contractcourt: add TestHtlcSuccessSecondStageResolutionSweeper
halseth Dec 9, 2020
2f33425
contractcourt/timeout_resolver: extract logic into spendHtlcOutput
halseth Dec 9, 2020
0c3b64a
contractcourt/timeout_resolver: extract logic into sweepSecondLevelTr…
halseth Dec 9, 2020
4992e41
contraccourt+input: create resolver for timeout second level
halseth Dec 9, 2020
bb406c8
contractcourt/htlc_timeout_test: expand timeout tests
halseth Dec 9, 2020
70eb526
rpctest: increase sweeper BatchWindow during itests
halseth Dec 9, 2020
4b9fbe2
itest/local_chain_claim test: mine one less blocks for anchor sweeps
halseth Dec 9, 2020
42c51b6
itest/channel_force_close test: handle HTLCs going through sweeper
halseth Dec 9, 2020
1627310
itest: add HTLC aggregation test
halseth Dec 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions contractcourt/briefcase.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"fmt"
"io"

"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
Expand Down Expand Up @@ -275,6 +277,13 @@ var (
// the full set of resolutions for a channel.
resolutionsKey = []byte("resolutions")

// resolutionsSignDetailsKey is the key under the logScope where we
// will store input.SignDetails for each HTLC resolution. If this is
// not found under the logScope, it means it was written before
// SignDetails was introduced, and should be set nil for each HTLC
// resolution.
resolutionsSignDetailsKey = []byte("resolutions-sign-details")

// anchorResolutionKey is the key under the logScope that we'll use to
// store the anchor resolution, if any.
anchorResolutionKey = []byte("anchor-resolution")
Expand Down Expand Up @@ -656,6 +665,10 @@ func (b *boltArbitratorLog) LogContractResolutions(c *ContractResolutions) error
}
}

// As we write the HTLC resolutions, we'll serialize the sign
// details for each, to store under a new key.
var signDetailsBuf bytes.Buffer

// With the output for the commitment transaction written, we
// can now write out the resolutions for the incoming and
// outgoing HTLC's.
Expand All @@ -668,6 +681,11 @@ func (b *boltArbitratorLog) LogContractResolutions(c *ContractResolutions) error
if err != nil {
return err
}

err = encodeSignDetails(&signDetailsBuf, htlc.SignDetails)
if err != nil {
return err
}
}
numOutgoing := uint32(len(c.HtlcResolutions.OutgoingHTLCs))
if err := binary.Write(&b, endian, numOutgoing); err != nil {
Expand All @@ -678,13 +696,28 @@ func (b *boltArbitratorLog) LogContractResolutions(c *ContractResolutions) error
if err != nil {
return err
}

err = encodeSignDetails(&signDetailsBuf, htlc.SignDetails)
if err != nil {
return err
}
}

// Put the resolutions under the resolutionsKey.
err = scopeBucket.Put(resolutionsKey, b.Bytes())
if err != nil {
return err
}

// We'll put the serialized sign details under its own key to
// stay backwards compatible.
err = scopeBucket.Put(
resolutionsSignDetailsKey, signDetailsBuf.Bytes(),
)
if err != nil {
return err
}

// Write out the anchor resolution if present.
if c.AnchorResolution != nil {
var b bytes.Buffer
Expand Down Expand Up @@ -779,6 +812,33 @@ func (b *boltArbitratorLog) FetchContractResolutions() (*ContractResolutions, er
}
}

// Now we attempt to get the sign details for our HTLC
// resolutions. If not present the channel is of a type that
// doesn't need them. If present there will be SignDetails
// encoded for each HTLC resolution.
signDetailsBytes := scopeBucket.Get(resolutionsSignDetailsKey)
if signDetailsBytes != nil {
r := bytes.NewReader(signDetailsBytes)

// They will be encoded in the same order as the
// resolutions: firs incoming HTLCs, then outgoing.
for i := uint32(0); i < numIncoming; i++ {
htlc := &c.HtlcResolutions.IncomingHTLCs[i]
htlc.SignDetails, err = decodeSignDetails(r)
if err != nil {
return err
}
}

for i := uint32(0); i < numOutgoing; i++ {
htlc := &c.HtlcResolutions.OutgoingHTLCs[i]
htlc.SignDetails, err = decodeSignDetails(r)
if err != nil {
return err
}
}
}

anchorResBytes := scopeBucket.Get(anchorResolutionKey)
if anchorResBytes != nil {
c.AnchorResolution = &lnwallet.AnchorResolution{}
Expand Down Expand Up @@ -941,6 +1001,11 @@ func (b *boltArbitratorLog) WipeHistory() error {
return err
}

err = scopeBucket.Delete(resolutionsSignDetailsKey)
if err != nil {
return err
}

// We'll delete any chain actions that are still stored by
// removing the enclosing bucket.
err = scopeBucket.DeleteNestedBucket(actionsBucketKey)
Expand Down Expand Up @@ -980,6 +1045,79 @@ func (b *boltArbitratorLog) checkpointContract(c ContractResolver,
}, func() {})
}

// encodeSignDetails encodes the gived SignDetails struct to the writer.
// SignDetails is allowed to be nil, in which we will encode that it is not
// present.
func encodeSignDetails(w io.Writer, s *input.SignDetails) error {
// If we don't have sign details, write false and return.
if s == nil {
return binary.Write(w, endian, false)
}

// Otherwise write true, and the contents of the SignDetails.
if err := binary.Write(w, endian, true); err != nil {
return err
}

err := input.WriteSignDescriptor(w, &s.SignDesc)
if err != nil {
return err
}
err = binary.Write(w, endian, uint32(s.SigHashType))
if err != nil {
return err
}

// Write the DER-encoded signature.
b := s.PeerSig.Serialize()
if err := wire.WriteVarBytes(w, 0, b); err != nil {
return err
}

return nil
}

// decodeSignDetails extracts a single SignDetails from the reader. It is
// allowed to return nil in case the SignDetails were empty.
func decodeSignDetails(r io.Reader) (*input.SignDetails, error) {
var present bool
if err := binary.Read(r, endian, &present); err != nil {
return nil, err
}

// Simply return nil if the next SignDetails was not present.
if !present {
return nil, nil
}

// Otherwise decode the elements of the SignDetails.
s := input.SignDetails{}
err := input.ReadSignDescriptor(r, &s.SignDesc)
if err != nil {
return nil, err
}

var sigHash uint32
err = binary.Read(r, endian, &sigHash)
if err != nil {
return nil, err
}
s.SigHashType = txscript.SigHashType(sigHash)

// Read DER-encoded signature.
rawSig, err := wire.ReadVarBytes(r, 0, 200, "signature")
if err != nil {
return nil, err
}
sig, err := btcec.ParseDERSignature(rawSig, btcec.S256())
if err != nil {
return nil, err
}
s.PeerSig = sig

return &s, nil
}

func encodeIncomingResolution(w io.Writer, i *lnwallet.IncomingHtlcResolution) error {
if _, err := w.Write(i.Preimage[:]); err != nil {
return err
Expand Down
116 changes: 107 additions & 9 deletions contractcourt/briefcase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,58 @@ var (
},
HashType: txscript.SigHashAll,
}

testTx = &wire.MsgTx{
Version: 2,
TxIn: []*wire.TxIn{
{
PreviousOutPoint: testChanPoint2,
SignatureScript: []byte{0x12, 0x34},
Witness: [][]byte{
{
0x00, 0x14, 0xee, 0x91, 0x41,
0x7e, 0x85, 0x6c, 0xde, 0x10,
0xa2, 0x91, 0x1e, 0xdc, 0xbd,
0xbd, 0x69, 0xe2, 0xef, 0xb5,
0x71, 0x48,
},
},
Sequence: 1,
},
},
TxOut: []*wire.TxOut{
{
Value: 5000000000,
PkScript: []byte{
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2,
0x86, 0x24, 0xe1, 0x81, 0x75, 0xe8,
0x51, 0xc9, 0x6b, 0x97, 0x3d, 0x81,
0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
},
},
},
LockTime: 123,
}

// A valid, DER-encoded signature (taken from btcec unit tests).
testSigBytes = []byte{
0x30, 0x44, 0x02, 0x20, 0x4e, 0x45, 0xe1, 0x69,
0x32, 0xb8, 0xaf, 0x51, 0x49, 0x61, 0xa1, 0xd3,
0xa1, 0xa2, 0x5f, 0xdf, 0x3f, 0x4f, 0x77, 0x32,
0xe9, 0xd6, 0x24, 0xc6, 0xc6, 0x15, 0x48, 0xab,
0x5f, 0xb8, 0xcd, 0x41, 0x02, 0x20, 0x18, 0x15,
0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, 0x48, 0x60,
0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, 0x83, 0x1c,
0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, 0x08, 0x22,
0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09,
}
testSig, _ = btcec.ParseDERSignature(testSigBytes, btcec.S256())

testSignDetails = &input.SignDetails{
SignDesc: testSignDesc,
SigHashType: txscript.SigHashSingle,
PeerSig: testSig,
}
)

func makeTestDB() (kvdb.Backend, func(), error) {
Expand Down Expand Up @@ -219,13 +271,13 @@ func assertResolversEqual(t *testing.T, originalResolver ContractResolver,
case *htlcOutgoingContestResolver:
diskRes := diskResolver.(*htlcOutgoingContestResolver)
assertTimeoutResEqual(
&ogRes.htlcTimeoutResolver, &diskRes.htlcTimeoutResolver,
ogRes.htlcTimeoutResolver, diskRes.htlcTimeoutResolver,
)

case *htlcIncomingContestResolver:
diskRes := diskResolver.(*htlcIncomingContestResolver)
assertSuccessResEqual(
&ogRes.htlcSuccessResolver, &diskRes.htlcSuccessResolver,
ogRes.htlcSuccessResolver, diskRes.htlcSuccessResolver,
)

if ogRes.htlcExpiry != diskRes.htlcExpiry {
Expand Down Expand Up @@ -323,13 +375,13 @@ func TestContractInsertionRetrieval(t *testing.T) {
contestTimeout := timeoutResolver
contestTimeout.htlcResolution.ClaimOutpoint = randOutPoint()
resolvers = append(resolvers, &htlcOutgoingContestResolver{
htlcTimeoutResolver: contestTimeout,
htlcTimeoutResolver: &contestTimeout,
})
contestSuccess := successResolver
contestSuccess.htlcResolution.ClaimOutpoint = randOutPoint()
resolvers = append(resolvers, &htlcIncomingContestResolver{
htlcExpiry: 100,
htlcSuccessResolver: contestSuccess,
htlcSuccessResolver: &contestSuccess,
})

// For quick lookup during the test, we'll create this map which allow
Expand Down Expand Up @@ -469,7 +521,7 @@ func TestContractSwapping(t *testing.T) {

// We'll create two resolvers, a regular timeout resolver, and the
// contest resolver that eventually turns into the timeout resolver.
timeoutResolver := htlcTimeoutResolver{
timeoutResolver := &htlcTimeoutResolver{
htlcResolution: lnwallet.OutgoingHtlcResolution{
Expiry: 99,
SignedTimeoutTx: nil,
Expand Down Expand Up @@ -497,7 +549,7 @@ func TestContractSwapping(t *testing.T) {

// With the resolver inserted, we'll now attempt to atomically swap it
// for its underlying timeout resolver.
err = testLog.SwapContract(contestResolver, &timeoutResolver)
err = testLog.SwapContract(contestResolver, timeoutResolver)
if err != nil {
t.Fatalf("unable to swap contracts: %v", err)
}
Expand All @@ -514,7 +566,7 @@ func TestContractSwapping(t *testing.T) {
}

// That single contract should be the underlying timeout resolver.
assertResolversEqual(t, &timeoutResolver, dbContracts[0])
assertResolversEqual(t, timeoutResolver, dbContracts[0])
}

// TestContractResolutionsStorage tests that we're able to properly store and
Expand Down Expand Up @@ -550,15 +602,54 @@ func TestContractResolutionsStorage(t *testing.T) {
ClaimOutpoint: randOutPoint(),
SweepSignDesc: testSignDesc,
},

// We add a resolution with SignDetails.
{
Preimage: testPreimage,
SignedSuccessTx: testTx,
SignDetails: testSignDetails,
CsvDelay: 900,
ClaimOutpoint: randOutPoint(),
SweepSignDesc: testSignDesc,
},

// We add a resolution with a signed tx, but no
// SignDetails.
{
Preimage: testPreimage,
SignedSuccessTx: testTx,
CsvDelay: 900,
ClaimOutpoint: randOutPoint(),
SweepSignDesc: testSignDesc,
},
},
OutgoingHTLCs: []lnwallet.OutgoingHtlcResolution{
// We add a resolution with a signed tx, but no
// SignDetails.
{
Expiry: 103,
SignedTimeoutTx: testTx,
CsvDelay: 923923,
ClaimOutpoint: randOutPoint(),
SweepSignDesc: testSignDesc,
},
// Resolution without signed tx.
{
Expiry: 103,
SignedTimeoutTx: nil,
CsvDelay: 923923,
ClaimOutpoint: randOutPoint(),
SweepSignDesc: testSignDesc,
},
// Resolution with SignDetails.
{
Expiry: 103,
SignedTimeoutTx: testTx,
SignDetails: testSignDetails,
CsvDelay: 923923,
ClaimOutpoint: randOutPoint(),
SweepSignDesc: testSignDesc,
},
},
},
AnchorResolution: &lnwallet.AnchorResolution{
Expand All @@ -585,8 +676,15 @@ func TestContractResolutionsStorage(t *testing.T) {
}

if !reflect.DeepEqual(&res, diskRes) {
t.Fatalf("resolution mismatch: expected %#v\n, got %#v",
&res, diskRes)
for _, h := range res.HtlcResolutions.IncomingHTLCs {
h.SweepSignDesc.KeyDesc.PubKey.Curve = nil
}
for _, h := range diskRes.HtlcResolutions.IncomingHTLCs {
h.SweepSignDesc.KeyDesc.PubKey.Curve = nil
}

t.Fatalf("resolution mismatch: expected %v\n, got %v",
spew.Sdump(&res), spew.Sdump(diskRes))
}

// We'll now delete the state, then attempt to retrieve the set of
Expand Down
Loading