Skip to content

Commit

Permalink
Merge branch 'lnd-16-0'
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed Oct 26, 2023
2 parents e43dfc4 + cd37d1a commit 91c7c01
Show file tree
Hide file tree
Showing 17 changed files with 1,175 additions and 1,595 deletions.
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# EditorConfig is awesome: https://EditorConfig.org

# Top-most EditorConfig file.
root = true

# Unix-style newlines with a newline ending every file.
[*.md]
end_of_line = lf
insert_final_newline = true
max_line_length = 80

# 8 space indentation for Golang code.
[*.go]
indent_style = tab
indent_size = 8
max_line_length = 80
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ env:
# go needs absolute directories, using the $HOME variable doesn't work here.
GOCACHE: /home/runner/work/go/pkg/build
GOPATH: /home/runner/work/go
GO_VERSION: 1.18.x
GO_VERSION: 1.19.x

jobs:
build:
Expand Down
123 changes: 123 additions & 0 deletions chainkit_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package lndclient

import (
"bytes"
"context"
"sync"
"time"

"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"google.golang.org/grpc"
)

// ChainKitClient exposes chain functionality.
type ChainKitClient interface {
// GetBlock returns a block given the corresponding block hash.
GetBlock(ctx context.Context, hash chainhash.Hash) (*wire.MsgBlock,
error)

// GetBestBlock returns the latest block hash and current height of the
// valid most-work chain.
GetBestBlock(ctx context.Context) (chainhash.Hash, int32, error)

// GetBlockHash returns the hash of the block in the best blockchain
// at the given height.
GetBlockHash(ctx context.Context, blockHeight int64) (chainhash.Hash,
error)
}

type chainKitClient struct {
client chainrpc.ChainKitClient
chainMac serializedMacaroon
timeout time.Duration

wg sync.WaitGroup
}

func newChainKitClient(conn grpc.ClientConnInterface,
chainMac serializedMacaroon, timeout time.Duration) *chainKitClient {

return &chainKitClient{
client: chainrpc.NewChainKitClient(conn),
chainMac: chainMac,
timeout: timeout,
}
}

func (s *chainKitClient) WaitForFinished() {
s.wg.Wait()
}

// GetBlock returns a block given the corresponding block hash.
func (s *chainKitClient) GetBlock(ctxParent context.Context,
hash chainhash.Hash) (*wire.MsgBlock, error) {

ctx, cancel := context.WithTimeout(ctxParent, s.timeout)
defer cancel()

macaroonAuth := s.chainMac.WithMacaroonAuth(ctx)
req := &chainrpc.GetBlockRequest{
BlockHash: hash[:],
}
resp, err := s.client.GetBlock(macaroonAuth, req)
if err != nil {
return nil, err
}

// Convert raw block bytes into wire.MsgBlock.
msgBlock := &wire.MsgBlock{}
blockReader := bytes.NewReader(resp.RawBlock)
err = msgBlock.Deserialize(blockReader)
if err != nil {
return nil, err
}

return msgBlock, nil
}

// GetBestBlock returns the block hash and current height from the valid
// most-work chain.
func (s *chainKitClient) GetBestBlock(ctxParent context.Context) (chainhash.Hash,
int32, error) {

ctx, cancel := context.WithTimeout(ctxParent, s.timeout)
defer cancel()

macaroonAuth := s.chainMac.WithMacaroonAuth(ctx)
resp, err := s.client.GetBestBlock(
macaroonAuth, &chainrpc.GetBestBlockRequest{},
)
if err != nil {
return chainhash.Hash{}, 0, err
}

// Cast gRPC block hash bytes as chain hash type.
var blockHash chainhash.Hash
copy(blockHash[:], resp.BlockHash)

return blockHash, resp.BlockHeight, nil
}

// GetBlockHash returns the hash of the block in the best blockchain at the
// given height.
func (s *chainKitClient) GetBlockHash(ctxParent context.Context,
blockHeight int64) (chainhash.Hash, error) {

ctx, cancel := context.WithTimeout(ctxParent, s.timeout)
defer cancel()

macaroonAuth := s.chainMac.WithMacaroonAuth(ctx)
req := &chainrpc.GetBlockHashRequest{BlockHeight: blockHeight}
resp, err := s.client.GetBlockHash(macaroonAuth, req)
if err != nil {
return chainhash.Hash{}, err
}

// Cast gRPC block hash bytes as chain hash type.
var blockHash chainhash.Hash
copy(blockHash[:], resp.BlockHash)

return blockHash, nil
}
113 changes: 97 additions & 16 deletions chainnotifier_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,60 @@ import (
"google.golang.org/grpc"
)

