diff --git a/funding/manager.go b/funding/manager.go index 50e22f9..45ed15a 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -305,12 +305,15 @@ func (m *Manager) deriveFundingShim(ourOrder order.Order, thawHeight uint32 selfChanBalance btcutil.Amount ) - if ourOrder.Type() == order.TypeBid { + ourOrderBid, ourOrderIsBid := ourOrder.(*order.Bid) + ourOrderIsSidecar := ourOrderIsBid && ourOrderBid.SidecarTicket != nil + + if ourOrderIsBid { bidNonce = ourOrder.Nonce() askNonce = matchedOrder.Order.Nonce() - thawHeight = ourOrder.(*order.Bid).LeaseDuration - selfChanBalance = ourOrder.(*order.Bid).SelfChanBalance + thawHeight = ourOrderBid.LeaseDuration + selfChanBalance = ourOrderBid.SelfChanBalance } else { bidNonce = matchedOrder.Order.Nonce() askNonce = ourOrder.Nonce() @@ -326,15 +329,30 @@ func (m *Manager) deriveFundingShim(ourOrder order.Order, // Next, we'll need to find the location of this channel output on the // funding transaction, so we'll re-compute the funding script from - // scratch. - ctxb := context.Background() - ourKeyLocator := ourOrder.Details().MultiSigKeyLocator - ourMultiSigKey, err := m.cfg.WalletKit.DeriveKey( - ctxb, &ourKeyLocator, - ) - if err != nil { - return nil, [32]byte{}, err + // scratch. If we're a taker or otherwise uninvolved in a sidecar order + // we have to re-derive our key from the locator. + var ourMultiSigKey *keychain.KeyDescriptor + if ourOrderIsSidecar && ourOrderBid.SidecarTicket.Recipient != nil { + recipient := ourOrderBid.SidecarTicket.Recipient + ourMultiSigKey = &keychain.KeyDescriptor{ + KeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamilyMultiSig, + Index: recipient.MultiSigKeyIndex, + }, + PubKey: recipient.MultiSigPubKey, + } + } else { + var err error + ctxb := context.Background() + ourKeyLocator := ourOrder.Details().MultiSigKeyLocator + ourMultiSigKey, err = m.cfg.WalletKit.DeriveKey( + ctxb, &ourKeyLocator, + ) + if err != nil { + return nil, [32]byte{}, err + } } + _, fundingOutput, err := input.GenFundingPkScript( ourMultiSigKey.PubKey.SerializeCompressed(), matchedOrder.MultiSigKey[:], int64(chanSize), @@ -367,8 +385,8 @@ func (m *Manager) deriveFundingShim(ourOrder order.Order, LocalKey: &lnrpc.KeyDescriptor{ RawKeyBytes: ourMultiSigKey.PubKey.SerializeCompressed(), KeyLoc: &lnrpc.KeyLocator{ - KeyFamily: int32(ourKeyLocator.Family), - KeyIndex: int32(ourKeyLocator.Index), + KeyFamily: int32(ourMultiSigKey.Family), + KeyIndex: int32(ourMultiSigKey.Index), }, }, RemoteKey: matchedOrder.MultiSigKey[:], diff --git a/funding/manager_test.go b/funding/manager_test.go index 28f43d6..a382e08 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -21,6 +21,7 @@ import ( "github.com/lightninglabs/pool/clientdb" "github.com/lightninglabs/pool/internal/test" "github.com/lightninglabs/pool/order" + "github.com/lightninglabs/pool/sidecar" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" @@ -384,7 +385,6 @@ func TestFundingManager(t *testing.T) { }, BatchTX: batchTx, } - txidHash := batchTx.TxHash() pendingChanID := order.PendingChanKey(ask.Nonce(), bid.Nonce()) // Make sure the channel preparations work as expected. We expect the @@ -410,28 +410,15 @@ func TestFundingManager(t *testing.T) { // Validate the shim. shim := h.baseClientMock.fundingShims[pendingChanID] require.NotNil(t, shim) - require.Equal(t, uint32(2500), shim.ThawHeight) - require.Equal(t, int64(order.SupplyUnit(4).ToSatoshis()), shim.Amt) - - chanPoint := &lnrpc.ChannelPoint{ - FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ - FundingTxidBytes: txidHash[:], - }, - OutputIndex: 1, - } - require.Equal(t, chanPoint, shim.ChanPoint) - require.Equal( - t, pubKeyBid.SerializeCompressed(), shim.LocalKey.RawKeyBytes, - ) - require.Equal( - t, int32(bid.Kit.MultiSigKeyLocator.Family), - shim.LocalKey.KeyLoc.KeyFamily, - ) - require.Equal( - t, int32(bid.Kit.MultiSigKeyLocator.Index), - shim.LocalKey.KeyLoc.KeyIndex, - ) - require.Equal(t, matchedAsk.MultiSigKey[:], shim.RemoteKey) + assertFundingShim( + t, shim, uint32(2500), int64(order.SupplyUnit(4).ToSatoshis()), + batchTx, 1, &keychain.KeyDescriptor{ + KeyLocator: keychain.KeyLocator{ + Family: bid.Kit.MultiSigKeyLocator.Family, + Index: bid.Kit.MultiSigKeyLocator.Index, + }, + PubKey: pubKeyBid, + }, matchedAsk.MultiSigKey[:]) // Next, make sure we get a partial reject error if we enable the "new // nodes only" flag and already have a channel with the matched node. @@ -566,6 +553,136 @@ func callBatchChannelSetup(t *testing.T, h *managerHarness, batch *order.Batch, require.Equal(t, 2, len(chanInfo)) } +// TestDeriveFundingShim makes sure the correct keys are used for creating a +// funding shim. +func TestDeriveFundingShim(t *testing.T) { + h := newManagerHarness(t) + defer h.stop() + + var ( + bidKeyIndex int32 = 101 + sidecarKeyIndex int32 = 102 + _, pubKeyAsk = test.CreateKey(100) + _, pubKeyBid = test.CreateKey(bidKeyIndex) + _, pubKeySidecar = test.CreateKey(sidecarKeyIndex) + askNonce = order.Nonce{1, 2, 3} + bidNonce = order.Nonce{3, 2, 1} + expectedKeyLocator = keychain.KeyLocator{ + Family: 1234, + Index: uint32(bidKeyIndex), + } + expectedKeyLocatorSidecar = keychain.KeyLocator{ + Family: keychain.KeyFamilyMultiSig, + Index: uint32(sidecarKeyIndex), + } + batchTx = &wire.MsgTx{ + TxOut: []*wire.TxOut{{}}, + } + ) + + askKit := order.NewKit(askNonce) + matchedAsk := &order.MatchedOrder{ + Order: &order.Ask{Kit: *askKit}, + UnitsFilled: 4, + } + copy(matchedAsk.MultiSigKey[:], pubKeyAsk.SerializeCompressed()) + + // First test is a normal bid where the funding key should be derived + // from the wallet + bid := &order.Bid{ + Kit: newKitFromTemplate(bidNonce, &order.Kit{ + MultiSigKeyLocator: expectedKeyLocator, + Units: 4, + LeaseDuration: 12345, + }), + } + _, batchTx.TxOut[0], _ = input.GenFundingPkScript( + pubKeyBid.SerializeCompressed(), + matchedAsk.MultiSigKey[:], int64(4*order.BaseSupplyUnit), + ) + shim, pendingChanID, err := h.mgr.deriveFundingShim( + bid, matchedAsk, batchTx, + ) + require.NoError(t, err) + require.Equal(t, order.PendingChanKey(askNonce, bidNonce), pendingChanID) + require.IsType(t, shim.Shim, &lnrpc.FundingShim_ChanPointShim{}) + + expectedKeyDescriptor := &keychain.KeyDescriptor{ + PubKey: pubKeyBid, + KeyLocator: expectedKeyLocator, + } + chanPointShim := shim.Shim.(*lnrpc.FundingShim_ChanPointShim) + assertFundingShim( + t, chanPointShim.ChanPointShim, 12345, 400_000, batchTx, 0, + expectedKeyDescriptor, pubKeyAsk.SerializeCompressed(), + ) + + // And the second test is with a sidecar channel bid. + ticket, err := sidecar.NewTicket( + sidecar.VersionDefault, 400_000, 0, 12345, pubKeyBid, + ) + require.NoError(t, err) + ticket.Recipient = &sidecar.Recipient{ + MultiSigPubKey: pubKeySidecar, + MultiSigKeyIndex: uint32(sidecarKeyIndex), + } + bid = &order.Bid{ + Kit: newKitFromTemplate(bidNonce, &order.Kit{ + MultiSigKeyLocator: expectedKeyLocator, + Units: 4, + LeaseDuration: 12345, + }), + SidecarTicket: ticket, + } + _, batchTx.TxOut[0], _ = input.GenFundingPkScript( + pubKeySidecar.SerializeCompressed(), + matchedAsk.MultiSigKey[:], int64(4*order.BaseSupplyUnit), + ) + shim, pendingChanID, err = h.mgr.deriveFundingShim( + bid, matchedAsk, batchTx, + ) + require.NoError(t, err) + require.Equal(t, order.PendingChanKey(askNonce, bidNonce), pendingChanID) + require.IsType(t, shim.Shim, &lnrpc.FundingShim_ChanPointShim{}) + + expectedKeyDescriptor = &keychain.KeyDescriptor{ + PubKey: pubKeySidecar, + KeyLocator: expectedKeyLocatorSidecar, + } + chanPointShim = shim.Shim.(*lnrpc.FundingShim_ChanPointShim) + assertFundingShim( + t, chanPointShim.ChanPointShim, 12345, 400_000, batchTx, 0, + expectedKeyDescriptor, pubKeyAsk.SerializeCompressed(), + ) +} + +// assertFundingShim asserts that the funding shim contains the data that we +// expect. +func assertFundingShim(t *testing.T, shim *lnrpc.ChanPointShim, + thawHeight uint32, amt int64, fundingTx *wire.MsgTx, + fundingOutputIndex uint32, localKey *keychain.KeyDescriptor, + remoteKey []byte) { + + require.Equal(t, thawHeight, shim.ThawHeight) + require.Equal(t, amt, shim.Amt) + + fundingTxHash := fundingTx.TxHash() + chanPoint := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ + FundingTxidBytes: fundingTxHash[:], + }, + OutputIndex: fundingOutputIndex, + } + require.Equal(t, chanPoint, shim.ChanPoint) + require.Equal( + t, localKey.PubKey.SerializeCompressed(), + shim.LocalKey.RawKeyBytes, + ) + require.Equal(t, int32(localKey.Family), shim.LocalKey.KeyLoc.KeyFamily) + require.Equal(t, int32(localKey.Index), shim.LocalKey.KeyLoc.KeyIndex) + require.Equal(t, remoteKey, shim.RemoteKey) +} + // TestWaitForPeerConnections tests the function that waits for peer connections // to be established before continuing with the funding process. func TestWaitForPeerConnections(t *testing.T) { diff --git a/order/batch.go b/order/batch.go index 265b39e..a35930a 100644 --- a/order/batch.go +++ b/order/batch.go @@ -213,17 +213,31 @@ type Fetcher func(Nonce) (Order, error) func ChannelOutput(batchTx *wire.MsgTx, wallet lndclient.WalletKitClient, ourOrder Order, otherOrder *MatchedOrder) (*wire.TxOut, uint32, error) { - // Re-derive our multisig key first. - ctxt, cancel := context.WithTimeout( - context.Background(), deriveKeyTimeout, - ) - defer cancel() - ourKey, err := wallet.DeriveKey( - ctxt, &ourOrder.Details().MultiSigKeyLocator, - ) - if err != nil { - return nil, 0, fmt.Errorf("could not derive our multisig key: "+ - "%v", err) + var ourKey []byte + ourOrderBid, isBid := ourOrder.(*Bid) + if isBid && ourOrderBid.SidecarTicket != nil { + r := ourOrderBid.SidecarTicket.Recipient + if r == nil || r.MultiSigPubKey == nil { + return nil, 0, fmt.Errorf("recipient information in " + + "sidecar ticket missing") + } + + ourKey = r.MultiSigPubKey.SerializeCompressed() + } else { + // Re-derive our multisig key first. + ctxt, cancel := context.WithTimeout( + context.Background(), deriveKeyTimeout, + ) + defer cancel() + ourKeyDesc, err := wallet.DeriveKey( + ctxt, &ourOrder.Details().MultiSigKeyLocator, + ) + if err != nil { + return nil, 0, fmt.Errorf("could not derive our "+ + "multisig key: %v", err) + } + + ourKey = ourKeyDesc.PubKey.SerializeCompressed() } // A self channel balance is added on top of the number of units filled. @@ -237,8 +251,7 @@ func ChannelOutput(batchTx *wire.MsgTx, wallet lndclient.WalletKitClient, // Gather the information we expect to find in the batch TX. expectedOutputSize := selfChanBalance + otherOrder.UnitsFilled.ToSatoshis() _, expectedOut, err := input.GenFundingPkScript( - ourKey.PubKey.SerializeCompressed(), otherOrder.MultiSigKey[:], - int64(expectedOutputSize), + ourKey, otherOrder.MultiSigKey[:], int64(expectedOutputSize), ) if err != nil { return nil, 0, fmt.Errorf("could not create multisig script: "+ diff --git a/order/batch_test.go b/order/batch_test.go index 9e8e3b8..2da8950 100644 --- a/order/batch_test.go +++ b/order/batch_test.go @@ -5,7 +5,12 @@ import ( "testing" "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/pool/internal/test" "github.com/lightninglabs/pool/poolscript" + "github.com/lightninglabs/pool/sidecar" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" "github.com/stretchr/testify/require" ) @@ -45,3 +50,79 @@ func TestDecrementingBatchIDs(t *testing.T) { // library prints the whole result which is huuuuuge. require.Equal(t, MaxBatchIDHistoryLookup, len(ids)) } + +// TestChannelOutput makes sure the correct keys are used for looking up an +// expected channel output. +func TestChannelOutput(t *testing.T) { + var ( + bidKeyIndex int32 = 101 + sidecarKeyIndex int32 = 102 + _, pubKeyAsk = test.CreateKey(100) + _, pubKeyBid = test.CreateKey(bidKeyIndex) + _, pubKeySidecar = test.CreateKey(sidecarKeyIndex) + mockWallet = test.NewMockWalletKit() + askNonce = Nonce{1, 2, 3} + bidNonce = Nonce{3, 2, 1} + batchTx = &wire.MsgTx{ + TxOut: []*wire.TxOut{{}}, + } + ) + + askKit := NewKit(askNonce) + matchedAsk := &MatchedOrder{ + Order: &Ask{Kit: *askKit}, + UnitsFilled: 4, + } + copy(matchedAsk.MultiSigKey[:], pubKeyAsk.SerializeCompressed()) + + // First test is a normal bid where the funding key should be derived + // from the wallet + bid := &Bid{ + Kit: newKitFromTemplate(bidNonce, &Kit{ + MultiSigKeyLocator: keychain.KeyLocator{ + Family: 1234, + Index: uint32(bidKeyIndex), + }, + Units: 4, + LeaseDuration: 12345, + }), + } + _, batchTx.TxOut[0], _ = input.GenFundingPkScript( + pubKeyBid.SerializeCompressed(), + matchedAsk.MultiSigKey[:], int64(4*BaseSupplyUnit), + ) + out, idx, err := ChannelOutput(batchTx, mockWallet, bid, matchedAsk) + require.NoError(t, err) + require.Equal(t, uint32(0), idx) + require.Equal(t, batchTx.TxOut[0], out) + + // And the second test is with a sidecar channel bid. + ticket, err := sidecar.NewTicket( + sidecar.VersionDefault, 400_000, 0, 12345, pubKeyBid, + ) + require.NoError(t, err) + ticket.Recipient = &sidecar.Recipient{ + MultiSigPubKey: pubKeySidecar, + MultiSigKeyIndex: uint32(sidecarKeyIndex), + } + bid = &Bid{ + Kit: newKitFromTemplate(bidNonce, &Kit{ + MultiSigKeyLocator: keychain.KeyLocator{ + Family: 1234, + Index: uint32(bidKeyIndex), + }, + Units: 4, + LeaseDuration: 12345, + }), + SidecarTicket: ticket, + } + _, batchTx.TxOut[0], _ = input.GenFundingPkScript( + pubKeySidecar.SerializeCompressed(), + matchedAsk.MultiSigKey[:], int64(4*BaseSupplyUnit), + ) + + out, idx, err = ChannelOutput(batchTx, mockWallet, bid, matchedAsk) + require.NoError(t, err) + require.Equal(t, uint32(0), idx) + require.Equal(t, batchTx.TxOut[0], out) +}