Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beat [3/4]: Implement Consumer on chainWatcher and resolvers #8922

Draft
wants to merge 29 commits into
base: yy-blockbeat
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9f0b94d
contractcourt: add verbose logging in resolvers
yyforyongyu Jun 20, 2024
354be8c
contractcourt: add methods to decide spend path in `htlcSuccessResolver`
yyforyongyu Jul 15, 2024
7ba62d5
contractcourt: add methods to send sweep requests in `htlcSuccessReso…
yyforyongyu Jul 15, 2024
40b65ee
contractcourt: add resolver handlers in `htlcSuccessResolver`
yyforyongyu Jul 15, 2024
37991e6
contractcourt: add `Launch` method to anchor/breach resolver
yyforyongyu Jun 24, 2024
2fd53b9
contractcourt: add `Launch` method to commit resolver
yyforyongyu Jun 20, 2024
5e3ce4c
contractcourt: add `Launch` method to htlc success resolver
yyforyongyu Jul 15, 2024
f013b2a
contractcourt: add methods to decide spend path in `htlcTimeoutResolver`
yyforyongyu Jul 15, 2024
963986b
contractcourt: add methods to handle sweep in `htlcTimeoutResolver`
yyforyongyu Jul 16, 2024
c9f642a
contractcourt: add methods to checkpoint states
yyforyongyu Jul 16, 2024
38233c0
contractcourt: add methods to resolve different paths
yyforyongyu Jul 16, 2024
5d9899e
contractcourt: add `Launch` method to htlc timeout resolver
yyforyongyu Jul 16, 2024
b3b86f8
contractcourt: add `Launch` method to incoming contest resolver
yyforyongyu Jun 20, 2024
492fceb
contractcourt: add `Launch` method to outgoing contest resolver
yyforyongyu Jun 20, 2024
622fc87
contractcourt: launch resolvers synchronously
yyforyongyu Jun 25, 2024
742b050
contractcourt: remove unused param in `deriveWaitHeight`
yyforyongyu Jun 25, 2024
c30ab9d
contractcourt: implement `Consumer` on `chainWatcher`
yyforyongyu Jun 20, 2024
7f0f144
chainio: enable spending txns lookup in `Blockbeat`
yyforyongyu Jul 18, 2024
79f4cd3
chainio+contractcourt: handle `blockbeat` in `chainWatcher`
yyforyongyu Jul 18, 2024
cf7e81b
contractcourt: use `ShortChanID` instead of `ChannelPoint` in log
yyforyongyu Jun 28, 2024
f9b77fd
contractcourt: fix race access to `c.activeResolvers`
yyforyongyu Jul 1, 2024
868265c
contractcourt: fix concurrent access to `resolved`
yyforyongyu Jul 10, 2024
ef95c61
contractcourt: fix concurrent access to `launched`
yyforyongyu Jul 11, 2024
7283893
multi: add new method `ChainArbitrator.RedispatchBlockbeat`
yyforyongyu Jul 8, 2024
3561f90
contractcourt: make sure output from legacy success tx is swept
yyforyongyu Jul 15, 2024
2cd82ff
itest: fix sweep tests and remove hacks
yyforyongyu Jun 20, 2024
61aa221
itest+lntest: fix channel force close test
yyforyongyu Jun 29, 2024
491796f
itest: fix multi hop tests
yyforyongyu Jul 16, 2024
e568154
itest: fix tests regarding the new sweeper behavior
yyforyongyu Jul 9, 2024
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
122 changes: 122 additions & 0 deletions chainio/blockbeat.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package chainio

import (
"errors"
"fmt"
"time"

"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chainntnfs"
Expand Down Expand Up @@ -162,3 +165,122 @@ func (b Beat) notifyAndWait(c Consumer) error {

return nil
}

// HasOutpointSpent queries the block to find a spending tx that spends the
// given outpoint. Returns the spend details if found, otherwise nil.
//
// NOTE: Part of the Blockbeat interface.
func (b Beat) HasOutpointSpent(outpoint wire.OutPoint) *chainntnfs.SpendDetail {
b.log.Tracef("Querying spending tx for outpoint=%v", outpoint)

// Iterate all the txns in this block.
for _, tx := range b.epoch.Block.Transactions {
txHash := tx.TxHash()

// Iterate all the inputs in this tx.
for i, txIn := range tx.TxIn {
// Skip if the input doesn't spend the outpoint.
if txIn.PreviousOutPoint != outpoint {
continue
}

// Found a match, return the spend details.
details := &chainntnfs.SpendDetail{
SpentOutPoint: &outpoint,
SpenderTxHash: &txHash,
SpendingTx: tx,
SpenderInputIndex: uint32(i),
SpendingHeight: b.epoch.Height,
}

return details
}
}

return nil
}

// ErrPkScriptMismatch is returned when the expected pkScript doesn't match the
// actual pkScript.
var ErrPkScriptMismatch = errors.New("pkscript mismatch")

// HasOutpointSpentByScript queries the block to find a spending tx that spends
// the given outpoint using the pkScript.
//
// NOTE: Part of the Blockbeat interface.
func (b Beat) HasOutpointSpentByScript(outpoint wire.OutPoint,
pkScript txscript.PkScript) (*chainntnfs.SpendDetail, error) {

b.log.Tracef("Querying spending tx for outpoint=%v, pkScript=%v",
outpoint, pkScript)

// For taproot outputs, we will skip matching the pkScript as we cannot
// derive the spent pkScript directly from the witness.
isTaproot := pkScript.Class() == txscript.WitnessV1TaprootTy

// matchTxIn is a helper closure that checks if the txIn spends the
// given outpoint using the specified pkScript. Returns an error if the
// outpoint is found but the pkScript doesn't match.
matchTxIn := func(txIn *wire.TxIn) (bool, error) {
prevOut := txIn.PreviousOutPoint

// Exit early if the input doesn't spend the outpoint.
if prevOut != outpoint {
return false, nil
}

// If this is a taproot output, we skip matching the pkScript.
if isTaproot {
return true, nil
}

// Compute the script and matches it with the pkScript.
script, err := txscript.ComputePkScript(
txIn.SignatureScript, txIn.Witness,
)
if err != nil {
b.log.Errorf("Failed to compute pkscript: %v", err)
return false, err
}

// Check if the scripts match.
if script != pkScript {
return false, fmt.Errorf("%w: want %v, got %v",
ErrPkScriptMismatch, pkScript, script)
}

return true, nil
}

// Iterate all the txns in this block.
for _, tx := range b.epoch.Block.Transactions {
txHash := tx.TxHash()

// Iterate all the inputs in this tx.
for i, txIn := range tx.TxIn {
// Check if the input spends the outpoint.
found, err := matchTxIn(txIn)
if err != nil {
return nil, err
}

// Skip if the input cannot be matched.
if !found {
continue
}

// Found a match, return the spend details.
details := &chainntnfs.SpendDetail{
SpentOutPoint: &outpoint,
SpenderTxHash: &txHash,
SpendingTx: tx,
SpenderInputIndex: uint32(i),
SpendingHeight: b.epoch.Height,
}

return details, nil
}
}

return nil, nil
}
17 changes: 17 additions & 0 deletions chainio/interface.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package chainio

import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
)

// Blockbeat defines an interface that can be used by subsystems to retrieve
// block data. It is sent by the BlockbeatDispatcher whenever a new block is
// received. Once the subsystem finishes processing the block, it must signal
Expand Down Expand Up @@ -32,6 +38,17 @@ type Blockbeat interface {
// DispatchSequential sends the blockbeat to the specified consumers
// sequentially.
DispatchSequential(consumers []Consumer) error

// HasOutpointSpentByScript queries the block to find a spending tx
// that spends the given outpoint using the pkScript. Return an error
// is the outpoint is spent but using a different pkScript.
HasOutpointSpentByScript(outpoint wire.OutPoint,
pkScript txscript.PkScript) (*chainntnfs.SpendDetail, error)

// HasOutpointSpent queries the block to find a spending tx that spends
// the given outpoint. Returns the spend details if found, otherwise
// nil.
HasOutpointSpent(outpoint wire.OutPoint) *chainntnfs.SpendDetail
}

// Consumer defines a blockbeat consumer interface. Subsystems that need block
Expand Down
70 changes: 70 additions & 0 deletions chainio/mocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package chainio

import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/stretchr/testify/mock"
)

// MockBeat is a mock implementation of the Beat interface.
type MockBeat struct {
mock.Mock
}

// Compile-time check to ensure MockBeat satisfies the Blockbeat interface.
var _ Blockbeat = (*MockBeat)(nil)

// Height returns the height of the block epoch.
func (m *MockBeat) Height() int32 {
args := m.Called()

return args.Get(0).(int32)
}

func (m *MockBeat) NotifyBlockProcessed(err error, quitChan chan struct{}) {
m.Called(err, quitChan)
}

// DispatchSequential takes a list of consumers and notify them about the new
// epoch sequentially.
func (m *MockBeat) DispatchSequential(consumers []Consumer) error {
args := m.Called(consumers)

return args.Error(0)
}

// DispatchConcurrent notifies each consumer concurrently about the blockbeat.
func (m *MockBeat) DispatchConcurrent(consumers []Consumer) error {
args := m.Called(consumers)

return args.Error(0)
}

// HasOutpointSpentByScript queries the block to find a spending tx that spends
// the given outpoint using the pkScript.
func (m *MockBeat) HasOutpointSpentByScript(outpoint wire.OutPoint,
pkScript txscript.PkScript) (*chainntnfs.SpendDetail, error) {

args := m.Called(outpoint, pkScript)

if args.Get(0) == nil {
return nil, args.Error(1)
}

return args.Get(0).(*chainntnfs.SpendDetail), args.Error(1)
}

// HasOutpointSpent queries the block to find a spending tx that spends the
// given outpoint. Returns the spend details if found, otherwise nil.
func (m *MockBeat) HasOutpointSpent(
outpoint wire.OutPoint) *chainntnfs.SpendDetail {

args := m.Called(outpoint)

if args.Get(0) == nil {
return nil
}

return args.Get(0).(*chainntnfs.SpendDetail)
}
13 changes: 12 additions & 1 deletion chanrestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt"
Expand Down Expand Up @@ -286,6 +287,9 @@ func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) e

ltndLog.Infof("Informing chain watchers of new restored channels")

// Create a slice of channel points.
chanPoints := make([]wire.OutPoint, 0, len(channelShells))

// Finally, we'll need to inform the chain arbitrator of these new
// channels so we'll properly watch for their ultimate closure on chain
// and sweep them via the DLP.
Expand All @@ -294,8 +298,15 @@ func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) e
if err != nil {
return err
}

chanPoints = append(
chanPoints, restoredChannel.Chan.FundingOutpoint,
)
}

// With all the channels restored, we'll now re-send the blockbeat.
c.chainArb.RedispatchBlockbeat(chanPoints)

return nil
}

Expand All @@ -314,7 +325,7 @@ func (s *server) ConnectPeer(nodePub *btcec.PublicKey, addrs []net.Addr) error {
// to ensure the new connection is created after this new link/channel
// is known.
if err := s.DisconnectPeer(nodePub); err != nil {
ltndLog.Infof("Peer(%v) is already connected, proceeding "+
ltndLog.Infof("Peer(%x) is already connected, proceeding "+
"with chan restore", nodePub.SerializeCompressed())
}

Expand Down
Loading
Loading