// notifierOptions is a set of functional options that allow callers to further
// modify the type of chain even notifications they receive.
type notifierOptions struct {
// includeBlock if true, then the dispatched confirmation notification
// will include the block that mined the transaction.
includeBlock bool

// reOrgChan if set, will be sent on if the transaction is re-organized
// out of the chain. This channel being set will also imply that we
// don't cancel the notification listener after having received one
// confirmation event. That means the caller manually needs to cancel
// the passed in context to cancel being notified once the required
// number of confirmations have been reached.
reOrgChan chan struct{}
}

// defaultNotifierOptions returns the set of default options for the notifier.
func defaultNotifierOptions() *notifierOptions {
return &notifierOptions{}
}

// NotifierOption is a functional option that allows a caller to modify the
// events received from the notifier.
type NotifierOption func(*notifierOptions)

// WithIncludeBlock is an optional argument that allows the caller to specify
// that the block that mined a transaction should be included in the response.
func WithIncludeBlock() NotifierOption {
return func(o *notifierOptions) {
o.includeBlock = true
}
}

// WithReOrgChan configures a channel that will be sent on if the transaction is
// re-organized out of the chain. This channel being set will also imply that we
// don't cancel the notification listener after having received one confirmation
// event. That means the caller manually needs to cancel the passed in context
// to cancel being notified once the required number of confirmations have been
// reached.
func WithReOrgChan(reOrgChan chan struct{}) NotifierOption {
return func(o *notifierOptions) {
o.reOrgChan = reOrgChan
}
}

// ChainNotifierClient exposes base lightning functionality.
type ChainNotifierClient interface {
RegisterBlockEpochNtfn(ctx context.Context) (
chan int32, chan error, error)

RegisterConfirmationsNtfn(ctx context.Context, txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint int32) (
chan *chainntnfs.TxConfirmation, chan error, error)
pkScript []byte, numConfs, heightHint int32,
opts ...NotifierOption) (chan *chainntnfs.TxConfirmation,
chan error, error)

RegisterSpendNtfn(ctx context.Context,
outpoint *wire.OutPoint, pkScript []byte, heightHint int32) (
Expand Down Expand Up @@ -126,20 +172,26 @@ func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context,
}

func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context,
txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32) (
chan *chainntnfs.TxConfirmation, chan error, error) {
txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32,
optFuncs ...NotifierOption) (chan *chainntnfs.TxConfirmation,
chan error, error) {

opts := defaultNotifierOptions()
for _, optFunc := range optFuncs {
optFunc(opts)
}

var txidSlice []byte
if txid != nil {
txidSlice = txid[:]
}
confStream, err := s.client.RegisterConfirmationsNtfn(
s.chainMac.WithMacaroonAuth(ctx),
&chainrpc.ConfRequest{
Script: pkScript,
NumConfs: uint32(numConfs),
HeightHint: uint32(heightHint),
Txid: txidSlice,
s.chainMac.WithMacaroonAuth(ctx), &chainrpc.ConfRequest{
Script: pkScript,
NumConfs: uint32(numConfs),
HeightHint: uint32(heightHint),
Txid: txidSlice,
IncludeBlock: opts.includeBlock,
},
)
if err != nil {
Expand All @@ -162,30 +214,60 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context,
}

switch c := confEvent.Event.(type) {
// Script confirmed
// Script confirmed.
case *chainrpc.ConfEvent_Conf:
tx, err := decodeTx(c.Conf.RawTx)
if err != nil {
errChan <- err
return
}

var block *wire.MsgBlock
if opts.includeBlock {
block, err = decodeBlock(
c.Conf.RawBlock,
)
if err != nil {
errChan <- err
return
}
}

blockHash, err := chainhash.NewHash(
c.Conf.BlockHash,
)
if err != nil {
errChan <- err
return
}

confChan <- &chainntnfs.TxConfirmation{
BlockHeight: c.Conf.BlockHeight,
BlockHash: blockHash,
Tx: tx,
TxIndex: c.Conf.TxIndex,
Block: block,
}
return

// Ignore reorg events, not supported.
// If we're running in re-org aware mode, then
// we don't return here, since we might want to
// be informed about the new block we got
// confirmed in after a re-org.
if opts.reOrgChan == nil {
return
}

// On a re-org, we just need to signal, we don't have
// any additional information. But we only signal if the
// caller requested to be notified about re-orgs.
case *chainrpc.ConfEvent_Reorg:
if opts.reOrgChan != nil {
select {
case opts.reOrgChan <- struct{}{}:
case <-ctx.Done():
return
}
}
continue

// Nil event, should never happen.
Expand All @@ -195,9 +277,8 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context,

// Unexpected type.
default:
errChan <- fmt.Errorf(
"conf event has unexpected type",
)
errChan <- fmt.Errorf("conf event has " +
"unexpected type")
return
}
}
Expand Down
Loading

0 comments on commit 91c7c01

Please sign in to comment.