mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-22 14:22:37 +01:00
protofsm: add daemon events for spend+conf registration
This commit is contained in:
parent
3bae7f32cd
commit
7f69ceb2d4
3 changed files with 186 additions and 1 deletions
|
@ -2,6 +2,7 @@ package protofsm
|
|||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
@ -21,7 +22,8 @@ type DaemonEventSet []DaemonEvent
|
|||
// DaemonEvents is a special type constraint that enumerates all the possible
|
||||
// types of daemon events.
|
||||
type DaemonEvents interface {
|
||||
SendMsgEvent[any] | BroadcastTxn
|
||||
SendMsgEvent[any] | BroadcastTxn | RegisterSpend[any] |
|
||||
RegisterConf[any]
|
||||
}
|
||||
|
||||
// SendPredicate is a function that returns true if the target message should
|
||||
|
@ -64,3 +66,53 @@ type BroadcastTxn struct {
|
|||
|
||||
// daemonSealed indicates that this struct is a DaemonEvent instance.
|
||||
func (b *BroadcastTxn) daemonSealed() {}
|
||||
|
||||
// RegisterSpend is used to request that a certain event is sent into the state
|
||||
// machien once the specified outpoint has been spent.
|
||||
type RegisterSpend[Event any] struct {
|
||||
// OutPoint is the outpoint on chain to watch.
|
||||
OutPoint wire.OutPoint
|
||||
|
||||
// PkScript is the script that we expect to be spent along with the
|
||||
// outpoint.
|
||||
PkScript []byte
|
||||
|
||||
// HeightHint is a value used to give the chain scanner a hint on how
|
||||
// far back it needs to start its search.
|
||||
HeightHint uint32
|
||||
|
||||
// PostSpendEvent is an event that's sent back to the requester once a
|
||||
// transaction spending the outpoint has been confirmed in the main
|
||||
// chain.
|
||||
PostSpendEvent fn.Option[Event]
|
||||
}
|
||||
|
||||
// daemonSealed indicates that this struct is a DaemonEvent instance.
|
||||
func (r *RegisterSpend[E]) daemonSealed() {}
|
||||
|
||||
// RegisterConf is used to request that a certain event is sent into the state
|
||||
// machien once the specified outpoint has been spent.
|
||||
type RegisterConf[Event any] struct {
|
||||
// Txid is the txid of the txn we want to watch the chain for.
|
||||
Txid chainhash.Hash
|
||||
|
||||
// PkScript is the script that we expect to be created along with the
|
||||
// outpoint.
|
||||
PkScript []byte
|
||||
|
||||
// HeightHint is a value used to give the chain scanner a hint on how
|
||||
// far back it needs to start its search.
|
||||
HeightHint uint32
|
||||
|
||||
// NumConfs is the number of confirmations that the spending
|
||||
// transaction needs to dispatch an event.
|
||||
NumConfs fn.Option[uint32]
|
||||
|
||||
// PostConfEvent is an event that's sent back to the requester once the
|
||||
// transaction specified above has confirmed in the chain with
|
||||
// sufficient depth.
|
||||
PostConfEvent fn.Option[Event]
|
||||
}
|
||||
|
||||
// daemonSealed indicates that this struct is a DaemonEvent instance.
|
||||
func (r *RegisterConf[E]) daemonSealed() {}
|
||||
|
|
|
@ -6,7 +6,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
@ -77,6 +79,28 @@ type DaemonAdapters interface {
|
|||
|
||||
// BroadcastTransaction broadcasts a transaction with the target label.
|
||||
BroadcastTransaction(*wire.MsgTx, string) error
|
||||
|
||||
// RegisterConfirmationsNtfn registers an intent to be notified once
|
||||
// txid reaches numConfs confirmations. We also pass in the pkScript as
|
||||
// the default light client instead needs to match on scripts created
|
||||
// in the block. If a nil txid is passed in, then not only should we
|
||||
// match on the script, but we should also dispatch once the
|
||||
// transaction containing the script reaches numConfs confirmations.
|
||||
// This can be useful in instances where we only know the script in
|
||||
// advance, but not the transaction containing it.
|
||||
//
|
||||
// TODO(roasbeef): could abstract further?
|
||||
RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte,
|
||||
numConfs, heightHint uint32,
|
||||
opts ...chainntnfs.NotifierOption,
|
||||
) (*chainntnfs.ConfirmationEvent, error)
|
||||
|
||||
// RegisterSpendNtfn registers an intent to be notified once the target
|
||||
// outpoint is successfully spent within a transaction. The script that
|
||||
// the outpoint creates must also be specified. This allows this
|
||||
// interface to be implemented by BIP 158-like filtering.
|
||||
RegisterSpendNtfn(outpoint *wire.OutPoint, pkScript []byte,
|
||||
heightHint uint32) (*chainntnfs.SpendEvent, error)
|
||||
}
|
||||
|
||||
// stateQuery is used by outside callers to query the internal state of the
|
||||
|
@ -282,6 +306,78 @@ func (s *StateMachine[Event, Env]) executeDaemonEvent(event DaemonEvent) error {
|
|||
}
|
||||
|
||||
return nil
|
||||
|
||||
// The state machine has requested a new event to be sent once a
|
||||
// transaction spending a specified outpoint has confirmed.
|
||||
case *RegisterSpend[Event]:
|
||||
spendEvent, err := s.daemon.RegisterSpendNtfn(
|
||||
&daemonEvent.OutPoint, daemonEvent.PkScript,
|
||||
daemonEvent.HeightHint,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to register spend: %w", err)
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-spendEvent.Spend:
|
||||
// If there's a post-send event, then
|
||||
// we'll send that into the current
|
||||
// state now.
|
||||
postSpend := daemonEvent.PostSpendEvent
|
||||
postSpend.WhenSome(func(e Event) {
|
||||
s.SendEvent(e)
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
case <-s.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
|
||||
// The state machine has requested a new event to be sent once a
|
||||
// specified txid+pkScript pair has confirmed.
|
||||
case *RegisterConf[Event]:
|
||||
numConfs := daemonEvent.NumConfs.UnwrapOr(1)
|
||||
confEvent, err := s.daemon.RegisterConfirmationsNtfn(
|
||||
&daemonEvent.Txid, daemonEvent.PkScript,
|
||||
numConfs, daemonEvent.HeightHint,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to register conf: %w", err)
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-confEvent.Confirmed:
|
||||
// If there's a post-conf event, then
|
||||
// we'll send that into the current
|
||||
// state now.
|
||||
//
|
||||
// TODO(roasbeef): refactor to
|
||||
// dispatchAfterRecv w/ above
|
||||
postConf := daemonEvent.PostConfEvent
|
||||
postConf.WhenSome(func(e Event) {
|
||||
s.SendEvent(e)
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
case <-s.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown daemon event: %T", event)
|
||||
|
|
|
@ -7,7 +7,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
@ -160,6 +162,16 @@ func assertStateTransitions[Event any, Env Environment](
|
|||
|
||||
type dummyAdapters struct {
|
||||
mock.Mock
|
||||
|
||||
confChan chan *chainntnfs.TxConfirmation
|
||||
spendChan chan *chainntnfs.SpendDetail
|
||||
}
|
||||
|
||||
func newDaemonAdapters() *dummyAdapters {
|
||||
return &dummyAdapters{
|
||||
confChan: make(chan *chainntnfs.TxConfirmation, 1),
|
||||
spendChan: make(chan *chainntnfs.SpendDetail, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dummyAdapters) SendMessages(pub btcec.PublicKey, msgs []lnwire.Message) error {
|
||||
|
@ -174,6 +186,31 @@ func (d *dummyAdapters) BroadcastTransaction(tx *wire.MsgTx, label string) error
|
|||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (d *dummyAdapters) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
||||
pkScript []byte, numConfs, heightHint uint32,
|
||||
opts ...chainntnfs.NotifierOption,
|
||||
) (*chainntnfs.ConfirmationEvent, error) {
|
||||
|
||||
args := d.Called(txid, pkScript, numConfs)
|
||||
|
||||
err := args.Error(0)
|
||||
return &chainntnfs.ConfirmationEvent{
|
||||
Confirmed: d.confChan,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (d *dummyAdapters) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
|
||||
|
||||
args := d.Called(outpoint, pkScript, heightHint)
|
||||
|
||||
err := args.Error(0)
|
||||
|
||||
return &chainntnfs.SpendEvent{
|
||||
Spend: d.spendChan,
|
||||
}, err
|
||||
}
|
||||
|
||||
// TestStateMachineInternalEvents tests that the state machine is able to add
|
||||
// new internal events to the event queue for further processing during a state
|
||||
// transition.
|
||||
|
|
Loading…
Add table
Reference in a new issue