Merge branch 'master' into master
This commit is contained in:
commit
88f5c9ca9c
@ -2,8 +2,11 @@ language: go
|
||||
go:
|
||||
- 1.8.5
|
||||
- 1.9.2
|
||||
sudo: false
|
||||
sudo: required
|
||||
install:
|
||||
- sudo add-apt-repository -y ppa:bitcoin/bitcoin -y
|
||||
- sudo apt-get update -q
|
||||
- sudo apt-get install bitcoind -y
|
||||
- GLIDE_TAG=v0.12.3
|
||||
- GLIDE_DOWNLOAD="https://github.com/Masterminds/glide/releases/download/$GLIDE_TAG/glide-$GLIDE_TAG-linux-amd64.tar.gz"
|
||||
- curl -L $GLIDE_DOWNLOAD | tar -xvz
|
||||
|
679
chainntnfs/bitcoindnotify/bitcoind.go
Normal file
679
chainntnfs/bitcoindnotify/bitcoind.go
Normal file
@ -0,0 +1,679 @@
|
||||
package bitcoindnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/roasbeef/btcd/btcjson"
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
"github.com/roasbeef/btcd/rpcclient"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/roasbeef/btcutil"
|
||||
"github.com/roasbeef/btcwallet/chain"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// notifierType uniquely identifies this concrete implementation of the
|
||||
// ChainNotifier interface.
|
||||
notifierType = "bitcoind"
|
||||
|
||||
// reorgSafetyLimit is assumed maximum depth of a chain reorganization.
|
||||
// After this many confirmation, transaction confirmation info will be
|
||||
// pruned.
|
||||
reorgSafetyLimit = 100
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrChainNotifierShuttingDown is used when we are trying to
|
||||
// measure a spend notification when notifier is already stopped.
|
||||
ErrChainNotifierShuttingDown = errors.New("chainntnfs: system interrupt " +
|
||||
"while attempting to register for spend notification.")
|
||||
)
|
||||
|
||||
// chainUpdate encapsulates an update to the current main chain. This struct is
|
||||
// used as an element within an unbounded queue in order to avoid blocking the
|
||||
// main rpc dispatch rule.
|
||||
type chainUpdate struct {
|
||||
blockHash *chainhash.Hash
|
||||
blockHeight int32
|
||||
}
|
||||
|
||||
// txUpdate encapsulates a transaction related notification sent from bitcoind
|
||||
// to the registered RPC client. This struct is used as an element within an
|
||||
// unbounded queue in order to avoid blocking the main rpc dispatch rule.
|
||||
type txUpdate struct {
|
||||
tx *btcutil.Tx
|
||||
details *btcjson.BlockDetails
|
||||
}
|
||||
|
||||
// TODO(roasbeef): generalize struct below:
|
||||
// * move chans to config, allow outside callers to handle send conditions
|
||||
|
||||
// BitcoindNotifier implements the ChainNotifier interface using a bitcoind
|
||||
// chain client. Multiple concurrent clients are supported. All notifications
|
||||
// are achieved via non-blocking sends on client channels.
|
||||
type BitcoindNotifier struct {
|
||||
spendClientCounter uint64 // To be used atomically.
|
||||
epochClientCounter uint64 // To be used atomically.
|
||||
|
||||
started int32 // To be used atomically.
|
||||
stopped int32 // To be used atomically.
|
||||
|
||||
heightMtx sync.RWMutex
|
||||
bestHeight int32
|
||||
|
||||
chainConn *chain.BitcoindClient
|
||||
|
||||
notificationCancels chan interface{}
|
||||
notificationRegistry chan interface{}
|
||||
|
||||
spendNotifications map[wire.OutPoint]map[uint64]*spendNotification
|
||||
|
||||
txConfNotifier *chainntnfs.TxConfNotifier
|
||||
|
||||
blockEpochClients map[uint64]*blockEpochRegistration
|
||||
|
||||
wg sync.WaitGroup
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// Ensure BitcoindNotifier implements the ChainNotifier interface at compile
|
||||
// time.
|
||||
var _ chainntnfs.ChainNotifier = (*BitcoindNotifier)(nil)
|
||||
|
||||
// New returns a new BitcoindNotifier instance. This function assumes the
|
||||
// bitcoind node detailed in the passed configuration is already running, and
|
||||
// willing to accept RPC requests and new zmq clients.
|
||||
func New(config *rpcclient.ConnConfig, zmqConnect string,
|
||||
params chaincfg.Params) (*BitcoindNotifier, error) {
|
||||
notifier := &BitcoindNotifier{
|
||||
notificationCancels: make(chan interface{}),
|
||||
notificationRegistry: make(chan interface{}),
|
||||
|
||||
blockEpochClients: make(map[uint64]*blockEpochRegistration),
|
||||
|
||||
spendNotifications: make(map[wire.OutPoint]map[uint64]*spendNotification),
|
||||
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Disable connecting to bitcoind within the rpcclient.New method. We
|
||||
// defer establishing the connection to our .Start() method.
|
||||
config.DisableConnectOnNew = true
|
||||
config.DisableAutoReconnect = false
|
||||
chainConn, err := chain.NewBitcoindClient(¶ms, config.Host,
|
||||
config.User, config.Pass, zmqConnect, 100*time.Millisecond)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notifier.chainConn = chainConn
|
||||
|
||||
return notifier, nil
|
||||
}
|
||||
|
||||
// Start connects to the running bitcoind node over websockets, registers for
|
||||
// block notifications, and finally launches all related helper goroutines.
|
||||
func (b *BitcoindNotifier) Start() error {
|
||||
// Already started?
|
||||
if atomic.AddInt32(&b.started, 1) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect to bitcoind, and register for notifications on connected,
|
||||
// and disconnected blocks.
|
||||
if err := b.chainConn.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.chainConn.NotifyBlocks(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, currentHeight, err := b.chainConn.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.heightMtx.Lock()
|
||||
b.bestHeight = currentHeight
|
||||
b.heightMtx.Unlock()
|
||||
|
||||
b.txConfNotifier = chainntnfs.NewTxConfNotifier(
|
||||
uint32(currentHeight), reorgSafetyLimit)
|
||||
|
||||
b.wg.Add(1)
|
||||
go b.notificationDispatcher()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop shutsdown the BitcoindNotifier.
|
||||
func (b *BitcoindNotifier) Stop() error {
|
||||
// Already shutting down?
|
||||
if atomic.AddInt32(&b.stopped, 1) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown the rpc client, this gracefully disconnects from bitcoind,
|
||||
// and cleans up all related resources.
|
||||
b.chainConn.Stop()
|
||||
|
||||
close(b.quit)
|
||||
b.wg.Wait()
|
||||
|
||||
// Notify all pending clients of our shutdown by closing the related
|
||||
// notification channels.
|
||||
for _, spendClients := range b.spendNotifications {
|
||||
for _, spendClient := range spendClients {
|
||||
close(spendClient.spendChan)
|
||||
}
|
||||
}
|
||||
for _, epochClient := range b.blockEpochClients {
|
||||
close(epochClient.epochChan)
|
||||
}
|
||||
b.txConfNotifier.TearDown()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// blockNtfn packages a notification of a connected/disconnected block along
|
||||
// with its height at the time.
|
||||
type blockNtfn struct {
|
||||
sha *chainhash.Hash
|
||||
height int32
|
||||
}
|
||||
|
||||
// notificationDispatcher is the primary goroutine which handles client
|
||||
// notification registrations, as well as notification dispatches.
|
||||
func (b *BitcoindNotifier) notificationDispatcher() {
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case cancelMsg := <-b.notificationCancels:
|
||||
switch msg := cancelMsg.(type) {
|
||||
case *spendCancel:
|
||||
chainntnfs.Log.Infof("Cancelling spend "+
|
||||
"notification for out_point=%v, "+
|
||||
"spend_id=%v", msg.op, msg.spendID)
|
||||
|
||||
// Before we attempt to close the spendChan,
|
||||
// ensure that the notification hasn't already
|
||||
// yet been dispatched.
|
||||
if outPointClients, ok := b.spendNotifications[msg.op]; ok {
|
||||
close(outPointClients[msg.spendID].spendChan)
|
||||
delete(b.spendNotifications[msg.op], msg.spendID)
|
||||
}
|
||||
|
||||
case *epochCancel:
|
||||
chainntnfs.Log.Infof("Cancelling epoch "+
|
||||
"notification, epoch_id=%v", msg.epochID)
|
||||
|
||||
// First, close the cancel channel for this
|
||||
// specific client, and wait for the client to
|
||||
// exit.
|
||||
close(b.blockEpochClients[msg.epochID].cancelChan)
|
||||
b.blockEpochClients[msg.epochID].wg.Wait()
|
||||
|
||||
// Once the client has exited, we can then
|
||||
// safely close the channel used to send epoch
|
||||
// notifications, in order to notify any
|
||||
// listeners that the intent has been
|
||||
// cancelled.
|
||||
close(b.blockEpochClients[msg.epochID].epochChan)
|
||||
delete(b.blockEpochClients, msg.epochID)
|
||||
|
||||
}
|
||||
case registerMsg := <-b.notificationRegistry:
|
||||
switch msg := registerMsg.(type) {
|
||||
case *spendNotification:
|
||||
chainntnfs.Log.Infof("New spend subscription: "+
|
||||
"utxo=%v", msg.targetOutpoint)
|
||||
op := *msg.targetOutpoint
|
||||
|
||||
if _, ok := b.spendNotifications[op]; !ok {
|
||||
b.spendNotifications[op] = make(map[uint64]*spendNotification)
|
||||
}
|
||||
b.spendNotifications[op][msg.spendID] = msg
|
||||
b.chainConn.NotifySpent([]*wire.OutPoint{&op})
|
||||
case *confirmationsNotification:
|
||||
chainntnfs.Log.Infof("New confirmations "+
|
||||
"subscription: txid=%v, numconfs=%v",
|
||||
msg.TxID, msg.NumConfirmations)
|
||||
|
||||
// Lookup whether the transaction is already included in the
|
||||
// active chain.
|
||||
txConf, err := b.historicalConfDetails(msg.TxID)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
}
|
||||
b.heightMtx.RLock()
|
||||
err = b.txConfNotifier.Register(&msg.ConfNtfn, txConf)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
}
|
||||
b.heightMtx.RUnlock()
|
||||
case *blockEpochRegistration:
|
||||
chainntnfs.Log.Infof("New block epoch subscription")
|
||||
b.blockEpochClients[msg.epochID] = msg
|
||||
}
|
||||
|
||||
case ntfn := <-b.chainConn.Notifications():
|
||||
switch item := ntfn.(type) {
|
||||
case chain.BlockConnected:
|
||||
b.heightMtx.Lock()
|
||||
if item.Height != b.bestHeight+1 {
|
||||
chainntnfs.Log.Warnf("Received blocks out of order: "+
|
||||
"current height=%d, new height=%d",
|
||||
b.bestHeight, item.Height)
|
||||
b.heightMtx.Unlock()
|
||||
continue
|
||||
}
|
||||
b.bestHeight = item.Height
|
||||
|
||||
rawBlock, err := b.chainConn.GetBlock(&item.Hash)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Errorf("Unable to get block: %v", err)
|
||||
b.heightMtx.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
chainntnfs.Log.Infof("New block: height=%v, sha=%v",
|
||||
item.Height, item.Hash)
|
||||
|
||||
b.notifyBlockEpochs(item.Height, &item.Hash)
|
||||
|
||||
txns := btcutil.NewBlock(rawBlock).Transactions()
|
||||
err = b.txConfNotifier.ConnectTip(&item.Hash,
|
||||
uint32(item.Height), txns)
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
}
|
||||
b.heightMtx.Unlock()
|
||||
continue
|
||||
|
||||
case chain.BlockDisconnected:
|
||||
b.heightMtx.Lock()
|
||||
if item.Height != b.bestHeight {
|
||||
chainntnfs.Log.Warnf("Received blocks "+
|
||||
"out of order: current height="+
|
||||
"%d, disconnected height=%d",
|
||||
b.bestHeight, item.Height)
|
||||
b.heightMtx.Unlock()
|
||||
continue
|
||||
}
|
||||
b.bestHeight = item.Height - 1
|
||||
|
||||
chainntnfs.Log.Infof("Block disconnected from "+
|
||||
"main chain: height=%v, sha=%v",
|
||||
item.Height, item.Hash)
|
||||
|
||||
err := b.txConfNotifier.DisconnectTip(
|
||||
uint32(item.Height))
|
||||
if err != nil {
|
||||
chainntnfs.Log.Error(err)
|
||||
}
|
||||
b.heightMtx.Unlock()
|
||||
|
||||
case chain.RelevantTx:
|
||||
tx := item.TxRecord.MsgTx
|
||||
// First, check if this transaction spends an output
|
||||
// that has an existing spend notification for it.
|
||||
for i, txIn := range tx.TxIn {
|
||||
prevOut := txIn.PreviousOutPoint
|
||||
|
||||
// If this transaction indeed does spend an
|
||||
// output which we have a registered
|
||||
// notification for, then create a spend
|
||||
// summary, finally sending off the details to
|
||||
// the notification subscriber.
|
||||
if clients, ok := b.spendNotifications[prevOut]; ok {
|
||||
spenderSha := tx.TxHash()
|
||||
spendDetails := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &prevOut,
|
||||
SpenderTxHash: &spenderSha,
|
||||
SpendingTx: &tx,
|
||||
SpenderInputIndex: uint32(i),
|
||||
}
|
||||
// TODO(roasbeef): after change to
|
||||
// loadfilter, only notify on block
|
||||
// inclusion?
|
||||
if item.Block != nil {
|
||||
spendDetails.SpendingHeight = item.Block.Height
|
||||
} else {
|
||||
b.heightMtx.RLock()
|
||||
spendDetails.SpendingHeight = b.bestHeight + 1
|
||||
b.heightMtx.RUnlock()
|
||||
}
|
||||
|
||||
for _, ntfn := range clients {
|
||||
chainntnfs.Log.Infof("Dispatching "+
|
||||
"spend notification for "+
|
||||
"outpoint=%v", ntfn.targetOutpoint)
|
||||
ntfn.spendChan <- spendDetails
|
||||
|
||||
// Close spendChan to ensure that any calls to Cancel will not
|
||||
// block. This is safe to do since the channel is buffered, and the
|
||||
// message can still be read by the receiver.
|
||||
close(ntfn.spendChan)
|
||||
}
|
||||
delete(b.spendNotifications, prevOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case <-b.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
b.wg.Done()
|
||||
}
|
||||
|
||||
// historicalConfDetails looks up whether a transaction is already included in a
|
||||
// block in the active chain and, if so, returns details about the confirmation.
|
||||
func (b *BitcoindNotifier) historicalConfDetails(txid *chainhash.Hash,
|
||||
) (*chainntnfs.TxConfirmation, error) {
|
||||
|
||||
// If the transaction already has some or all of the confirmations,
|
||||
// then we may be able to dispatch it immediately.
|
||||
// TODO: fall back to scanning blocks if txindex isn't on.
|
||||
tx, err := b.chainConn.GetRawTransactionVerbose(txid)
|
||||
if err != nil || tx == nil || tx.BlockHash == "" {
|
||||
if err == nil {
|
||||
return nil, nil
|
||||
}
|
||||
// Do not return an error if the transaction was not found.
|
||||
if jsonErr, ok := err.(*btcjson.RPCError); ok {
|
||||
if jsonErr.Code == btcjson.ErrRPCNoTxInfo {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unable to query for txid(%v): %v", txid, err)
|
||||
}
|
||||
|
||||
// As we need to fully populate the returned TxConfirmation struct,
|
||||
// grab the block in which the transaction was confirmed so we can
|
||||
// locate its exact index within the block.
|
||||
blockHash, err := chainhash.NewHashFromStr(tx.BlockHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get block hash %v for historical "+
|
||||
"dispatch: %v", tx.BlockHash, err)
|
||||
}
|
||||
block, err := b.chainConn.GetBlockVerbose(blockHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get block hash: %v", err)
|
||||
}
|
||||
|
||||
// If the block obtained, locate the transaction's index within the
|
||||
// block so we can give the subscriber full confirmation details.
|
||||
txIndex := -1
|
||||
targetTxidStr := txid.String()
|
||||
for i, txHash := range block.Tx {
|
||||
if txHash == targetTxidStr {
|
||||
txIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if txIndex == -1 {
|
||||
return nil, fmt.Errorf("unable to locate tx %v in block %v",
|
||||
txid, blockHash)
|
||||
}
|
||||
|
||||
txConf := chainntnfs.TxConfirmation{
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: uint32(block.Height),
|
||||
TxIndex: uint32(txIndex),
|
||||
}
|
||||
return &txConf, nil
|
||||
}
|
||||
|
||||
// notifyBlockEpochs notifies all registered block epoch clients of the newly
|
||||
// connected block to the main chain.
|
||||
func (b *BitcoindNotifier) notifyBlockEpochs(newHeight int32, newSha *chainhash.Hash) {
|
||||
epoch := &chainntnfs.BlockEpoch{
|
||||
Height: newHeight,
|
||||
Hash: newSha,
|
||||
}
|
||||
|
||||
for _, epochClient := range b.blockEpochClients {
|
||||
b.wg.Add(1)
|
||||
epochClient.wg.Add(1)
|
||||
go func(ntfnChan chan *chainntnfs.BlockEpoch, cancelChan chan struct{},
|
||||
clientWg *sync.WaitGroup) {
|
||||
|
||||
// TODO(roasbeef): move to goroutine per client, use sync queue
|
||||
|
||||
defer clientWg.Done()
|
||||
defer b.wg.Done()
|
||||
|
||||
select {
|
||||
case ntfnChan <- epoch:
|
||||
|
||||
case <-cancelChan:
|
||||
return
|
||||
|
||||
case <-b.quit:
|
||||
return
|
||||
}
|
||||
|
||||
}(epochClient.epochChan, epochClient.cancelChan, &epochClient.wg)
|
||||
}
|
||||
}
|
||||
|
||||
// spendNotification couples a target outpoint along with the channel used for
|
||||
// notifications once a spend of the outpoint has been detected.
|
||||
type spendNotification struct {
|
||||
targetOutpoint *wire.OutPoint
|
||||
|
||||
spendChan chan *chainntnfs.SpendDetail
|
||||
|
||||
spendID uint64
|
||||
}
|
||||
|
||||
// spendCancel is a message sent to the BitcoindNotifier when a client wishes
|
||||
// to cancel an outstanding spend notification that has yet to be dispatched.
|
||||
type spendCancel struct {
|
||||
// op is the target outpoint of the notification to be cancelled.
|
||||
op wire.OutPoint
|
||||
|
||||
// spendID the ID of the notification to cancel.
|
||||
spendID uint64
|
||||
}
|
||||
|
||||
// RegisterSpendNtfn registers an intent to be notified once the target
|
||||
// outpoint has been spent by a transaction on-chain. Once a spend of the target
|
||||
// outpoint has been detected, the details of the spending event will be sent
|
||||
// across the 'Spend' channel.
|
||||
func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||
_ uint32) (*chainntnfs.SpendEvent, error) {
|
||||
|
||||
if err := b.chainConn.NotifySpent([]*wire.OutPoint{outpoint}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ntfn := &spendNotification{
|
||||
targetOutpoint: outpoint,
|
||||
spendChan: make(chan *chainntnfs.SpendDetail, 1),
|
||||
spendID: atomic.AddUint64(&b.spendClientCounter, 1),
|
||||
}
|
||||
|
||||
select {
|
||||
case <-b.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
case b.notificationRegistry <- ntfn:
|
||||
}
|
||||
|
||||
// The following conditional checks to ensure that when a spend notification
|
||||
// is registered, the output hasn't already been spent. If the output
|
||||
// is no longer in the UTXO set, the chain will be rescanned from the point
|
||||
// where the output was added. The rescan will dispatch the notification.
|
||||
txout, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if txout == nil {
|
||||
// TODO: fall back to scanning blocks if txindex isn't on.
|
||||
transaction, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
|
||||
if err != nil {
|
||||
jsonErr, ok := err.(*btcjson.RPCError)
|
||||
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if transaction != nil {
|
||||
blockhash, err := chainhash.NewHashFromStr(transaction.BlockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rewind the rescan, since the btcwallet bitcoind
|
||||
// back-end doesn't support that.
|
||||
blockHeight, err := b.chainConn.GetBlockHeight(blockhash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.heightMtx.Lock()
|
||||
currentHeight := b.bestHeight
|
||||
b.bestHeight = blockHeight
|
||||
for i := currentHeight; i > blockHeight; i-- {
|
||||
err = b.txConfNotifier.DisconnectTip(uint32(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
b.heightMtx.Unlock()
|
||||
|
||||
ops := []*wire.OutPoint{outpoint}
|
||||
if err := b.chainConn.Rescan(blockhash, nil, ops); err != nil {
|
||||
chainntnfs.Log.Errorf("Rescan for spend "+
|
||||
"notification txout failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &chainntnfs.SpendEvent{
|
||||
Spend: ntfn.spendChan,
|
||||
Cancel: func() {
|
||||
cancel := &spendCancel{
|
||||
op: *outpoint,
|
||||
spendID: ntfn.spendID,
|
||||
}
|
||||
|
||||
// Submit spend cancellation to notification dispatcher.
|
||||
select {
|
||||
case b.notificationCancels <- cancel:
|
||||
// Cancellation is being handled, drain the spend chan until it is
|
||||
// closed before yielding to the caller.
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-ntfn.spendChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
case <-b.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
case <-b.quit:
|
||||
}
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// confirmationNotification represents a client's intent to receive a
|
||||
// notification once the target txid reaches numConfirmations confirmations.
|
||||
type confirmationsNotification struct {
|
||||
chainntnfs.ConfNtfn
|
||||
}
|
||||
|
||||
// RegisterConfirmationsNtfn registers a notification with BitcoindNotifier
|
||||
// which will be triggered once the txid reaches numConfs number of
|
||||
// confirmations.
|
||||
func (b *BitcoindNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
||||
numConfs, _ uint32) (*chainntnfs.ConfirmationEvent, error) {
|
||||
|
||||
ntfn := &confirmationsNotification{
|
||||
chainntnfs.ConfNtfn{
|
||||
TxID: txid,
|
||||
NumConfirmations: numConfs,
|
||||
Event: chainntnfs.NewConfirmationEvent(),
|
||||
},
|
||||
}
|
||||
|
||||
select {
|
||||
case <-b.quit:
|
||||
return nil, ErrChainNotifierShuttingDown
|
||||
case b.notificationRegistry <- ntfn:
|
||||
return ntfn.Event, nil
|
||||
}
|
||||
}
|
||||
|
||||
// blockEpochRegistration represents a client's intent to receive a
|
||||
// notification with each newly connected block.
|
||||
type blockEpochRegistration struct {
|
||||
epochID uint64
|
||||
|
||||
epochChan chan *chainntnfs.BlockEpoch
|
||||
|
||||
cancelChan chan struct{}
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// epochCancel is a message sent to the BitcoindNotifier when a client wishes
|
||||
// to cancel an outstanding epoch notification that has yet to be dispatched.
|
||||
type epochCancel struct {
|
||||
epochID uint64
|
||||
}
|
||||
|
||||
// RegisterBlockEpochNtfn returns a BlockEpochEvent which subscribes the
|
||||
// caller to receive notifications, of each new block connected to the main
|
||||
// chain.
|
||||
func (b *BitcoindNotifier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) {
|
||||
registration := &blockEpochRegistration{
|
||||
epochChan: make(chan *chainntnfs.BlockEpoch, 20),
|
||||
cancelChan: make(chan struct{}),
|
||||
epochID: atomic.AddUint64(&b.epochClientCounter, 1),
|
||||
}
|
||||
|
||||
select {
|
||||
case <-b.quit:
|
||||
return nil, errors.New("chainntnfs: system interrupt while " +
|
||||
"attempting to register for block epoch notification.")
|
||||
case b.notificationRegistry <- registration:
|
||||
return &chainntnfs.BlockEpochEvent{
|
||||
Epochs: registration.epochChan,
|
||||
Cancel: func() {
|
||||
cancel := &epochCancel{
|
||||
epochID: registration.epochID,
|
||||
}
|
||||
|
||||
// Submit epoch cancellation to notification dispatcher.
|
||||
select {
|
||||
case b.notificationCancels <- cancel:
|
||||
// Cancellation is being handled, drain the epoch channel until it is
|
||||
// closed before yielding to caller.
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-registration.epochChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
case <-b.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
case <-b.quit:
|
||||
}
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
53
chainntnfs/bitcoindnotify/driver.go
Normal file
53
chainntnfs/bitcoindnotify/driver.go
Normal file
@ -0,0 +1,53 @@
|
||||
package bitcoindnotify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/rpcclient"
|
||||
)
|
||||
|
||||
// createNewNotifier creates a new instance of the ChainNotifier interface
|
||||
// implemented by BitcoindNotifier.
|
||||
func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) {
|
||||
if len(args) != 3 {
|
||||
return nil, fmt.Errorf("incorrect number of arguments to "+
|
||||
".New(...), expected 3, instead passed %v", len(args))
|
||||
}
|
||||
|
||||
config, ok := args[0].(*rpcclient.ConnConfig)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("first argument to bitcoindnotifier." +
|
||||
"New is incorrect, expected a *rpcclient.ConnConfig")
|
||||
}
|
||||
|
||||
zmqConnect, ok := args[1].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("second argument to bitcoindnotifier." +
|
||||
"New is incorrect, expected a string")
|
||||
}
|
||||
|
||||
params, ok := args[2].(chaincfg.Params)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("third argument to bitcoindnotifier." +
|
||||
"New is incorrect, expected a chaincfg.Params")
|
||||
}
|
||||
|
||||
return New(config, zmqConnect, params)
|
||||
}
|
||||
|
||||
// init registers a driver for the BtcdNotifier concrete implementation of the
|
||||
// chainntnfs.ChainNotifier interface.
|
||||
func init() {
|
||||
// Register the driver.
|
||||
notifier := &chainntnfs.NotifierDriver{
|
||||
NotifierType: notifierType,
|
||||
New: createNewNotifier,
|
||||
}
|
||||
|
||||
if err := chainntnfs.RegisterNotifier(notifier); err != nil {
|
||||
panic(fmt.Sprintf("failed to register notifier driver '%s': %v",
|
||||
notifierType, err))
|
||||
}
|
||||
}
|
@ -5,7 +5,9 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -13,6 +15,7 @@ import (
|
||||
|
||||
"github.com/lightninglabs/neutrino"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/ltcsuite/ltcd/btcjson"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
"github.com/roasbeef/btcwallet/walletdb"
|
||||
|
||||
@ -24,6 +27,10 @@ import (
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/roasbeef/btcutil"
|
||||
|
||||
// Required to auto-register the bitcoind backed ChainNotifier
|
||||
// implementation.
|
||||
_ "github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify"
|
||||
|
||||
// Required to auto-register the btcd backed ChainNotifier
|
||||
// implementation.
|
||||
_ "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
||||
@ -32,7 +39,8 @@ import (
|
||||
// implementation.
|
||||
_ "github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
|
||||
|
||||
_ "github.com/roasbeef/btcwallet/walletdb/bdb" // Required to register the boltdb walletdb implementation.
|
||||
// Required to register the boltdb walletdb implementation.
|
||||
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -43,7 +51,7 @@ var (
|
||||
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
|
||||
}
|
||||
|
||||
netParams = &chaincfg.SimNetParams
|
||||
netParams = &chaincfg.RegressionNetParams
|
||||
privKey, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKey)
|
||||
addrPk, _ = btcutil.NewAddressPubKey(pubKey.SerializeCompressed(),
|
||||
netParams)
|
||||
@ -65,6 +73,39 @@ func getTestTxId(miner *rpctest.Harness) (*chainhash.Hash, error) {
|
||||
return miner.SendOutputs(outputs, 10)
|
||||
}
|
||||
|
||||
func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error {
|
||||
var found bool
|
||||
var tx *btcutil.Tx
|
||||
var err error
|
||||
timeout := time.After(10 * time.Second)
|
||||
for !found {
|
||||
// Do a short wait
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timeout after 10s")
|
||||
default:
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Check for the harness' knowledge of the txid
|
||||
tx, err = r.Node.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case *btcjson.RPCError:
|
||||
if e.Code == btcjson.ErrRPCNoTxInfo {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
}
|
||||
return err
|
||||
}
|
||||
if tx != nil && tx.MsgTx().TxHash() == *txid {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testSingleConfirmationNotification(miner *rpctest.Harness,
|
||||
notifier chainntnfs.ChainNotifier, t *testing.T) {
|
||||
|
||||
@ -80,6 +121,11 @@ func testSingleConfirmationNotification(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create test tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
_, currentHeight, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get current height: %v", err)
|
||||
@ -143,6 +189,11 @@ func testMultiConfirmationNotification(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create test addr: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
_, currentHeight, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get current height: %v", err)
|
||||
@ -201,6 +252,11 @@ func testBatchConfirmationNotification(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
confIntents[i] = confIntent
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
initialConfHeight := uint32(currentHeight + 1)
|
||||
@ -252,6 +308,11 @@ func createSpendableOutput(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create test addr: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
// Mine a single block which should include that txid above.
|
||||
if _, err := miner.Node.Generate(1); err != nil {
|
||||
t.Fatalf("unable to generate single block: %v", err)
|
||||
@ -342,6 +403,11 @@ func testSpendNotification(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to broadcast tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, spenderSha)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
// Now we mine a single block, which should include our spend. The
|
||||
// notification should also be sent off.
|
||||
if _, err := miner.Node.Generate(1); err != nil {
|
||||
@ -453,6 +519,11 @@ func testMultiClientConfirmationNotification(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create test tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const (
|
||||
numConfsClients = 5
|
||||
@ -514,6 +585,11 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create test tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid3)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
// Generate another block containing tx 3, but we won't register conf
|
||||
// notifications for this tx until much later. The notifier must check
|
||||
// older blocks when the confirmation event is registered below to ensure
|
||||
@ -529,11 +605,21 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create test tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid1)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
txid2, err := getTestTxId(miner)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid2)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
_, currentHeight, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get current height: %v", err)
|
||||
@ -654,6 +740,11 @@ func testLazyNtfnConsumer(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create test tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
_, currentHeight, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get current height: %v", err)
|
||||
@ -686,6 +777,11 @@ func testLazyNtfnConsumer(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create test tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
_, currentHeight, err = miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get current height: %v", err)
|
||||
@ -736,6 +832,11 @@ func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to create test addr: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
// Mine a single block which should include that txid above.
|
||||
if _, err := miner.Node.Generate(1); err != nil {
|
||||
t.Fatalf("unable to generate single block: %v", err)
|
||||
@ -789,6 +890,11 @@ func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to brodacst tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, spenderSha)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
// Now we mine an additional block, which should include our spend.
|
||||
if _, err := miner.Node.Generate(1); err != nil {
|
||||
t.Fatalf("unable to generate single block: %v", err)
|
||||
@ -877,6 +983,11 @@ func testCancelSpendNtfn(node *rpctest.Harness,
|
||||
t.Fatalf("unable to brodacst tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(node, spenderSha)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
// Now we mine a single block, which should include our spend. The
|
||||
// notification should also be sent off.
|
||||
if _, err := node.Node.Generate(1); err != nil {
|
||||
@ -1021,6 +1132,11 @@ func testReorgConf(miner *rpctest.Harness, notifier chainntnfs.ChainNotifier,
|
||||
t.Fatalf("unable to create test tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
_, currentHeight, err := miner.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get current height: %v", err)
|
||||
@ -1094,11 +1210,16 @@ func testReorgConf(miner *rpctest.Harness, notifier chainntnfs.ChainNotifier,
|
||||
t.Fatalf("unable to get raw tx: %v", err)
|
||||
}
|
||||
|
||||
_, err = miner2.Node.SendRawTransaction(tx.MsgTx(), false)
|
||||
txid, err = miner2.Node.SendRawTransaction(tx.MsgTx(), false)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get send tx: %v", err)
|
||||
}
|
||||
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
|
||||
_, err = miner.Node.Generate(3)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate single block: %v", err)
|
||||
@ -1206,12 +1327,73 @@ func TestInterfaces(t *testing.T) {
|
||||
|
||||
switch notifierType {
|
||||
|
||||
case "bitcoind":
|
||||
// Start a bitcoind instance.
|
||||
tempBitcoindDir, err := ioutil.TempDir("", "bitcoind")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create temp dir: %v", err)
|
||||
}
|
||||
zmqPath := "ipc:///" + tempBitcoindDir + "/weks.socket"
|
||||
cleanUp1 := func() {
|
||||
os.RemoveAll(tempBitcoindDir)
|
||||
}
|
||||
cleanUp = cleanUp1
|
||||
rpcPort := rand.Int()%(65536-1024) + 1024
|
||||
bitcoind := exec.Command(
|
||||
"bitcoind",
|
||||
"-datadir="+tempBitcoindDir,
|
||||
"-regtest",
|
||||
"-connect="+p2pAddr,
|
||||
"-txindex",
|
||||
"-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6f"+
|
||||
"d$507c670e800a95284294edb5773b05544b"+
|
||||
"220110063096c221be9933c82d38e1",
|
||||
fmt.Sprintf("-rpcport=%d", rpcPort),
|
||||
"-disablewallet",
|
||||
"-zmqpubrawblock="+zmqPath,
|
||||
"-zmqpubrawtx="+zmqPath,
|
||||
)
|
||||
err = bitcoind.Start()
|
||||
if err != nil {
|
||||
cleanUp1()
|
||||
t.Fatalf("Couldn't start bitcoind: %v", err)
|
||||
}
|
||||
cleanUp2 := func() {
|
||||
bitcoind.Process.Kill()
|
||||
bitcoind.Wait()
|
||||
cleanUp1()
|
||||
}
|
||||
cleanUp = cleanUp2
|
||||
|
||||
// Wait for the bitcoind instance to start up.
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// Start the FilteredChainView implementation instance.
|
||||
config := rpcclient.ConnConfig{
|
||||
Host: fmt.Sprintf(
|
||||
"127.0.0.1:%d", rpcPort),
|
||||
User: "weks",
|
||||
Pass: "weks",
|
||||
DisableAutoReconnect: false,
|
||||
DisableConnectOnNew: true,
|
||||
DisableTLS: true,
|
||||
HTTPPostMode: true,
|
||||
}
|
||||
|
||||
notifier, err = notifierDriver.New(&config, zmqPath,
|
||||
*netParams)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create %v notifier: %v",
|
||||
notifierType, err)
|
||||
}
|
||||
|
||||
case "btcd":
|
||||
notifier, err = notifierDriver.New(&rpcConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create %v notifier: %v",
|
||||
notifierType, err)
|
||||
}
|
||||
cleanUp = func() {}
|
||||
|
||||
case "neutrino":
|
||||
spvDir, err := ioutil.TempDir("", "neutrino")
|
||||
@ -1241,8 +1423,8 @@ func TestInterfaces(t *testing.T) {
|
||||
spvNode.Start()
|
||||
|
||||
cleanUp = func() {
|
||||
spvDatabase.Close()
|
||||
spvNode.Stop()
|
||||
spvDatabase.Close()
|
||||
os.RemoveAll(spvDir)
|
||||
}
|
||||
|
||||
|
184
chainregistry.go
184
chainregistry.go
@ -4,21 +4,23 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lightninglabs/neutrino"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/chainview"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
"github.com/roasbeef/btcd/rpcclient"
|
||||
@ -27,28 +29,11 @@ import (
|
||||
"github.com/roasbeef/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
// defaultBitcoinForwardingPolicy is the default forwarding policy used for
|
||||
// Bitcoin channels.
|
||||
var defaultBitcoinForwardingPolicy = htlcswitch.ForwardingPolicy{
|
||||
MinHTLC: lnwire.NewMSatFromSatoshis(1),
|
||||
BaseFee: lnwire.NewMSatFromSatoshis(1),
|
||||
FeeRate: 1,
|
||||
TimeLockDelta: 144,
|
||||
}
|
||||
|
||||
// defaultLitecoinForwardingPolicy is the default forwarding policy used for
|
||||
// Litecoin channels.
|
||||
var defaultLitecoinForwardingPolicy = htlcswitch.ForwardingPolicy{
|
||||
MinHTLC: lnwire.NewMSatFromSatoshis(1),
|
||||
BaseFee: 1,
|
||||
FeeRate: 1,
|
||||
TimeLockDelta: 576,
|
||||
}
|
||||
|
||||
// defaultChannelConstraints is the default set of channel constraints that are
|
||||
// meant to be used when initially funding a channel.
|
||||
//
|
||||
// TODO(roasbeef): have one for both chains
|
||||
// TODO(halseth): make configurable at startup?
|
||||
var defaultChannelConstraints = channeldb.ChannelConstraints{
|
||||
DustLimit: lnwallet.DefaultDustLimit(),
|
||||
MaxAcceptedHtlcs: lnwallet.MaxHTLCNumber / 2,
|
||||
@ -119,12 +104,22 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
||||
|
||||
switch registeredChains.PrimaryChain() {
|
||||
case bitcoinChain:
|
||||
cc.routingPolicy = defaultBitcoinForwardingPolicy
|
||||
cc.routingPolicy = htlcswitch.ForwardingPolicy{
|
||||
MinHTLC: cfg.Bitcoin.MinHTLC,
|
||||
BaseFee: cfg.Bitcoin.BaseFee,
|
||||
FeeRate: cfg.Bitcoin.FeeRate,
|
||||
TimeLockDelta: cfg.Bitcoin.TimeLockDelta,
|
||||
}
|
||||
cc.feeEstimator = lnwallet.StaticFeeEstimator{
|
||||
FeeRate: 50,
|
||||
}
|
||||
case litecoinChain:
|
||||
cc.routingPolicy = defaultLitecoinForwardingPolicy
|
||||
cc.routingPolicy = htlcswitch.ForwardingPolicy{
|
||||
MinHTLC: cfg.Litecoin.MinHTLC,
|
||||
BaseFee: cfg.Litecoin.BaseFee,
|
||||
FeeRate: cfg.Litecoin.FeeRate,
|
||||
TimeLockDelta: cfg.Litecoin.TimeLockDelta,
|
||||
}
|
||||
cc.feeEstimator = lnwallet.StaticFeeEstimator{
|
||||
FeeRate: 100,
|
||||
}
|
||||
@ -142,15 +137,17 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
cleanUp func()
|
||||
rawRPCConn *chain.RPCClient
|
||||
err error
|
||||
cleanUp func()
|
||||
btcdConn *chain.RPCClient
|
||||
bitcoindConn *chain.BitcoindClient
|
||||
)
|
||||
|
||||
// If spv mode is active, then we'll be using a distinct set of
|
||||
// chainControl interfaces that interface directly with the p2p network
|
||||
// of the selected chain.
|
||||
if cfg.NeutrinoMode.Active {
|
||||
switch homeChainConfig.Node {
|
||||
case "neutrino":
|
||||
// First we'll open the database file for neutrino, creating
|
||||
// the database if needed.
|
||||
dbName := filepath.Join(cfg.DataDir, "neutrino.db")
|
||||
@ -197,21 +194,121 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
||||
cleanUp = func() {
|
||||
defer nodeDatabase.Close()
|
||||
}
|
||||
} else {
|
||||
case "bitcoind":
|
||||
// Otherwise, we'll be speaking directly via RPC and ZMQ to a
|
||||
// bitcoind node. If the specified host for the btcd/ltcd RPC
|
||||
// server already has a port specified, then we use that
|
||||
// directly. Otherwise, we assume the default port according to
|
||||
// the selected chain parameters.
|
||||
var bitcoindHost string
|
||||
if strings.Contains(cfg.BitcoindMode.RPCHost, ":") {
|
||||
bitcoindHost = cfg.BitcoindMode.RPCHost
|
||||
} else {
|
||||
// The RPC ports specified in chainparams.go assume
|
||||
// btcd, which picks a different port so that btcwallet
|
||||
// can use the same RPC port as bitcoind. We convert
|
||||
// this back to the btcwallet/bitcoind port.
|
||||
rpcPort, err := strconv.Atoi(activeNetParams.rpcPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rpcPort -= 2
|
||||
bitcoindHost = fmt.Sprintf("%v:%d",
|
||||
cfg.BitcoindMode.RPCHost, rpcPort)
|
||||
if cfg.Bitcoin.RegTest {
|
||||
conn, err := net.Dial("tcp", bitcoindHost)
|
||||
if err != nil || conn == nil {
|
||||
rpcPort = 18443
|
||||
bitcoindHost = fmt.Sprintf("%v:%d",
|
||||
cfg.BitcoindMode.RPCHost,
|
||||
rpcPort)
|
||||
} else {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitcoindUser := cfg.BitcoindMode.RPCUser
|
||||
bitcoindPass := cfg.BitcoindMode.RPCPass
|
||||
rpcConfig := &rpcclient.ConnConfig{
|
||||
Host: bitcoindHost,
|
||||
User: bitcoindUser,
|
||||
Pass: bitcoindPass,
|
||||
DisableConnectOnNew: true,
|
||||
DisableAutoReconnect: false,
|
||||
DisableTLS: true,
|
||||
HTTPPostMode: true,
|
||||
}
|
||||
cc.chainNotifier, err = bitcoindnotify.New(rpcConfig,
|
||||
cfg.BitcoindMode.ZMQPath, *activeNetParams.Params)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Next, we'll create an instance of the bitcoind chain view to
|
||||
// be used within the routing layer.
|
||||
cc.chainView, err = chainview.NewBitcoindFilteredChainView(
|
||||
*rpcConfig, cfg.BitcoindMode.ZMQPath,
|
||||
*activeNetParams.Params)
|
||||
if err != nil {
|
||||
srvrLog.Errorf("unable to create chain view: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Create a special rpc+ZMQ client for bitcoind which will be
|
||||
// used by the wallet for notifications, calls, etc.
|
||||
bitcoindConn, err = chain.NewBitcoindClient(
|
||||
activeNetParams.Params, bitcoindHost, bitcoindUser,
|
||||
bitcoindPass, cfg.BitcoindMode.ZMQPath,
|
||||
time.Millisecond*100)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
walletConfig.ChainSource = bitcoindConn
|
||||
|
||||
// If we're not in regtest mode, then we'll attempt to use a
|
||||
// proper fee estimator for testnet.
|
||||
if !cfg.Bitcoin.RegTest {
|
||||
ltndLog.Infof("Initializing bitcoind backed fee estimator")
|
||||
|
||||
// Finally, we'll re-initialize the fee estimator, as
|
||||
// if we're using bitcoind as a backend, then we can
|
||||
// use live fee estimates, rather than a statically
|
||||
// coded value.
|
||||
fallBackFeeRate := btcutil.Amount(25)
|
||||
cc.feeEstimator, err = lnwallet.NewBitcoindFeeEstimator(
|
||||
*rpcConfig, fallBackFeeRate,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := cc.feeEstimator.Start(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
case "btcd":
|
||||
// Otherwise, we'll be speaking directly via RPC to a node.
|
||||
//
|
||||
// So first we'll load btcd/ltcd's TLS cert for the RPC
|
||||
// connection. If a raw cert was specified in the config, then
|
||||
// we'll set that directly. Otherwise, we attempt to read the
|
||||
// cert from the path specified in the config.
|
||||
var btcdMode *btcdConfig
|
||||
switch {
|
||||
case cfg.Bitcoin.Active:
|
||||
btcdMode = cfg.BtcdMode
|
||||
case cfg.Litecoin.Active:
|
||||
btcdMode = cfg.LtcdMode
|
||||
}
|
||||
var rpcCert []byte
|
||||
if homeChainConfig.RawRPCCert != "" {
|
||||
rpcCert, err = hex.DecodeString(homeChainConfig.RawRPCCert)
|
||||
if btcdMode.RawRPCCert != "" {
|
||||
rpcCert, err = hex.DecodeString(btcdMode.RawRPCCert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
certFile, err := os.Open(homeChainConfig.RPCCert)
|
||||
certFile, err := os.Open(btcdMode.RPCCert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -229,15 +326,15 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
||||
// we assume the default port according to the selected chain
|
||||
// parameters.
|
||||
var btcdHost string
|
||||
if strings.Contains(homeChainConfig.RPCHost, ":") {
|
||||
btcdHost = homeChainConfig.RPCHost
|
||||
if strings.Contains(btcdMode.RPCHost, ":") {
|
||||
btcdHost = btcdMode.RPCHost
|
||||
} else {
|
||||
btcdHost = fmt.Sprintf("%v:%v", homeChainConfig.RPCHost,
|
||||
btcdHost = fmt.Sprintf("%v:%v", btcdMode.RPCHost,
|
||||
activeNetParams.rpcPort)
|
||||
}
|
||||
|
||||
btcdUser := homeChainConfig.RPCUser
|
||||
btcdPass := homeChainConfig.RPCPass
|
||||
btcdUser := btcdMode.RPCUser
|
||||
btcdPass := btcdMode.RPCPass
|
||||
rpcConfig := &rpcclient.ConnConfig{
|
||||
Host: btcdHost,
|
||||
Endpoint: "ws",
|
||||
@ -270,7 +367,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
||||
}
|
||||
|
||||
walletConfig.ChainSource = chainRPC
|
||||
rawRPCConn = chainRPC
|
||||
btcdConn = chainRPC
|
||||
|
||||
// If we're not in simnet or regtest mode, then we'll attempt
|
||||
// to use a proper fee estimator for testnet.
|
||||
@ -294,6 +391,9 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown node type: %s",
|
||||
homeChainConfig.Node)
|
||||
}
|
||||
|
||||
wc, err := btcwallet.New(*walletConfig)
|
||||
@ -335,7 +435,7 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
||||
// As a final check, if we're using the RPC backend, we'll ensure that
|
||||
// the btcd node has the txindex set. Atm, this is required in order to
|
||||
// properly perform historical confirmation+spend dispatches.
|
||||
if !cfg.NeutrinoMode.Active {
|
||||
if homeChainConfig.Node != "neutrino" {
|
||||
// In order to check to see if we have the txindex up to date
|
||||
// and active, we'll try to fetch the first transaction in the
|
||||
// latest block via the index. If this doesn't succeed, then we
|
||||
@ -352,12 +452,18 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
|
||||
}
|
||||
|
||||
firstTxHash := bestBlock.Transactions[0].TxHash()
|
||||
_, err = rawRPCConn.GetRawTransaction(&firstTxHash)
|
||||
switch homeChainConfig.Node {
|
||||
case "btcd":
|
||||
_, err = btcdConn.GetRawTransaction(&firstTxHash)
|
||||
case "bitcoind":
|
||||
_, err = bitcoindConn.GetRawTransactionVerbose(&firstTxHash)
|
||||
}
|
||||
if err != nil {
|
||||
// If the node doesn't have the txindex set, then we'll
|
||||
// halt startup, as we can't proceed in this state.
|
||||
return nil, nil, fmt.Errorf("btcd detected to not " +
|
||||
"have --txindex active, cannot proceed")
|
||||
return nil, nil, fmt.Errorf("%s detected to not "+
|
||||
"have --txindex active, cannot proceed",
|
||||
homeChainConfig.Node)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -431,6 +431,11 @@ var openChannelCommand = cli.Command{
|
||||
"must be explicitly told about it to be able " +
|
||||
"to route through it",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "min_htlc_msat",
|
||||
Usage: "(optional) the minimum value we will require " +
|
||||
"for incoming HTLCs on the channel",
|
||||
},
|
||||
},
|
||||
Action: actionDecorator(openChannel),
|
||||
}
|
||||
@ -456,8 +461,9 @@ func openChannel(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
req := &lnrpc.OpenChannelRequest{
|
||||
TargetConf: int32(ctx.Int64("conf_target")),
|
||||
SatPerByte: ctx.Int64("sat_per_byte"),
|
||||
TargetConf: int32(ctx.Int64("conf_target")),
|
||||
SatPerByte: ctx.Int64("sat_per_byte"),
|
||||
MinHtlcMsat: ctx.Int64("min_htlc_msat"),
|
||||
}
|
||||
|
||||
switch {
|
||||
@ -1970,7 +1976,7 @@ var feeReportCommand = cli.Command{
|
||||
Usage: "display the current fee policies of all active channels",
|
||||
Description: `
|
||||
Returns the current fee policies of all active channels.
|
||||
Fee policies can be updated using the updateFees command.`,
|
||||
Fee policies can be updated using the updatechanpolicy command.`,
|
||||
Action: actionDecorator(feeReport),
|
||||
}
|
||||
|
||||
@ -1989,13 +1995,13 @@ func feeReport(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var updateFeesCommand = cli.Command{
|
||||
Name: "updatefees",
|
||||
Usage: "update the fee policy for all channels, or a single channel",
|
||||
ArgsUsage: "base_fee_msat fee_rate [channel_point]",
|
||||
var updateChannelPolicyCommand = cli.Command{
|
||||
Name: "updatechanpolicy",
|
||||
Usage: "update the channel policy for all channels, or a single channel",
|
||||
ArgsUsage: "base_fee_msat fee_rate time_lock_delta [channel_point]",
|
||||
Description: `
|
||||
Updates the fee policy for all channels, or just a particular channel
|
||||
identified by it's channel point. The fee update will be committed, and
|
||||
Updates the channel policy for all channels, or just a particular channel
|
||||
identified by its channel point. The update will be committed, and
|
||||
broadcast to the rest of the network within the next batch.
|
||||
Channel points are encoded as: funding_txid:output_index`,
|
||||
Flags: []cli.Flag{
|
||||
@ -2011,6 +2017,11 @@ var updateFeesCommand = cli.Command{
|
||||
"proportionally based on the value of each " +
|
||||
"forwarded HTLC, the lowest possible rate is 0.000001",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "time_lock_delta",
|
||||
Usage: "the CLTV delta that will be applied to all " +
|
||||
"forwarded HTLCs",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "chan_point",
|
||||
Usage: "The channel whose fee policy should be " +
|
||||
@ -2018,18 +2029,19 @@ var updateFeesCommand = cli.Command{
|
||||
"will be updated. Takes the form of: txid:output_index",
|
||||
},
|
||||
},
|
||||
Action: actionDecorator(updateFees),
|
||||
Action: actionDecorator(updateChannelPolicy),
|
||||
}
|
||||
|
||||
func updateFees(ctx *cli.Context) error {
|
||||
func updateChannelPolicy(ctx *cli.Context) error {
|
||||
ctxb := context.Background()
|
||||
client, cleanUp := getClient(ctx)
|
||||
defer cleanUp()
|
||||
|
||||
var (
|
||||
baseFee int64
|
||||
feeRate float64
|
||||
err error
|
||||
baseFee int64
|
||||
feeRate float64
|
||||
timeLockDelta int64
|
||||
err error
|
||||
)
|
||||
args := ctx.Args()
|
||||
|
||||
@ -2060,10 +2072,26 @@ func updateFees(ctx *cli.Context) error {
|
||||
return fmt.Errorf("fee_rate argument missing")
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctx.IsSet("time_lock_delta"):
|
||||
timeLockDelta = ctx.Int64("time_lock_delta")
|
||||
case args.Present():
|
||||
timeLockDelta, err = strconv.ParseInt(args.First(), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode time_lock_delta: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
args = args.Tail()
|
||||
default:
|
||||
return fmt.Errorf("time_lock_delta argument missing")
|
||||
}
|
||||
|
||||
var (
|
||||
chanPoint *lnrpc.ChannelPoint
|
||||
chanPointStr string
|
||||
)
|
||||
|
||||
switch {
|
||||
case ctx.IsSet("chan_point"):
|
||||
chanPointStr = ctx.String("chan_point")
|
||||
@ -2093,22 +2121,23 @@ func updateFees(ctx *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
req := &lnrpc.FeeUpdateRequest{
|
||||
BaseFeeMsat: baseFee,
|
||||
FeeRate: feeRate,
|
||||
req := &lnrpc.PolicyUpdateRequest{
|
||||
BaseFeeMsat: baseFee,
|
||||
FeeRate: feeRate,
|
||||
TimeLockDelta: uint32(timeLockDelta),
|
||||
}
|
||||
|
||||
if chanPoint != nil {
|
||||
req.Scope = &lnrpc.FeeUpdateRequest_ChanPoint{
|
||||
req.Scope = &lnrpc.PolicyUpdateRequest_ChanPoint{
|
||||
ChanPoint: chanPoint,
|
||||
}
|
||||
} else {
|
||||
req.Scope = &lnrpc.FeeUpdateRequest_Global{
|
||||
req.Scope = &lnrpc.PolicyUpdateRequest_Global{
|
||||
Global: true,
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := client.UpdateFees(ctxb, req)
|
||||
resp, err := client.UpdateChannelPolicy(ctxb, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ func main() {
|
||||
signMessageCommand,
|
||||
verifyMessageCommand,
|
||||
feeReportCommand,
|
||||
updateFeesCommand,
|
||||
updateChannelPolicyCommand,
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
|
363
config.go
363
config.go
@ -9,6 +9,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
@ -16,7 +17,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
flags "github.com/btcsuite/go-flags"
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
"github.com/lightningnetwork/lnd/brontide"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
@ -38,9 +39,22 @@ const (
|
||||
defaultPeerPort = 9735
|
||||
defaultRPCHost = "localhost"
|
||||
defaultMaxPendingChannels = 1
|
||||
defaultNumChanConfs = 3
|
||||
defaultNoEncryptWallet = false
|
||||
defaultTrickleDelay = 30 * 1000
|
||||
|
||||
// minTimeLockDelta is the minimum timelock we require for incoming
|
||||
// HTLCs on our channels.
|
||||
minTimeLockDelta = 4
|
||||
|
||||
defaultBitcoinMinHTLCMSat = 1000
|
||||
defaultBitcoinBaseFeeMSat = 1000
|
||||
defaultBitcoinFeeRate = 1
|
||||
defaultBitcoinTimeLockDelta = 144
|
||||
|
||||
defaultLitecoinMinHTLCMSat = 1000
|
||||
defaultLitecoinBaseFeeMSat = 1000
|
||||
defaultLitecoinFeeRate = 1
|
||||
defaultLitecoinTimeLockDelta = 576
|
||||
)
|
||||
|
||||
var (
|
||||
@ -59,25 +73,29 @@ var (
|
||||
|
||||
ltcdHomeDir = btcutil.AppDataDir("ltcd", false)
|
||||
defaultLtcdRPCCertFile = filepath.Join(ltcdHomeDir, "rpc.cert")
|
||||
|
||||
bitcoindHomeDir = btcutil.AppDataDir("bitcoin", false)
|
||||
)
|
||||
|
||||
type chainConfig struct {
|
||||
Active bool `long:"active" description:"If the chain should be active or not."`
|
||||
ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."`
|
||||
|
||||
RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."`
|
||||
RPCUser string `long:"rpcuser" description:"Username for RPC connections"`
|
||||
RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
|
||||
RPCCert string `long:"rpccert" description:"File containing the daemon's certificate file"`
|
||||
RawRPCCert string `long:"rawrpccert" description:"The raw bytes of the daemon's PEM-encoded certificate chain which will be used to authenticate the RPC connection."`
|
||||
Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino"`
|
||||
|
||||
TestNet3 bool `long:"testnet" description:"Use the test network"`
|
||||
SimNet bool `long:"simnet" description:"Use the simulation test network"`
|
||||
RegTest bool `long:"regtest" description:"Use the regression test network"`
|
||||
|
||||
DefaultNumChanConfs int `long:"defaultchanconfs" description:"The default number of confirmations a channel must have before it's considered open. If this is not set, we will scale the value according to the channel size."`
|
||||
DefaultRemoteDelay int `long:"defaultremotedelay" description:"The default number of blocks we will require our channel counterparty to wait before accessing its funds in case of unilateral close. If this is not set, we will scale the value according to the channel size."`
|
||||
MinHTLC lnwire.MilliSatoshi `long:"minhtlc" description:"The smallest HTLC we are willing to forward on our channels, in millisatoshi"`
|
||||
BaseFee lnwire.MilliSatoshi `long:"basefee" description:"The base fee in millisatoshi we will charge for forwarding payments on our channels"`
|
||||
FeeRate lnwire.MilliSatoshi `long:"feerate" description:"The fee rate used when forwarding payments on our channels. The total fee charged is basefee + (amount * feerate / 1000000), where amount is the forwarded amount."`
|
||||
TimeLockDelta uint32 `long:"timelockdelta" description:"The CLTV delta we will subtract from a forwarded HTLC's timelock value"`
|
||||
}
|
||||
|
||||
type neutrinoConfig struct {
|
||||
Active bool `long:"active" description:"If SPV mode should be active or not."`
|
||||
AddPeers []string `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
|
||||
ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"`
|
||||
MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers"`
|
||||
@ -85,6 +103,21 @@ type neutrinoConfig struct {
|
||||
BanThreshold uint32 `long:"banthreshold" description:"Maximum allowed ban score before disconnecting and banning misbehaving peers."`
|
||||
}
|
||||
|
||||
type btcdConfig struct {
|
||||
RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."`
|
||||
RPCUser string `long:"rpcuser" description:"Username for RPC connections"`
|
||||
RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
|
||||
RPCCert string `long:"rpccert" description:"File containing the daemon's certificate file"`
|
||||
RawRPCCert string `long:"rawrpccert" description:"The raw bytes of the daemon's PEM-encoded certificate chain which will be used to authenticate the RPC connection."`
|
||||
}
|
||||
|
||||
type bitcoindConfig struct {
|
||||
RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."`
|
||||
RPCUser string `long:"rpcuser" description:"Username for RPC connections"`
|
||||
RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
|
||||
ZMQPath string `long:"zmqpath" description:"The path to the ZMQ socket providing at least raw blocks. Raw transactions can be handled as well."`
|
||||
}
|
||||
|
||||
type autoPilotConfig struct {
|
||||
// TODO(roasbeef): add
|
||||
Active bool `long:"active" description:"If the autopilot agent should be active or not."`
|
||||
@ -124,13 +157,14 @@ type config struct {
|
||||
HodlHTLC bool `long:"hodlhtlc" description:"Activate the hodl HTLC mode. With hodl HTLC mode, all incoming HTLCs will be accepted by the receiving node, but no attempt will be made to settle the payment with the sender."`
|
||||
MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
|
||||
|
||||
Litecoin *chainConfig `group:"Litecoin" namespace:"litecoin"`
|
||||
Bitcoin *chainConfig `group:"Bitcoin" namespace:"bitcoin"`
|
||||
|
||||
DefaultNumChanConfs int `long:"defaultchanconfs" description:"The default number of confirmations a channel must have before it's considered open."`
|
||||
|
||||
Bitcoin *chainConfig `group:"Bitcoin" namespace:"bitcoin"`
|
||||
BtcdMode *btcdConfig `group:"btcd" namespace:"btcd"`
|
||||
BitcoindMode *bitcoindConfig `group:"bitcoind" namespace:"bitcoind"`
|
||||
NeutrinoMode *neutrinoConfig `group:"neutrino" namespace:"neutrino"`
|
||||
|
||||
Litecoin *chainConfig `group:"Litecoin" namespace:"litecoin"`
|
||||
LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"`
|
||||
|
||||
Autopilot *autoPilotConfig `group:"autopilot" namespace:"autopilot"`
|
||||
|
||||
NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."`
|
||||
@ -150,28 +184,44 @@ type config struct {
|
||||
// 4) Parse CLI options and overwrite/add any specified options
|
||||
func loadConfig() (*config, error) {
|
||||
defaultCfg := config{
|
||||
ConfigFile: defaultConfigFile,
|
||||
DataDir: defaultDataDir,
|
||||
DebugLevel: defaultLogLevel,
|
||||
TLSCertPath: defaultTLSCertPath,
|
||||
TLSKeyPath: defaultTLSKeyPath,
|
||||
AdminMacPath: defaultAdminMacPath,
|
||||
ReadMacPath: defaultReadMacPath,
|
||||
LogDir: defaultLogDir,
|
||||
PeerPort: defaultPeerPort,
|
||||
RPCPort: defaultRPCPort,
|
||||
RESTPort: defaultRESTPort,
|
||||
MaxPendingChannels: defaultMaxPendingChannels,
|
||||
DefaultNumChanConfs: defaultNumChanConfs,
|
||||
NoEncryptWallet: defaultNoEncryptWallet,
|
||||
ConfigFile: defaultConfigFile,
|
||||
DataDir: defaultDataDir,
|
||||
DebugLevel: defaultLogLevel,
|
||||
TLSCertPath: defaultTLSCertPath,
|
||||
TLSKeyPath: defaultTLSKeyPath,
|
||||
AdminMacPath: defaultAdminMacPath,
|
||||
ReadMacPath: defaultReadMacPath,
|
||||
LogDir: defaultLogDir,
|
||||
PeerPort: defaultPeerPort,
|
||||
RPCPort: defaultRPCPort,
|
||||
RESTPort: defaultRESTPort,
|
||||
Bitcoin: &chainConfig{
|
||||
MinHTLC: defaultBitcoinMinHTLCMSat,
|
||||
BaseFee: defaultBitcoinBaseFeeMSat,
|
||||
FeeRate: defaultBitcoinFeeRate,
|
||||
TimeLockDelta: defaultBitcoinTimeLockDelta,
|
||||
Node: "btcd",
|
||||
},
|
||||
BtcdMode: &btcdConfig{
|
||||
RPCHost: defaultRPCHost,
|
||||
RPCCert: defaultBtcdRPCCertFile,
|
||||
},
|
||||
BitcoindMode: &bitcoindConfig{
|
||||
RPCHost: defaultRPCHost,
|
||||
},
|
||||
Litecoin: &chainConfig{
|
||||
MinHTLC: defaultLitecoinMinHTLCMSat,
|
||||
BaseFee: defaultLitecoinBaseFeeMSat,
|
||||
FeeRate: defaultLitecoinFeeRate,
|
||||
TimeLockDelta: defaultLitecoinTimeLockDelta,
|
||||
Node: "btcd",
|
||||
},
|
||||
LtcdMode: &btcdConfig{
|
||||
RPCHost: defaultRPCHost,
|
||||
RPCCert: defaultLtcdRPCCertFile,
|
||||
},
|
||||
MaxPendingChannels: defaultMaxPendingChannels,
|
||||
NoEncryptWallet: defaultNoEncryptWallet,
|
||||
Autopilot: &autoPilotConfig{
|
||||
MaxChannels: 5,
|
||||
Allocation: 0.6,
|
||||
@ -227,28 +277,18 @@ func loadConfig() (*config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
// At this moment, multiple active chains are not supported.
|
||||
if cfg.Litecoin.Active && cfg.Bitcoin.Active {
|
||||
case cfg.Litecoin.Active && cfg.Bitcoin.Active:
|
||||
str := "%s: Currently both Bitcoin and Litecoin cannot be " +
|
||||
"active together"
|
||||
err := fmt.Errorf(str, funcName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
// The SPV mode implemented currently doesn't support Litecoin, so the
|
||||
// two modes are incompatible.
|
||||
case cfg.NeutrinoMode.Active && cfg.Litecoin.Active:
|
||||
str := "%s: The light client mode currently supported does " +
|
||||
"not yet support execution on the Litecoin network"
|
||||
err := fmt.Errorf(str, funcName)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf(str, funcName)
|
||||
|
||||
// Either Bitcoin must be active, or Litecoin must be active.
|
||||
// Otherwise, we don't know which chain we're on.
|
||||
case !cfg.Bitcoin.Active && !cfg.Litecoin.Active:
|
||||
return nil, fmt.Errorf("either bitcoin.active or " +
|
||||
"litecoin.active must be set to 1 (true)")
|
||||
return nil, fmt.Errorf("%s: either bitcoin.active or "+
|
||||
"litecoin.active must be set to 1 (true)", funcName)
|
||||
|
||||
case cfg.Litecoin.Active:
|
||||
if cfg.Litecoin.SimNet {
|
||||
@ -256,25 +296,31 @@ func loadConfig() (*config, error) {
|
||||
return nil, fmt.Errorf(str, funcName)
|
||||
}
|
||||
|
||||
if cfg.Litecoin.TimeLockDelta < minTimeLockDelta {
|
||||
return nil, fmt.Errorf("timelockdelta must be at least %v",
|
||||
minTimeLockDelta)
|
||||
}
|
||||
|
||||
if cfg.Litecoin.Node != "btcd" {
|
||||
str := "%s: only ltcd (`btcd`) mode supported for litecoin at this time"
|
||||
return nil, fmt.Errorf(str, funcName)
|
||||
}
|
||||
|
||||
// The litecoin chain is the current active chain. However
|
||||
// throughout the codebase we required chiancfg.Params. So as a
|
||||
// throughout the codebase we required chaincfg.Params. So as a
|
||||
// temporary hack, we'll mutate the default net params for
|
||||
// bitcoin with the litecoin specific information.
|
||||
paramCopy := bitcoinTestNetParams
|
||||
applyLitecoinParams(¶mCopy)
|
||||
activeNetParams = paramCopy
|
||||
|
||||
if !cfg.NeutrinoMode.Active {
|
||||
// Attempt to parse out the RPC credentials for the
|
||||
// litecoin chain if the information wasn't specified
|
||||
err := parseRPCParams(cfg.Litecoin, litecoinChain, funcName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to load RPC credentials for "+
|
||||
"ltcd: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
err := parseRPCParams(cfg.Litecoin, cfg.LtcdMode, litecoinChain,
|
||||
funcName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to load RPC credentials for "+
|
||||
"ltcd: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg.Litecoin.ChainDir = filepath.Join(cfg.DataDir, litecoinChain.String())
|
||||
|
||||
// Finally we'll register the litecoin chain as our current
|
||||
@ -305,15 +351,34 @@ func loadConfig() (*config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cfg.NeutrinoMode.Active {
|
||||
// If needed, we'll attempt to automatically configure
|
||||
// the RPC control plan for the target btcd node.
|
||||
err := parseRPCParams(cfg.Bitcoin, bitcoinChain, funcName)
|
||||
if cfg.Bitcoin.TimeLockDelta < minTimeLockDelta {
|
||||
return nil, fmt.Errorf("timelockdelta must be at least %v",
|
||||
minTimeLockDelta)
|
||||
}
|
||||
|
||||
switch cfg.Bitcoin.Node {
|
||||
case "btcd":
|
||||
err := parseRPCParams(cfg.Bitcoin, cfg.BtcdMode,
|
||||
bitcoinChain, funcName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to load RPC credentials for "+
|
||||
"btcd: %v", err)
|
||||
err := fmt.Errorf("unable to load RPC "+
|
||||
"credentials for btcd: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
case "bitcoind":
|
||||
if cfg.Bitcoin.SimNet {
|
||||
return nil, fmt.Errorf("%s: bitcoind does not "+
|
||||
"support simnet", funcName)
|
||||
}
|
||||
err := parseRPCParams(cfg.Bitcoin, cfg.BitcoindMode,
|
||||
bitcoinChain, funcName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to load RPC "+
|
||||
"credentials for bitcoind: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
case "neutrino":
|
||||
// No need to get RPC parameters.
|
||||
}
|
||||
|
||||
cfg.Bitcoin.ChainDir = filepath.Join(cfg.DataDir, bitcoinChain.String())
|
||||
@ -504,12 +569,20 @@ func noiseDial(idPriv *btcec.PrivateKey) func(net.Addr) (net.Conn, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseRPCParams(cConfig *chainConfig, net chainCode, funcName string) error {
|
||||
// If the rpcuser and rpcpass parameters aren't set, then we'll attempt
|
||||
// to automatically obtain the proper credentials for btcd and set
|
||||
// them within the configuration.
|
||||
if cConfig.RPCUser != "" || cConfig.RPCPass != "" {
|
||||
return nil
|
||||
func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode,
|
||||
funcName string) error {
|
||||
// If the configuration has already set the RPCUser and RPCPass, and
|
||||
// if we're either not using bitcoind mode or the ZMQ path is already
|
||||
// specified, we can return.
|
||||
switch conf := nodeConfig.(type) {
|
||||
case *btcdConfig:
|
||||
if conf.RPCUser != "" || conf.RPCPass != "" {
|
||||
return nil
|
||||
}
|
||||
case *bitcoindConfig:
|
||||
if conf.RPCUser != "" || conf.RPCPass != "" || conf.ZMQPath != "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in simnet mode, then the running btcd instance won't read
|
||||
@ -521,33 +594,62 @@ func parseRPCParams(cConfig *chainConfig, net chainCode, funcName string) error
|
||||
return fmt.Errorf(str, funcName)
|
||||
}
|
||||
|
||||
daemonName := "btcd"
|
||||
if net == litecoinChain {
|
||||
daemonName = "ltcd"
|
||||
var daemonName, homeDir, confFile string
|
||||
switch net {
|
||||
case bitcoinChain:
|
||||
switch cConfig.Node {
|
||||
case "btcd":
|
||||
daemonName = "btcd"
|
||||
homeDir = btcdHomeDir
|
||||
confFile = "btcd"
|
||||
case "bitcoind":
|
||||
daemonName = "bitcoind"
|
||||
homeDir = bitcoindHomeDir
|
||||
confFile = "bitcoin"
|
||||
}
|
||||
case litecoinChain:
|
||||
switch cConfig.Node {
|
||||
case "btcd":
|
||||
daemonName = "ltcd"
|
||||
homeDir = ltcdHomeDir
|
||||
confFile = "ltcd"
|
||||
case "bitcoind":
|
||||
return fmt.Errorf("bitcoind mode doesn't work with Litecoin yet")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Attempting automatic RPC configuration to " + daemonName)
|
||||
|
||||
homeDir := btcdHomeDir
|
||||
if net == litecoinChain {
|
||||
homeDir = ltcdHomeDir
|
||||
}
|
||||
confFile := filepath.Join(homeDir, fmt.Sprintf("%v.conf", daemonName))
|
||||
rpcUser, rpcPass, err := extractRPCParams(confFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to extract RPC "+
|
||||
"credentials: %v, cannot start w/o RPC connection",
|
||||
err)
|
||||
confFile = filepath.Join(homeDir, fmt.Sprintf("%v.conf", confFile))
|
||||
switch cConfig.Node {
|
||||
case "btcd":
|
||||
nConf := nodeConfig.(*btcdConfig)
|
||||
rpcUser, rpcPass, err := extractBtcdRPCParams(confFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to extract RPC credentials:"+
|
||||
" %v, cannot start w/o RPC connection",
|
||||
err)
|
||||
}
|
||||
nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
|
||||
case "bitcoind":
|
||||
nConf := nodeConfig.(*bitcoindConfig)
|
||||
rpcUser, rpcPass, zmqPath, err := extractBitcoindRPCParams(confFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to extract RPC credentials:"+
|
||||
" %v, cannot start w/o RPC connection",
|
||||
err)
|
||||
}
|
||||
nConf.RPCUser, nConf.RPCPass, nConf.ZMQPath = rpcUser, rpcPass, zmqPath
|
||||
}
|
||||
|
||||
fmt.Printf("Automatically obtained %v's RPC credentials\n", daemonName)
|
||||
cConfig.RPCUser, cConfig.RPCPass = rpcUser, rpcPass
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractRPCParams attempts to extract the RPC credentials for an existing
|
||||
// extractBtcdRPCParams attempts to extract the RPC credentials for an existing
|
||||
// btcd instance. The passed path is expected to be the location of btcd's
|
||||
// application data directory on the target system.
|
||||
func extractRPCParams(btcdConfigPath string) (string, string, error) {
|
||||
func extractBtcdRPCParams(btcdConfigPath string) (string, string, error) {
|
||||
// First, we'll open up the btcd configuration file found at the target
|
||||
// destination.
|
||||
btcdConfigFile, err := os.Open(btcdConfigPath)
|
||||
@ -557,7 +659,7 @@ func extractRPCParams(btcdConfigPath string) (string, string, error) {
|
||||
defer btcdConfigFile.Close()
|
||||
|
||||
// With the file open extract the contents of the configuration file so
|
||||
// we can attempt o locate the RPC credentials.
|
||||
// we can attempt to locate the RPC credentials.
|
||||
configContents, err := ioutil.ReadAll(btcdConfigFile)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@ -589,3 +691,100 @@ func extractRPCParams(btcdConfigPath string) (string, string, error) {
|
||||
|
||||
return string(userSubmatches[1]), string(passSubmatches[1]), nil
|
||||
}
|
||||
|
||||
// extractBitcoindParams attempts to extract the RPC credentials for an
|
||||
// existing bitcoind node instance. The passed path is expected to be the
|
||||
// location of bitcoind's bitcoin.conf on the target system. The routine looks
|
||||
// for a cookie first, optionally following the datadir configuration option in
|
||||
// the bitcoin.conf. If it doesn't find one, it looks for rpcuser/rpcpassword.
|
||||
func extractBitcoindRPCParams(bitcoindConfigPath string) (string, string, string, error) {
|
||||
|
||||
// First, we'll open up the bitcoind configuration file found at the
|
||||
// target destination.
|
||||
bitcoindConfigFile, err := os.Open(bitcoindConfigPath)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
defer bitcoindConfigFile.Close()
|
||||
|
||||
// With the file open extract the contents of the configuration file so
|
||||
// we can attempt to locate the RPC credentials.
|
||||
configContents, err := ioutil.ReadAll(bitcoindConfigFile)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
// First, we look for the ZMQ path for raw blocks. If raw transactions
|
||||
// are sent over this interface, we can also get unconfirmed txs.
|
||||
zmqPathRE, err := regexp.Compile(`(?m)^\s*zmqpubrawblock=([^\s]+)`)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
zmqPathSubmatches := zmqPathRE.FindSubmatch(configContents)
|
||||
if len(zmqPathSubmatches) < 2 {
|
||||
return "", "", "", fmt.Errorf("unable to find zmqpubrawblock in config")
|
||||
}
|
||||
|
||||
// Next, we'll try to find an auth cookie. We need to detect the chain
|
||||
// by seeing if one is specified in the configuration file.
|
||||
dataDir := path.Dir(bitcoindConfigPath)
|
||||
dataDirRE, err := regexp.Compile(`(?m)^\s*datadir=([^\s]+)`)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
dataDirSubmatches := dataDirRE.FindSubmatch(configContents)
|
||||
if dataDirSubmatches != nil {
|
||||
dataDir = string(dataDirSubmatches[1])
|
||||
}
|
||||
|
||||
chainDir := "/"
|
||||
netRE, err := regexp.Compile(`(?m)^\s*(testnet|regtest)=[\d]+`)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
netSubmatches := netRE.FindSubmatch(configContents)
|
||||
if netSubmatches != nil {
|
||||
switch string(netSubmatches[1]) {
|
||||
case "testnet":
|
||||
chainDir = "/testnet3/"
|
||||
case "regtest":
|
||||
chainDir = "/regtest/"
|
||||
}
|
||||
}
|
||||
|
||||
cookie, err := ioutil.ReadFile(dataDir + chainDir + ".cookie")
|
||||
if err == nil {
|
||||
splitCookie := strings.Split(string(cookie), ":")
|
||||
if len(splitCookie) == 2 {
|
||||
return splitCookie[0], splitCookie[1],
|
||||
string(zmqPathSubmatches[1]), nil
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find a cookie, so we attempt to locate the RPC user using
|
||||
// a regular expression. If we don't have a match for our regular
|
||||
// expression then we'll exit with an error.
|
||||
rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser=([^\s]+)`)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
|
||||
if userSubmatches == nil {
|
||||
return "", "", "", fmt.Errorf("unable to find rpcuser in config")
|
||||
}
|
||||
|
||||
// Similarly, we'll use another regular expression to find the set
|
||||
// rpcpass (if any). If we can't find the pass, then we'll exit with an
|
||||
// error.
|
||||
rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpassword=([^\s]+)`)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
|
||||
if passSubmatches == nil {
|
||||
return "", "", "", fmt.Errorf("unable to find rpcpassword in config")
|
||||
}
|
||||
|
||||
return string(userSubmatches[1]), string(passSubmatches[1]),
|
||||
string(zmqPathSubmatches[1]), nil
|
||||
}
|
||||
|
@ -42,13 +42,13 @@ type networkMsg struct {
|
||||
err chan error
|
||||
}
|
||||
|
||||
// feeUpdateRequest is a request that is sent to the server when a caller
|
||||
// wishes to update the fees for a particular set of channels. New UpdateFee
|
||||
// messages will be crafted to be sent out during the next broadcast epoch and
|
||||
// the fee updates committed to the lower layer.
|
||||
type feeUpdateRequest struct {
|
||||
// chanPolicyUpdateRequest is a request that is sent to the server when a caller
|
||||
// wishes to update the channel policy (fees e.g.) for a particular set of
|
||||
// channels. New ChannelUpdate messages will be crafted to be sent out during
|
||||
// the next broadcast epoch and the fee updates committed to the lower layer.
|
||||
type chanPolicyUpdateRequest struct {
|
||||
targetChans []wire.OutPoint
|
||||
newSchema routing.FeeSchema
|
||||
newSchema routing.ChannelPolicy
|
||||
|
||||
errResp chan error
|
||||
}
|
||||
@ -175,9 +175,9 @@ type AuthenticatedGossiper struct {
|
||||
// networkHandler.
|
||||
networkMsgs chan *networkMsg
|
||||
|
||||
// feeUpdates is a channel that requests to update the fee schedule of
|
||||
// a set of channels is sent over.
|
||||
feeUpdates chan *feeUpdateRequest
|
||||
// chanPolicyUpdates is a channel that requests to update the forwarding
|
||||
// policy of a set of channels is sent over.
|
||||
chanPolicyUpdates chan *chanPolicyUpdateRequest
|
||||
|
||||
// bestHeight is the height of the block at the tip of the main chain
|
||||
// as we know it.
|
||||
@ -202,7 +202,7 @@ func New(cfg Config, selfKey *btcec.PublicKey) (*AuthenticatedGossiper, error) {
|
||||
cfg: &cfg,
|
||||
networkMsgs: make(chan *networkMsg),
|
||||
quit: make(chan struct{}),
|
||||
feeUpdates: make(chan *feeUpdateRequest),
|
||||
chanPolicyUpdates: make(chan *chanPolicyUpdateRequest),
|
||||
prematureAnnouncements: make(map[uint32][]*networkMsg),
|
||||
prematureChannelUpdates: make(map[uint64][]*networkMsg),
|
||||
waitingProofs: storage,
|
||||
@ -224,16 +224,35 @@ func (d *AuthenticatedGossiper) SynchronizeNode(pub *btcec.PublicKey) error {
|
||||
// containing all the messages to be sent to the target peer.
|
||||
var announceMessages []lnwire.Message
|
||||
|
||||
makeNodeAnn := func(n *channeldb.LightningNode) *lnwire.NodeAnnouncement {
|
||||
alias, _ := lnwire.NewNodeAlias(n.Alias)
|
||||
return &lnwire.NodeAnnouncement{
|
||||
Signature: n.AuthSig,
|
||||
Timestamp: uint32(n.LastUpdate.Unix()),
|
||||
Addresses: n.Addresses,
|
||||
NodeID: n.PubKey,
|
||||
Features: n.Features.RawFeatureVector,
|
||||
RGBColor: n.Color,
|
||||
Alias: alias,
|
||||
}
|
||||
}
|
||||
|
||||
// As peers are expecting channel announcements before node
|
||||
// announcements, we first retrieve the initial announcement, as well as
|
||||
// the latest channel update announcement for both of the directed edges
|
||||
// that make up each channel, and queue these to be sent to the peer.
|
||||
var numEdges uint32
|
||||
var (
|
||||
numEdges uint32
|
||||
numNodes uint32
|
||||
)
|
||||
if err := d.cfg.Router.ForEachChannel(func(chanInfo *channeldb.ChannelEdgeInfo,
|
||||
e1, e2 *channeldb.ChannelEdgePolicy) error {
|
||||
|
||||
// First, using the parameters of the channel, along with the
|
||||
// channel authentication proof, we'll create re-create the
|
||||
// original authenticated channel announcement.
|
||||
// original authenticated channel announcement. If the channel
|
||||
// also has known validated nodes, then we'll send that as
|
||||
// well.
|
||||
if chanInfo.AuthProof != nil {
|
||||
chanAnn, e1Ann, e2Ann := createChanAnnouncement(
|
||||
chanInfo.AuthProof, chanInfo, e1, e2)
|
||||
@ -241,9 +260,29 @@ func (d *AuthenticatedGossiper) SynchronizeNode(pub *btcec.PublicKey) error {
|
||||
announceMessages = append(announceMessages, chanAnn)
|
||||
if e1Ann != nil {
|
||||
announceMessages = append(announceMessages, e1Ann)
|
||||
|
||||
// If this edge has a validated node
|
||||
// announcement, then we'll send that as well.
|
||||
if e1.Node.HaveNodeAnnouncement {
|
||||
nodeAnn := makeNodeAnn(e1.Node)
|
||||
announceMessages = append(
|
||||
announceMessages, nodeAnn,
|
||||
)
|
||||
numNodes++
|
||||
}
|
||||
}
|
||||
if e2Ann != nil {
|
||||
announceMessages = append(announceMessages, e2Ann)
|
||||
|
||||
// If this edge has a validated node
|
||||
// announcement, then we'll send that as well.
|
||||
if e2.Node.HaveNodeAnnouncement {
|
||||
nodeAnn := makeNodeAnn(e2.Node)
|
||||
announceMessages = append(
|
||||
announceMessages, nodeAnn,
|
||||
)
|
||||
numNodes++
|
||||
}
|
||||
}
|
||||
|
||||
numEdges++
|
||||
@ -255,38 +294,6 @@ func (d *AuthenticatedGossiper) SynchronizeNode(pub *btcec.PublicKey) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Run through all the vertexes in the graph, retrieving the data for
|
||||
// the node announcements we originally retrieved.
|
||||
var numNodes uint32
|
||||
if err := d.cfg.Router.ForEachNode(func(node *channeldb.LightningNode) error {
|
||||
// If this is a node we never received a node announcement for,
|
||||
// we skip it.
|
||||
if !node.HaveNodeAnnouncement {
|
||||
return nil
|
||||
}
|
||||
|
||||
alias, err := lnwire.NewNodeAlias(node.Alias)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ann := &lnwire.NodeAnnouncement{
|
||||
Signature: node.AuthSig,
|
||||
Timestamp: uint32(node.LastUpdate.Unix()),
|
||||
Addresses: node.Addresses,
|
||||
NodeID: node.PubKey,
|
||||
Alias: alias,
|
||||
Features: node.Features.RawFeatureVector,
|
||||
RGBColor: node.Color,
|
||||
}
|
||||
announceMessages = append(announceMessages, ann)
|
||||
|
||||
numNodes++
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Syncing channel graph state with %x, sending %v "+
|
||||
"vertexes and %v edges", pub.SerializeCompressed(),
|
||||
numNodes, numEdges)
|
||||
@ -296,24 +303,24 @@ func (d *AuthenticatedGossiper) SynchronizeNode(pub *btcec.PublicKey) error {
|
||||
return d.cfg.SendToPeer(pub, announceMessages...)
|
||||
}
|
||||
|
||||
// PropagateFeeUpdate signals the AuthenticatedGossiper to update the fee
|
||||
// schema for the specified channels. If no channels are specified, then the
|
||||
// fee update will be applied to all outgoing channels from the source node.
|
||||
// Fee updates are done in two stages: first, the AuthenticatedGossiper ensures
|
||||
// the updated has been committed by dependant sub-systems, then it signs and
|
||||
// broadcasts new updates to the network.
|
||||
func (d *AuthenticatedGossiper) PropagateFeeUpdate(newSchema routing.FeeSchema,
|
||||
chanPoints ...wire.OutPoint) error {
|
||||
// PropagateChanPolicyUpdate signals the AuthenticatedGossiper to update the
|
||||
// channel forwarding policies for the specified channels. If no channels are
|
||||
// specified, then the update will be applied to all outgoing channels from the
|
||||
// source node. Policy updates are done in two stages: first, the
|
||||
// AuthenticatedGossiper ensures the update has been committed by dependant
|
||||
// sub-systems, then it signs and broadcasts new updates to the network.
|
||||
func (d *AuthenticatedGossiper) PropagateChanPolicyUpdate(
|
||||
newSchema routing.ChannelPolicy, chanPoints ...wire.OutPoint) error {
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
feeUpdate := &feeUpdateRequest{
|
||||
policyUpdate := &chanPolicyUpdateRequest{
|
||||
targetChans: chanPoints,
|
||||
newSchema: newSchema,
|
||||
errResp: errChan,
|
||||
}
|
||||
|
||||
select {
|
||||
case d.feeUpdates <- feeUpdate:
|
||||
case d.chanPolicyUpdates <- policyUpdate:
|
||||
return <-errChan
|
||||
case <-d.quit:
|
||||
return fmt.Errorf("AuthenticatedGossiper shutting down")
|
||||
@ -823,17 +830,18 @@ func (d *AuthenticatedGossiper) networkHandler() {
|
||||
|
||||
for {
|
||||
select {
|
||||
// A new fee update has arrived. We'll commit it to the
|
||||
// A new policy update has arrived. We'll commit it to the
|
||||
// sub-systems below us, then craft, sign, and broadcast a new
|
||||
// ChannelUpdate for the set of affected clients.
|
||||
case feeUpdate := <-d.feeUpdates:
|
||||
case policyUpdate := <-d.chanPolicyUpdates:
|
||||
// First, we'll now create new fully signed updates for
|
||||
// the affected channels and also update the underlying
|
||||
// graph with the new state.
|
||||
newChanUpdates, err := d.processFeeChanUpdate(feeUpdate)
|
||||
newChanUpdates, err := d.processChanPolicyUpdate(policyUpdate)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to craft fee updates: %v", err)
|
||||
feeUpdate.errResp <- err
|
||||
log.Errorf("Unable to craft policy updates: %v",
|
||||
err)
|
||||
policyUpdate.errResp <- err
|
||||
continue
|
||||
}
|
||||
|
||||
@ -842,7 +850,7 @@ func (d *AuthenticatedGossiper) networkHandler() {
|
||||
// start of the next epoch.
|
||||
announcements.AddMsgs(newChanUpdates...)
|
||||
|
||||
feeUpdate.errResp <- nil
|
||||
policyUpdate.errResp <- nil
|
||||
|
||||
case announcement := <-d.networkMsgs:
|
||||
// Channel annoucnement signatures are the only message
|
||||
@ -1072,18 +1080,19 @@ func (d *AuthenticatedGossiper) retransmitStaleChannels() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// processFeeChanUpdate generates a new set of channel updates with the new fee
|
||||
// schema applied for each specified channel identified by its channel point.
|
||||
// In the case that no channel points are specified, then the fee update will
|
||||
// processChanPolicyUpdate generates a new set of channel updates with the new
|
||||
// channel policy applied for each specified channel identified by its channel
|
||||
// point. In the case that no channel points are specified, then the update will
|
||||
// be applied to all channels. Finally, the backing ChannelGraphSource is
|
||||
// updated with the latest information reflecting the applied fee updates.
|
||||
// updated with the latest information reflecting the applied updates.
|
||||
//
|
||||
// TODO(roasbeef): generalize into generic for any channel update
|
||||
func (d *AuthenticatedGossiper) processFeeChanUpdate(feeUpdate *feeUpdateRequest) ([]networkMsg, error) {
|
||||
func (d *AuthenticatedGossiper) processChanPolicyUpdate(
|
||||
policyUpdate *chanPolicyUpdateRequest) ([]networkMsg, error) {
|
||||
// First, we'll construct a set of all the channels that need to be
|
||||
// updated.
|
||||
chansToUpdate := make(map[wire.OutPoint]struct{})
|
||||
for _, chanPoint := range feeUpdate.targetChans {
|
||||
for _, chanPoint := range policyUpdate.targetChans {
|
||||
chansToUpdate[chanPoint] = struct{}{}
|
||||
}
|
||||
|
||||
@ -1104,11 +1113,14 @@ func (d *AuthenticatedGossiper) processFeeChanUpdate(feeUpdate *feeUpdateRequest
|
||||
}
|
||||
|
||||
// Apply the new fee schema to the edge.
|
||||
edge.FeeBaseMSat = feeUpdate.newSchema.BaseFee
|
||||
edge.FeeBaseMSat = policyUpdate.newSchema.BaseFee
|
||||
edge.FeeProportionalMillionths = lnwire.MilliSatoshi(
|
||||
feeUpdate.newSchema.FeeRate,
|
||||
policyUpdate.newSchema.FeeRate,
|
||||
)
|
||||
|
||||
// Apply the new TimeLockDelta.
|
||||
edge.TimeLockDelta = uint16(policyUpdate.newSchema.TimeLockDelta)
|
||||
|
||||
// Re-sign and update the backing ChannelGraphSource, and
|
||||
// retrieve our ChannelUpdate to broadcast.
|
||||
_, chanUpdate, err := d.updateChannel(info, edge)
|
||||
@ -1981,12 +1993,18 @@ func (d *AuthenticatedGossiper) sendAnnSigReliably(
|
||||
func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo,
|
||||
edge *channeldb.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement, *lnwire.ChannelUpdate, error) {
|
||||
|
||||
edge.LastUpdate = time.Now()
|
||||
// Make sure timestamp is always increased, such that our update
|
||||
// gets propagated.
|
||||
timestamp := time.Now().Unix()
|
||||
if timestamp <= edge.LastUpdate.Unix() {
|
||||
timestamp = edge.LastUpdate.Unix() + 1
|
||||
}
|
||||
edge.LastUpdate = time.Unix(timestamp, 0)
|
||||
chanUpdate := &lnwire.ChannelUpdate{
|
||||
Signature: edge.Signature,
|
||||
ChainHash: info.ChainHash,
|
||||
ShortChannelID: lnwire.NewShortChanIDFromInt(edge.ChannelID),
|
||||
Timestamp: uint32(edge.LastUpdate.Unix()),
|
||||
Timestamp: uint32(timestamp),
|
||||
Flags: edge.Flags,
|
||||
TimeLockDelta: edge.TimeLockDelta,
|
||||
HtlcMinimumMsat: edge.MinHTLC,
|
||||
|
@ -44,15 +44,20 @@ RPCPASS=$(set_default "$RPCPASS" "devpass")
|
||||
DEBUG=$(set_default "$DEBUG" "debug")
|
||||
NETWORK=$(set_default "$NETWORK" "simnet")
|
||||
CHAIN=$(set_default "$CHAIN" "bitcoin")
|
||||
BACKEND="btcd"
|
||||
if [[ "$CHAIN" == "litecoin" ]]; then
|
||||
BACKEND="ltcd"
|
||||
fi
|
||||
|
||||
lnd \
|
||||
--noencryptwallet \
|
||||
--logdir="/data" \
|
||||
"--$CHAIN.rpccert"="/rpc/rpc.cert" \
|
||||
"--$CHAIN.active" \
|
||||
"--$CHAIN.$NETWORK" \
|
||||
"--$CHAIN.rpchost"="blockchain" \
|
||||
"--$CHAIN.rpcuser"="$RPCUSER" \
|
||||
"--$CHAIN.rpcpass"="$RPCPASS" \
|
||||
"--$CHAIN.node"="btcd" \
|
||||
"--$BACKEND.rpccert"="/rpc/rpc.cert" \
|
||||
"--$BACKEND.rpchost"="blockchain" \
|
||||
"--$BACKEND.rpcuser"="$RPCUSER" \
|
||||
"--$BACKEND.rpcpass"="$RPCPASS" \
|
||||
--debuglevel="$DEBUG" \
|
||||
"$@"
|
||||
|
@ -164,7 +164,7 @@ To run lnd in neutrino mode, run `lnd` with the following arguments, (swapping
|
||||
in `--bitcoin.simnet` for `simnet` mode if needed), and also your own `btcd`
|
||||
node if available:
|
||||
```
|
||||
lnd --bitcoin.active --bitcoin.testnet --debuglevel=debug --neutrino.active --neutrino.connect=faucet.lightning.community
|
||||
lnd --bitcoin.active --bitcoin.testnet --debuglevel=debug --bitcoin.node=neutrino --neutrino.connect=faucet.lightning.community
|
||||
```
|
||||
|
||||
#### Running lnd using the btcd backend
|
||||
@ -174,7 +174,22 @@ Otherwise, replace `--bitcoin.testnet` with `--bitcoin.simnet`. If you are
|
||||
installing `lnd` in preparation for the
|
||||
[tutorial](http://dev.lightning.community/tutorial), you may skip this step.
|
||||
```
|
||||
lnd --bitcoin.active --bitcoin.testnet --debuglevel=debug --bitcoin.rpcuser=kek --bitcoin.rpcpass=kek --externalip=X.X.X.X
|
||||
lnd --bitcoin.active --bitcoin.testnet --debuglevel=debug --btcd.rpcuser=kek --btcd.rpcpass=kek --externalip=X.X.X.X
|
||||
```
|
||||
|
||||
#### Running lnd using the bitcoind backend
|
||||
|
||||
In order to run `lnd` with a `bitcoind` back-end, the `bitcoind` instance must
|
||||
be configured with `--txindex` just like `btcd` above. In addition, you'll need
|
||||
to configure the `bitcoind` instance with `--zmqpubrawblock` and `--zmqpubrawtx`
|
||||
(the latter is optional but allows you to see unconfirmed transactions in your
|
||||
wallet). They must be combined in the same ZMQ socket address. Then, run this
|
||||
command after `bitcoind` has finished syncing on testnet. Otherwise, replace
|
||||
`--bitcoin.testnet` with `--bitcoin.regtest`. Please note that the `rpcuser`
|
||||
and `rpcpass` parameters can typically be determined by `lnd` for a `bitcoind`
|
||||
instance running under the same user, including when using cookie auth.
|
||||
```
|
||||
lnd --bitcoin.active --bitcoin.testnet --debuglevel=debug --bitcoin.node=bitcoind --bitcoind.rpcuser=kek --bitcoind.rpcpass=kek --externalip=X.X.X.X
|
||||
```
|
||||
|
||||
#### Network Reachability
|
||||
@ -196,7 +211,7 @@ at the command line, you can create an `lnd.conf`.
|
||||
**On Linux, located at:**
|
||||
`~/.lnd/lnd.conf`
|
||||
|
||||
Here's a sample `lnd.conf` to get you started:
|
||||
Here's a sample `lnd.conf` for `btcd` to get you started:
|
||||
```
|
||||
[Application Options]
|
||||
debuglevel=trace
|
||||
@ -210,7 +225,9 @@ bitcoin.active=1
|
||||
Notice the `[Bitcoin]` section. This section houses the parameters for the
|
||||
Bitcoin chain. `lnd` also supports Litecoin testnet4 (but not both BTC and LTC
|
||||
at the same time), so when working with Litecoin be sure to set to parameters
|
||||
for Litecoin accordingly.
|
||||
for Litecoin accordingly. For node configuration, the sections are called
|
||||
`[Btcd]`, `[Bitcoind]`, `[Neutrino]`, and `[Ltcd]` depending on which chain
|
||||
and node type you're using.
|
||||
|
||||
# Accurate as of:
|
||||
- _roasbeef/btcd commit:_ `f8c02aff4e7a807ba0c1349e2db03695d8e790e8`
|
||||
|
184
docs/grpc/java.md
Normal file
184
docs/grpc/java.md
Normal file
@ -0,0 +1,184 @@
|
||||
|
||||
# How to write a Java gRPC client for the Lightning Network Daemon
|
||||
|
||||
This section enumerates what you need to do to write a client that communicates
|
||||
with lnd in Java. We'll be using Maven as our build tool.
|
||||
|
||||
### Prerequisites
|
||||
- Maven
|
||||
- running lnd
|
||||
- running btcd
|
||||
|
||||
### Setup and Installation
|
||||
#### Project Structure
|
||||
```
|
||||
.
|
||||
├── pom.xml
|
||||
└── src
|
||||
├── main
|
||||
├── java
|
||||
│ └── Main.java
|
||||
├── proto
|
||||
├── google
|
||||
│ └── api
|
||||
│ ├── annotations.proto
|
||||
│ └── http.proto
|
||||
└── lnrpc
|
||||
└── rpc.proto
|
||||
|
||||
```
|
||||
Note the ***proto*** folder, where all the proto files are kept.
|
||||
|
||||
- [rpc.proto](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/rpc.proto)
|
||||
- [annotations.proto](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/third_party/googleapis/google/api/annotations.proto)
|
||||
- [http.proto](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/third_party/googleapis/google/api/http.proto)
|
||||
|
||||
#### pom.xml
|
||||
```
|
||||
<properties>
|
||||
<grpc.version>1.8.0</grpc.version>
|
||||
</properties>
|
||||
```
|
||||
The following dependencies are required.
|
||||
```
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty</artifactId>
|
||||
<version>${grpc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
<version>${grpc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
<version>${grpc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-tcnative-boringssl-static</artifactId>
|
||||
<version>2.0.7.Final</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
In the build section, we'll need to configure the following things :
|
||||
```
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
<version>1.5.0.Final</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.xolstice.maven.plugins</groupId>
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<configuration>
|
||||
<protocArtifact>com.google.protobuf:protoc:3.4.0:exe:${os.detected.classifier}</protocArtifact>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
<goal>compile-custom</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
```
|
||||
#### Main.java
|
||||
```java
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.netty.GrpcSslContexts;
|
||||
import io.grpc.netty.NettyChannelBuilder;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import lnrpc.LightningGrpc;
|
||||
import lnrpc.LightningGrpc.LightningBlockingStub;
|
||||
import lnrpc.Rpc.*;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.io.File;
|
||||
|
||||
public class Main {
|
||||
|
||||
private static final String CERT_PATH = "/Users/user/Library/Application Support/Lnd/tls.cert";
|
||||
private static final String HOST = "localhost";
|
||||
private static final int PORT = 10009;
|
||||
|
||||
public static void main(String... args) throws SSLException {
|
||||
SslContext sslContext = GrpcSslContexts.forClient().trustManager(new File(CERT_PATH)).build();
|
||||
NettyChannelBuilder channelBuilder = NettyChannelBuilder.forAddress(HOST, PORT);
|
||||
ManagedChannel channel = channelBuilder.sslContext(sslContext).build();
|
||||
LightningBlockingStub stub = LightningGrpc.newBlockingStub(channel);
|
||||
|
||||
GetInfoResponse response = stub.getInfo(GetInfoRequest.getDefaultInstance());
|
||||
System.out.println(response.getIdentityPubkey());
|
||||
}
|
||||
}
|
||||
```
|
||||
#### Running the example
|
||||
Execute the following command in the directory where the **pom.xml** file is located.
|
||||
```
|
||||
mvn compile exec:java -Dexec.mainClass="Main" -Dexec.cleanupDaemonThreads=false
|
||||
```
|
||||
##### Sample ouput
|
||||
```
|
||||
[INFO] Scanning for projects...
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Detecting the operating system and CPU architecture
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] os.detected.name: osx
|
||||
[INFO] os.detected.arch: x86_64
|
||||
[INFO] os.detected.version: 10.13
|
||||
[INFO] os.detected.version.major: 10
|
||||
[INFO] os.detected.version.minor: 13
|
||||
[INFO] os.detected.classifier: osx-x86_64
|
||||
[INFO]
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Building lightning-client 0.0.1-SNAPSHOT
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO]
|
||||
[INFO] --- protobuf-maven-plugin:0.5.0:compile (default) @ lightning-client ---
|
||||
[INFO] Compiling 3 proto file(s) to /Users/user/Documents/Projects/lightningclient/target/generated-sources/protobuf/java
|
||||
[INFO]
|
||||
[INFO] --- protobuf-maven-plugin:0.5.0:compile-custom (default) @ lightning-client ---
|
||||
[INFO] Compiling 3 proto file(s) to /Users/user/Documents/Projects/lightningclient/target/generated-sources/protobuf/grpc-java
|
||||
[INFO]
|
||||
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ lightning-client ---
|
||||
[INFO] Using 'UTF-8' encoding to copy filtered resources.
|
||||
[INFO] Copying 0 resource
|
||||
[INFO] Copying 3 resources
|
||||
[INFO] Copying 3 resources
|
||||
[INFO]
|
||||
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ lightning-client ---
|
||||
[INFO] Changes detected - recompiling the module!
|
||||
[INFO] Compiling 12 source files to /Users/user/Documents/Projects/lightningclient/target/classes
|
||||
[INFO]
|
||||
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ lightning-client ---
|
||||
032562215c38dede6f1f2f262ff4c8db58a38ecf889e8e907eee8e4c320e0b5e81
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 7.408 s
|
||||
[INFO] Finished at: 2018-01-13T19:05:49+01:00
|
||||
[INFO] Final Memory: 30M/589M
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
### Java proto options
|
||||
|
||||
There are 2 options available that can be used in the *rpc.proto* file :
|
||||
|
||||
* option java_multiple_files = true;
|
||||
* option java_package = "network.lightning.rpc";
|
||||
>The package you want to use for your generated Java classes. If no explicit java_package option is given in the .proto file, then by default the proto package (specified using the "package" keyword in the .proto file) will be used. However, proto packages generally do not make good Java packages since proto packages are not expected to start with reverse domain names. If not generating Java code, this option has no effect.
|
@ -32,8 +32,6 @@ const (
|
||||
// TODO(roasbeef): tune
|
||||
msgBufferSize = 50
|
||||
|
||||
defaultCsvDelay = 4
|
||||
|
||||
// maxFundingAmount is a soft-limit of the maximum channel size
|
||||
// accepted within the Lightning Protocol Currently. This limit is
|
||||
// currently defined in BOLT-0002, and serves as an initial
|
||||
@ -43,6 +41,13 @@ const (
|
||||
// TODO(roasbeef): add command line param to modify
|
||||
maxFundingAmount = btcutil.Amount(1 << 24)
|
||||
|
||||
// minRemoteDelay and maxRemoteDelay is the extremes of the CSV delay
|
||||
// we will require the remote to use for its commitment transaction.
|
||||
// The actual delay we will require will be somewhere between these
|
||||
// values, depending on channel size.
|
||||
minRemoteDelay = 144
|
||||
maxRemoteDelay = 2016
|
||||
|
||||
// maxWaitNumBlocksFundingConf is the maximum number of blocks to wait
|
||||
// for the funding transaction to be confirmed before forgetting about
|
||||
// the channel. 288 blocks is ~48 hrs
|
||||
@ -2101,6 +2106,8 @@ func (f *fundingManager) newChanAnnouncement(localPubKey, remotePubKey *btcec.Pu
|
||||
chanFlags = 1
|
||||
}
|
||||
|
||||
// We announce the channel with the default values. Some of
|
||||
// these values can later be changed by crafting a new ChannelUpdate.
|
||||
chanUpdateAnn := &lnwire.ChannelUpdate{
|
||||
ShortChannelID: shortChanID,
|
||||
ChainHash: chainHash,
|
||||
@ -2252,6 +2259,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
||||
remoteAmt = msg.remoteFundingAmt
|
||||
capacity = localAmt + remoteAmt
|
||||
ourDustLimit = lnwallet.DefaultDustLimit()
|
||||
minHtlc = msg.minHtlc
|
||||
)
|
||||
|
||||
fndgLog.Infof("Initiating fundingRequest(localAmt=%v, remoteAmt=%v, "+
|
||||
@ -2321,9 +2329,14 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
||||
// delay we require given the total amount of funds within the channel.
|
||||
remoteCsvDelay := f.cfg.RequiredRemoteDelay(capacity)
|
||||
|
||||
// If no minimum HTLC value was specified, use the default one.
|
||||
if minHtlc == 0 {
|
||||
minHtlc = f.cfg.DefaultRoutingPolicy.MinHTLC
|
||||
}
|
||||
|
||||
// Once the reservation has been created, and indexed, queue a funding
|
||||
// request to the remote peer, kicking off the funding workflow.
|
||||
reservation.RegisterMinHTLC(f.cfg.DefaultRoutingPolicy.MinHTLC)
|
||||
reservation.RegisterMinHTLC(minHtlc)
|
||||
ourContribution := reservation.OurContribution()
|
||||
|
||||
// Finally, we'll use the current value of the channels and our default
|
||||
|
@ -264,8 +264,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||
},
|
||||
NumRequiredConfs: func(chanAmt btcutil.Amount,
|
||||
pushAmt lnwire.MilliSatoshi) uint16 {
|
||||
|
||||
return uint16(cfg.DefaultNumChanConfs)
|
||||
return 3
|
||||
},
|
||||
RequiredRemoteDelay: func(amt btcutil.Amount) uint16 {
|
||||
return 4
|
||||
|
12
glide.lock
generated
12
glide.lock
generated
@ -1,5 +1,5 @@
|
||||
hash: 8438e391bed32638a8e14402992af50b7656b380f69741cf8422584c2e1f2b31
|
||||
updated: 2017-12-13T15:13:22.311098343-08:00
|
||||
hash: 59d73259b4445fdb5982f2ec2538f53805e6e789c26b98b0fdfef43b7d0676a4
|
||||
updated: 2018-01-12T11:04:32.444091731-07:00
|
||||
imports:
|
||||
- name: github.com/aead/chacha20
|
||||
version: d31a916ded42d1640b9d89a26f8abd53cc96790c
|
||||
@ -21,8 +21,8 @@ imports:
|
||||
version: 84c8d2346e9fc8c7b947e243b9c24e6df9fd206a
|
||||
- name: github.com/btcsuite/fastsha256
|
||||
version: 637e656429416087660c84436a2a035d69d54e2e
|
||||
- name: github.com/btcsuite/go-flags
|
||||
version: 6c288d648c1cc1befcb90cb5511dcacf64ae8e61
|
||||
- name: github.com/jessevdk/go-flags
|
||||
version: f88afde2fa19a30cf50ba4b05b3d13bc6bae3079
|
||||
- name: github.com/btcsuite/go-socks
|
||||
version: 4720035b7bfd2a9bb130b1c184f8bbe41b6f0d0f
|
||||
subpackages:
|
||||
@ -86,6 +86,8 @@ imports:
|
||||
version: 946bd9fbed05568b0f3cd188353d8aa28f38b688
|
||||
subpackages:
|
||||
- internal/socket
|
||||
- name: github.com/lightninglabs/gozmq
|
||||
version: b0bbb30a99d00b25c3304994d20aac68607b75c0
|
||||
- name: github.com/roasbeef/btcd
|
||||
version: 9978b939c33973be19b932fa7b936079bb7ba38d
|
||||
subpackages:
|
||||
@ -116,7 +118,7 @@ imports:
|
||||
- hdkeychain
|
||||
- txsort
|
||||
- name: github.com/roasbeef/btcwallet
|
||||
version: 3037a033935cca719ba3c472870e7d080443a82f
|
||||
version: 6c1491e092f39d6ed4e9103b9dc8322d04ca98bb
|
||||
subpackages:
|
||||
- chain
|
||||
- internal/helpers
|
||||
|
@ -4,7 +4,7 @@ import:
|
||||
version: ^1.2.1
|
||||
- package: github.com/btcsuite/btclog
|
||||
version: 84c8d2346e9fc8c7b947e243b9c24e6df9fd206a
|
||||
- package: github.com/btcsuite/go-flags
|
||||
- package: github.com/jessevdk/go-flags
|
||||
- package: github.com/jrick/logrotate
|
||||
version: a93b200c26cbae3bb09dd0dc2c7c7fe1468a034a
|
||||
- package: github.com/davecgh/go-spew
|
||||
@ -24,6 +24,7 @@ import:
|
||||
- txscript
|
||||
- wire
|
||||
- connmgr
|
||||
- package: github.com/lightninglabs/gozmq
|
||||
- package: github.com/roasbeef/btcrpcclient
|
||||
version: d0f4db8b4dad0ca3d569b804f21247c3dd96acbb
|
||||
- package: github.com/roasbeef/btcutil
|
||||
@ -34,7 +35,7 @@ import:
|
||||
- hdkeychain
|
||||
- txsort
|
||||
- package: github.com/roasbeef/btcwallet
|
||||
version: 3037a033935cca719ba3c472870e7d080443a82f
|
||||
version: 6c1491e092f39d6ed4e9103b9dc8322d04ca98bb
|
||||
subpackages:
|
||||
- chain
|
||||
- waddrmgr
|
||||
|
@ -39,7 +39,9 @@ const (
|
||||
// the error possibly carrying along a ChannelUpdate message that includes the
|
||||
// latest policy.
|
||||
type ForwardingPolicy struct {
|
||||
// MinHTLC is the smallest HTLC that is to be forwarded.
|
||||
// MinHTLC is the smallest HTLC that is to be forwarded. This is
|
||||
// set when a channel is first opened, and will be static for the
|
||||
// lifetime of the channel.
|
||||
MinHTLC lnwire.MilliSatoshi
|
||||
|
||||
// BaseFee is the base fee, expressed in milli-satoshi that must be
|
||||
@ -604,9 +606,6 @@ out:
|
||||
// with a "null" field in the new policy, we'll
|
||||
// only update to the set sub policy if the new
|
||||
// value isn't uninitialized.
|
||||
if req.policy.MinHTLC != 0 {
|
||||
l.cfg.FwrdingPolicy.MinHTLC = req.policy.MinHTLC
|
||||
}
|
||||
if req.policy.BaseFee != 0 {
|
||||
l.cfg.FwrdingPolicy.BaseFee = req.policy.BaseFee
|
||||
}
|
||||
|
73
lnd.go
73
lnd.go
@ -31,8 +31,8 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
flags "github.com/btcsuite/go-flags"
|
||||
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
"github.com/lightningnetwork/lnd/autopilot"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
@ -298,16 +298,71 @@ func lndMain() error {
|
||||
return nil, fmt.Errorf("unable to find channel")
|
||||
},
|
||||
DefaultRoutingPolicy: activeChainControl.routingPolicy,
|
||||
NumRequiredConfs: func(chanAmt btcutil.Amount, pushAmt lnwire.MilliSatoshi) uint16 {
|
||||
// TODO(roasbeef): add configurable mapping
|
||||
// * simple switch initially
|
||||
// * assign coefficient, etc
|
||||
return uint16(cfg.DefaultNumChanConfs)
|
||||
NumRequiredConfs: func(chanAmt btcutil.Amount,
|
||||
pushAmt lnwire.MilliSatoshi) uint16 {
|
||||
// For large channels we increase the number
|
||||
// of confirmations we require for the
|
||||
// channel to be considered open. As it is
|
||||
// always the responder that gets to choose
|
||||
// value, the pushAmt is value being pushed
|
||||
// to us. This means we have more to lose
|
||||
// in the case this gets re-orged out, and
|
||||
// we will require more confirmations before
|
||||
// we consider it open.
|
||||
// TODO(halseth): Use Litecoin params in case
|
||||
// of LTC channels.
|
||||
|
||||
// In case the user has explicitly specified
|
||||
// a default value for the number of
|
||||
// confirmations, we use it.
|
||||
defaultConf := uint16(cfg.Bitcoin.DefaultNumChanConfs)
|
||||
if defaultConf != 0 {
|
||||
return defaultConf
|
||||
}
|
||||
|
||||
// If not we return a value scaled linearly
|
||||
// between 3 and 6, depending on channel size.
|
||||
// TODO(halseth): Use 1 as minimum?
|
||||
minConf := uint64(3)
|
||||
maxConf := uint64(6)
|
||||
maxChannelSize := uint64(
|
||||
lnwire.NewMSatFromSatoshis(maxFundingAmount))
|
||||
stake := lnwire.NewMSatFromSatoshis(chanAmt) + pushAmt
|
||||
conf := maxConf * uint64(stake) / maxChannelSize
|
||||
if conf < minConf {
|
||||
conf = minConf
|
||||
}
|
||||
if conf > maxConf {
|
||||
conf = maxConf
|
||||
}
|
||||
return uint16(conf)
|
||||
},
|
||||
RequiredRemoteDelay: func(chanAmt btcutil.Amount) uint16 {
|
||||
// TODO(roasbeef): add additional hooks for
|
||||
// configuration
|
||||
return 4
|
||||
// We scale the remote CSV delay (the time the
|
||||
// remote have to claim funds in case of a unilateral
|
||||
// close) linearly from minRemoteDelay blocks
|
||||
// for small channels, to maxRemoteDelay blocks
|
||||
// for channels of size maxFundingAmount.
|
||||
// TODO(halseth): Litecoin parameter for LTC.
|
||||
|
||||
// In case the user has explicitly specified
|
||||
// a default value for the remote delay, we
|
||||
// use it.
|
||||
defaultDelay := uint16(cfg.Bitcoin.DefaultRemoteDelay)
|
||||
if defaultDelay > 0 {
|
||||
return defaultDelay
|
||||
}
|
||||
|
||||
// If not we scale according to channel size.
|
||||
delay := uint16(maxRemoteDelay *
|
||||
chanAmt / maxFundingAmount)
|
||||
if delay < minRemoteDelay {
|
||||
delay = minRemoteDelay
|
||||
}
|
||||
if delay > maxRemoteDelay {
|
||||
delay = maxRemoteDelay
|
||||
}
|
||||
return delay
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
341
lnd_test.go
341
lnd_test.go
@ -467,6 +467,290 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
|
||||
}
|
||||
|
||||
// testUpdateChannelPolicy tests that policy updates made to a channel
|
||||
// gets propagated to other nodes in the network.
|
||||
func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
timeout := time.Duration(time.Second * 5)
|
||||
ctxb := context.Background()
|
||||
|
||||
// Launch notification clients for all nodes, such that we can
|
||||
// get notified when they discover new channels and updates
|
||||
// in the graph.
|
||||
aliceUpdates, aQuit := subscribeGraphNotifications(t, ctxb, net.Alice)
|
||||
defer close(aQuit)
|
||||
bobUpdates, bQuit := subscribeGraphNotifications(t, ctxb, net.Bob)
|
||||
defer close(bQuit)
|
||||
|
||||
chanAmt := maxFundingAmount
|
||||
pushAmt := btcutil.Amount(100000)
|
||||
|
||||
// Create a channel Alice->Bob.
|
||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||
chanPoint := openChannelAndAssert(ctxt, t, net, net.Alice, net.Bob,
|
||||
chanAmt, pushAmt)
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, time.Second*15)
|
||||
err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("alice didn't report channel: %v", err)
|
||||
}
|
||||
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("bob didn't report channel: %v", err)
|
||||
}
|
||||
|
||||
// Create Carol and a new channel Bob->Carol.
|
||||
carol, err := net.NewNode(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create new nodes: %v", err)
|
||||
}
|
||||
carolUpdates, cQuit := subscribeGraphNotifications(t, ctxb, carol)
|
||||
defer close(cQuit)
|
||||
|
||||
if err := net.ConnectNodes(ctxb, carol, net.Bob); err != nil {
|
||||
t.Fatalf("unable to connect dave to alice: %v", err)
|
||||
}
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
chanPoint2 := openChannelAndAssert(ctxt, t, net, net.Bob, carol,
|
||||
chanAmt, pushAmt)
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, time.Second*15)
|
||||
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint2)
|
||||
if err != nil {
|
||||
t.Fatalf("bob didn't report channel: %v", err)
|
||||
}
|
||||
err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint2)
|
||||
if err != nil {
|
||||
t.Fatalf("carol didn't report channel: %v", err)
|
||||
}
|
||||
|
||||
// Update the fees for the channel Alice->Bob, and make sure
|
||||
// all nodes learn about it.
|
||||
const feeBase = 1000000
|
||||
baseFee := int64(1500)
|
||||
feeRate := int64(12)
|
||||
timeLockDelta := uint32(66)
|
||||
|
||||
req := &lnrpc.PolicyUpdateRequest{
|
||||
BaseFeeMsat: baseFee,
|
||||
FeeRate: float64(feeRate),
|
||||
TimeLockDelta: timeLockDelta,
|
||||
}
|
||||
req.Scope = &lnrpc.PolicyUpdateRequest_ChanPoint{
|
||||
ChanPoint: chanPoint,
|
||||
}
|
||||
|
||||
_, err = net.Alice.UpdateChannelPolicy(ctxb, req)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get alice's balance: %v", err)
|
||||
}
|
||||
|
||||
// txStr returns the string representation of the channel's
|
||||
// funding tx.
|
||||
txStr := func(chanPoint *lnrpc.ChannelPoint) string {
|
||||
fundingTxID, err := chainhash.NewHash(chanPoint.FundingTxid)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
cp := wire.OutPoint{
|
||||
Hash: *fundingTxID,
|
||||
Index: chanPoint.OutputIndex,
|
||||
}
|
||||
return cp.String()
|
||||
}
|
||||
|
||||
// A closure that is used to wait for a channel updates that matches
|
||||
// the channel policy update done by Alice.
|
||||
waitForChannelUpdate := func(graphUpdates chan *lnrpc.GraphTopologyUpdate,
|
||||
chanPoints ...*lnrpc.ChannelPoint) {
|
||||
// Create a map containing all the channel points we are
|
||||
// waiting for updates for.
|
||||
cps := make(map[string]bool)
|
||||
for _, chanPoint := range chanPoints {
|
||||
cps[txStr(chanPoint)] = true
|
||||
}
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case graphUpdate := <-graphUpdates:
|
||||
if len(graphUpdate.ChannelUpdates) == 0 {
|
||||
continue
|
||||
}
|
||||
chanUpdate := graphUpdate.ChannelUpdates[0]
|
||||
fundingTxStr := txStr(chanUpdate.ChanPoint)
|
||||
if _, ok := cps[fundingTxStr]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if chanUpdate.AdvertisingNode != net.Alice.PubKeyStr {
|
||||
continue
|
||||
}
|
||||
|
||||
policy := chanUpdate.RoutingPolicy
|
||||
if policy.FeeBaseMsat != baseFee {
|
||||
continue
|
||||
}
|
||||
if policy.FeeRateMilliMsat != feeRate*feeBase {
|
||||
continue
|
||||
}
|
||||
if policy.TimeLockDelta != timeLockDelta {
|
||||
continue
|
||||
}
|
||||
|
||||
// We got a policy update that matched the
|
||||
// values and channel point of what we
|
||||
// expected, delete it from the map.
|
||||
delete(cps, fundingTxStr)
|
||||
|
||||
// If we have no more channel points we are
|
||||
// waiting for, break out of the loop.
|
||||
if len(cps) == 0 {
|
||||
break Loop
|
||||
}
|
||||
case <-time.After(20 * time.Second):
|
||||
t.Fatalf("did not receive channel update")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all nodes to have seen the policy update done by Alice.
|
||||
waitForChannelUpdate(aliceUpdates, chanPoint)
|
||||
waitForChannelUpdate(bobUpdates, chanPoint)
|
||||
waitForChannelUpdate(carolUpdates, chanPoint)
|
||||
|
||||
// assertChannelPolicy asserts that the passed node's known channel
|
||||
// policy for the passed chanPoint is consistent with Alice's current
|
||||
// expected policy values.
|
||||
assertChannelPolicy := func(node *lntest.HarnessNode,
|
||||
chanPoint *lnrpc.ChannelPoint) {
|
||||
// Get a DescribeGraph from the node.
|
||||
descReq := &lnrpc.ChannelGraphRequest{}
|
||||
chanGraph, err := node.DescribeGraph(ctxb, descReq)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for alice's routing table: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
edgeFound := false
|
||||
for _, e := range chanGraph.Edges {
|
||||
if e.ChanPoint == txStr(chanPoint) {
|
||||
edgeFound = true
|
||||
if e.Node1Pub == net.Alice.PubKeyStr {
|
||||
if e.Node1Policy.FeeBaseMsat != baseFee {
|
||||
t.Fatalf("expected base fee "+
|
||||
"%v, got %v", baseFee,
|
||||
e.Node1Policy.FeeBaseMsat)
|
||||
}
|
||||
if e.Node1Policy.FeeRateMilliMsat != feeRate*feeBase {
|
||||
t.Fatalf("expected fee rate "+
|
||||
"%v, got %v", feeRate*feeBase,
|
||||
e.Node1Policy.FeeRateMilliMsat)
|
||||
}
|
||||
if e.Node1Policy.TimeLockDelta != timeLockDelta {
|
||||
t.Fatalf("expected time lock "+
|
||||
"delta %v, got %v",
|
||||
timeLockDelta,
|
||||
e.Node1Policy.TimeLockDelta)
|
||||
}
|
||||
} else {
|
||||
if e.Node2Policy.FeeBaseMsat != baseFee {
|
||||
t.Fatalf("expected base fee "+
|
||||
"%v, got %v", baseFee,
|
||||
e.Node2Policy.FeeBaseMsat)
|
||||
}
|
||||
if e.Node2Policy.FeeRateMilliMsat != feeRate*feeBase {
|
||||
t.Fatalf("expected fee rate "+
|
||||
"%v, got %v", feeRate*feeBase,
|
||||
e.Node2Policy.FeeRateMilliMsat)
|
||||
}
|
||||
if e.Node2Policy.TimeLockDelta != timeLockDelta {
|
||||
t.Fatalf("expected time lock "+
|
||||
"delta %v, got %v",
|
||||
timeLockDelta,
|
||||
e.Node2Policy.TimeLockDelta)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !edgeFound {
|
||||
t.Fatalf("did not find edge")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Check that all nodes now know about Alice's updated policy.
|
||||
assertChannelPolicy(net.Alice, chanPoint)
|
||||
assertChannelPolicy(net.Bob, chanPoint)
|
||||
assertChannelPolicy(carol, chanPoint)
|
||||
|
||||
// Open channel to Carol.
|
||||
if err := net.ConnectNodes(ctxb, net.Alice, carol); err != nil {
|
||||
t.Fatalf("unable to connect dave to alice: %v", err)
|
||||
}
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
chanPoint3 := openChannelAndAssert(ctxt, t, net, net.Alice, carol,
|
||||
chanAmt, pushAmt)
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, time.Second*15)
|
||||
err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint3)
|
||||
if err != nil {
|
||||
t.Fatalf("alice didn't report channel: %v", err)
|
||||
}
|
||||
err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint3)
|
||||
if err != nil {
|
||||
t.Fatalf("bob didn't report channel: %v", err)
|
||||
}
|
||||
|
||||
// Make a global update, and check that both channels'
|
||||
// new policies get propagated.
|
||||
baseFee = int64(800)
|
||||
feeRate = int64(123)
|
||||
timeLockDelta = uint32(22)
|
||||
|
||||
req = &lnrpc.PolicyUpdateRequest{
|
||||
BaseFeeMsat: baseFee,
|
||||
FeeRate: float64(feeRate),
|
||||
TimeLockDelta: timeLockDelta,
|
||||
}
|
||||
req.Scope = &lnrpc.PolicyUpdateRequest_Global{}
|
||||
|
||||
_, err = net.Alice.UpdateChannelPolicy(ctxb, req)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get alice's balance: %v", err)
|
||||
}
|
||||
|
||||
// Wait for all nodes to have seen the policy updates
|
||||
// for both of Alice's channels.
|
||||
waitForChannelUpdate(aliceUpdates, chanPoint, chanPoint3)
|
||||
waitForChannelUpdate(bobUpdates, chanPoint, chanPoint3)
|
||||
waitForChannelUpdate(carolUpdates, chanPoint, chanPoint3)
|
||||
|
||||
// And finally check that all nodes remembers the policy
|
||||
// update they received.
|
||||
assertChannelPolicy(net.Alice, chanPoint)
|
||||
assertChannelPolicy(net.Bob, chanPoint)
|
||||
assertChannelPolicy(carol, chanPoint)
|
||||
|
||||
assertChannelPolicy(net.Alice, chanPoint3)
|
||||
assertChannelPolicy(net.Bob, chanPoint3)
|
||||
assertChannelPolicy(carol, chanPoint3)
|
||||
|
||||
// Close the channels.
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
closeChannelAndAssert(ctxt, t, net, net.Bob, chanPoint2, false)
|
||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint3, false)
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
|
||||
// Clean up carol's node.
|
||||
if err := net.ShutdownNode(carol); err != nil {
|
||||
t.Fatalf("unable to shutdown carol: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// testOpenChannelAfterReorg tests that in the case where we have an open
|
||||
// channel where the funding tx gets reorged out, the channel will no
|
||||
// longer be present in the node's routing table.
|
||||
@ -793,7 +1077,7 @@ func testChannelFundingPersistence(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
// confirmation before it's open, with the current set of defaults,
|
||||
// we'll need to create a new node instance.
|
||||
const numConfs = 5
|
||||
carolArgs := []string{fmt.Sprintf("--defaultchanconfs=%v", numConfs)}
|
||||
carolArgs := []string{fmt.Sprintf("--bitcoin.defaultchanconfs=%v", numConfs)}
|
||||
carol, err := net.NewNode(carolArgs)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create new node: %v", err)
|
||||
@ -1095,7 +1379,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
// TODO(roasbeef): should check default value in config here
|
||||
// instead, or make delay a param
|
||||
defaultCSV := uint32(4)
|
||||
defaultCLTV := defaultBitcoinForwardingPolicy.TimeLockDelta
|
||||
defaultCLTV := uint32(defaultBitcoinTimeLockDelta)
|
||||
|
||||
// Since we'd like to test failure scenarios with outstanding htlcs,
|
||||
// we'll introduce another node into our test network: Carol.
|
||||
@ -3878,28 +4162,22 @@ out:
|
||||
}
|
||||
}
|
||||
|
||||
func testGraphTopologyNotifications(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
const chanAmt = maxFundingAmount
|
||||
timeout := time.Duration(time.Second * 5)
|
||||
ctxb := context.Background()
|
||||
|
||||
// We'll first start by establishing a notification client to Alice which
|
||||
// will send us notifications upon detected changes in the channel graph.
|
||||
// subscribeGraphNotifications subscribes to channel graph updates and launches
|
||||
// a goroutine that forwards these to the returned channel.
|
||||
func subscribeGraphNotifications(t *harnessTest, ctxb context.Context,
|
||||
node *lntest.HarnessNode) (chan *lnrpc.GraphTopologyUpdate, chan struct{}) {
|
||||
// We'll first start by establishing a notification client which will
|
||||
// send us notifications upon detected changes in the channel graph.
|
||||
req := &lnrpc.GraphTopologySubscription{}
|
||||
topologyClient, err := net.Alice.SubscribeChannelGraph(ctxb, req)
|
||||
topologyClient, err := node.SubscribeChannelGraph(ctxb, req)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create topology client: %v", err)
|
||||
}
|
||||
|
||||
// Open a new channel between Alice and Bob.
|
||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||
chanPoint := openChannelAndAssert(ctxt, t, net, net.Alice, net.Bob,
|
||||
chanAmt, 0)
|
||||
|
||||
// We'll launch a goroutine that'll be responsible for proxying all
|
||||
// notifications recv'd from the client into the channel below.
|
||||
quit := make(chan struct{})
|
||||
graphUpdates := make(chan *lnrpc.GraphTopologyUpdate, 4)
|
||||
graphUpdates := make(chan *lnrpc.GraphTopologyUpdate, 20)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
@ -3916,7 +4194,8 @@ func testGraphTopologyNotifications(net *lntest.NetworkHarness, t *harnessTest)
|
||||
if err == io.EOF {
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unable to recv graph update: %v", err)
|
||||
t.Fatalf("unable to recv graph update: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
select {
|
||||
@ -3927,6 +4206,21 @@ func testGraphTopologyNotifications(net *lntest.NetworkHarness, t *harnessTest)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return graphUpdates, quit
|
||||
}
|
||||
|
||||
func testGraphTopologyNotifications(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
const chanAmt = maxFundingAmount
|
||||
timeout := time.Duration(time.Second * 5)
|
||||
ctxb := context.Background()
|
||||
|
||||
// Let Alice subscribe to graph notifications.
|
||||
graphUpdates, quit := subscribeGraphNotifications(t, ctxb, net.Alice)
|
||||
|
||||
// Open a new channel between Alice and Bob.
|
||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||
chanPoint := openChannelAndAssert(ctxt, t, net, net.Alice, net.Bob,
|
||||
chanAmt, 0)
|
||||
|
||||
// The channel opening above should've triggered a few notifications
|
||||
// sent to the notification client. We'll expect two channel updates,
|
||||
@ -4680,6 +4974,10 @@ var testsCases = []*testCase{
|
||||
name: "basic funding flow",
|
||||
test: testBasicChannelFunding,
|
||||
},
|
||||
{
|
||||
name: "update channel policy",
|
||||
test: testUpdateChannelPolicy,
|
||||
},
|
||||
{
|
||||
name: "open channel reorg test",
|
||||
test: testOpenChannelAfterReorg,
|
||||
@ -4847,6 +5145,15 @@ func TestLightningNetworkDaemon(t *testing.T) {
|
||||
|
||||
t.Logf("Running %v integration tests", len(testsCases))
|
||||
for _, testCase := range testsCases {
|
||||
logLine := fmt.Sprintf("STARTING ============ %v ============\n",
|
||||
testCase.name)
|
||||
if err := lndHarness.Alice.AddToLog(logLine); err != nil {
|
||||
t.Fatalf("unable to add to log: %v", err)
|
||||
}
|
||||
if err := lndHarness.Bob.AddToLog(logLine); err != nil {
|
||||
t.Fatalf("unable to add to log: %v", err)
|
||||
}
|
||||
|
||||
success := t.Run(testCase.name, func(t1 *testing.T) {
|
||||
ht := newHarnessTest(t1)
|
||||
ht.RunTestCase(testCase, lndHarness)
|
||||
|
@ -89,8 +89,6 @@ description):
|
||||
amount of payment.
|
||||
* GetNetworkInfo
|
||||
* Returns some network level statistics.
|
||||
* SetAlias
|
||||
* Sets the node alias which is to be advertised on the network.
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
|
759
lnrpc/rpc.pb.go
759
lnrpc/rpc.pb.go
@ -98,8 +98,8 @@ It has these top-level messages:
|
||||
FeeReportRequest
|
||||
ChannelFeeReport
|
||||
FeeReportResponse
|
||||
FeeUpdateRequest
|
||||
FeeUpdateResponse
|
||||
PolicyUpdateRequest
|
||||
PolicyUpdateResponse
|
||||
*/
|
||||
package lnrpc
|
||||
|
||||
@ -1555,6 +1555,8 @@ type OpenChannelRequest struct {
|
||||
SatPerByte int64 `protobuf:"varint,7,opt,name=sat_per_byte,json=satPerByte" json:"sat_per_byte,omitempty"`
|
||||
// / Whether this channel should be private, not announced to the greater network.
|
||||
Private bool `protobuf:"varint,8,opt,name=private" json:"private,omitempty"`
|
||||
// / The minimum value in millisatoshi we will require for incoming HTLCs on the channel.
|
||||
MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat" json:"min_htlc_msat,omitempty"`
|
||||
}
|
||||
|
||||
func (m *OpenChannelRequest) Reset() { *m = OpenChannelRequest{} }
|
||||
@ -1618,6 +1620,13 @@ func (m *OpenChannelRequest) GetPrivate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *OpenChannelRequest) GetMinHtlcMsat() int64 {
|
||||
if m != nil {
|
||||
return m.MinHtlcMsat
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type OpenStatusUpdate struct {
|
||||
// Types that are valid to be assigned to Update:
|
||||
// *OpenStatusUpdate_ChanPending
|
||||
@ -3477,111 +3486,120 @@ func (m *FeeReportResponse) GetChannelFees() []*ChannelFeeReport {
|
||||
return nil
|
||||
}
|
||||
|
||||
type FeeUpdateRequest struct {
|
||||
type PolicyUpdateRequest struct {
|
||||
// Types that are valid to be assigned to Scope:
|
||||
// *FeeUpdateRequest_Global
|
||||
// *FeeUpdateRequest_ChanPoint
|
||||
Scope isFeeUpdateRequest_Scope `protobuf_oneof:"scope"`
|
||||
// *PolicyUpdateRequest_Global
|
||||
// *PolicyUpdateRequest_ChanPoint
|
||||
Scope isPolicyUpdateRequest_Scope `protobuf_oneof:"scope"`
|
||||
// / The base fee charged regardless of the number of milli-satoshis sent.
|
||||
BaseFeeMsat int64 `protobuf:"varint,3,opt,name=base_fee_msat" json:"base_fee_msat,omitempty"`
|
||||
// / The effective fee rate in milli-satoshis. The precision of this value goes up to 6 decimal places, so 1e-6.
|
||||
FeeRate float64 `protobuf:"fixed64,4,opt,name=fee_rate" json:"fee_rate,omitempty"`
|
||||
// / The required timelock delta for HTLCs forwarded over the channel.
|
||||
TimeLockDelta uint32 `protobuf:"varint,5,opt,name=time_lock_delta" json:"time_lock_delta,omitempty"`
|
||||
}
|
||||
|
||||
func (m *FeeUpdateRequest) Reset() { *m = FeeUpdateRequest{} }
|
||||
func (m *FeeUpdateRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*FeeUpdateRequest) ProtoMessage() {}
|
||||
func (*FeeUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{90} }
|
||||
func (m *PolicyUpdateRequest) Reset() { *m = PolicyUpdateRequest{} }
|
||||
func (m *PolicyUpdateRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*PolicyUpdateRequest) ProtoMessage() {}
|
||||
func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{90} }
|
||||
|
||||
type isFeeUpdateRequest_Scope interface {
|
||||
isFeeUpdateRequest_Scope()
|
||||
type isPolicyUpdateRequest_Scope interface {
|
||||
isPolicyUpdateRequest_Scope()
|
||||
}
|
||||
|
||||
type FeeUpdateRequest_Global struct {
|
||||
type PolicyUpdateRequest_Global struct {
|
||||
Global bool `protobuf:"varint,1,opt,name=global,oneof"`
|
||||
}
|
||||
type FeeUpdateRequest_ChanPoint struct {
|
||||
type PolicyUpdateRequest_ChanPoint struct {
|
||||
ChanPoint *ChannelPoint `protobuf:"bytes,2,opt,name=chan_point,oneof"`
|
||||
}
|
||||
|
||||
func (*FeeUpdateRequest_Global) isFeeUpdateRequest_Scope() {}
|
||||
func (*FeeUpdateRequest_ChanPoint) isFeeUpdateRequest_Scope() {}
|
||||
func (*PolicyUpdateRequest_Global) isPolicyUpdateRequest_Scope() {}
|
||||
func (*PolicyUpdateRequest_ChanPoint) isPolicyUpdateRequest_Scope() {}
|
||||
|
||||
func (m *FeeUpdateRequest) GetScope() isFeeUpdateRequest_Scope {
|
||||
func (m *PolicyUpdateRequest) GetScope() isPolicyUpdateRequest_Scope {
|
||||
if m != nil {
|
||||
return m.Scope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *FeeUpdateRequest) GetGlobal() bool {
|
||||
if x, ok := m.GetScope().(*FeeUpdateRequest_Global); ok {
|
||||
func (m *PolicyUpdateRequest) GetGlobal() bool {
|
||||
if x, ok := m.GetScope().(*PolicyUpdateRequest_Global); ok {
|
||||
return x.Global
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *FeeUpdateRequest) GetChanPoint() *ChannelPoint {
|
||||
if x, ok := m.GetScope().(*FeeUpdateRequest_ChanPoint); ok {
|
||||
func (m *PolicyUpdateRequest) GetChanPoint() *ChannelPoint {
|
||||
if x, ok := m.GetScope().(*PolicyUpdateRequest_ChanPoint); ok {
|
||||
return x.ChanPoint
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *FeeUpdateRequest) GetBaseFeeMsat() int64 {
|
||||
func (m *PolicyUpdateRequest) GetBaseFeeMsat() int64 {
|
||||
if m != nil {
|
||||
return m.BaseFeeMsat
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *FeeUpdateRequest) GetFeeRate() float64 {
|
||||
func (m *PolicyUpdateRequest) GetFeeRate() float64 {
|
||||
if m != nil {
|
||||
return m.FeeRate
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *PolicyUpdateRequest) GetTimeLockDelta() uint32 {
|
||||
if m != nil {
|
||||
return m.TimeLockDelta
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// XXX_OneofFuncs is for the internal use of the proto package.
|
||||
func (*FeeUpdateRequest) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
|
||||
return _FeeUpdateRequest_OneofMarshaler, _FeeUpdateRequest_OneofUnmarshaler, _FeeUpdateRequest_OneofSizer, []interface{}{
|
||||
(*FeeUpdateRequest_Global)(nil),
|
||||
(*FeeUpdateRequest_ChanPoint)(nil),
|
||||
func (*PolicyUpdateRequest) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
|
||||
return _PolicyUpdateRequest_OneofMarshaler, _PolicyUpdateRequest_OneofUnmarshaler, _PolicyUpdateRequest_OneofSizer, []interface{}{
|
||||
(*PolicyUpdateRequest_Global)(nil),
|
||||
(*PolicyUpdateRequest_ChanPoint)(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func _FeeUpdateRequest_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
|
||||
m := msg.(*FeeUpdateRequest)
|
||||
func _PolicyUpdateRequest_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
|
||||
m := msg.(*PolicyUpdateRequest)
|
||||
// scope
|
||||
switch x := m.Scope.(type) {
|
||||
case *FeeUpdateRequest_Global:
|
||||
case *PolicyUpdateRequest_Global:
|
||||
t := uint64(0)
|
||||
if x.Global {
|
||||
t = 1
|
||||
}
|
||||
b.EncodeVarint(1<<3 | proto.WireVarint)
|
||||
b.EncodeVarint(t)
|
||||
case *FeeUpdateRequest_ChanPoint:
|
||||
case *PolicyUpdateRequest_ChanPoint:
|
||||
b.EncodeVarint(2<<3 | proto.WireBytes)
|
||||
if err := b.EncodeMessage(x.ChanPoint); err != nil {
|
||||
return err
|
||||
}
|
||||
case nil:
|
||||
default:
|
||||
return fmt.Errorf("FeeUpdateRequest.Scope has unexpected type %T", x)
|
||||
return fmt.Errorf("PolicyUpdateRequest.Scope has unexpected type %T", x)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _FeeUpdateRequest_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
|
||||
m := msg.(*FeeUpdateRequest)
|
||||
func _PolicyUpdateRequest_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
|
||||
m := msg.(*PolicyUpdateRequest)
|
||||
switch tag {
|
||||
case 1: // scope.global
|
||||
if wire != proto.WireVarint {
|
||||
return true, proto.ErrInternalBadWireType
|
||||
}
|
||||
x, err := b.DecodeVarint()
|
||||
m.Scope = &FeeUpdateRequest_Global{x != 0}
|
||||
m.Scope = &PolicyUpdateRequest_Global{x != 0}
|
||||
return true, err
|
||||
case 2: // scope.chan_point
|
||||
if wire != proto.WireBytes {
|
||||
@ -3589,21 +3607,21 @@ func _FeeUpdateRequest_OneofUnmarshaler(msg proto.Message, tag, wire int, b *pro
|
||||
}
|
||||
msg := new(ChannelPoint)
|
||||
err := b.DecodeMessage(msg)
|
||||
m.Scope = &FeeUpdateRequest_ChanPoint{msg}
|
||||
m.Scope = &PolicyUpdateRequest_ChanPoint{msg}
|
||||
return true, err
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func _FeeUpdateRequest_OneofSizer(msg proto.Message) (n int) {
|
||||
m := msg.(*FeeUpdateRequest)
|
||||
func _PolicyUpdateRequest_OneofSizer(msg proto.Message) (n int) {
|
||||
m := msg.(*PolicyUpdateRequest)
|
||||
// scope
|
||||
switch x := m.Scope.(type) {
|
||||
case *FeeUpdateRequest_Global:
|
||||
case *PolicyUpdateRequest_Global:
|
||||
n += proto.SizeVarint(1<<3 | proto.WireVarint)
|
||||
n += 1
|
||||
case *FeeUpdateRequest_ChanPoint:
|
||||
case *PolicyUpdateRequest_ChanPoint:
|
||||
s := proto.Size(x.ChanPoint)
|
||||
n += proto.SizeVarint(2<<3 | proto.WireBytes)
|
||||
n += proto.SizeVarint(uint64(s))
|
||||
@ -3615,13 +3633,13 @@ func _FeeUpdateRequest_OneofSizer(msg proto.Message) (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
type FeeUpdateResponse struct {
|
||||
type PolicyUpdateResponse struct {
|
||||
}
|
||||
|
||||
func (m *FeeUpdateResponse) Reset() { *m = FeeUpdateResponse{} }
|
||||
func (m *FeeUpdateResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*FeeUpdateResponse) ProtoMessage() {}
|
||||
func (*FeeUpdateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{91} }
|
||||
func (m *PolicyUpdateResponse) Reset() { *m = PolicyUpdateResponse{} }
|
||||
func (m *PolicyUpdateResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*PolicyUpdateResponse) ProtoMessage() {}
|
||||
func (*PolicyUpdateResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{91} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*CreateWalletRequest)(nil), "lnrpc.CreateWalletRequest")
|
||||
@ -3718,8 +3736,8 @@ func init() {
|
||||
proto.RegisterType((*FeeReportRequest)(nil), "lnrpc.FeeReportRequest")
|
||||
proto.RegisterType((*ChannelFeeReport)(nil), "lnrpc.ChannelFeeReport")
|
||||
proto.RegisterType((*FeeReportResponse)(nil), "lnrpc.FeeReportResponse")
|
||||
proto.RegisterType((*FeeUpdateRequest)(nil), "lnrpc.FeeUpdateRequest")
|
||||
proto.RegisterType((*FeeUpdateResponse)(nil), "lnrpc.FeeUpdateResponse")
|
||||
proto.RegisterType((*PolicyUpdateRequest)(nil), "lnrpc.PolicyUpdateRequest")
|
||||
proto.RegisterType((*PolicyUpdateResponse)(nil), "lnrpc.PolicyUpdateResponse")
|
||||
proto.RegisterEnum("lnrpc.NewAddressRequest_AddressType", NewAddressRequest_AddressType_name, NewAddressRequest_AddressType_value)
|
||||
}
|
||||
|
||||
@ -4033,10 +4051,10 @@ type LightningClient interface {
|
||||
// FeeReport allows the caller to obtain a report detailing the current fee
|
||||
// schedule enforced by the node globally for each channel.
|
||||
FeeReport(ctx context.Context, in *FeeReportRequest, opts ...grpc.CallOption) (*FeeReportResponse, error)
|
||||
// * lncli: `updatefees`
|
||||
// UpdateFees allows the caller to update the fee schedule for all channels
|
||||
// globally, or a particular channel.
|
||||
UpdateFees(ctx context.Context, in *FeeUpdateRequest, opts ...grpc.CallOption) (*FeeUpdateResponse, error)
|
||||
// * lncli: `updatechanpolicy`
|
||||
// UpdateChannelPolicy allows the caller to update the fee schedule and
|
||||
// channel policies for all channels globally, or a particular channel.
|
||||
UpdateChannelPolicy(ctx context.Context, in *PolicyUpdateRequest, opts ...grpc.CallOption) (*PolicyUpdateResponse, error)
|
||||
}
|
||||
|
||||
type lightningClient struct {
|
||||
@ -4517,9 +4535,9 @@ func (c *lightningClient) FeeReport(ctx context.Context, in *FeeReportRequest, o
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *lightningClient) UpdateFees(ctx context.Context, in *FeeUpdateRequest, opts ...grpc.CallOption) (*FeeUpdateResponse, error) {
|
||||
out := new(FeeUpdateResponse)
|
||||
err := grpc.Invoke(ctx, "/lnrpc.Lightning/UpdateFees", in, out, c.cc, opts...)
|
||||
func (c *lightningClient) UpdateChannelPolicy(ctx context.Context, in *PolicyUpdateRequest, opts ...grpc.CallOption) (*PolicyUpdateResponse, error) {
|
||||
out := new(PolicyUpdateResponse)
|
||||
err := grpc.Invoke(ctx, "/lnrpc.Lightning/UpdateChannelPolicy", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -4719,10 +4737,10 @@ type LightningServer interface {
|
||||
// FeeReport allows the caller to obtain a report detailing the current fee
|
||||
// schedule enforced by the node globally for each channel.
|
||||
FeeReport(context.Context, *FeeReportRequest) (*FeeReportResponse, error)
|
||||
// * lncli: `updatefees`
|
||||
// UpdateFees allows the caller to update the fee schedule for all channels
|
||||
// globally, or a particular channel.
|
||||
UpdateFees(context.Context, *FeeUpdateRequest) (*FeeUpdateResponse, error)
|
||||
// * lncli: `updatechanpolicy`
|
||||
// UpdateChannelPolicy allows the caller to update the fee schedule and
|
||||
// channel policies for all channels globally, or a particular channel.
|
||||
UpdateChannelPolicy(context.Context, *PolicyUpdateRequest) (*PolicyUpdateResponse, error)
|
||||
}
|
||||
|
||||
func RegisterLightningServer(s *grpc.Server, srv LightningServer) {
|
||||
@ -5418,20 +5436,20 @@ func _Lightning_FeeReport_Handler(srv interface{}, ctx context.Context, dec func
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Lightning_UpdateFees_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(FeeUpdateRequest)
|
||||
func _Lightning_UpdateChannelPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PolicyUpdateRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LightningServer).UpdateFees(ctx, in)
|
||||
return srv.(LightningServer).UpdateChannelPolicy(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/lnrpc.Lightning/UpdateFees",
|
||||
FullMethod: "/lnrpc.Lightning/UpdateChannelPolicy",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LightningServer).UpdateFees(ctx, req.(*FeeUpdateRequest))
|
||||
return srv.(LightningServer).UpdateChannelPolicy(ctx, req.(*PolicyUpdateRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
@ -5565,8 +5583,8 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{
|
||||
Handler: _Lightning_FeeReport_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateFees",
|
||||
Handler: _Lightning_UpdateFees_Handler,
|
||||
MethodName: "UpdateChannelPolicy",
|
||||
Handler: _Lightning_UpdateChannelPolicy_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
@ -5608,309 +5626,310 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{
|
||||
func init() { proto.RegisterFile("rpc.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 4855 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7b, 0xcd, 0x8f, 0x1c, 0x49,
|
||||
0x56, 0xb8, 0xb3, 0xba, 0xfa, 0xa3, 0x5e, 0x55, 0xf5, 0x47, 0x74, 0xbb, 0xbb, 0x5c, 0xf6, 0x78,
|
||||
0xed, 0xd8, 0xd1, 0x4c, 0xff, 0xfc, 0x1b, 0xdc, 0x76, 0x2f, 0x3b, 0xcc, 0x8e, 0x81, 0x91, 0xbf,
|
||||
0x7b, 0xd8, 0x1e, 0x4f, 0x6f, 0xb6, 0x67, 0x06, 0x76, 0x85, 0x8a, 0xec, 0xaa, 0xe8, 0xea, 0x5c,
|
||||
0x67, 0x65, 0xe6, 0x64, 0x46, 0x75, 0xbb, 0xd6, 0x58, 0x82, 0x05, 0x21, 0x21, 0x81, 0xf6, 0x00,
|
||||
0x02, 0xed, 0x61, 0xb9, 0x70, 0x81, 0x03, 0xfc, 0x03, 0x48, 0xfc, 0x01, 0x2b, 0x21, 0x90, 0xf6,
|
||||
0x84, 0xc4, 0x0d, 0x4e, 0xcb, 0x99, 0x3b, 0x7a, 0xf1, 0x95, 0x11, 0x99, 0xd9, 0xb6, 0x97, 0x5d,
|
||||
0xb8, 0x55, 0xbc, 0x78, 0xf9, 0x22, 0xe2, 0xc5, 0x8b, 0xf7, 0x5d, 0xd0, 0xca, 0xd2, 0xe1, 0xcd,
|
||||
0x34, 0x4b, 0x78, 0x42, 0xe6, 0xa3, 0x38, 0x4b, 0x87, 0xfd, 0x2b, 0xe3, 0x24, 0x19, 0x47, 0x6c,
|
||||
0x27, 0x48, 0xc3, 0x9d, 0x20, 0x8e, 0x13, 0x1e, 0xf0, 0x30, 0x89, 0x73, 0x89, 0x44, 0x6f, 0xc3,
|
||||
0xfa, 0xfd, 0x8c, 0x05, 0x9c, 0x7d, 0x11, 0x44, 0x11, 0xe3, 0x3e, 0xfb, 0x72, 0xca, 0x72, 0x4e,
|
||||
0xfa, 0xb0, 0x94, 0x06, 0x79, 0x7e, 0x96, 0x64, 0xa3, 0x9e, 0x77, 0xcd, 0xdb, 0xee, 0xf8, 0x66,
|
||||
0x4c, 0x37, 0x61, 0xc3, 0xfd, 0x24, 0x4f, 0x93, 0x38, 0x67, 0x48, 0xea, 0xb3, 0x38, 0x4a, 0x86,
|
||||
0xcf, 0x7e, 0x26, 0x52, 0xee, 0x27, 0x8a, 0xd4, 0x0f, 0x1b, 0xd0, 0x7e, 0x9a, 0x05, 0x71, 0x1e,
|
||||
0x0c, 0x71, 0xb3, 0xa4, 0x07, 0x8b, 0xfc, 0xf9, 0xe0, 0x24, 0xc8, 0x4f, 0x04, 0x89, 0x96, 0xaf,
|
||||
0x87, 0x64, 0x13, 0x16, 0x82, 0x49, 0x32, 0x8d, 0x79, 0xaf, 0x71, 0xcd, 0xdb, 0x9e, 0xf3, 0xd5,
|
||||
0x88, 0xbc, 0x07, 0x6b, 0xf1, 0x74, 0x32, 0x18, 0x26, 0xf1, 0x71, 0x98, 0x4d, 0xe4, 0x91, 0x7b,
|
||||
0x73, 0xd7, 0xbc, 0xed, 0x79, 0xbf, 0x3a, 0x41, 0xae, 0x02, 0x1c, 0xe1, 0x36, 0xe4, 0x12, 0x4d,
|
||||
0xb1, 0x84, 0x05, 0x21, 0x14, 0x3a, 0x6a, 0xc4, 0xc2, 0xf1, 0x09, 0xef, 0xcd, 0x0b, 0x42, 0x0e,
|
||||
0x0c, 0x69, 0xf0, 0x70, 0xc2, 0x06, 0x39, 0x0f, 0x26, 0x69, 0x6f, 0x41, 0xec, 0xc6, 0x82, 0x88,
|
||||
0xf9, 0x84, 0x07, 0xd1, 0xe0, 0x98, 0xb1, 0xbc, 0xb7, 0xa8, 0xe6, 0x0d, 0x84, 0xbc, 0x03, 0xcb,
|
||||
0x23, 0x96, 0xf3, 0x41, 0x30, 0x1a, 0x65, 0x2c, 0xcf, 0x59, 0xde, 0x5b, 0xba, 0x36, 0xb7, 0xdd,
|
||||
0xf2, 0x4b, 0x50, 0xda, 0x83, 0xcd, 0xc7, 0x8c, 0x5b, 0xdc, 0xc9, 0x15, 0xa7, 0xe9, 0x3e, 0x10,
|
||||
0x0b, 0xfc, 0x80, 0xf1, 0x20, 0x8c, 0x72, 0xf2, 0x3e, 0x74, 0xb8, 0x85, 0xdc, 0xf3, 0xae, 0xcd,
|
||||
0x6d, 0xb7, 0x77, 0xc9, 0x4d, 0x21, 0x1d, 0x37, 0xad, 0x0f, 0x7c, 0x07, 0x8f, 0xfe, 0x8b, 0x07,
|
||||
0xed, 0x43, 0x16, 0x8f, 0xf4, 0x3d, 0x12, 0x68, 0xe2, 0x4e, 0xd4, 0x1d, 0x8a, 0xdf, 0xe4, 0x2b,
|
||||
0xd0, 0x16, 0xbb, 0xcb, 0x79, 0x16, 0xc6, 0x63, 0x71, 0x05, 0x2d, 0x1f, 0x10, 0x74, 0x28, 0x20,
|
||||
0x64, 0x15, 0xe6, 0x82, 0x09, 0x17, 0x8c, 0x9f, 0xf3, 0xf1, 0x27, 0xb9, 0x0e, 0x9d, 0x34, 0x98,
|
||||
0x4d, 0x58, 0xcc, 0x0b, 0x66, 0x77, 0xfc, 0xb6, 0x82, 0xed, 0x21, 0xb7, 0x6f, 0xc2, 0xba, 0x8d,
|
||||
0xa2, 0xa9, 0xcf, 0x0b, 0xea, 0x6b, 0x16, 0xa6, 0x5a, 0xe4, 0x5d, 0x58, 0xd1, 0xf8, 0x99, 0xdc,
|
||||
0xac, 0x60, 0x7f, 0xcb, 0x5f, 0x56, 0x60, 0xcd, 0xa0, 0x3f, 0xf7, 0xa0, 0x23, 0x8f, 0x24, 0xe5,
|
||||
0x8c, 0xbc, 0x0d, 0x5d, 0xfd, 0x25, 0xcb, 0xb2, 0x24, 0x53, 0xd2, 0xe5, 0x02, 0xc9, 0x0d, 0x58,
|
||||
0xd5, 0x80, 0x34, 0x63, 0xe1, 0x24, 0x18, 0x33, 0x71, 0xd4, 0x8e, 0x5f, 0x81, 0x93, 0xdd, 0x82,
|
||||
0x62, 0x96, 0x4c, 0x39, 0x13, 0x47, 0x6f, 0xef, 0x76, 0x14, 0xbb, 0x7d, 0x84, 0xf9, 0x2e, 0x0a,
|
||||
0xfd, 0xbe, 0x07, 0x9d, 0xfb, 0x27, 0x41, 0x1c, 0xb3, 0xe8, 0x20, 0x09, 0x63, 0x8e, 0xe2, 0x76,
|
||||
0x3c, 0x8d, 0x47, 0x61, 0x3c, 0x1e, 0xf0, 0xe7, 0xa1, 0x7e, 0x36, 0x0e, 0x0c, 0x37, 0x65, 0x8f,
|
||||
0x91, 0x49, 0x8a, 0xff, 0x15, 0x38, 0xd2, 0x4b, 0xa6, 0x3c, 0x9d, 0xf2, 0x41, 0x18, 0x8f, 0xd8,
|
||||
0x73, 0xb1, 0xa7, 0xae, 0xef, 0xc0, 0xe8, 0xaf, 0xc3, 0xea, 0x3e, 0xca, 0x71, 0x1c, 0xc6, 0xe3,
|
||||
0xbb, 0x52, 0xd8, 0xf0, 0x71, 0xa5, 0xd3, 0xa3, 0x67, 0x6c, 0xa6, 0xf8, 0xa2, 0x46, 0x28, 0x0a,
|
||||
0x27, 0x49, 0xce, 0xd5, 0x7a, 0xe2, 0x37, 0xfd, 0x77, 0x0f, 0x56, 0x90, 0xb7, 0x9f, 0x04, 0xf1,
|
||||
0x4c, 0x8b, 0xcc, 0x3e, 0x74, 0x90, 0xd4, 0xd3, 0xe4, 0xae, 0x7c, 0xa2, 0x52, 0xf4, 0xb6, 0x15,
|
||||
0x2f, 0x4a, 0xd8, 0x37, 0x6d, 0xd4, 0x87, 0x31, 0xcf, 0x66, 0xbe, 0xf3, 0x35, 0x0a, 0x1b, 0x0f,
|
||||
0xb2, 0x31, 0xe3, 0xe2, 0xf1, 0xaa, 0xc7, 0x0c, 0x12, 0x74, 0x3f, 0x89, 0x8f, 0xc9, 0x35, 0xe8,
|
||||
0xe4, 0x01, 0x1f, 0xa4, 0x2c, 0x1b, 0x1c, 0xcd, 0x38, 0x13, 0x02, 0x33, 0xe7, 0x43, 0x1e, 0xf0,
|
||||
0x03, 0x96, 0xdd, 0x9b, 0x71, 0xd6, 0xff, 0x08, 0xd6, 0x2a, 0xab, 0xa0, 0x8c, 0x16, 0x47, 0xc4,
|
||||
0x9f, 0x64, 0x03, 0xe6, 0x4f, 0x83, 0x68, 0xca, 0x94, 0x4e, 0x91, 0x83, 0x0f, 0x1b, 0x1f, 0x78,
|
||||
0xf4, 0x1d, 0x58, 0x2d, 0xb6, 0xad, 0x84, 0x88, 0x40, 0xd3, 0xdc, 0x52, 0xcb, 0x17, 0xbf, 0xe9,
|
||||
0xef, 0x7b, 0x12, 0xf1, 0x7e, 0x12, 0x9a, 0xf7, 0x89, 0x88, 0xf8, 0x8c, 0x35, 0x22, 0xfe, 0x3e,
|
||||
0x57, 0x7f, 0xfd, 0xfc, 0x87, 0xa5, 0xef, 0xc2, 0x9a, 0xb5, 0x85, 0x57, 0x6c, 0xf6, 0xaf, 0x3c,
|
||||
0x58, 0x7b, 0xc2, 0xce, 0xd4, 0xad, 0xeb, 0xdd, 0x7e, 0x00, 0x4d, 0x3e, 0x4b, 0x99, 0xc0, 0x5c,
|
||||
0xde, 0x7d, 0x5b, 0x5d, 0x5a, 0x05, 0xef, 0xa6, 0x1a, 0x3e, 0x9d, 0xa5, 0xcc, 0x17, 0x5f, 0xd0,
|
||||
0x4f, 0xa1, 0x6d, 0x01, 0xc9, 0x16, 0xac, 0x7f, 0xf1, 0xf1, 0xd3, 0x27, 0x0f, 0x0f, 0x0f, 0x07,
|
||||
0x07, 0x9f, 0xdd, 0xfb, 0xe6, 0xc3, 0xdf, 0x1a, 0xec, 0xdd, 0x3d, 0xdc, 0x5b, 0xbd, 0x40, 0x36,
|
||||
0x81, 0x3c, 0x79, 0x78, 0xf8, 0xf4, 0xe1, 0x03, 0x07, 0xee, 0x91, 0x15, 0x68, 0xdb, 0x80, 0x06,
|
||||
0xed, 0x43, 0xef, 0x09, 0x3b, 0xfb, 0x22, 0xe4, 0x31, 0xcb, 0x73, 0x77, 0x79, 0x7a, 0x13, 0x88,
|
||||
0xbd, 0x27, 0x75, 0xcc, 0x1e, 0x2c, 0x2a, 0x8d, 0xa9, 0x0d, 0x86, 0x1a, 0xd2, 0x77, 0x80, 0x1c,
|
||||
0x86, 0xe3, 0xf8, 0x13, 0x96, 0xe7, 0xc1, 0x98, 0xe9, 0xc3, 0xae, 0xc2, 0xdc, 0x24, 0x1f, 0xab,
|
||||
0x87, 0x86, 0x3f, 0xe9, 0xd7, 0x60, 0xdd, 0xc1, 0x53, 0x84, 0xaf, 0x40, 0x2b, 0x0f, 0xc7, 0x71,
|
||||
0xc0, 0xa7, 0x19, 0x53, 0xa4, 0x0b, 0x00, 0x7d, 0x04, 0x1b, 0x9f, 0xb3, 0x2c, 0x3c, 0x9e, 0xbd,
|
||||
0x8e, 0xbc, 0x4b, 0xa7, 0x51, 0xa6, 0xf3, 0x10, 0x2e, 0x96, 0xe8, 0xa8, 0xe5, 0xa5, 0x64, 0xaa,
|
||||
0xfb, 0x5b, 0xf2, 0xe5, 0xc0, 0x7a, 0xa7, 0x0d, 0xfb, 0x9d, 0xd2, 0xcf, 0x80, 0xdc, 0x4f, 0xe2,
|
||||
0x98, 0x0d, 0xf9, 0x01, 0x63, 0x99, 0xde, 0xcc, 0xff, 0xb7, 0xc4, 0xb0, 0xbd, 0xbb, 0xa5, 0x2e,
|
||||
0xb6, 0xfc, 0xf8, 0x95, 0x7c, 0x12, 0x68, 0xa6, 0x2c, 0x9b, 0x08, 0xc2, 0x4b, 0xbe, 0xf8, 0x4d,
|
||||
0x77, 0x60, 0xdd, 0x21, 0x5b, 0xf0, 0x3c, 0x65, 0x2c, 0x1b, 0xa8, 0xdd, 0xcd, 0xfb, 0x7a, 0x48,
|
||||
0x6f, 0xc3, 0xc5, 0x07, 0x61, 0x3e, 0xac, 0x6e, 0x05, 0x3f, 0x99, 0x1e, 0x0d, 0x8a, 0xe7, 0xa7,
|
||||
0x87, 0x68, 0xe5, 0xca, 0x9f, 0x28, 0xdf, 0xe0, 0x8f, 0x3c, 0x68, 0xee, 0x3d, 0xdd, 0xbf, 0x8f,
|
||||
0x8e, 0x45, 0x18, 0x0f, 0x93, 0x09, 0xda, 0x06, 0xc9, 0x0e, 0x33, 0x3e, 0xf7, 0x59, 0x5d, 0x81,
|
||||
0x96, 0x30, 0x29, 0x68, 0xb8, 0xc5, 0xa3, 0xea, 0xf8, 0x05, 0x00, 0x9d, 0x06, 0xf6, 0x3c, 0x0d,
|
||||
0x33, 0xe1, 0x15, 0x68, 0x5b, 0xdf, 0x14, 0xca, 0xb2, 0x3a, 0x41, 0x7f, 0xda, 0x84, 0xee, 0xdd,
|
||||
0x21, 0x0f, 0x4f, 0x99, 0x52, 0xde, 0x62, 0x55, 0x01, 0x50, 0xfb, 0x51, 0x23, 0x34, 0x33, 0x19,
|
||||
0x9b, 0x24, 0x9c, 0x0d, 0x9c, 0x6b, 0x72, 0x81, 0x88, 0x35, 0x94, 0x84, 0x06, 0x29, 0x9a, 0x01,
|
||||
0xb1, 0xbf, 0x96, 0xef, 0x02, 0x91, 0x65, 0x08, 0x40, 0x2e, 0xe3, 0xce, 0x9a, 0xbe, 0x1e, 0x22,
|
||||
0x3f, 0x86, 0x41, 0x1a, 0x0c, 0x43, 0x3e, 0x53, 0xda, 0xc0, 0x8c, 0x91, 0x76, 0x94, 0x0c, 0x83,
|
||||
0x68, 0x70, 0x14, 0x44, 0x41, 0x3c, 0x64, 0xca, 0x3f, 0x71, 0x81, 0xe8, 0x82, 0xa8, 0x2d, 0x69,
|
||||
0x34, 0xe9, 0xa6, 0x94, 0xa0, 0xe8, 0xca, 0x0c, 0x93, 0xc9, 0x24, 0xe4, 0xe8, 0xb9, 0xf4, 0x96,
|
||||
0xa4, 0xe6, 0x29, 0x20, 0xe2, 0x24, 0x72, 0x74, 0x26, 0x79, 0xd8, 0x92, 0xab, 0x39, 0x40, 0xa4,
|
||||
0x72, 0xcc, 0x98, 0xd0, 0x60, 0xcf, 0xce, 0x7a, 0x20, 0xa9, 0x14, 0x10, 0xbc, 0x8d, 0x69, 0x9c,
|
||||
0x33, 0xce, 0x23, 0x36, 0x32, 0x1b, 0x6a, 0x0b, 0xb4, 0xea, 0x04, 0xb9, 0x05, 0xeb, 0xd2, 0x99,
|
||||
0xca, 0x03, 0x9e, 0xe4, 0x27, 0x61, 0x3e, 0xc8, 0x59, 0xcc, 0x7b, 0x1d, 0x81, 0x5f, 0x37, 0x45,
|
||||
0x3e, 0x80, 0xad, 0x12, 0x38, 0x63, 0x43, 0x16, 0x9e, 0xb2, 0x51, 0xaf, 0x2b, 0xbe, 0x3a, 0x6f,
|
||||
0x9a, 0x5c, 0x83, 0x36, 0xfa, 0x90, 0xd3, 0x74, 0x14, 0x70, 0x96, 0xf7, 0x96, 0xc5, 0x3d, 0xd8,
|
||||
0x20, 0x72, 0x1b, 0xba, 0x29, 0x93, 0x56, 0xf8, 0x84, 0x47, 0xc3, 0xbc, 0xb7, 0x22, 0x4c, 0x5f,
|
||||
0x5b, 0x3d, 0x36, 0x94, 0x5f, 0xdf, 0xc5, 0x40, 0xd1, 0x1c, 0xe6, 0xa7, 0x83, 0x11, 0x8b, 0x82,
|
||||
0x59, 0x6f, 0x55, 0x08, 0x5d, 0x01, 0xa0, 0x17, 0x61, 0x7d, 0x3f, 0xcc, 0xb9, 0x92, 0x34, 0xa3,
|
||||
0xfd, 0xf6, 0x60, 0xc3, 0x05, 0xab, 0xb7, 0x78, 0x0b, 0x96, 0x94, 0xd8, 0xe4, 0xbd, 0xb6, 0x58,
|
||||
0x7a, 0x43, 0x2d, 0xed, 0x48, 0xac, 0x6f, 0xb0, 0xe8, 0x1f, 0x36, 0xa0, 0x89, 0xef, 0xec, 0xfc,
|
||||
0x37, 0x69, 0x3f, 0xf0, 0x86, 0xf3, 0xc0, 0x6d, 0x75, 0x3b, 0xe7, 0xa8, 0x5b, 0xe1, 0x59, 0xcf,
|
||||
0x38, 0x53, 0xb7, 0x21, 0x25, 0xd6, 0x82, 0x14, 0xf3, 0x19, 0x1b, 0x9e, 0x0a, 0xb1, 0x35, 0xf3,
|
||||
0x08, 0x41, 0xa1, 0x46, 0x33, 0x27, 0xbe, 0x96, 0x32, 0x6b, 0xc6, 0x7a, 0x4e, 0x7c, 0xb9, 0x58,
|
||||
0xcc, 0x89, 0xef, 0x7a, 0xb0, 0x18, 0xc6, 0x47, 0xc9, 0x34, 0x1e, 0x09, 0xf9, 0x5c, 0xf2, 0xf5,
|
||||
0x10, 0xf9, 0x9c, 0x0a, 0xef, 0x28, 0x9c, 0x30, 0x25, 0x98, 0x05, 0x80, 0x12, 0x74, 0x83, 0x72,
|
||||
0xa1, 0x71, 0x0c, 0x93, 0xdf, 0x87, 0x35, 0x0b, 0xa6, 0x38, 0x7c, 0x1d, 0xe6, 0xf1, 0xf4, 0xda,
|
||||
0x9f, 0xd6, 0x37, 0x2b, 0x54, 0x95, 0x9c, 0xa1, 0xab, 0xb0, 0xfc, 0x98, 0xf1, 0x8f, 0xe3, 0xe3,
|
||||
0x44, 0x53, 0xfa, 0xe3, 0x39, 0x58, 0x31, 0x20, 0x45, 0x68, 0x1b, 0x56, 0xc2, 0x11, 0x8b, 0x79,
|
||||
0xc8, 0x67, 0x03, 0xc7, 0xdb, 0x2a, 0x83, 0x51, 0xf9, 0x07, 0x51, 0x18, 0xe4, 0x4a, 0x7d, 0xc8,
|
||||
0x01, 0xd9, 0x85, 0x0d, 0x94, 0x3c, 0x2d, 0x4c, 0xe6, 0xda, 0xa5, 0x93, 0x57, 0x3b, 0x87, 0x8f,
|
||||
0x05, 0xe1, 0x52, 0x3d, 0x15, 0x9f, 0x48, 0x55, 0x57, 0x37, 0x85, 0x5c, 0x93, 0x94, 0xf0, 0xc8,
|
||||
0xf3, 0x52, 0x3a, 0x0d, 0xa0, 0x12, 0x1f, 0x2d, 0x48, 0x07, 0xb3, 0x1c, 0x1f, 0x59, 0x31, 0xd6,
|
||||
0x52, 0x25, 0xc6, 0xda, 0x86, 0x95, 0x7c, 0x16, 0x0f, 0xd9, 0x68, 0xc0, 0x13, 0x5c, 0x37, 0x8c,
|
||||
0xc5, 0xed, 0x2c, 0xf9, 0x65, 0xb0, 0x88, 0x06, 0x59, 0xce, 0x63, 0xc6, 0x85, 0xd6, 0x58, 0xf2,
|
||||
0xf5, 0x10, 0x15, 0xb0, 0x40, 0x91, 0x42, 0xdf, 0xf2, 0xd5, 0x08, 0xad, 0xd8, 0x34, 0x0b, 0xf3,
|
||||
0x5e, 0x47, 0x40, 0xc5, 0x6f, 0xfa, 0x3d, 0x61, 0x1c, 0x4d, 0x10, 0xf8, 0x99, 0x78, 0xb9, 0xe4,
|
||||
0x32, 0xb4, 0xe4, 0x9e, 0xf2, 0x93, 0x40, 0x87, 0xab, 0x02, 0x70, 0x78, 0x12, 0x60, 0xec, 0xe2,
|
||||
0x1c, 0x53, 0xbe, 0x82, 0xb6, 0x80, 0xed, 0xc9, 0x53, 0xbe, 0x0d, 0xcb, 0x3a, 0xbc, 0xcc, 0x07,
|
||||
0x11, 0x3b, 0xe6, 0xda, 0xd9, 0x8e, 0xa7, 0x13, 0x5c, 0x2e, 0xdf, 0x67, 0xc7, 0x9c, 0x3e, 0x81,
|
||||
0x35, 0xf5, 0x02, 0x3f, 0x4d, 0x99, 0x5e, 0xfa, 0x1b, 0x65, 0xfd, 0x2f, 0x0d, 0xf4, 0xba, 0x92,
|
||||
0x2c, 0x3b, 0x42, 0x28, 0x19, 0x05, 0xea, 0x03, 0x51, 0xd3, 0xf7, 0xa3, 0x24, 0x67, 0x8a, 0x20,
|
||||
0x85, 0xce, 0x30, 0x4a, 0xf2, 0x72, 0x18, 0x61, 0xc3, 0x90, 0x97, 0xf9, 0x74, 0x38, 0xc4, 0x97,
|
||||
0x2b, 0x4d, 0xbc, 0x1e, 0xd2, 0xbf, 0xf1, 0x60, 0x5d, 0x50, 0xd3, 0xba, 0xc2, 0xf8, 0x85, 0x6f,
|
||||
0xbe, 0xcd, 0xce, 0xd0, 0x0e, 0x6b, 0x36, 0x60, 0xfe, 0x38, 0xc9, 0x86, 0x4c, 0xad, 0x24, 0x07,
|
||||
0xbf, 0x08, 0x4f, 0xf7, 0x5f, 0x3d, 0x58, 0x13, 0x5b, 0x3d, 0xe4, 0x01, 0x9f, 0xe6, 0xea, 0xf8,
|
||||
0xbf, 0x0a, 0x5d, 0x3c, 0x2a, 0xd3, 0xe2, 0xaf, 0x36, 0xba, 0x61, 0x5e, 0xaa, 0x80, 0x4a, 0xe4,
|
||||
0xbd, 0x0b, 0xbe, 0x8b, 0x4c, 0x3e, 0x82, 0x8e, 0x9d, 0x23, 0x10, 0x7b, 0x6e, 0xef, 0x5e, 0xd2,
|
||||
0xa7, 0xac, 0x48, 0xce, 0xde, 0x05, 0xdf, 0xf9, 0x80, 0xdc, 0x01, 0x10, 0x96, 0x59, 0x90, 0x55,
|
||||
0x61, 0xe0, 0x25, 0x97, 0x49, 0xd6, 0x65, 0xed, 0x5d, 0xf0, 0x2d, 0xf4, 0x7b, 0x4b, 0xb0, 0x20,
|
||||
0x4d, 0x09, 0x7d, 0x0c, 0x5d, 0x67, 0xa7, 0x8e, 0x07, 0xdf, 0x91, 0x1e, 0x7c, 0x25, 0xc0, 0x6b,
|
||||
0xd4, 0x04, 0x78, 0xff, 0xd0, 0x00, 0x82, 0xd2, 0x56, 0xba, 0xce, 0x77, 0x60, 0x59, 0xb1, 0xdf,
|
||||
0x75, 0xde, 0x4a, 0x50, 0x61, 0xf3, 0x92, 0x91, 0xe3, 0xc1, 0x74, 0x7c, 0x1b, 0x44, 0x6e, 0x02,
|
||||
0xb1, 0x86, 0x3a, 0x6a, 0x97, 0xf6, 0xa0, 0x66, 0x06, 0x15, 0x97, 0x74, 0x3f, 0x74, 0xbc, 0xaa,
|
||||
0x3c, 0xb6, 0xa6, 0xb8, 0xdf, 0xda, 0x39, 0x91, 0x4c, 0x9a, 0xe6, 0x27, 0x68, 0x93, 0xb5, 0x8f,
|
||||
0xa3, 0xc7, 0x65, 0x41, 0x5a, 0x78, 0xad, 0x20, 0x2d, 0x96, 0x05, 0x49, 0x58, 0xb8, 0x2c, 0x3c,
|
||||
0x0d, 0x38, 0xd3, 0x56, 0x43, 0x0d, 0xe9, 0x4f, 0x3c, 0x58, 0x45, 0xee, 0x39, 0x12, 0xf6, 0x21,
|
||||
0x08, 0x01, 0x7f, 0x43, 0x01, 0x73, 0x70, 0x7f, 0x7e, 0xf9, 0xfa, 0x00, 0x5a, 0x82, 0x60, 0x92,
|
||||
0xb2, 0x58, 0x89, 0x57, 0xcf, 0x15, 0xaf, 0x42, 0xb7, 0xec, 0x5d, 0xf0, 0x0b, 0x64, 0x4b, 0xb8,
|
||||
0xfe, 0xd9, 0x83, 0xb6, 0xda, 0xe6, 0xff, 0xd8, 0xa5, 0xee, 0xc3, 0x12, 0xca, 0x99, 0xe5, 0xb1,
|
||||
0x9a, 0x31, 0xea, 0xf4, 0x09, 0x46, 0x34, 0x68, 0xc4, 0x1c, 0x77, 0xba, 0x0c, 0x46, 0x8b, 0x24,
|
||||
0xd4, 0x68, 0x3e, 0xe0, 0x61, 0x34, 0xd0, 0xb3, 0x2a, 0xd1, 0x56, 0x37, 0x85, 0xda, 0x24, 0xe7,
|
||||
0xc1, 0x98, 0x29, 0x63, 0x23, 0x07, 0x18, 0x37, 0xa8, 0x03, 0x95, 0x5d, 0xa5, 0x1f, 0x03, 0x6c,
|
||||
0x55, 0xa6, 0x8c, 0xbb, 0xa4, 0x3c, 0xc4, 0x28, 0x9c, 0x1c, 0x25, 0xc6, 0xd9, 0xf4, 0x6c, 0xe7,
|
||||
0xd1, 0x99, 0x22, 0x63, 0xb8, 0xa8, 0xad, 0x2a, 0xf2, 0xb4, 0xb0, 0xa1, 0x0d, 0xe1, 0x0e, 0xdc,
|
||||
0x76, 0x65, 0xa0, 0xbc, 0xa0, 0x86, 0xdb, 0xef, 0xb1, 0x9e, 0x1e, 0x39, 0x81, 0x9e, 0x31, 0xdf,
|
||||
0x4a, 0x71, 0x5b, 0x26, 0x1e, 0xd7, 0x7a, 0xef, 0x35, 0x6b, 0x09, 0x2d, 0x33, 0xd2, 0xcb, 0x9c,
|
||||
0x4b, 0x8d, 0xcc, 0xe0, 0xaa, 0x9e, 0x13, 0x9a, 0xb9, 0xba, 0x5e, 0xf3, 0x8d, 0xce, 0xf6, 0x08,
|
||||
0x3f, 0x76, 0x17, 0x7d, 0x0d, 0xe1, 0xfe, 0x8f, 0x3d, 0x58, 0x76, 0xc9, 0xa1, 0xe8, 0xa8, 0xa8,
|
||||
0x43, 0xab, 0x0e, 0xed, 0x16, 0x95, 0xc0, 0xd5, 0xb8, 0xa9, 0x51, 0x17, 0x37, 0xd9, 0xd1, 0xd1,
|
||||
0xdc, 0xeb, 0xa2, 0xa3, 0xe6, 0x9b, 0x45, 0x47, 0xf3, 0x75, 0xd1, 0x51, 0xff, 0xbf, 0x3c, 0x20,
|
||||
0xd5, 0xfb, 0x25, 0x8f, 0x65, 0xe0, 0x16, 0xb3, 0x48, 0xe9, 0x89, 0x5f, 0x7a, 0x33, 0x19, 0xd1,
|
||||
0x3c, 0xd4, 0x5f, 0xa3, 0xb0, 0xda, 0x8a, 0xc0, 0x76, 0x46, 0xba, 0x7e, 0xdd, 0x54, 0x29, 0x5e,
|
||||
0x6b, 0xbe, 0x3e, 0x5e, 0x9b, 0x7f, 0x7d, 0xbc, 0xb6, 0x50, 0x8e, 0xd7, 0xfa, 0xbf, 0x0b, 0x5d,
|
||||
0xe7, 0xd6, 0x7f, 0x71, 0x27, 0x2e, 0x3b, 0x32, 0xf2, 0x82, 0x1d, 0x58, 0xff, 0x3f, 0x1b, 0x40,
|
||||
0xaa, 0x92, 0xf7, 0x7f, 0xba, 0x07, 0x21, 0x47, 0x8e, 0x02, 0x99, 0x53, 0x72, 0xe4, 0xa8, 0x8e,
|
||||
0xff, 0x4d, 0xa5, 0xf8, 0x1e, 0xac, 0x65, 0x6c, 0x98, 0x9c, 0xb2, 0xcc, 0x8a, 0x99, 0xe5, 0x55,
|
||||
0x55, 0x27, 0xd0, 0x95, 0x73, 0xa3, 0xd4, 0x25, 0xa7, 0x36, 0x60, 0x59, 0x86, 0x52, 0xb0, 0x4a,
|
||||
0xbf, 0x01, 0x1b, 0xb2, 0x64, 0x73, 0x4f, 0x92, 0xd2, 0xde, 0xc4, 0x75, 0xe8, 0x9c, 0xc9, 0x34,
|
||||
0xdd, 0x20, 0x89, 0xa3, 0x99, 0x32, 0x22, 0x6d, 0x05, 0xfb, 0x34, 0x8e, 0x66, 0xf4, 0x47, 0x1e,
|
||||
0x5c, 0x2c, 0x7d, 0x5b, 0x64, 0xe3, 0xa5, 0xaa, 0x75, 0xf5, 0xaf, 0x0b, 0xc4, 0x23, 0x2a, 0x19,
|
||||
0xb7, 0x8e, 0x28, 0x4d, 0x52, 0x75, 0x02, 0x59, 0x38, 0x8d, 0xab, 0xf8, 0xf2, 0x62, 0xea, 0xa6,
|
||||
0xe8, 0x16, 0x5c, 0x54, 0x97, 0xef, 0x9e, 0x8d, 0xee, 0xc2, 0x66, 0x79, 0xa2, 0xc8, 0x7c, 0xb9,
|
||||
0x5b, 0xd6, 0x43, 0xfa, 0x11, 0x90, 0x6f, 0x4d, 0x59, 0x36, 0x13, 0x79, 0x7f, 0x93, 0x5a, 0xdd,
|
||||
0x2a, 0x87, 0xd8, 0x0b, 0xe9, 0xf4, 0xe8, 0x9b, 0x6c, 0xa6, 0xcb, 0x25, 0x0d, 0x53, 0x2e, 0xa1,
|
||||
0x77, 0x60, 0xdd, 0x21, 0x60, 0x58, 0xb5, 0x20, 0x6a, 0x07, 0x3a, 0xfc, 0x74, 0xeb, 0x0b, 0x6a,
|
||||
0x8e, 0xfe, 0xa5, 0x07, 0x73, 0x7b, 0x49, 0x6a, 0xe7, 0x8c, 0x3c, 0x37, 0x67, 0xa4, 0x74, 0xe7,
|
||||
0xc0, 0xa8, 0xc6, 0x86, 0x7a, 0xf9, 0x36, 0x10, 0x35, 0x5f, 0x30, 0xe1, 0x18, 0x80, 0x1d, 0x27,
|
||||
0xd9, 0x59, 0x90, 0x8d, 0x14, 0xff, 0x4a, 0x50, 0xdc, 0x7e, 0xa1, 0x60, 0xf0, 0x27, 0x3a, 0x0d,
|
||||
0x22, 0x71, 0x36, 0x53, 0x31, 0xa3, 0x1a, 0xd1, 0x1f, 0x78, 0x30, 0x2f, 0xf6, 0x8a, 0xaf, 0x41,
|
||||
0xde, 0xaf, 0x28, 0x95, 0x89, 0xbc, 0x9c, 0x27, 0x5f, 0x43, 0x09, 0x5c, 0x2a, 0xa0, 0x35, 0x2a,
|
||||
0x05, 0xb4, 0x2b, 0xd0, 0x92, 0xa3, 0xa2, 0xe2, 0x54, 0x00, 0xc8, 0x55, 0x68, 0x9e, 0x24, 0xa9,
|
||||
0xb6, 0x61, 0xa0, 0x13, 0x31, 0x49, 0xea, 0x0b, 0x38, 0xbd, 0x01, 0x2b, 0x4f, 0x92, 0x11, 0xb3,
|
||||
0xa2, 0xf5, 0x73, 0xaf, 0x89, 0xfe, 0x9e, 0x07, 0x4b, 0x1a, 0x99, 0x6c, 0x43, 0x13, 0x4d, 0x51,
|
||||
0xc9, 0xf9, 0x33, 0xe9, 0x54, 0xc4, 0xf3, 0x05, 0x06, 0xaa, 0x10, 0x11, 0x1b, 0x16, 0xae, 0x82,
|
||||
0x8e, 0x0c, 0x0b, 0x23, 0x8c, 0xee, 0xb8, 0xd8, 0x73, 0xc9, 0x58, 0x95, 0xa0, 0xf4, 0x6f, 0x3d,
|
||||
0xe8, 0x3a, 0x6b, 0xa0, 0x83, 0x1e, 0x05, 0x39, 0x57, 0x29, 0x28, 0xc5, 0x44, 0x1b, 0x64, 0x67,
|
||||
0x76, 0x1a, 0x6e, 0x66, 0xc7, 0x64, 0x16, 0xe6, 0xec, 0xcc, 0xc2, 0x2d, 0x68, 0x15, 0xc5, 0xc8,
|
||||
0xa6, 0xa3, 0x1a, 0x70, 0x45, 0x9d, 0x28, 0x2e, 0x90, 0x90, 0xce, 0x30, 0x89, 0x92, 0x4c, 0xd5,
|
||||
0xea, 0xe4, 0x80, 0xde, 0x81, 0xb6, 0x85, 0x8f, 0xdb, 0x88, 0x19, 0x3f, 0x4b, 0xb2, 0x67, 0x3a,
|
||||
0xc1, 0xa4, 0x86, 0xa6, 0x40, 0xd2, 0x28, 0x0a, 0x24, 0xf4, 0xef, 0x3c, 0xe8, 0xa2, 0xa4, 0x84,
|
||||
0xf1, 0xf8, 0x20, 0x89, 0xc2, 0xe1, 0x4c, 0x48, 0x8c, 0x16, 0x8a, 0xc1, 0x88, 0x45, 0x3c, 0x30,
|
||||
0x12, 0xe3, 0x82, 0xd1, 0xe6, 0x4f, 0xc2, 0x58, 0xa8, 0x2c, 0x25, 0x2f, 0x66, 0x8c, 0x92, 0x8f,
|
||||
0xb6, 0xeb, 0x28, 0xc8, 0xd9, 0x60, 0x82, 0xe1, 0x84, 0xd2, 0xd5, 0x0e, 0x10, 0xd5, 0x07, 0x02,
|
||||
0xb2, 0x80, 0xb3, 0xc1, 0x24, 0x8c, 0xa2, 0x50, 0xe2, 0x4a, 0x09, 0xaf, 0x9b, 0xc2, 0x30, 0xab,
|
||||
0xad, 0xd4, 0xc4, 0xc3, 0xd1, 0x58, 0xe6, 0x4a, 0x95, 0x23, 0x62, 0x9e, 0x9f, 0x05, 0xd1, 0xf3,
|
||||
0x8e, 0xeb, 0x62, 0x41, 0xca, 0xd7, 0x3a, 0x57, 0xbd, 0xd6, 0x2b, 0xd0, 0x42, 0xf1, 0xba, 0x2d,
|
||||
0x7c, 0x24, 0x59, 0xbb, 0x2e, 0x00, 0x7a, 0x76, 0x57, 0xcc, 0xce, 0x17, 0xb3, 0x02, 0xe0, 0x78,
|
||||
0x45, 0x0b, 0x25, 0xaf, 0xe8, 0x03, 0xe8, 0x28, 0x32, 0x82, 0xef, 0x22, 0x5c, 0x2a, 0x04, 0xdc,
|
||||
0xb9, 0x13, 0xdf, 0xc1, 0xd4, 0x5f, 0xee, 0xea, 0x2f, 0x97, 0x5e, 0xf7, 0xa5, 0xc6, 0xa4, 0x17,
|
||||
0x61, 0x5d, 0x31, 0xef, 0x71, 0x16, 0xa4, 0x27, 0x5a, 0xf5, 0x8e, 0x4c, 0x81, 0x54, 0x80, 0xc9,
|
||||
0x0d, 0x98, 0xc7, 0xcf, 0xb4, 0xf6, 0xab, 0x7f, 0x74, 0x12, 0x85, 0x6c, 0xc3, 0x3c, 0x1b, 0x8d,
|
||||
0x99, 0xf6, 0xcc, 0x89, 0x1b, 0x23, 0xe1, 0x1d, 0xf9, 0x12, 0x01, 0x55, 0x00, 0x42, 0x4b, 0x2a,
|
||||
0xc0, 0xd5, 0x9c, 0x0b, 0x38, 0xfc, 0x78, 0x44, 0x37, 0x80, 0x3c, 0x91, 0x52, 0x6b, 0xe7, 0xf7,
|
||||
0xfe, 0x60, 0x0e, 0xda, 0x16, 0x18, 0x5f, 0xf3, 0x18, 0x37, 0x3c, 0x18, 0x85, 0xc1, 0x84, 0x71,
|
||||
0x96, 0x29, 0x49, 0x2d, 0x41, 0x85, 0x82, 0x3d, 0x1d, 0x0f, 0x92, 0x29, 0x1f, 0x8c, 0xd8, 0x38,
|
||||
0x63, 0xd2, 0xa0, 0x79, 0x7e, 0x09, 0x8a, 0x78, 0x93, 0xe0, 0xb9, 0x8d, 0x27, 0xe5, 0xa1, 0x04,
|
||||
0xd5, 0xd9, 0x3a, 0xc9, 0xa3, 0x66, 0x91, 0xad, 0x93, 0x1c, 0x29, 0xeb, 0xa1, 0xf9, 0x1a, 0x3d,
|
||||
0xf4, 0x3e, 0x6c, 0x4a, 0x8d, 0xa3, 0xde, 0xe6, 0xa0, 0x24, 0x26, 0xe7, 0xcc, 0x92, 0x1b, 0xb0,
|
||||
0x8a, 0x7b, 0xd6, 0x02, 0x9e, 0x87, 0xdf, 0x93, 0x71, 0xb6, 0xe7, 0x57, 0xe0, 0x88, 0x8b, 0xcf,
|
||||
0xd1, 0xc1, 0x95, 0xc5, 0x84, 0x0a, 0x5c, 0xe0, 0x06, 0xcf, 0x5d, 0xdc, 0x96, 0xc2, 0x2d, 0xc1,
|
||||
0x69, 0x17, 0xda, 0x87, 0x3c, 0x49, 0xf5, 0xa5, 0x2c, 0x43, 0x47, 0x0e, 0x55, 0x01, 0xe9, 0x32,
|
||||
0x5c, 0x12, 0x52, 0xf4, 0x34, 0x49, 0x93, 0x28, 0x19, 0xcf, 0x0e, 0xa7, 0x47, 0xf9, 0x30, 0x0b,
|
||||
0x53, 0xf4, 0x98, 0xe9, 0x3f, 0x79, 0xb0, 0xee, 0xcc, 0xaa, 0x50, 0xff, 0x97, 0xa5, 0x48, 0x9b,
|
||||
0x9c, 0xbf, 0x14, 0xbc, 0x35, 0x4b, 0x1d, 0x4a, 0x44, 0x99, 0x12, 0xf9, 0x4c, 0x95, 0x01, 0xee,
|
||||
0xc2, 0x8a, 0xde, 0x99, 0xfe, 0x50, 0x4a, 0x61, 0xaf, 0x2a, 0x85, 0xea, 0xfb, 0x65, 0xf5, 0x81,
|
||||
0x26, 0xf1, 0x6b, 0xd2, 0xef, 0x64, 0x23, 0x71, 0x46, 0x1d, 0xf3, 0xf5, 0xf5, 0xf7, 0xb6, 0xb3,
|
||||
0xab, 0x77, 0x30, 0x34, 0xc0, 0x9c, 0xfe, 0x89, 0x07, 0x50, 0xec, 0x0e, 0x05, 0xa3, 0x50, 0xe9,
|
||||
0x9e, 0xc8, 0x86, 0x5a, 0xea, 0xfb, 0x3a, 0x74, 0x4c, 0xce, 0xb9, 0xb0, 0x12, 0x6d, 0x0d, 0x43,
|
||||
0x0f, 0xe5, 0x5d, 0x58, 0x19, 0x47, 0xc9, 0x91, 0xb0, 0xb9, 0xa2, 0x56, 0x99, 0xab, 0x32, 0xda,
|
||||
0xb2, 0x04, 0x3f, 0x52, 0xd0, 0xc2, 0xa4, 0x34, 0x2d, 0x93, 0x42, 0xff, 0xb4, 0x61, 0x32, 0x9f,
|
||||
0xc5, 0x99, 0xcf, 0x7d, 0x65, 0x64, 0xb7, 0xa2, 0x1c, 0xcf, 0x49, 0x34, 0x8a, 0xec, 0xc6, 0xc1,
|
||||
0x6b, 0x03, 0xbd, 0x3b, 0xb0, 0x9c, 0x49, 0xed, 0xa3, 0x55, 0x53, 0xf3, 0x15, 0xaa, 0xa9, 0x9b,
|
||||
0x39, 0x76, 0xe7, 0xff, 0xc1, 0x6a, 0x30, 0x3a, 0x65, 0x19, 0x0f, 0x85, 0xc7, 0x2f, 0x8c, 0xbe,
|
||||
0x54, 0xa8, 0x2b, 0x16, 0x5c, 0xd8, 0xe2, 0x77, 0x61, 0x45, 0x95, 0x2e, 0x0d, 0xa6, 0xea, 0x48,
|
||||
0x29, 0xc0, 0x88, 0x48, 0xff, 0x5a, 0x27, 0x59, 0xdd, 0x3b, 0x3c, 0x9f, 0x23, 0xf6, 0xe9, 0x1a,
|
||||
0xa5, 0xd3, 0x7d, 0x55, 0x25, 0x3c, 0x47, 0x3a, 0xac, 0x50, 0xa9, 0x67, 0x09, 0x54, 0x09, 0x6a,
|
||||
0x97, 0xa5, 0xcd, 0x37, 0x61, 0x29, 0xfd, 0xd1, 0x1c, 0x2c, 0x7e, 0x1c, 0x9f, 0x26, 0xe1, 0x50,
|
||||
0xa4, 0x1f, 0x27, 0x6c, 0x92, 0xe8, 0x06, 0x02, 0xfc, 0x8d, 0x16, 0x5d, 0xd4, 0xc6, 0x52, 0xae,
|
||||
0xf2, 0x82, 0x7a, 0x88, 0xd6, 0x2d, 0x2b, 0x9a, 0x66, 0xa4, 0xa4, 0x58, 0x10, 0xf4, 0x0f, 0x33,
|
||||
0xbb, 0x0f, 0x48, 0x8d, 0x8a, 0x0e, 0x8c, 0x79, 0xab, 0x03, 0x43, 0x24, 0xab, 0x65, 0xd9, 0x4f,
|
||||
0xb0, 0x73, 0xc9, 0xd7, 0x43, 0xe1, 0xc7, 0x66, 0x4c, 0x06, 0xbd, 0xc2, 0x4e, 0x2e, 0x2a, 0x3f,
|
||||
0xd6, 0x06, 0xa2, 0x2d, 0x95, 0x1f, 0x48, 0x1c, 0xa9, 0x6b, 0x6c, 0x10, 0xfa, 0x16, 0xe5, 0x56,
|
||||
0xa2, 0x96, 0xbc, 0xe2, 0x12, 0x18, 0x15, 0xd2, 0x88, 0x19, 0xbd, 0x21, 0xcf, 0x00, 0xb2, 0x29,
|
||||
0xa8, 0x0c, 0xb7, 0xbc, 0x60, 0x59, 0xbe, 0x54, 0x23, 0xe1, 0x83, 0x04, 0x51, 0x74, 0x14, 0x0c,
|
||||
0x9f, 0x89, 0x06, 0x2f, 0x51, 0xad, 0x6c, 0xf9, 0x2e, 0x10, 0x77, 0x3d, 0x8c, 0xf8, 0xe9, 0x40,
|
||||
0x91, 0xe8, 0xca, 0x6a, 0xa3, 0x05, 0xa2, 0x9f, 0x03, 0xb9, 0x3b, 0x1a, 0xa9, 0x1b, 0x32, 0x31,
|
||||
0x42, 0xc1, 0x5b, 0xcf, 0xe1, 0x6d, 0xcd, 0x19, 0x1b, 0xb5, 0x67, 0xa4, 0x0f, 0xa1, 0x7d, 0x60,
|
||||
0xf5, 0x65, 0x89, 0xcb, 0xd4, 0x1d, 0x59, 0x4a, 0x00, 0x2c, 0x88, 0xb5, 0x60, 0xc3, 0x5e, 0x90,
|
||||
0xfe, 0x0a, 0x90, 0xfd, 0x30, 0xe7, 0x66, 0x7f, 0x26, 0x54, 0x34, 0x19, 0x2f, 0x2b, 0x54, 0x54,
|
||||
0x30, 0x11, 0x2a, 0xde, 0x95, 0x45, 0xcf, 0xf2, 0xc1, 0x6e, 0xc0, 0x52, 0x28, 0x41, 0x5a, 0x0f,
|
||||
0x2f, 0x2b, 0x01, 0xd6, 0x98, 0x66, 0x1e, 0x1d, 0x0a, 0x05, 0x74, 0xd4, 0xfc, 0x0f, 0x3c, 0x58,
|
||||
0x54, 0x47, 0x43, 0x73, 0xe8, 0x74, 0xa4, 0xc9, 0x83, 0x39, 0xb0, 0xfa, 0x8e, 0xa0, 0xaa, 0xd4,
|
||||
0xcd, 0xd5, 0x49, 0x1d, 0x81, 0x66, 0x1a, 0xf0, 0x13, 0xe1, 0x41, 0xb7, 0x7c, 0xf1, 0x5b, 0x47,
|
||||
0x4a, 0xf3, 0x26, 0x52, 0xd2, 0x05, 0x5e, 0xb5, 0x29, 0x93, 0xb5, 0xbc, 0x27, 0x0b, 0xbc, 0x05,
|
||||
0xb8, 0xe0, 0x81, 0xda, 0x60, 0x99, 0x07, 0x0a, 0xd5, 0x37, 0xf3, 0xb4, 0x0f, 0xbd, 0x07, 0x2c,
|
||||
0x62, 0x9c, 0xdd, 0x8d, 0xa2, 0x32, 0xfd, 0xcb, 0x70, 0xa9, 0x66, 0x4e, 0x59, 0xca, 0x47, 0xb0,
|
||||
0xf6, 0x80, 0x1d, 0x4d, 0xc7, 0xfb, 0xec, 0xb4, 0x28, 0x18, 0x10, 0x68, 0xe6, 0x27, 0xc9, 0x99,
|
||||
0xba, 0x2f, 0xf1, 0x9b, 0xbc, 0x05, 0x10, 0x21, 0xce, 0x20, 0x4f, 0xd9, 0x50, 0xb7, 0xb3, 0x08,
|
||||
0xc8, 0x61, 0xca, 0x86, 0xf4, 0x7d, 0x20, 0x36, 0x1d, 0x75, 0x04, 0x7c, 0x8d, 0xd3, 0xa3, 0x41,
|
||||
0x3e, 0xcb, 0x39, 0x9b, 0xe8, 0x3e, 0x1d, 0x1b, 0x44, 0xdf, 0x85, 0xce, 0x41, 0x30, 0xf3, 0xd9,
|
||||
0x97, 0xaa, 0xd1, 0x0f, 0x03, 0xb2, 0x60, 0x86, 0xe2, 0x69, 0x02, 0x32, 0x31, 0x4d, 0xff, 0xb1,
|
||||
0x01, 0x0b, 0x12, 0x13, 0xa9, 0x8e, 0x58, 0xce, 0xc3, 0x58, 0xa6, 0xd5, 0x15, 0x55, 0x0b, 0x54,
|
||||
0xb9, 0xef, 0x46, 0xcd, 0x7d, 0x2b, 0x17, 0x49, 0x97, 0xfe, 0xd5, 0xc5, 0x3a, 0x30, 0x11, 0x6f,
|
||||
0x86, 0x13, 0x26, 0xfb, 0x3d, 0x9b, 0x2a, 0xde, 0xd4, 0x80, 0x52, 0xe4, 0x5b, 0xbc, 0x79, 0xb9,
|
||||
0x3f, 0x2d, 0x88, 0xca, 0x2c, 0xd8, 0xa0, 0x5a, 0xcd, 0xb2, 0x28, 0x3b, 0xfb, 0x2a, 0x9a, 0xa5,
|
||||
0xa2, 0x41, 0x96, 0xde, 0x40, 0x83, 0x48, 0xbf, 0xc9, 0xd1, 0x20, 0x04, 0x56, 0x1f, 0x31, 0xe6,
|
||||
0xb3, 0x34, 0xc9, 0x4c, 0xb7, 0xe4, 0x0f, 0x3d, 0x58, 0x55, 0x16, 0xc1, 0xcc, 0x91, 0xeb, 0x8e,
|
||||
0xf9, 0xf0, 0xea, 0x32, 0xad, 0x6f, 0x43, 0x57, 0x04, 0x50, 0x18, 0x1d, 0x89, 0x68, 0x49, 0xe5,
|
||||
0x14, 0x1c, 0x20, 0xee, 0x49, 0xe7, 0x0e, 0x27, 0x61, 0xa4, 0x18, 0x6c, 0x83, 0xd0, 0xd4, 0xe9,
|
||||
0x00, 0x4b, 0xb0, 0xd7, 0xf3, 0xcd, 0x98, 0x1e, 0xc0, 0x9a, 0xb5, 0x5f, 0x25, 0x50, 0x77, 0x40,
|
||||
0xd7, 0x1b, 0x65, 0x8a, 0x40, 0xbe, 0x8b, 0x2d, 0xd7, 0xb8, 0x15, 0x9f, 0x39, 0xc8, 0xf4, 0xef,
|
||||
0x3d, 0xc1, 0x02, 0xe5, 0x43, 0x99, 0xfe, 0xa4, 0x05, 0xe9, 0xd6, 0x48, 0x69, 0xdf, 0xbb, 0xe0,
|
||||
0xab, 0x31, 0xf9, 0xfa, 0x1b, 0x7a, 0x26, 0xa6, 0xae, 0x77, 0x0e, 0x6f, 0xe6, 0xea, 0x78, 0xf3,
|
||||
0x8a, 0x93, 0xdf, 0x5b, 0x84, 0xf9, 0x7c, 0x98, 0xa4, 0x8c, 0xae, 0x0b, 0x16, 0xe8, 0xfd, 0x4a,
|
||||
0x16, 0xec, 0xfe, 0x9b, 0x07, 0xcb, 0x32, 0xb9, 0x26, 0xfb, 0xaa, 0x59, 0x46, 0x30, 0x76, 0xb2,
|
||||
0xda, 0xb5, 0x89, 0x71, 0x1d, 0xab, 0x6d, 0xdf, 0xfd, 0xcb, 0xb5, 0x73, 0xda, 0x6f, 0xfe, 0xfe,
|
||||
0x4f, 0xfe, 0xe3, 0xcf, 0x1a, 0x17, 0xe9, 0xea, 0xce, 0xe9, 0xed, 0x1d, 0xa1, 0xe2, 0xd8, 0x99,
|
||||
0xc0, 0xf8, 0xd0, 0xbb, 0x81, 0xab, 0xd8, 0x9d, 0xdc, 0x66, 0x95, 0x9a, 0x8e, 0x70, 0xb3, 0x4a,
|
||||
0x6d, 0xeb, 0xb7, 0xb3, 0xca, 0x54, 0x60, 0x98, 0x55, 0x76, 0x7f, 0xda, 0x87, 0x96, 0x09, 0xf2,
|
||||
0xc8, 0x77, 0xa1, 0xeb, 0x24, 0x12, 0x89, 0x26, 0x5c, 0x97, 0x9a, 0xec, 0x5f, 0xa9, 0x9f, 0x54,
|
||||
0xcb, 0x5e, 0x15, 0xcb, 0xf6, 0xc8, 0x26, 0x2e, 0xab, 0xb2, 0x77, 0x3b, 0x22, 0xc3, 0x2a, 0x7b,
|
||||
0x0e, 0x9e, 0xc1, 0xb2, 0x9b, 0xfc, 0x23, 0x57, 0xdc, 0xdb, 0x2e, 0xad, 0xf6, 0xd6, 0x39, 0xb3,
|
||||
0x6a, 0xb9, 0x2b, 0x62, 0xb9, 0x4d, 0xb2, 0x61, 0x2f, 0x67, 0x82, 0x2f, 0x26, 0xba, 0x44, 0xec,
|
||||
0x16, 0x6f, 0xa2, 0xe9, 0xd5, 0xb7, 0x7e, 0xf7, 0x2f, 0x55, 0xdb, 0xb9, 0x55, 0xff, 0x37, 0xed,
|
||||
0x89, 0xa5, 0x08, 0x11, 0x0c, 0xb5, 0x3b, 0xbc, 0xc9, 0x77, 0xa0, 0x65, 0x1a, 0x44, 0xc9, 0x96,
|
||||
0xd5, 0x95, 0x6b, 0x77, 0xad, 0xf6, 0x7b, 0xd5, 0x89, 0xba, 0xab, 0xb2, 0x29, 0xa3, 0x40, 0xec,
|
||||
0xc3, 0x45, 0x65, 0x71, 0x8f, 0xd8, 0xcf, 0x72, 0x92, 0x9a, 0xc6, 0xf4, 0x5b, 0x1e, 0xb9, 0x03,
|
||||
0x4b, 0xba, 0xef, 0x96, 0x6c, 0xd6, 0xf7, 0x0f, 0xf7, 0xb7, 0x2a, 0x70, 0xa5, 0x17, 0xee, 0x02,
|
||||
0x14, 0x2d, 0xa2, 0xa4, 0x77, 0x5e, 0x27, 0xab, 0x61, 0x62, 0x4d, 0x3f, 0xe9, 0x58, 0x74, 0xc8,
|
||||
0xba, 0x1d, 0xa8, 0xe4, 0x2b, 0x05, 0x7e, 0x6d, 0x6f, 0xea, 0x2b, 0x08, 0xd2, 0x4d, 0xc1, 0xbb,
|
||||
0x55, 0xb2, 0x8c, 0xbc, 0x8b, 0xd9, 0x99, 0xee, 0x97, 0x7a, 0x00, 0x6d, 0xab, 0xed, 0x94, 0x68,
|
||||
0x0a, 0xd5, 0x96, 0xd5, 0x7e, 0xbf, 0x6e, 0x4a, 0x6d, 0xf7, 0x37, 0xa0, 0xeb, 0xf4, 0x8f, 0x9a,
|
||||
0x97, 0x51, 0xd7, 0x9d, 0x6a, 0x5e, 0x46, 0x7d, 0xcb, 0xe9, 0xb7, 0xa1, 0x6d, 0x75, 0x7b, 0x12,
|
||||
0xab, 0x42, 0x5d, 0xea, 0xe6, 0x34, 0x3b, 0xaa, 0x69, 0x0e, 0xa5, 0x1b, 0xe2, 0xbc, 0xcb, 0xb4,
|
||||
0x85, 0xe7, 0x15, 0x4d, 0x43, 0x28, 0x24, 0xdf, 0x85, 0x65, 0xb7, 0xcb, 0xd3, 0xbc, 0xaa, 0xda,
|
||||
0x7e, 0x51, 0xf3, 0xaa, 0xce, 0x69, 0x0d, 0x55, 0x02, 0x79, 0x63, 0xdd, 0x2c, 0xb2, 0xf3, 0x42,
|
||||
0xa5, 0x38, 0x5f, 0x92, 0x6f, 0xa1, 0xea, 0x50, 0x5d, 0x5c, 0xa4, 0xe8, 0x7a, 0x75, 0x7b, 0xbd,
|
||||
0x8c, 0xb4, 0x57, 0x1a, 0xbe, 0xe8, 0x9a, 0x20, 0xde, 0x26, 0xc5, 0x09, 0xc8, 0x27, 0xb0, 0xa8,
|
||||
0xba, 0xb9, 0xc8, 0xc5, 0x42, 0xaa, 0xad, 0x84, 0x50, 0x7f, 0xb3, 0x0c, 0x56, 0xc4, 0xd6, 0x05,
|
||||
0xb1, 0x2e, 0x69, 0x23, 0xb1, 0x31, 0xe3, 0x21, 0xd2, 0x88, 0x61, 0xa5, 0x54, 0x95, 0x32, 0x8f,
|
||||
0xa5, 0xbe, 0xa6, 0xdd, 0xbf, 0xfa, 0xea, 0x62, 0x96, 0xab, 0x66, 0xb4, 0x7a, 0xd9, 0xd1, 0x2d,
|
||||
0x08, 0xbf, 0x0d, 0x1d, 0xbb, 0x79, 0xd0, 0xe8, 0xec, 0x9a, 0x46, 0x43, 0xa3, 0xb3, 0xeb, 0xba,
|
||||
0x0d, 0xf5, 0xe5, 0x92, 0x8e, 0xbd, 0x0c, 0xf9, 0x36, 0xac, 0x58, 0xf5, 0xcf, 0xc3, 0x59, 0x3c,
|
||||
0x34, 0xc2, 0x53, 0xed, 0x43, 0xe9, 0xd7, 0x19, 0x4f, 0xba, 0x25, 0x08, 0xaf, 0x51, 0x87, 0x30,
|
||||
0x0a, 0xce, 0x7d, 0x68, 0xdb, 0xb5, 0xd5, 0x57, 0xd0, 0xdd, 0xb2, 0xa6, 0xec, 0xe6, 0x8d, 0x5b,
|
||||
0x1e, 0xf9, 0x0b, 0x0f, 0x3a, 0x76, 0x87, 0x13, 0x71, 0xb2, 0x2a, 0x25, 0x3a, 0x3d, 0x7b, 0xce,
|
||||
0x26, 0x44, 0x9f, 0x88, 0x4d, 0xee, 0xdd, 0x78, 0xe4, 0x30, 0xf9, 0x85, 0xe3, 0x14, 0xdd, 0xb4,
|
||||
0xff, 0x93, 0xf1, 0xb2, 0x3c, 0x69, 0xf7, 0xe9, 0xbc, 0xbc, 0xe5, 0x91, 0x0f, 0xe5, 0x3f, 0x6f,
|
||||
0x74, 0x80, 0x42, 0x2c, 0xc5, 0x56, 0x66, 0x97, 0xfd, 0x77, 0x96, 0x6d, 0xef, 0x96, 0x47, 0x7e,
|
||||
0x47, 0xfe, 0x0d, 0x43, 0x7d, 0x2b, 0xb8, 0xfe, 0xa6, 0xdf, 0xd3, 0xb7, 0xc5, 0x49, 0xae, 0xd2,
|
||||
0x4b, 0xce, 0x49, 0xca, 0x9a, 0xfd, 0x00, 0xa0, 0x88, 0x36, 0x49, 0x29, 0xf4, 0x32, 0x3a, 0xaf,
|
||||
0x1a, 0x90, 0xba, 0xb7, 0xa9, 0x23, 0x34, 0xa9, 0x06, 0x3a, 0x56, 0x9c, 0x97, 0x9b, 0xeb, 0xac,
|
||||
0x46, 0x8d, 0xfd, 0x7e, 0xdd, 0x94, 0xa2, 0xff, 0x55, 0x41, 0xff, 0x2d, 0x72, 0xd9, 0xa6, 0xbf,
|
||||
0xf3, 0xc2, 0x8e, 0x32, 0x5f, 0x92, 0xcf, 0xa1, 0xbb, 0x9f, 0x24, 0xcf, 0xa6, 0xa9, 0x49, 0x68,
|
||||
0xb8, 0x71, 0x13, 0x46, 0xba, 0xfd, 0xd2, 0xa1, 0xe8, 0x75, 0x41, 0xf9, 0x32, 0xb9, 0xe4, 0x52,
|
||||
0x2e, 0x62, 0xdf, 0x97, 0x24, 0x80, 0x35, 0x63, 0xef, 0xcc, 0x41, 0xfa, 0x2e, 0x1d, 0x3b, 0x04,
|
||||
0xad, 0xac, 0xe1, 0x78, 0x20, 0x66, 0x8d, 0x5c, 0xd3, 0xbc, 0xe5, 0x91, 0x03, 0xe8, 0x3c, 0x60,
|
||||
0xc3, 0x64, 0xc4, 0x54, 0xa8, 0xb3, 0x5e, 0xec, 0xdc, 0xc4, 0x48, 0xfd, 0xae, 0x03, 0x74, 0x35,
|
||||
0x40, 0x1a, 0xcc, 0x32, 0xf6, 0xe5, 0xce, 0x0b, 0x15, 0x44, 0xbd, 0xd4, 0x1a, 0x40, 0x07, 0x7e,
|
||||
0x8e, 0x06, 0x28, 0x45, 0x8a, 0x8e, 0x06, 0xa8, 0x44, 0x8a, 0x8e, 0x06, 0xd0, 0x81, 0x27, 0x89,
|
||||
0x30, 0x7e, 0x2c, 0x05, 0x97, 0xc6, 0x6a, 0x9e, 0x17, 0x92, 0xf6, 0xaf, 0x9d, 0x8f, 0xe0, 0xae,
|
||||
0x76, 0xc3, 0x5d, 0xed, 0x10, 0xba, 0x0f, 0x98, 0x64, 0x96, 0xac, 0x12, 0xf4, 0x5d, 0x95, 0x62,
|
||||
0x57, 0x14, 0xca, 0xea, 0x46, 0xcc, 0xb9, 0x2a, 0x5e, 0xa4, 0xe8, 0xc9, 0x77, 0xa0, 0xfd, 0x98,
|
||||
0x71, 0x5d, 0x16, 0x30, 0xbe, 0x47, 0xa9, 0x4e, 0xd0, 0xaf, 0xa9, 0x2a, 0xd0, 0x6b, 0x82, 0x5a,
|
||||
0x9f, 0xf4, 0x0c, 0xb5, 0x1d, 0x36, 0x1a, 0x33, 0xf9, 0xf8, 0x07, 0xe1, 0xe8, 0x25, 0xf9, 0x4d,
|
||||
0x41, 0xdc, 0x54, 0x12, 0x37, 0xad, 0x6c, 0xb2, 0x4d, 0x7c, 0xa5, 0x04, 0xaf, 0xa3, 0x1c, 0x27,
|
||||
0x23, 0x66, 0x19, 0xbb, 0x18, 0xda, 0x56, 0xd9, 0xd8, 0x3c, 0xa8, 0x6a, 0x2d, 0xda, 0x3c, 0xa8,
|
||||
0x9a, 0x2a, 0x33, 0xdd, 0x16, 0xeb, 0x50, 0x72, 0xad, 0x58, 0x47, 0x56, 0x96, 0x8b, 0x95, 0x76,
|
||||
0x5e, 0x04, 0x13, 0xfe, 0x92, 0x7c, 0x21, 0x5a, 0x9d, 0xed, 0xd2, 0x47, 0xe1, 0xfb, 0x94, 0xab,
|
||||
0x24, 0x86, 0x59, 0xd6, 0x94, 0xeb, 0x0f, 0xc9, 0xa5, 0x84, 0x4d, 0xfc, 0x3a, 0xc0, 0x21, 0x4f,
|
||||
0xd2, 0x07, 0x01, 0x9b, 0x24, 0x71, 0xa1, 0xc9, 0x8a, 0xf4, 0x7e, 0xa1, 0xc9, 0xac, 0x1c, 0x3f,
|
||||
0xf9, 0xc2, 0xf2, 0x3e, 0x9d, 0xca, 0x91, 0x16, 0xae, 0x73, 0x2b, 0x00, 0x86, 0x21, 0x35, 0x55,
|
||||
0x80, 0x5b, 0x1e, 0xfa, 0x92, 0x45, 0x2a, 0xc3, 0xf8, 0x92, 0x95, 0x2c, 0x89, 0x51, 0x83, 0x35,
|
||||
0x79, 0x8f, 0x03, 0x68, 0x15, 0xf1, 0xb4, 0x36, 0x4f, 0xe5, 0xe8, 0xdb, 0xd8, 0x9b, 0x4a, 0x98,
|
||||
0x4b, 0x57, 0x05, 0xab, 0x80, 0x2c, 0x21, 0xab, 0x44, 0xe5, 0xfb, 0x29, 0x80, 0xdc, 0xe0, 0x23,
|
||||
0x1c, 0x59, 0x24, 0x9d, 0x68, 0xd6, 0x26, 0xe9, 0x86, 0x8d, 0xda, 0x1d, 0xa1, 0x86, 0xe4, 0x87,
|
||||
0xde, 0x8d, 0xa3, 0x05, 0xf1, 0x0f, 0xe1, 0xaf, 0xfd, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6e,
|
||||
0x8c, 0x0d, 0xd9, 0x53, 0x3c, 0x00, 0x00,
|
||||
// 4877 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x3b, 0x4d, 0x8f, 0x1c, 0x49,
|
||||
0x56, 0xce, 0xea, 0xea, 0x8f, 0x7a, 0x55, 0xd5, 0x1f, 0xd1, 0xed, 0xee, 0x72, 0xd9, 0xeb, 0xb5,
|
||||
0x63, 0x47, 0x33, 0x8d, 0x19, 0xdc, 0x76, 0x2f, 0x3b, 0xcc, 0x8e, 0x81, 0x91, 0xbf, 0x7b, 0xd8,
|
||||
0x1e, 0x4f, 0x6f, 0xb6, 0x67, 0x06, 0x76, 0x85, 0x8a, 0xec, 0xaa, 0xe8, 0xea, 0x5c, 0x67, 0x65,
|
||||
0xe6, 0x64, 0x46, 0x75, 0xbb, 0xd6, 0x58, 0x82, 0x05, 0x21, 0x21, 0x81, 0xf6, 0x00, 0x02, 0xed,
|
||||
0x61, 0xb9, 0x70, 0x81, 0x03, 0xbf, 0x00, 0x89, 0x1f, 0xb0, 0x12, 0x02, 0x69, 0x4f, 0x08, 0x2e,
|
||||
0x08, 0x4e, 0x70, 0xe6, 0xc0, 0x0d, 0xbd, 0xf8, 0xca, 0x88, 0xcc, 0x6c, 0xdb, 0xcb, 0x2e, 0xdc,
|
||||
0x2a, 0x5e, 0xbc, 0x7c, 0x11, 0xf1, 0xe2, 0xc5, 0xfb, 0x2e, 0x68, 0x65, 0xe9, 0xf0, 0x66, 0x9a,
|
||||
0x25, 0x3c, 0x21, 0xf3, 0x51, 0x9c, 0xa5, 0xc3, 0xfe, 0x95, 0x71, 0x92, 0x8c, 0x23, 0xb6, 0x13,
|
||||
0xa4, 0xe1, 0x4e, 0x10, 0xc7, 0x09, 0x0f, 0x78, 0x98, 0xc4, 0xb9, 0x44, 0xa2, 0xb7, 0x61, 0xfd,
|
||||
0x7e, 0xc6, 0x02, 0xce, 0x3e, 0x0f, 0xa2, 0x88, 0x71, 0x9f, 0x7d, 0x31, 0x65, 0x39, 0x27, 0x7d,
|
||||
0x58, 0x4a, 0x83, 0x3c, 0x3f, 0x4b, 0xb2, 0x51, 0xcf, 0xbb, 0xe6, 0x6d, 0x77, 0x7c, 0x33, 0xa6,
|
||||
0x9b, 0xb0, 0xe1, 0x7e, 0x92, 0xa7, 0x49, 0x9c, 0x33, 0x24, 0xf5, 0x69, 0x1c, 0x25, 0xc3, 0x67,
|
||||
0x3f, 0x11, 0x29, 0xf7, 0x13, 0x45, 0xea, 0x07, 0x0d, 0x68, 0x3f, 0xcd, 0x82, 0x38, 0x0f, 0x86,
|
||||
0xb8, 0x59, 0xd2, 0x83, 0x45, 0xfe, 0x7c, 0x70, 0x12, 0xe4, 0x27, 0x82, 0x44, 0xcb, 0xd7, 0x43,
|
||||
0xb2, 0x09, 0x0b, 0xc1, 0x24, 0x99, 0xc6, 0xbc, 0xd7, 0xb8, 0xe6, 0x6d, 0xcf, 0xf9, 0x6a, 0x44,
|
||||
0xde, 0x85, 0xb5, 0x78, 0x3a, 0x19, 0x0c, 0x93, 0xf8, 0x38, 0xcc, 0x26, 0xf2, 0xc8, 0xbd, 0xb9,
|
||||
0x6b, 0xde, 0xf6, 0xbc, 0x5f, 0x9d, 0x20, 0x57, 0x01, 0x8e, 0x70, 0x1b, 0x72, 0x89, 0xa6, 0x58,
|
||||
0xc2, 0x82, 0x10, 0x0a, 0x1d, 0x35, 0x62, 0xe1, 0xf8, 0x84, 0xf7, 0xe6, 0x05, 0x21, 0x07, 0x86,
|
||||
0x34, 0x78, 0x38, 0x61, 0x83, 0x9c, 0x07, 0x93, 0xb4, 0xb7, 0x20, 0x76, 0x63, 0x41, 0xc4, 0x7c,
|
||||
0xc2, 0x83, 0x68, 0x70, 0xcc, 0x58, 0xde, 0x5b, 0x54, 0xf3, 0x06, 0x42, 0xde, 0x86, 0xe5, 0x11,
|
||||
0xcb, 0xf9, 0x20, 0x18, 0x8d, 0x32, 0x96, 0xe7, 0x2c, 0xef, 0x2d, 0x5d, 0x9b, 0xdb, 0x6e, 0xf9,
|
||||
0x25, 0x28, 0xed, 0xc1, 0xe6, 0x63, 0xc6, 0x2d, 0xee, 0xe4, 0x8a, 0xd3, 0x74, 0x1f, 0x88, 0x05,
|
||||
0x7e, 0xc0, 0x78, 0x10, 0x46, 0x39, 0x79, 0x0f, 0x3a, 0xdc, 0x42, 0xee, 0x79, 0xd7, 0xe6, 0xb6,
|
||||
0xdb, 0xbb, 0xe4, 0xa6, 0x90, 0x8e, 0x9b, 0xd6, 0x07, 0xbe, 0x83, 0x47, 0xff, 0xd1, 0x83, 0xf6,
|
||||
0x21, 0x8b, 0x47, 0xfa, 0x1e, 0x09, 0x34, 0x71, 0x27, 0xea, 0x0e, 0xc5, 0x6f, 0xf2, 0x65, 0x68,
|
||||
0x8b, 0xdd, 0xe5, 0x3c, 0x0b, 0xe3, 0xb1, 0xb8, 0x82, 0x96, 0x0f, 0x08, 0x3a, 0x14, 0x10, 0xb2,
|
||||
0x0a, 0x73, 0xc1, 0x84, 0x0b, 0xc6, 0xcf, 0xf9, 0xf8, 0x93, 0x5c, 0x87, 0x4e, 0x1a, 0xcc, 0x26,
|
||||
0x2c, 0xe6, 0x05, 0xb3, 0x3b, 0x7e, 0x5b, 0xc1, 0xf6, 0x90, 0xdb, 0x37, 0x61, 0xdd, 0x46, 0xd1,
|
||||
0xd4, 0xe7, 0x05, 0xf5, 0x35, 0x0b, 0x53, 0x2d, 0xf2, 0x0e, 0xac, 0x68, 0xfc, 0x4c, 0x6e, 0x56,
|
||||
0xb0, 0xbf, 0xe5, 0x2f, 0x2b, 0xb0, 0x66, 0xd0, 0x9f, 0x7a, 0xd0, 0x91, 0x47, 0x92, 0x72, 0x46,
|
||||
0xde, 0x82, 0xae, 0xfe, 0x92, 0x65, 0x59, 0x92, 0x29, 0xe9, 0x72, 0x81, 0xe4, 0x06, 0xac, 0x6a,
|
||||
0x40, 0x9a, 0xb1, 0x70, 0x12, 0x8c, 0x99, 0x38, 0x6a, 0xc7, 0xaf, 0xc0, 0xc9, 0x6e, 0x41, 0x31,
|
||||
0x4b, 0xa6, 0x9c, 0x89, 0xa3, 0xb7, 0x77, 0x3b, 0x8a, 0xdd, 0x3e, 0xc2, 0x7c, 0x17, 0x85, 0x7e,
|
||||
0xcf, 0x83, 0xce, 0xfd, 0x93, 0x20, 0x8e, 0x59, 0x74, 0x90, 0x84, 0x31, 0x47, 0x71, 0x3b, 0x9e,
|
||||
0xc6, 0xa3, 0x30, 0x1e, 0x0f, 0xf8, 0xf3, 0x50, 0x3f, 0x1b, 0x07, 0x86, 0x9b, 0xb2, 0xc7, 0xc8,
|
||||
0x24, 0xc5, 0xff, 0x0a, 0x1c, 0xe9, 0x25, 0x53, 0x9e, 0x4e, 0xf9, 0x20, 0x8c, 0x47, 0xec, 0xb9,
|
||||
0xd8, 0x53, 0xd7, 0x77, 0x60, 0xf4, 0x57, 0x61, 0x75, 0x1f, 0xe5, 0x38, 0x0e, 0xe3, 0xf1, 0x5d,
|
||||
0x29, 0x6c, 0xf8, 0xb8, 0xd2, 0xe9, 0xd1, 0x33, 0x36, 0x53, 0x7c, 0x51, 0x23, 0x14, 0x85, 0x93,
|
||||
0x24, 0xe7, 0x6a, 0x3d, 0xf1, 0x9b, 0xfe, 0x9b, 0x07, 0x2b, 0xc8, 0xdb, 0x8f, 0x83, 0x78, 0xa6,
|
||||
0x45, 0x66, 0x1f, 0x3a, 0x48, 0xea, 0x69, 0x72, 0x57, 0x3e, 0x51, 0x29, 0x7a, 0xdb, 0x8a, 0x17,
|
||||
0x25, 0xec, 0x9b, 0x36, 0xea, 0xc3, 0x98, 0x67, 0x33, 0xdf, 0xf9, 0x1a, 0x85, 0x8d, 0x07, 0xd9,
|
||||
0x98, 0x71, 0xf1, 0x78, 0xd5, 0x63, 0x06, 0x09, 0xba, 0x9f, 0xc4, 0xc7, 0xe4, 0x1a, 0x74, 0xf2,
|
||||
0x80, 0x0f, 0x52, 0x96, 0x0d, 0x8e, 0x66, 0x9c, 0x09, 0x81, 0x99, 0xf3, 0x21, 0x0f, 0xf8, 0x01,
|
||||
0xcb, 0xee, 0xcd, 0x38, 0xeb, 0x7f, 0x08, 0x6b, 0x95, 0x55, 0x50, 0x46, 0x8b, 0x23, 0xe2, 0x4f,
|
||||
0xb2, 0x01, 0xf3, 0xa7, 0x41, 0x34, 0x65, 0x4a, 0xa7, 0xc8, 0xc1, 0x07, 0x8d, 0xf7, 0x3d, 0xfa,
|
||||
0x36, 0xac, 0x16, 0xdb, 0x56, 0x42, 0x44, 0xa0, 0x69, 0x6e, 0xa9, 0xe5, 0x8b, 0xdf, 0xf4, 0x77,
|
||||
0x3d, 0x89, 0x78, 0x3f, 0x09, 0xcd, 0xfb, 0x44, 0x44, 0x7c, 0xc6, 0x1a, 0x11, 0x7f, 0x9f, 0xab,
|
||||
0xbf, 0x7e, 0xfa, 0xc3, 0xd2, 0x77, 0x60, 0xcd, 0xda, 0xc2, 0x2b, 0x36, 0xfb, 0x17, 0x1e, 0xac,
|
||||
0x3d, 0x61, 0x67, 0xea, 0xd6, 0xf5, 0x6e, 0xdf, 0x87, 0x26, 0x9f, 0xa5, 0x4c, 0x60, 0x2e, 0xef,
|
||||
0xbe, 0xa5, 0x2e, 0xad, 0x82, 0x77, 0x53, 0x0d, 0x9f, 0xce, 0x52, 0xe6, 0x8b, 0x2f, 0xe8, 0x27,
|
||||
0xd0, 0xb6, 0x80, 0x64, 0x0b, 0xd6, 0x3f, 0xff, 0xe8, 0xe9, 0x93, 0x87, 0x87, 0x87, 0x83, 0x83,
|
||||
0x4f, 0xef, 0x7d, 0xe3, 0xe1, 0x6f, 0x0c, 0xf6, 0xee, 0x1e, 0xee, 0xad, 0x5e, 0x20, 0x9b, 0x40,
|
||||
0x9e, 0x3c, 0x3c, 0x7c, 0xfa, 0xf0, 0x81, 0x03, 0xf7, 0xc8, 0x0a, 0xb4, 0x6d, 0x40, 0x83, 0xf6,
|
||||
0xa1, 0xf7, 0x84, 0x9d, 0x7d, 0x1e, 0xf2, 0x98, 0xe5, 0xb9, 0xbb, 0x3c, 0xbd, 0x09, 0xc4, 0xde,
|
||||
0x93, 0x3a, 0x66, 0x0f, 0x16, 0x95, 0xc6, 0xd4, 0x06, 0x43, 0x0d, 0xe9, 0xdb, 0x40, 0x0e, 0xc3,
|
||||
0x71, 0xfc, 0x31, 0xcb, 0xf3, 0x60, 0xcc, 0xf4, 0x61, 0x57, 0x61, 0x6e, 0x92, 0x8f, 0xd5, 0x43,
|
||||
0xc3, 0x9f, 0xf4, 0xab, 0xb0, 0xee, 0xe0, 0x29, 0xc2, 0x57, 0xa0, 0x95, 0x87, 0xe3, 0x38, 0xe0,
|
||||
0xd3, 0x8c, 0x29, 0xd2, 0x05, 0x80, 0x3e, 0x82, 0x8d, 0xcf, 0x58, 0x16, 0x1e, 0xcf, 0x5e, 0x47,
|
||||
0xde, 0xa5, 0xd3, 0x28, 0xd3, 0x79, 0x08, 0x17, 0x4b, 0x74, 0xd4, 0xf2, 0x52, 0x32, 0xd5, 0xfd,
|
||||
0x2d, 0xf9, 0x72, 0x60, 0xbd, 0xd3, 0x86, 0xfd, 0x4e, 0xe9, 0xa7, 0x40, 0xee, 0x27, 0x71, 0xcc,
|
||||
0x86, 0xfc, 0x80, 0xb1, 0x4c, 0x6f, 0xe6, 0xe7, 0x2d, 0x31, 0x6c, 0xef, 0x6e, 0xa9, 0x8b, 0x2d,
|
||||
0x3f, 0x7e, 0x25, 0x9f, 0x04, 0x9a, 0x29, 0xcb, 0x26, 0x82, 0xf0, 0x92, 0x2f, 0x7e, 0xd3, 0x1d,
|
||||
0x58, 0x77, 0xc8, 0x16, 0x3c, 0x4f, 0x19, 0xcb, 0x06, 0x6a, 0x77, 0xf3, 0xbe, 0x1e, 0xd2, 0xdb,
|
||||
0x70, 0xf1, 0x41, 0x98, 0x0f, 0xab, 0x5b, 0xc1, 0x4f, 0xa6, 0x47, 0x83, 0xe2, 0xf9, 0xe9, 0x21,
|
||||
0x5a, 0xb9, 0xf2, 0x27, 0xca, 0x37, 0xf8, 0x03, 0x0f, 0x9a, 0x7b, 0x4f, 0xf7, 0xef, 0xa3, 0x63,
|
||||
0x11, 0xc6, 0xc3, 0x64, 0x82, 0xb6, 0x41, 0xb2, 0xc3, 0x8c, 0xcf, 0x7d, 0x56, 0x57, 0xa0, 0x25,
|
||||
0x4c, 0x0a, 0x1a, 0x6e, 0xf1, 0xa8, 0x3a, 0x7e, 0x01, 0x40, 0xa7, 0x81, 0x3d, 0x4f, 0xc3, 0x4c,
|
||||
0x78, 0x05, 0xda, 0xd6, 0x37, 0x85, 0xb2, 0xac, 0x4e, 0xd0, 0xff, 0x68, 0x42, 0xf7, 0xee, 0x90,
|
||||
0x87, 0xa7, 0x4c, 0x29, 0x6f, 0xb1, 0xaa, 0x00, 0xa8, 0xfd, 0xa8, 0x11, 0x9a, 0x99, 0x8c, 0x4d,
|
||||
0x12, 0xce, 0x06, 0xce, 0x35, 0xb9, 0x40, 0xc4, 0x1a, 0x4a, 0x42, 0x83, 0x14, 0xcd, 0x80, 0xd8,
|
||||
0x5f, 0xcb, 0x77, 0x81, 0xc8, 0x32, 0x04, 0x20, 0x97, 0x71, 0x67, 0x4d, 0x5f, 0x0f, 0x91, 0x1f,
|
||||
0xc3, 0x20, 0x0d, 0x86, 0x21, 0x9f, 0x29, 0x6d, 0x60, 0xc6, 0x48, 0x3b, 0x4a, 0x86, 0x41, 0x34,
|
||||
0x38, 0x0a, 0xa2, 0x20, 0x1e, 0x32, 0xe5, 0x9f, 0xb8, 0x40, 0x74, 0x41, 0xd4, 0x96, 0x34, 0x9a,
|
||||
0x74, 0x53, 0x4a, 0x50, 0x74, 0x65, 0x86, 0xc9, 0x64, 0x12, 0x72, 0xf4, 0x5c, 0x7a, 0x4b, 0x52,
|
||||
0xf3, 0x14, 0x10, 0x71, 0x12, 0x39, 0x3a, 0x93, 0x3c, 0x6c, 0xc9, 0xd5, 0x1c, 0x20, 0x52, 0x39,
|
||||
0x66, 0x4c, 0x68, 0xb0, 0x67, 0x67, 0x3d, 0x90, 0x54, 0x0a, 0x08, 0xde, 0xc6, 0x34, 0xce, 0x19,
|
||||
0xe7, 0x11, 0x1b, 0x99, 0x0d, 0xb5, 0x05, 0x5a, 0x75, 0x82, 0xdc, 0x82, 0x75, 0xe9, 0x4c, 0xe5,
|
||||
0x01, 0x4f, 0xf2, 0x93, 0x30, 0x1f, 0xe4, 0x2c, 0xe6, 0xbd, 0x8e, 0xc0, 0xaf, 0x9b, 0x22, 0xef,
|
||||
0xc3, 0x56, 0x09, 0x9c, 0xb1, 0x21, 0x0b, 0x4f, 0xd9, 0xa8, 0xd7, 0x15, 0x5f, 0x9d, 0x37, 0x4d,
|
||||
0xae, 0x41, 0x1b, 0x7d, 0xc8, 0x69, 0x3a, 0x0a, 0x38, 0xcb, 0x7b, 0xcb, 0xe2, 0x1e, 0x6c, 0x10,
|
||||
0xb9, 0x0d, 0xdd, 0x94, 0x49, 0x2b, 0x7c, 0xc2, 0xa3, 0x61, 0xde, 0x5b, 0x11, 0xa6, 0xaf, 0xad,
|
||||
0x1e, 0x1b, 0xca, 0xaf, 0xef, 0x62, 0xa0, 0x68, 0x0e, 0xf3, 0xd3, 0xc1, 0x88, 0x45, 0xc1, 0xac,
|
||||
0xb7, 0x2a, 0x84, 0xae, 0x00, 0xd0, 0x8b, 0xb0, 0xbe, 0x1f, 0xe6, 0x5c, 0x49, 0x9a, 0xd1, 0x7e,
|
||||
0x7b, 0xb0, 0xe1, 0x82, 0xd5, 0x5b, 0xbc, 0x05, 0x4b, 0x4a, 0x6c, 0xf2, 0x5e, 0x5b, 0x2c, 0xbd,
|
||||
0xa1, 0x96, 0x76, 0x24, 0xd6, 0x37, 0x58, 0xf4, 0xf7, 0x1b, 0xd0, 0xc4, 0x77, 0x76, 0xfe, 0x9b,
|
||||
0xb4, 0x1f, 0x78, 0xc3, 0x79, 0xe0, 0xb6, 0xba, 0x9d, 0x73, 0xd4, 0xad, 0xf0, 0xac, 0x67, 0x9c,
|
||||
0xa9, 0xdb, 0x90, 0x12, 0x6b, 0x41, 0x8a, 0xf9, 0x8c, 0x0d, 0x4f, 0x85, 0xd8, 0x9a, 0x79, 0x84,
|
||||
0xa0, 0x50, 0xa3, 0x99, 0x13, 0x5f, 0x4b, 0x99, 0x35, 0x63, 0x3d, 0x27, 0xbe, 0x5c, 0x2c, 0xe6,
|
||||
0xc4, 0x77, 0x3d, 0x58, 0x0c, 0xe3, 0xa3, 0x64, 0x1a, 0x8f, 0x84, 0x7c, 0x2e, 0xf9, 0x7a, 0x88,
|
||||
0x7c, 0x4e, 0x85, 0x77, 0x14, 0x4e, 0x98, 0x12, 0xcc, 0x02, 0x40, 0x09, 0xba, 0x41, 0xb9, 0xd0,
|
||||
0x38, 0x86, 0xc9, 0xef, 0xc1, 0x9a, 0x05, 0x53, 0x1c, 0xbe, 0x0e, 0xf3, 0x78, 0x7a, 0xed, 0x4f,
|
||||
0xeb, 0x9b, 0x15, 0xaa, 0x4a, 0xce, 0xd0, 0x55, 0x58, 0x7e, 0xcc, 0xf8, 0x47, 0xf1, 0x71, 0xa2,
|
||||
0x29, 0xfd, 0xe1, 0x1c, 0xac, 0x18, 0x90, 0x22, 0xb4, 0x0d, 0x2b, 0xe1, 0x88, 0xc5, 0x3c, 0xe4,
|
||||
0xb3, 0x81, 0xe3, 0x6d, 0x95, 0xc1, 0xa8, 0xfc, 0x83, 0x28, 0x0c, 0x72, 0xa5, 0x3e, 0xe4, 0x80,
|
||||
0xec, 0xc2, 0x06, 0x4a, 0x9e, 0x16, 0x26, 0x73, 0xed, 0xd2, 0xc9, 0xab, 0x9d, 0xc3, 0xc7, 0x82,
|
||||
0x70, 0xa9, 0x9e, 0x8a, 0x4f, 0xa4, 0xaa, 0xab, 0x9b, 0x42, 0xae, 0x49, 0x4a, 0x78, 0xe4, 0x79,
|
||||
0x29, 0x9d, 0x06, 0x50, 0x89, 0x8f, 0x16, 0xa4, 0x83, 0x59, 0x8e, 0x8f, 0xac, 0x18, 0x6b, 0xa9,
|
||||
0x12, 0x63, 0x6d, 0xc3, 0x4a, 0x3e, 0x8b, 0x87, 0x6c, 0x34, 0xe0, 0x09, 0xae, 0x1b, 0xc6, 0xe2,
|
||||
0x76, 0x96, 0xfc, 0x32, 0x58, 0x44, 0x83, 0x2c, 0xe7, 0x31, 0xe3, 0x42, 0x6b, 0x2c, 0xf9, 0x7a,
|
||||
0x88, 0x0a, 0x58, 0xa0, 0x48, 0xa1, 0x6f, 0xf9, 0x6a, 0x84, 0x56, 0x6c, 0x9a, 0x85, 0x79, 0xaf,
|
||||
0x23, 0xa0, 0xe2, 0x37, 0xfd, 0xae, 0x30, 0x8e, 0x26, 0x08, 0xfc, 0x54, 0xbc, 0x5c, 0x72, 0x19,
|
||||
0x5a, 0x72, 0x4f, 0xf9, 0x49, 0xa0, 0xc3, 0x55, 0x01, 0x38, 0x3c, 0x09, 0x30, 0x76, 0x71, 0x8e,
|
||||
0x29, 0x5f, 0x41, 0x5b, 0xc0, 0xf6, 0xe4, 0x29, 0xdf, 0x82, 0x65, 0x1d, 0x5e, 0xe6, 0x83, 0x88,
|
||||
0x1d, 0x73, 0xed, 0x6c, 0xc7, 0xd3, 0x09, 0x2e, 0x97, 0xef, 0xb3, 0x63, 0x4e, 0x9f, 0xc0, 0x9a,
|
||||
0x7a, 0x81, 0x9f, 0xa4, 0x4c, 0x2f, 0xfd, 0xf5, 0xb2, 0xfe, 0x97, 0x06, 0x7a, 0x5d, 0x49, 0x96,
|
||||
0x1d, 0x21, 0x94, 0x8c, 0x02, 0xf5, 0x81, 0xa8, 0xe9, 0xfb, 0x51, 0x92, 0x33, 0x45, 0x90, 0x42,
|
||||
0x67, 0x18, 0x25, 0x79, 0x39, 0x8c, 0xb0, 0x61, 0xc8, 0xcb, 0x7c, 0x3a, 0x1c, 0xe2, 0xcb, 0x95,
|
||||
0x26, 0x5e, 0x0f, 0xe9, 0x5f, 0x79, 0xb0, 0x2e, 0xa8, 0x69, 0x5d, 0x61, 0xfc, 0xc2, 0x37, 0xdf,
|
||||
0x66, 0x67, 0x68, 0x87, 0x35, 0x1b, 0x30, 0x7f, 0x9c, 0x64, 0x43, 0xa6, 0x56, 0x92, 0x83, 0x9f,
|
||||
0x85, 0xa7, 0xfb, 0x4f, 0x1e, 0xac, 0x89, 0xad, 0x1e, 0xf2, 0x80, 0x4f, 0x73, 0x75, 0xfc, 0x5f,
|
||||
0x86, 0x2e, 0x1e, 0x95, 0x69, 0xf1, 0x57, 0x1b, 0xdd, 0x30, 0x2f, 0x55, 0x40, 0x25, 0xf2, 0xde,
|
||||
0x05, 0xdf, 0x45, 0x26, 0x1f, 0x42, 0xc7, 0xce, 0x11, 0x88, 0x3d, 0xb7, 0x77, 0x2f, 0xe9, 0x53,
|
||||
0x56, 0x24, 0x67, 0xef, 0x82, 0xef, 0x7c, 0x40, 0xee, 0x00, 0x08, 0xcb, 0x2c, 0xc8, 0xaa, 0x30,
|
||||
0xf0, 0x92, 0xcb, 0x24, 0xeb, 0xb2, 0xf6, 0x2e, 0xf8, 0x16, 0xfa, 0xbd, 0x25, 0x58, 0x90, 0xa6,
|
||||
0x84, 0x3e, 0x86, 0xae, 0xb3, 0x53, 0xc7, 0x83, 0xef, 0x48, 0x0f, 0xbe, 0x12, 0xe0, 0x35, 0x6a,
|
||||
0x02, 0xbc, 0x7f, 0x6d, 0x00, 0x41, 0x69, 0x2b, 0x5d, 0xe7, 0xdb, 0xb0, 0xac, 0xd8, 0xef, 0x3a,
|
||||
0x6f, 0x25, 0xa8, 0xb0, 0x79, 0xc9, 0xc8, 0xf1, 0x60, 0x3a, 0xbe, 0x0d, 0x22, 0x37, 0x81, 0x58,
|
||||
0x43, 0x1d, 0xb5, 0x4b, 0x7b, 0x50, 0x33, 0x83, 0x8a, 0x4b, 0xba, 0x1f, 0x3a, 0x5e, 0x55, 0x1e,
|
||||
0x5b, 0x53, 0xdc, 0x6f, 0xed, 0x9c, 0x48, 0x26, 0x4d, 0xf3, 0x13, 0xb4, 0xc9, 0xda, 0xc7, 0xd1,
|
||||
0xe3, 0xb2, 0x20, 0x2d, 0xbc, 0x56, 0x90, 0x16, 0xcb, 0x82, 0x24, 0x2c, 0x5c, 0x16, 0x9e, 0x06,
|
||||
0x9c, 0x69, 0xab, 0xa1, 0x86, 0xe8, 0xd2, 0x4c, 0xc2, 0x58, 0x98, 0xea, 0xc1, 0x04, 0x57, 0x57,
|
||||
0x2e, 0x8d, 0x03, 0xa4, 0x3f, 0xf6, 0x60, 0x15, 0x79, 0xec, 0xc8, 0xe1, 0x07, 0x20, 0x9e, 0xc1,
|
||||
0x1b, 0x8a, 0xa1, 0x83, 0xfb, 0xd3, 0x4b, 0xe1, 0xfb, 0xd0, 0x12, 0x04, 0x93, 0x94, 0xc5, 0x4a,
|
||||
0x08, 0x7b, 0xae, 0x10, 0x16, 0x1a, 0x68, 0xef, 0x82, 0x5f, 0x20, 0x5b, 0x22, 0xf8, 0x0f, 0x1e,
|
||||
0xb4, 0xd5, 0x36, 0xff, 0xd7, 0x8e, 0x77, 0x1f, 0x96, 0x50, 0x1a, 0x2d, 0xbf, 0xd6, 0x8c, 0x51,
|
||||
0xf3, 0x4f, 0x30, 0xee, 0x41, 0x53, 0xe7, 0x38, 0xdd, 0x65, 0x30, 0xda, 0x2d, 0xa1, 0x6c, 0xf3,
|
||||
0x01, 0x0f, 0xa3, 0x81, 0x9e, 0x55, 0xe9, 0xb8, 0xba, 0x29, 0xd4, 0x39, 0x39, 0x0f, 0xc6, 0x4c,
|
||||
0x99, 0x24, 0x39, 0xc0, 0xe8, 0x42, 0x1d, 0xa8, 0xec, 0x50, 0xfd, 0x08, 0x60, 0xab, 0x32, 0x65,
|
||||
0x9c, 0x2a, 0xe5, 0x47, 0x46, 0xe1, 0xe4, 0x28, 0x31, 0x2e, 0xa9, 0x67, 0xbb, 0x98, 0xce, 0x14,
|
||||
0x19, 0xc3, 0x45, 0x6d, 0x7b, 0x91, 0xa7, 0x85, 0xa5, 0x6d, 0x08, 0xa7, 0xe1, 0xb6, 0x2b, 0x03,
|
||||
0xe5, 0x05, 0x35, 0xdc, 0x7e, 0xb5, 0xf5, 0xf4, 0xc8, 0x09, 0xf4, 0x8c, 0x91, 0x57, 0xea, 0xdd,
|
||||
0x72, 0x04, 0x70, 0xad, 0x77, 0x5f, 0xb3, 0x96, 0xd0, 0x45, 0x23, 0xbd, 0xcc, 0xb9, 0xd4, 0xc8,
|
||||
0x0c, 0xae, 0xea, 0x39, 0xa1, 0xbf, 0xab, 0xeb, 0x35, 0xdf, 0xe8, 0x6c, 0x8f, 0xf0, 0x63, 0x77,
|
||||
0xd1, 0xd7, 0x10, 0xee, 0xff, 0xc8, 0x83, 0x65, 0x97, 0x1c, 0x8a, 0x8e, 0x8a, 0x4d, 0xb4, 0x82,
|
||||
0xd1, 0xce, 0x53, 0x09, 0x5c, 0x8d, 0xae, 0x1a, 0x75, 0xd1, 0x95, 0x1d, 0x43, 0xcd, 0xbd, 0x2e,
|
||||
0x86, 0x6a, 0xbe, 0x59, 0x0c, 0x35, 0x5f, 0x17, 0x43, 0xf5, 0xff, 0xcb, 0x03, 0x52, 0xbd, 0x5f,
|
||||
0xf2, 0x58, 0x86, 0x77, 0x31, 0x8b, 0x94, 0x9e, 0xf8, 0x85, 0x37, 0x93, 0x11, 0xcd, 0x43, 0xfd,
|
||||
0x35, 0x0a, 0xab, 0xad, 0x08, 0x6c, 0x97, 0xa5, 0xeb, 0xd7, 0x4d, 0x95, 0xa2, 0xba, 0xe6, 0xeb,
|
||||
0xa3, 0xba, 0xf9, 0xd7, 0x47, 0x75, 0x0b, 0xe5, 0xa8, 0xae, 0xff, 0xdb, 0xd0, 0x75, 0x6e, 0xfd,
|
||||
0x67, 0x77, 0xe2, 0xb2, 0xbb, 0x23, 0x2f, 0xd8, 0x81, 0xf5, 0xff, 0xb3, 0x01, 0xa4, 0x2a, 0x79,
|
||||
0xff, 0xaf, 0x7b, 0x10, 0x72, 0xe4, 0x28, 0x90, 0x39, 0x25, 0x47, 0x8e, 0xea, 0xf8, 0xbf, 0x54,
|
||||
0x8a, 0xef, 0xc2, 0x5a, 0xc6, 0x86, 0xc9, 0x29, 0xcb, 0xac, 0xc8, 0x5a, 0x5e, 0x55, 0x75, 0x02,
|
||||
0x1d, 0x3e, 0x37, 0x96, 0x5d, 0x72, 0x2a, 0x08, 0x96, 0x65, 0x28, 0x85, 0xb4, 0xf4, 0xeb, 0xb0,
|
||||
0x21, 0x0b, 0x3b, 0xf7, 0x24, 0x29, 0xed, 0x73, 0x5c, 0x87, 0xce, 0x99, 0x4c, 0xe6, 0x0d, 0x92,
|
||||
0x38, 0x9a, 0x29, 0x23, 0xd2, 0x56, 0xb0, 0x4f, 0xe2, 0x68, 0x46, 0x7f, 0xe8, 0xc1, 0xc5, 0xd2,
|
||||
0xb7, 0x45, 0xce, 0x5e, 0xaa, 0x5a, 0x57, 0xff, 0xba, 0x40, 0x3c, 0xa2, 0x92, 0x71, 0xeb, 0x88,
|
||||
0xd2, 0x24, 0x55, 0x27, 0x90, 0x85, 0xd3, 0xb8, 0x8a, 0x2f, 0x2f, 0xa6, 0x6e, 0x8a, 0x6e, 0xc1,
|
||||
0x45, 0x75, 0xf9, 0xee, 0xd9, 0xe8, 0x2e, 0x6c, 0x96, 0x27, 0x8a, 0xfc, 0x98, 0xbb, 0x65, 0x3d,
|
||||
0xa4, 0x1f, 0x02, 0xf9, 0xe6, 0x94, 0x65, 0x33, 0x51, 0x1d, 0x30, 0x09, 0xd8, 0xad, 0x72, 0x20,
|
||||
0xbe, 0x90, 0x4e, 0x8f, 0xbe, 0xc1, 0x66, 0xba, 0xa8, 0xd2, 0x30, 0x45, 0x15, 0x7a, 0x07, 0xd6,
|
||||
0x1d, 0x02, 0x86, 0x55, 0x0b, 0xa2, 0xc2, 0xa0, 0x83, 0x54, 0xb7, 0x0a, 0xa1, 0xe6, 0xe8, 0x9f,
|
||||
0x7b, 0x30, 0xb7, 0x97, 0xa4, 0x76, 0x66, 0xc9, 0x73, 0x33, 0x4b, 0x4a, 0x77, 0x0e, 0x8c, 0x6a,
|
||||
0x6c, 0xa8, 0x97, 0x6f, 0x03, 0x51, 0xf3, 0x05, 0x13, 0x8e, 0x61, 0xda, 0x71, 0x92, 0x9d, 0x05,
|
||||
0xd9, 0x48, 0xf1, 0xaf, 0x04, 0xc5, 0xed, 0x17, 0x0a, 0x06, 0x7f, 0xa2, 0xd3, 0x20, 0xd2, 0x6b,
|
||||
0x33, 0x15, 0x59, 0xaa, 0x11, 0xfd, 0xbe, 0x07, 0xf3, 0x62, 0xaf, 0xf8, 0x1a, 0xe4, 0xfd, 0x8a,
|
||||
0x82, 0x9a, 0xc8, 0xde, 0x79, 0xf2, 0x35, 0x94, 0xc0, 0xa5, 0x32, 0x5b, 0xa3, 0x52, 0x66, 0xbb,
|
||||
0x02, 0x2d, 0x39, 0x2a, 0xea, 0x52, 0x05, 0x80, 0x5c, 0x85, 0xe6, 0x49, 0x92, 0x6a, 0x1b, 0x06,
|
||||
0x3a, 0x5d, 0x93, 0xa4, 0xbe, 0x80, 0xd3, 0x1b, 0xb0, 0xf2, 0x24, 0x19, 0x31, 0x2b, 0xa6, 0x3f,
|
||||
0xf7, 0x9a, 0xe8, 0xef, 0x78, 0xb0, 0xa4, 0x91, 0xc9, 0x36, 0x34, 0xd1, 0x14, 0x95, 0x9c, 0x3f,
|
||||
0x93, 0x74, 0x45, 0x3c, 0x5f, 0x60, 0xa0, 0x0a, 0x11, 0x11, 0x64, 0xe1, 0x2a, 0xe8, 0xf8, 0xb1,
|
||||
0x30, 0xc2, 0xe8, 0xb4, 0x8b, 0x3d, 0x97, 0x8c, 0x55, 0x09, 0x4a, 0xff, 0xda, 0x83, 0xae, 0xb3,
|
||||
0x06, 0xba, 0xf1, 0x51, 0x90, 0x73, 0x95, 0xa8, 0x52, 0x4c, 0xb4, 0x41, 0x76, 0xfe, 0xa7, 0xe1,
|
||||
0xe6, 0x7f, 0x4c, 0xfe, 0x61, 0xce, 0xce, 0x3f, 0xdc, 0x82, 0x56, 0x51, 0xb2, 0x6c, 0x3a, 0xaa,
|
||||
0x01, 0x57, 0xd4, 0xe9, 0xe4, 0x02, 0x09, 0xe9, 0x0c, 0x93, 0x28, 0xc9, 0x54, 0x45, 0x4f, 0x0e,
|
||||
0xe8, 0x1d, 0x68, 0x5b, 0xf8, 0xb8, 0x8d, 0x98, 0xf1, 0xb3, 0x24, 0x7b, 0xa6, 0xd3, 0x50, 0x6a,
|
||||
0x68, 0xca, 0x28, 0x8d, 0xa2, 0x8c, 0x42, 0xff, 0xc6, 0x83, 0x2e, 0x4a, 0x4a, 0x18, 0x8f, 0x0f,
|
||||
0x92, 0x28, 0x1c, 0xce, 0x84, 0xc4, 0x68, 0xa1, 0x18, 0x8c, 0x58, 0xc4, 0x03, 0x23, 0x31, 0x2e,
|
||||
0x18, 0x6d, 0xbe, 0xf6, 0xe2, 0x95, 0xbc, 0x98, 0x31, 0x4a, 0x3e, 0xda, 0xae, 0xa3, 0x20, 0x67,
|
||||
0xd2, 0xed, 0x57, 0xba, 0xda, 0x01, 0xa2, 0xfa, 0x40, 0x40, 0x16, 0x70, 0x36, 0x98, 0x84, 0x51,
|
||||
0x14, 0x4a, 0x5c, 0x29, 0xe1, 0x75, 0x53, 0xf4, 0x6f, 0x1b, 0xd0, 0x56, 0x6a, 0xe2, 0xe1, 0x68,
|
||||
0x2c, 0x33, 0xaa, 0xca, 0x11, 0x31, 0xcf, 0xcf, 0x82, 0xe8, 0x79, 0xc7, 0x75, 0xb1, 0x20, 0xe5,
|
||||
0x6b, 0x9d, 0xab, 0x5e, 0xeb, 0x15, 0x68, 0xa1, 0x78, 0xdd, 0x16, 0x3e, 0x92, 0xac, 0x70, 0x17,
|
||||
0x00, 0x3d, 0xbb, 0x2b, 0x66, 0xe7, 0x8b, 0x59, 0x01, 0x70, 0xbc, 0xa2, 0x85, 0x92, 0x57, 0xf4,
|
||||
0x3e, 0x74, 0x14, 0x19, 0xc1, 0x77, 0x11, 0x54, 0x15, 0x02, 0xee, 0xdc, 0x89, 0xef, 0x60, 0xea,
|
||||
0x2f, 0x77, 0xf5, 0x97, 0x4b, 0xaf, 0xfb, 0x52, 0x63, 0xd2, 0x8b, 0xb0, 0xae, 0x98, 0xf7, 0x38,
|
||||
0x0b, 0xd2, 0x13, 0xad, 0x7a, 0x47, 0xa6, 0x8c, 0x2a, 0xc0, 0xe4, 0x06, 0xcc, 0xe3, 0x67, 0x5a,
|
||||
0xfb, 0xd5, 0x3f, 0x3a, 0x89, 0x42, 0xb6, 0x61, 0x9e, 0x8d, 0xc6, 0x4c, 0x7b, 0xe6, 0xc4, 0x8d,
|
||||
0x91, 0xf0, 0x8e, 0x7c, 0x89, 0x80, 0x2a, 0x00, 0xa1, 0x25, 0x15, 0xe0, 0x6a, 0xce, 0x05, 0x1c,
|
||||
0x7e, 0x34, 0xa2, 0x1b, 0x40, 0x9e, 0x48, 0xa9, 0xb5, 0xb3, 0x80, 0xbf, 0x37, 0x07, 0x6d, 0x0b,
|
||||
0x8c, 0xaf, 0x79, 0x8c, 0x1b, 0x1e, 0x8c, 0xc2, 0x60, 0xc2, 0x38, 0xcb, 0x94, 0xa4, 0x96, 0xa0,
|
||||
0x42, 0xc1, 0x9e, 0x8e, 0x07, 0xc9, 0x94, 0x0f, 0x46, 0x6c, 0x9c, 0x31, 0x69, 0xd0, 0x3c, 0xbf,
|
||||
0x04, 0x45, 0xbc, 0x49, 0xf0, 0xdc, 0xc6, 0x93, 0xf2, 0x50, 0x82, 0xea, 0x9c, 0x9e, 0xe4, 0x51,
|
||||
0xb3, 0xc8, 0xe9, 0x49, 0x8e, 0x94, 0xf5, 0xd0, 0x7c, 0x8d, 0x1e, 0x7a, 0x0f, 0x36, 0xa5, 0xc6,
|
||||
0x51, 0x6f, 0x73, 0x50, 0x12, 0x93, 0x73, 0x66, 0xc9, 0x0d, 0x58, 0xc5, 0x3d, 0x6b, 0x01, 0xcf,
|
||||
0xc3, 0xef, 0xca, 0x68, 0xdc, 0xf3, 0x2b, 0x70, 0xc4, 0xc5, 0xe7, 0xe8, 0xe0, 0xca, 0x92, 0x43,
|
||||
0x05, 0x2e, 0x70, 0x83, 0xe7, 0x2e, 0x6e, 0x4b, 0xe1, 0x96, 0xe0, 0xb4, 0x0b, 0xed, 0x43, 0x9e,
|
||||
0xa4, 0xfa, 0x52, 0x96, 0xa1, 0x23, 0x87, 0xaa, 0xcc, 0x74, 0x19, 0x2e, 0x09, 0x29, 0x7a, 0x9a,
|
||||
0xa4, 0x49, 0x94, 0x8c, 0x67, 0x87, 0xd3, 0xa3, 0x7c, 0x98, 0x85, 0x29, 0x7a, 0xcc, 0xf4, 0xef,
|
||||
0x3d, 0x58, 0x77, 0x66, 0x55, 0xa8, 0xff, 0x8b, 0x52, 0xa4, 0x4d, 0x65, 0x40, 0x0a, 0xde, 0x9a,
|
||||
0xa5, 0x0e, 0x25, 0xa2, 0x4c, 0x9c, 0x7c, 0xaa, 0x8a, 0x05, 0x77, 0x61, 0x45, 0xef, 0x4c, 0x7f,
|
||||
0x28, 0xa5, 0xb0, 0x57, 0x95, 0x42, 0xf5, 0xfd, 0xb2, 0xfa, 0x40, 0x93, 0xf8, 0x15, 0xe9, 0x77,
|
||||
0xb2, 0x91, 0x38, 0xa3, 0x8e, 0xf9, 0xfa, 0xfa, 0x7b, 0xdb, 0xd9, 0xd5, 0x3b, 0x18, 0x1a, 0x60,
|
||||
0x4e, 0xff, 0xc8, 0x03, 0x28, 0x76, 0x87, 0x82, 0x51, 0xa8, 0x74, 0x4f, 0xe4, 0x4c, 0x2d, 0xf5,
|
||||
0x7d, 0x1d, 0x3a, 0x26, 0x33, 0x5d, 0x58, 0x89, 0xb6, 0x86, 0xa1, 0x87, 0xf2, 0x0e, 0xac, 0x8c,
|
||||
0xa3, 0xe4, 0x48, 0xd8, 0x5c, 0x51, 0xd1, 0xcc, 0x55, 0xb1, 0x6d, 0x59, 0x82, 0x1f, 0x29, 0x68,
|
||||
0x61, 0x52, 0x9a, 0x96, 0x49, 0xa1, 0x7f, 0xdc, 0x30, 0xf9, 0xd1, 0xe2, 0xcc, 0xe7, 0xbe, 0x32,
|
||||
0xb2, 0x5b, 0x51, 0x8e, 0xe7, 0xa4, 0x23, 0x45, 0x76, 0xe3, 0xe0, 0xb5, 0x81, 0xde, 0x1d, 0x58,
|
||||
0xce, 0xa4, 0xf6, 0xd1, 0xaa, 0xa9, 0xf9, 0x0a, 0xd5, 0xd4, 0xcd, 0x1c, 0xbb, 0xf3, 0x73, 0xb0,
|
||||
0x1a, 0x8c, 0x4e, 0x59, 0xc6, 0x43, 0xe1, 0xf1, 0x0b, 0xa3, 0x2f, 0x15, 0xea, 0x8a, 0x05, 0x17,
|
||||
0xb6, 0xf8, 0x1d, 0x58, 0x51, 0x05, 0x4e, 0x83, 0xa9, 0xfa, 0x56, 0x0a, 0x30, 0x22, 0xd2, 0xbf,
|
||||
0xd4, 0xa9, 0x58, 0xf7, 0x0e, 0xcf, 0xe7, 0x88, 0x7d, 0xba, 0x46, 0xe9, 0x74, 0x5f, 0x51, 0x69,
|
||||
0xd1, 0x91, 0x0e, 0x2b, 0x54, 0x82, 0x5a, 0x02, 0x55, 0x1a, 0xdb, 0x65, 0x69, 0xf3, 0x4d, 0x58,
|
||||
0x4a, 0x7f, 0x38, 0x07, 0x8b, 0x1f, 0xc5, 0xa7, 0x49, 0x38, 0x14, 0x49, 0xca, 0x09, 0x9b, 0x24,
|
||||
0xba, 0xcd, 0x00, 0x7f, 0xa3, 0x45, 0x17, 0x15, 0xb4, 0x94, 0xab, 0xec, 0xa1, 0x1e, 0xa2, 0x75,
|
||||
0xcb, 0x8a, 0xd6, 0x1a, 0x29, 0x29, 0x16, 0x04, 0xfd, 0xc3, 0xcc, 0xee, 0x16, 0x52, 0xa3, 0xa2,
|
||||
0x4f, 0x63, 0xde, 0xea, 0xd3, 0x10, 0x29, 0x6d, 0x59, 0x1c, 0x14, 0xec, 0x5c, 0xf2, 0xf5, 0x50,
|
||||
0xf8, 0xb1, 0x19, 0x93, 0x41, 0xaf, 0xb0, 0x93, 0x8b, 0xca, 0x8f, 0xb5, 0x81, 0x68, 0x4b, 0xe5,
|
||||
0x07, 0x12, 0x47, 0xea, 0x1a, 0x1b, 0x84, 0xbe, 0x45, 0xb9, 0xe1, 0xa8, 0x25, 0xaf, 0xb8, 0x04,
|
||||
0x46, 0x85, 0x34, 0x62, 0x46, 0x6f, 0xc8, 0x33, 0x80, 0x6c, 0x1d, 0x2a, 0xc3, 0x2d, 0x2f, 0x58,
|
||||
0x16, 0x39, 0xd5, 0x48, 0xf8, 0x20, 0x41, 0x14, 0x1d, 0x05, 0xc3, 0x67, 0xa2, 0x0d, 0x4c, 0xd4,
|
||||
0x34, 0x5b, 0xbe, 0x0b, 0xc4, 0x5d, 0x0f, 0x23, 0x7e, 0x3a, 0x50, 0x24, 0xba, 0xb2, 0x26, 0x69,
|
||||
0x81, 0xe8, 0x67, 0x40, 0xee, 0x8e, 0x46, 0xea, 0x86, 0x4c, 0x8c, 0x50, 0xf0, 0xd6, 0x73, 0x78,
|
||||
0x5b, 0x73, 0xc6, 0x46, 0xed, 0x19, 0xe9, 0x43, 0x68, 0x1f, 0x58, 0xdd, 0x5b, 0xe2, 0x32, 0x75,
|
||||
0xdf, 0x96, 0x12, 0x00, 0x0b, 0x62, 0x2d, 0xd8, 0xb0, 0x17, 0xa4, 0xbf, 0x04, 0x64, 0x3f, 0xcc,
|
||||
0xb9, 0xd9, 0x9f, 0x09, 0x15, 0x4d, 0xc6, 0xcb, 0x0a, 0x15, 0x15, 0x4c, 0x84, 0x8a, 0x77, 0x65,
|
||||
0x69, 0xb4, 0x7c, 0xb0, 0x1b, 0xb0, 0x14, 0x4a, 0x90, 0xd6, 0xc3, 0xcb, 0x4a, 0x80, 0x35, 0xa6,
|
||||
0x99, 0x47, 0x87, 0x42, 0x01, 0x1d, 0x35, 0xff, 0x7d, 0x0f, 0x16, 0xd5, 0xd1, 0xd0, 0x1c, 0x3a,
|
||||
0x7d, 0x6b, 0xf2, 0x60, 0x0e, 0xac, 0xbe, 0x6f, 0xa8, 0x2a, 0x75, 0x73, 0x75, 0x52, 0x47, 0xa0,
|
||||
0x99, 0x06, 0xfc, 0x44, 0x78, 0xd0, 0x2d, 0x5f, 0xfc, 0xd6, 0x91, 0xd2, 0xbc, 0x89, 0x94, 0x74,
|
||||
0x19, 0x58, 0x6d, 0xca, 0x64, 0x2d, 0xef, 0xc9, 0x32, 0x70, 0x01, 0x2e, 0x78, 0xa0, 0x36, 0x58,
|
||||
0xe6, 0x81, 0x42, 0xf5, 0xcd, 0x3c, 0xed, 0x43, 0xef, 0x01, 0x8b, 0x18, 0x67, 0x77, 0xa3, 0xa8,
|
||||
0x4c, 0xff, 0x32, 0x5c, 0xaa, 0x99, 0x53, 0x96, 0xf2, 0x11, 0xac, 0x3d, 0x60, 0x47, 0xd3, 0xf1,
|
||||
0x3e, 0x3b, 0x2d, 0xca, 0x0a, 0x04, 0x9a, 0xf9, 0x49, 0x72, 0xa6, 0xee, 0x4b, 0xfc, 0x26, 0x5f,
|
||||
0x02, 0x88, 0x10, 0x67, 0x90, 0xa7, 0x6c, 0xa8, 0x9b, 0x5e, 0x04, 0xe4, 0x30, 0x65, 0x43, 0xfa,
|
||||
0x1e, 0x10, 0x9b, 0x8e, 0x3a, 0x02, 0xbe, 0xc6, 0xe9, 0xd1, 0x20, 0x9f, 0xe5, 0x9c, 0x4d, 0x74,
|
||||
0x37, 0x8f, 0x0d, 0xa2, 0xef, 0x40, 0xe7, 0x20, 0x98, 0xf9, 0xec, 0x0b, 0xd5, 0x0e, 0x88, 0x01,
|
||||
0x59, 0x30, 0x43, 0xf1, 0x34, 0x01, 0x99, 0x98, 0xa6, 0x7f, 0xd7, 0x80, 0x05, 0x89, 0x89, 0x54,
|
||||
0x47, 0x2c, 0xe7, 0x61, 0x2c, 0xd3, 0xea, 0x8a, 0xaa, 0x05, 0xaa, 0xdc, 0x77, 0xa3, 0xe6, 0xbe,
|
||||
0x95, 0x8b, 0xa4, 0x1b, 0x04, 0xd4, 0xc5, 0x3a, 0x30, 0x11, 0x6f, 0x86, 0x13, 0x26, 0xbb, 0x42,
|
||||
0x9b, 0x2a, 0xde, 0xd4, 0x80, 0x52, 0xe4, 0x5b, 0xbc, 0x79, 0xb9, 0x3f, 0x2d, 0x88, 0xca, 0x2c,
|
||||
0xd8, 0xa0, 0x5a, 0xcd, 0xb2, 0x28, 0xfb, 0xff, 0x2a, 0x9a, 0xa5, 0xa2, 0x41, 0x96, 0xde, 0x40,
|
||||
0x83, 0x48, 0xbf, 0xc9, 0xd1, 0x20, 0x04, 0x56, 0x1f, 0x31, 0xe6, 0xb3, 0x34, 0xc9, 0x4c, 0x4f,
|
||||
0xe5, 0x0f, 0x3c, 0x58, 0x55, 0x16, 0xc1, 0xcc, 0x91, 0xeb, 0x8e, 0xf9, 0xf0, 0xea, 0x32, 0xad,
|
||||
0x6f, 0x41, 0x57, 0x04, 0x50, 0x18, 0x1d, 0x89, 0x68, 0x49, 0xe5, 0x14, 0x1c, 0x20, 0xee, 0x49,
|
||||
0xe7, 0x0e, 0x27, 0x61, 0xa4, 0x18, 0x6c, 0x83, 0xd0, 0xd4, 0xe9, 0x00, 0x4b, 0xb0, 0xd7, 0xf3,
|
||||
0xcd, 0x98, 0x1e, 0xc0, 0x9a, 0xb5, 0x5f, 0x25, 0x50, 0x77, 0x40, 0x57, 0x25, 0x65, 0x8a, 0x40,
|
||||
0xbe, 0x8b, 0x2d, 0xd7, 0xb8, 0x15, 0x9f, 0x39, 0xc8, 0xf4, 0x9f, 0x3d, 0x58, 0x97, 0x86, 0x5e,
|
||||
0xb9, 0x51, 0xa6, 0x91, 0x69, 0x41, 0x7a, 0x36, 0x52, 0xe0, 0xf7, 0x2e, 0xf8, 0x6a, 0x4c, 0xbe,
|
||||
0xf6, 0x86, 0xce, 0x89, 0x29, 0x00, 0x9e, 0xc3, 0x9e, 0xb9, 0x3a, 0xf6, 0xbc, 0xe2, 0xf0, 0x75,
|
||||
0x01, 0xf0, 0x7c, 0x6d, 0x00, 0x7c, 0x6f, 0x11, 0xe6, 0xf3, 0x61, 0x92, 0x32, 0xba, 0x09, 0x1b,
|
||||
0xee, 0xe1, 0x24, 0xcb, 0x76, 0xff, 0xc5, 0x83, 0x65, 0x99, 0x8c, 0x93, 0xdd, 0xda, 0x2c, 0x23,
|
||||
0x18, 0x6b, 0x59, 0x4d, 0xe0, 0xc4, 0xb8, 0x9a, 0xd5, 0x66, 0xf2, 0xfe, 0xe5, 0xda, 0x39, 0xed,
|
||||
0x67, 0x7f, 0xef, 0xc7, 0xff, 0xfe, 0x27, 0x8d, 0x8b, 0x74, 0x75, 0xe7, 0xf4, 0xf6, 0x8e, 0x50,
|
||||
0x89, 0xec, 0x4c, 0x60, 0x7c, 0xe0, 0xdd, 0xc0, 0x55, 0xec, 0xfe, 0x70, 0xb3, 0x4a, 0x4d, 0x9f,
|
||||
0xb9, 0x59, 0xa5, 0xb6, 0xa1, 0xdc, 0x59, 0x65, 0x2a, 0x30, 0xcc, 0x2a, 0xbb, 0xff, 0xdd, 0x87,
|
||||
0x96, 0x09, 0x0a, 0xc9, 0x77, 0xa0, 0xeb, 0x24, 0x1e, 0x89, 0x26, 0x5c, 0x97, 0xca, 0xec, 0x5f,
|
||||
0xa9, 0x9f, 0x54, 0xcb, 0x5e, 0x15, 0xcb, 0xf6, 0xc8, 0x26, 0x2e, 0xab, 0xb2, 0x7d, 0x3b, 0x22,
|
||||
0x23, 0x2b, 0x3b, 0x19, 0x9e, 0xc1, 0xb2, 0x9b, 0x2c, 0x24, 0x57, 0x5c, 0xd1, 0x28, 0xad, 0xf6,
|
||||
0xa5, 0x73, 0x66, 0xd5, 0x72, 0x57, 0xc4, 0x72, 0x9b, 0x64, 0xc3, 0x5e, 0xce, 0x04, 0x6b, 0x4c,
|
||||
0xf4, 0x9e, 0xd8, 0x8d, 0xe3, 0x44, 0xd3, 0xab, 0x6f, 0x28, 0xef, 0x5f, 0xaa, 0x36, 0x89, 0xab,
|
||||
0xae, 0x72, 0xda, 0x13, 0x4b, 0x11, 0x22, 0x18, 0x6a, 0xf7, 0x8d, 0x93, 0x6f, 0x43, 0xcb, 0xb4,
|
||||
0x9d, 0x92, 0x2d, 0xab, 0xd7, 0xd7, 0xee, 0x85, 0xed, 0xf7, 0xaa, 0x13, 0x75, 0x57, 0x65, 0x53,
|
||||
0x46, 0x81, 0xd8, 0x87, 0x8b, 0xca, 0x42, 0x1f, 0xb1, 0x9f, 0xe4, 0x24, 0x35, 0xed, 0xee, 0xb7,
|
||||
0x3c, 0x72, 0x07, 0x96, 0x74, 0x37, 0x2f, 0xd9, 0xac, 0xef, 0x4a, 0xee, 0x6f, 0x55, 0xe0, 0x4a,
|
||||
0x8f, 0xdc, 0x05, 0x28, 0x1a, 0x4f, 0x49, 0xef, 0xbc, 0xfe, 0x58, 0xc3, 0xc4, 0x9a, 0x2e, 0xd5,
|
||||
0xb1, 0xe8, 0xbb, 0x75, 0xfb, 0x5a, 0xc9, 0x97, 0x0b, 0xfc, 0xda, 0x8e, 0xd7, 0x57, 0x10, 0xa4,
|
||||
0x9b, 0x82, 0x77, 0xab, 0x64, 0x19, 0x79, 0x17, 0xb3, 0x33, 0xdd, 0x85, 0xf5, 0x00, 0xda, 0x56,
|
||||
0x33, 0x2b, 0xd1, 0x14, 0xaa, 0x8d, 0xb0, 0xfd, 0x7e, 0xdd, 0x94, 0xda, 0xee, 0xaf, 0x41, 0xd7,
|
||||
0xe9, 0x4a, 0x35, 0x2f, 0xa3, 0xae, 0xe7, 0xd5, 0xbc, 0x8c, 0xfa, 0x46, 0xd6, 0x6f, 0x41, 0xdb,
|
||||
0xea, 0x21, 0x25, 0x56, 0x45, 0xbb, 0xd4, 0x23, 0x6a, 0x76, 0x54, 0xd3, 0x72, 0x4a, 0x37, 0xc4,
|
||||
0x79, 0x97, 0x69, 0x0b, 0xcf, 0x2b, 0x5a, 0x91, 0x50, 0x48, 0xbe, 0x03, 0xcb, 0x6e, 0xef, 0xa8,
|
||||
0x79, 0x55, 0xb5, 0x5d, 0xa8, 0xe6, 0x55, 0x9d, 0xd3, 0x70, 0xaa, 0x04, 0xf2, 0xc6, 0xba, 0x59,
|
||||
0x64, 0xe7, 0x85, 0x4a, 0x89, 0xbe, 0x24, 0xdf, 0x44, 0xd5, 0xa1, 0x7a, 0xc3, 0x48, 0xd1, 0x4b,
|
||||
0xeb, 0x76, 0x90, 0x19, 0x69, 0xaf, 0xb4, 0x91, 0xd1, 0x35, 0x41, 0xbc, 0x4d, 0x8a, 0x13, 0x90,
|
||||
0x8f, 0x61, 0x51, 0xf5, 0x88, 0x91, 0x8b, 0x85, 0x54, 0x5b, 0x09, 0xa4, 0xfe, 0x66, 0x19, 0xac,
|
||||
0x88, 0xad, 0x0b, 0x62, 0x5d, 0xd2, 0x46, 0x62, 0x63, 0xc6, 0x43, 0xa4, 0x11, 0xc3, 0x4a, 0xa9,
|
||||
0x8a, 0x65, 0x1e, 0x4b, 0x7d, 0x0d, 0xbc, 0x7f, 0xf5, 0xd5, 0xc5, 0x2f, 0x57, 0xcd, 0x68, 0xf5,
|
||||
0xb2, 0xa3, 0x5b, 0x16, 0x7e, 0x13, 0x3a, 0x76, 0x4b, 0xa2, 0xd1, 0xd9, 0x35, 0xed, 0x8b, 0x46,
|
||||
0x67, 0xd7, 0xf5, 0x30, 0xea, 0xcb, 0x25, 0x1d, 0x7b, 0x19, 0xf2, 0x2d, 0x58, 0xb1, 0xea, 0xa5,
|
||||
0x87, 0xb3, 0x78, 0x68, 0x84, 0xa7, 0xda, 0xdd, 0xd2, 0xaf, 0xb3, 0xb4, 0x74, 0x4b, 0x10, 0x5e,
|
||||
0xa3, 0x0e, 0x61, 0x14, 0x9c, 0xfb, 0xd0, 0xb6, 0x6b, 0xb1, 0xaf, 0xa0, 0xbb, 0x65, 0x4d, 0xd9,
|
||||
0xcd, 0x1e, 0xb7, 0x3c, 0xf2, 0x67, 0x1e, 0x74, 0xec, 0xbe, 0x29, 0xe2, 0x64, 0x61, 0x4a, 0x74,
|
||||
0x7a, 0xf6, 0x9c, 0x4d, 0x88, 0x3e, 0x11, 0x9b, 0xdc, 0xbb, 0xf1, 0xc8, 0x61, 0xf2, 0x0b, 0xc7,
|
||||
0x89, 0xba, 0x69, 0xff, 0xd3, 0xe3, 0x65, 0x79, 0xd2, 0xee, 0xfe, 0x79, 0x79, 0xcb, 0x23, 0x1f,
|
||||
0xc8, 0xff, 0xf3, 0xe8, 0x80, 0x86, 0x58, 0x8a, 0xad, 0xcc, 0x2e, 0xfb, 0x4f, 0x32, 0xdb, 0xde,
|
||||
0x2d, 0x8f, 0xfc, 0x96, 0xfc, 0x73, 0x87, 0xfa, 0x56, 0x70, 0xfd, 0x4d, 0xbf, 0xa7, 0x6f, 0x89,
|
||||
0x93, 0x5c, 0xa5, 0x97, 0x9c, 0x93, 0x94, 0x35, 0xfb, 0x01, 0x40, 0x11, 0x9d, 0x92, 0x52, 0xa8,
|
||||
0x66, 0x74, 0x5e, 0x35, 0x80, 0x75, 0x6f, 0x53, 0x47, 0x74, 0x52, 0x0d, 0x74, 0xac, 0xb8, 0x30,
|
||||
0x37, 0xd7, 0x59, 0x8d, 0x32, 0xfb, 0xfd, 0xba, 0x29, 0x45, 0xff, 0x2b, 0x82, 0xfe, 0x97, 0xc8,
|
||||
0x65, 0x9b, 0xfe, 0xce, 0x0b, 0x3b, 0x2a, 0x7d, 0x49, 0x3e, 0x83, 0xee, 0x7e, 0x92, 0x3c, 0x9b,
|
||||
0xa6, 0x26, 0x01, 0xe2, 0xc6, 0x59, 0x18, 0x19, 0xf7, 0x4b, 0x87, 0xa2, 0xd7, 0x05, 0xe5, 0xcb,
|
||||
0xe4, 0x92, 0x4b, 0xb9, 0x88, 0x95, 0x5f, 0x92, 0x00, 0xd6, 0x8c, 0xbd, 0x33, 0x07, 0xe9, 0xbb,
|
||||
0x74, 0xec, 0x90, 0xb5, 0xb2, 0x86, 0xe3, 0x81, 0x98, 0x35, 0x72, 0x4d, 0xf3, 0x96, 0x47, 0x0e,
|
||||
0xa0, 0xf3, 0x80, 0x0d, 0x93, 0x11, 0x53, 0xa1, 0xd1, 0x7a, 0xb1, 0x73, 0x13, 0x53, 0xf5, 0xbb,
|
||||
0x0e, 0xd0, 0xd5, 0x00, 0x69, 0x30, 0xcb, 0xd8, 0x17, 0x3b, 0x2f, 0x54, 0xd0, 0xf5, 0x52, 0x6b,
|
||||
0x00, 0x1d, 0x28, 0x3a, 0x1a, 0xa0, 0x14, 0x59, 0x3a, 0x1a, 0xa0, 0x12, 0x59, 0x3a, 0x1a, 0x40,
|
||||
0x07, 0xaa, 0x24, 0xc2, 0x78, 0xb3, 0x14, 0x8c, 0x1a, 0xab, 0x79, 0x5e, 0x08, 0xdb, 0xbf, 0x76,
|
||||
0x3e, 0x82, 0xbb, 0xda, 0x0d, 0x77, 0xb5, 0x43, 0xe8, 0x3e, 0x60, 0x92, 0x59, 0xb2, 0xaa, 0xd0,
|
||||
0x77, 0x55, 0x8a, 0x5d, 0x81, 0x28, 0xab, 0x1b, 0x31, 0xe7, 0xaa, 0x78, 0x91, 0xd2, 0x27, 0xdf,
|
||||
0x86, 0xf6, 0x63, 0xc6, 0x75, 0x19, 0xc1, 0xf8, 0x1e, 0xa5, 0xba, 0x42, 0xbf, 0xa6, 0x0a, 0x41,
|
||||
0xaf, 0x09, 0x6a, 0x7d, 0xd2, 0x33, 0xd4, 0x76, 0xd8, 0x68, 0xcc, 0xe4, 0xe3, 0x1f, 0x84, 0xa3,
|
||||
0x97, 0xe4, 0xd7, 0x05, 0x71, 0x53, 0x79, 0xdc, 0xb4, 0xb2, 0xcf, 0x36, 0xf1, 0x95, 0x12, 0xbc,
|
||||
0x8e, 0x72, 0x9c, 0x8c, 0x98, 0x65, 0xec, 0x62, 0x68, 0x5b, 0x65, 0x66, 0xf3, 0xa0, 0xaa, 0xb5,
|
||||
0x6b, 0xf3, 0xa0, 0x6a, 0xaa, 0xd2, 0x74, 0x5b, 0xac, 0x43, 0xc9, 0xb5, 0x62, 0x1d, 0x59, 0x89,
|
||||
0x2e, 0x56, 0xda, 0x79, 0x11, 0x4c, 0xf8, 0x4b, 0xf2, 0xb9, 0x68, 0xa0, 0xb6, 0x4b, 0x25, 0x85,
|
||||
0xef, 0x53, 0xae, 0xaa, 0x18, 0x66, 0x59, 0x53, 0xae, 0x3f, 0x24, 0x97, 0x12, 0x36, 0xf1, 0x6b,
|
||||
0x00, 0x87, 0x3c, 0x49, 0x1f, 0x04, 0x6c, 0x92, 0xc4, 0x85, 0x26, 0x2b, 0xca, 0x01, 0x85, 0x26,
|
||||
0xb3, 0x6a, 0x02, 0xe4, 0x73, 0xcb, 0xfb, 0x74, 0x2a, 0x4d, 0x5a, 0xb8, 0xce, 0xad, 0x18, 0x18,
|
||||
0x86, 0xd4, 0x54, 0x0d, 0x6e, 0x79, 0xe8, 0x4b, 0x16, 0xa9, 0x0f, 0xe3, 0x4b, 0x56, 0xb2, 0x2a,
|
||||
0x46, 0x0d, 0xd6, 0xe4, 0x49, 0x0e, 0xa0, 0x55, 0xc4, 0xdf, 0xda, 0x3c, 0x95, 0xa3, 0x75, 0x63,
|
||||
0x6f, 0x2a, 0x61, 0x31, 0x5d, 0x15, 0xac, 0x02, 0xb2, 0x84, 0xac, 0x12, 0x95, 0xf2, 0x21, 0xac,
|
||||
0xcb, 0x0d, 0x1a, 0xe3, 0x29, 0x12, 0xdc, 0xfa, 0x24, 0x35, 0x61, 0xb0, 0x79, 0xcd, 0x75, 0x51,
|
||||
0xa4, 0xf6, 0x4e, 0xa8, 0x59, 0xe1, 0x03, 0xef, 0xc6, 0xd1, 0x82, 0xf8, 0x1b, 0xf2, 0x57, 0xff,
|
||||
0x27, 0x00, 0x00, 0xff, 0xff, 0x33, 0xf0, 0xc8, 0xda, 0xb8, 0x3c, 0x00, 0x00,
|
||||
}
|
||||
|
@ -523,15 +523,15 @@ func request_Lightning_FeeReport_0(ctx context.Context, marshaler runtime.Marsha
|
||||
|
||||
}
|
||||
|
||||
func request_Lightning_UpdateFees_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq FeeUpdateRequest
|
||||
func request_Lightning_UpdateChannelPolicy_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq PolicyUpdateRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.UpdateFees(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
msg, err := client.UpdateChannelPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
@ -1452,7 +1452,7 @@ func RegisterLightningHandler(ctx context.Context, mux *runtime.ServeMux, conn *
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Lightning_UpdateFees_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
mux.Handle("POST", pattern_Lightning_UpdateChannelPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
@ -1470,14 +1470,14 @@ func RegisterLightningHandler(ctx context.Context, mux *runtime.ServeMux, conn *
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Lightning_UpdateFees_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
resp, md, err := request_Lightning_UpdateChannelPolicy_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Lightning_UpdateFees_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
forward_Lightning_UpdateChannelPolicy_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
@ -1539,7 +1539,7 @@ var (
|
||||
|
||||
pattern_Lightning_FeeReport_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "fees"}, ""))
|
||||
|
||||
pattern_Lightning_UpdateFees_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "fees"}, ""))
|
||||
pattern_Lightning_UpdateChannelPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "fees"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
@ -1597,5 +1597,5 @@ var (
|
||||
|
||||
forward_Lightning_FeeReport_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Lightning_UpdateFees_0 = runtime.ForwardResponseMessage
|
||||
forward_Lightning_UpdateChannelPolicy_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
|
@ -441,11 +441,11 @@ service Lightning {
|
||||
};
|
||||
}
|
||||
|
||||
/** lncli: `updatefees`
|
||||
UpdateFees allows the caller to update the fee schedule for all channels
|
||||
globally, or a particular channel.
|
||||
/** lncli: `updatechanpolicy`
|
||||
UpdateChannelPolicy allows the caller to update the fee schedule and
|
||||
channel policies for all channels globally, or a particular channel.
|
||||
*/
|
||||
rpc UpdateFees(FeeUpdateRequest) returns (FeeUpdateResponse) {
|
||||
rpc UpdateChannelPolicy(PolicyUpdateRequest) returns (PolicyUpdateResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/fees"
|
||||
body: "*"
|
||||
@ -877,6 +877,9 @@ message OpenChannelRequest {
|
||||
|
||||
/// Whether this channel should be private, not announced to the greater network.
|
||||
bool private = 8 [json_name = "private"];
|
||||
|
||||
/// The minimum value in millisatoshi we will require for incoming HTLCs on the channel.
|
||||
int64 min_htlc_msat = 9 [json_name = "min_htlc_msat"];
|
||||
}
|
||||
message OpenStatusUpdate {
|
||||
oneof update {
|
||||
@ -1401,12 +1404,12 @@ message FeeReportResponse {
|
||||
repeated ChannelFeeReport channel_fees = 1 [json_name = "channel_fees"];
|
||||
}
|
||||
|
||||
message FeeUpdateRequest {
|
||||
message PolicyUpdateRequest {
|
||||
oneof scope {
|
||||
/// If set, then this fee update applies to all currently active channels.
|
||||
/// If set, then this update applies to all currently active channels.
|
||||
bool global = 1 [json_name = "global"] ;
|
||||
|
||||
/// If set, this fee update will target a specific channel.
|
||||
/// If set, this update will target a specific channel.
|
||||
ChannelPoint chan_point = 2 [json_name = "chan_point"];
|
||||
}
|
||||
|
||||
@ -1415,6 +1418,9 @@ message FeeUpdateRequest {
|
||||
|
||||
/// The effective fee rate in milli-satoshis. The precision of this value goes up to 6 decimal places, so 1e-6.
|
||||
double fee_rate = 4 [json_name = "fee_rate"];
|
||||
|
||||
/// The required timelock delta for HTLCs forwarded over the channel.
|
||||
uint32 time_lock_delta = 5 [json_name = "time_lock_delta"];
|
||||
}
|
||||
message FeeUpdateResponse {
|
||||
message PolicyUpdateResponse {
|
||||
}
|
||||
|
@ -222,13 +222,13 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"summary": "* lncli: `updatefees`\nUpdateFees allows the caller to update the fee schedule for all channels\nglobally, or a particular channel.",
|
||||
"operationId": "UpdateFees",
|
||||
"summary": "* lncli: `updatechanpolicy`\nUpdateChannelPolicy allows the caller to update the fee schedule and\nchannel policies for all channels globally, or a particular channel.",
|
||||
"operationId": "UpdateChannelPolicy",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/lnrpcFeeUpdateResponse"
|
||||
"$ref": "#/definitions/lnrpcPolicyUpdateResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -238,7 +238,7 @@
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/lnrpcFeeUpdateRequest"
|
||||
"$ref": "#/definitions/lnrpcPolicyUpdateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -1166,33 +1166,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"lnrpcFeeUpdateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"global": {
|
||||
"type": "boolean",
|
||||
"format": "boolean",
|
||||
"description": "/ If set, then this fee update applies to all currently active channels."
|
||||
},
|
||||
"chan_point": {
|
||||
"$ref": "#/definitions/lnrpcChannelPoint",
|
||||
"description": "/ If set, this fee update will target a specific channel."
|
||||
},
|
||||
"base_fee_msat": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "/ The base fee charged regardless of the number of milli-satoshis sent."
|
||||
},
|
||||
"fee_rate": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "/ The effective fee rate in milli-satoshis. The precision of this value goes up to 6 decimal places, so 1e-6."
|
||||
}
|
||||
}
|
||||
},
|
||||
"lnrpcFeeUpdateResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"lnrpcGetInfoResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -1616,6 +1589,11 @@
|
||||
"type": "boolean",
|
||||
"format": "boolean",
|
||||
"description": "/ Whether this channel should be private, not announced to the greater network."
|
||||
},
|
||||
"min_htlc_msat": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "/ The minimum value in millisatoshi we will require for incoming HTLCs on the channel."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1826,6 +1804,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"lnrpcPolicyUpdateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"global": {
|
||||
"type": "boolean",
|
||||
"format": "boolean",
|
||||
"description": "/ If set, then this update applies to all currently active channels."
|
||||
},
|
||||
"chan_point": {
|
||||
"$ref": "#/definitions/lnrpcChannelPoint",
|
||||
"description": "/ If set, this update will target a specific channel."
|
||||
},
|
||||
"base_fee_msat": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "/ The base fee charged regardless of the number of milli-satoshis sent."
|
||||
},
|
||||
"fee_rate": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "/ The effective fee rate in milli-satoshis. The precision of this value goes up to 6 decimal places, so 1e-6."
|
||||
},
|
||||
"time_lock_delta": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "/ The required timelock delta for HTLCs forwarded over the channel."
|
||||
}
|
||||
}
|
||||
},
|
||||
"lnrpcPolicyUpdateResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"lnrpcQueryRoutesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -122,11 +122,12 @@ func (cfg nodeConfig) genArgs() []string {
|
||||
args = append(args, "--nobootstrap")
|
||||
args = append(args, "--noencryptwallet")
|
||||
args = append(args, "--debuglevel=debug")
|
||||
args = append(args, "--defaultchanconfs=1")
|
||||
args = append(args, fmt.Sprintf("--bitcoin.rpchost=%v", cfg.RPCConfig.Host))
|
||||
args = append(args, fmt.Sprintf("--bitcoin.rpcuser=%v", cfg.RPCConfig.User))
|
||||
args = append(args, fmt.Sprintf("--bitcoin.rpcpass=%v", cfg.RPCConfig.Pass))
|
||||
args = append(args, fmt.Sprintf("--bitcoin.rawrpccert=%v", encodedCert))
|
||||
args = append(args, "--bitcoin.defaultchanconfs=1")
|
||||
args = append(args, "--bitcoin.defaultremotedelay=4")
|
||||
args = append(args, fmt.Sprintf("--btcd.rpchost=%v", cfg.RPCConfig.Host))
|
||||
args = append(args, fmt.Sprintf("--btcd.rpcuser=%v", cfg.RPCConfig.User))
|
||||
args = append(args, fmt.Sprintf("--btcd.rpcpass=%v", cfg.RPCConfig.Pass))
|
||||
args = append(args, fmt.Sprintf("--btcd.rawrpccert=%v", encodedCert))
|
||||
args = append(args, fmt.Sprintf("--rpcport=%v", cfg.RPCPort))
|
||||
args = append(args, fmt.Sprintf("--peerport=%v", cfg.P2PPort))
|
||||
args = append(args, fmt.Sprintf("--logdir=%v", cfg.LogDir))
|
||||
@ -163,6 +164,7 @@ type HarnessNode struct {
|
||||
|
||||
cmd *exec.Cmd
|
||||
pidFile string
|
||||
logFile *os.File
|
||||
|
||||
// processExit is a channel that's closed once it's detected that the
|
||||
// process this instance of HarnessNode is bound to has exited.
|
||||
@ -231,10 +233,10 @@ func (hn *HarnessNode) start(lndError chan<- error) error {
|
||||
// If the logoutput flag is passed, redirect output from the nodes to
|
||||
// log files.
|
||||
if *logOutput {
|
||||
logFile := fmt.Sprintf("output%d.log", hn.NodeID)
|
||||
fileName := fmt.Sprintf("output%d.log", hn.NodeID)
|
||||
|
||||
// Create file if not exists, otherwise append.
|
||||
file, err := os.OpenFile(logFile,
|
||||
file, err := os.OpenFile(fileName,
|
||||
os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -246,6 +248,10 @@ func (hn *HarnessNode) start(lndError chan<- error) error {
|
||||
|
||||
// Pass the node's stdout only to the file.
|
||||
hn.cmd.Stdout = file
|
||||
|
||||
// Let the node keep a reference to this file, such
|
||||
// that we can add to it if necessary.
|
||||
hn.logFile = file
|
||||
}
|
||||
|
||||
if err := hn.cmd.Start(); err != nil {
|
||||
@ -304,6 +310,19 @@ func (hn *HarnessNode) start(lndError chan<- error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddToLog adds a line of choice to the node's logfile. This is useful
|
||||
// to interleave test output with output from the node.
|
||||
func (hn *HarnessNode) AddToLog(line string) error {
|
||||
// If this node was not set up with a log file, just return early.
|
||||
if hn.logFile == nil {
|
||||
return nil
|
||||
}
|
||||
if _, err := hn.logFile.WriteString(line); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writePidFile writes the process ID of the running lnd process to a .pid file.
|
||||
func (hn *HarnessNode) writePidFile() error {
|
||||
filePath := filepath.Join(hn.cfg.BaseDir, fmt.Sprintf("%v.pid", hn.NodeID))
|
||||
|
@ -18,6 +18,10 @@ var (
|
||||
// ErrOutputSpent is returned by the GetUtxo method if the target output
|
||||
// for lookup has already been spent.
|
||||
ErrOutputSpent = errors.New("target output has been spent")
|
||||
|
||||
// ErrOutputNotFound signals that the desired output could not be
|
||||
// located.
|
||||
ErrOutputNotFound = errors.New("target output was not found")
|
||||
)
|
||||
|
||||
// GetBestBlock returns the current height and hash of the best known block
|
||||
@ -25,23 +29,7 @@ var (
|
||||
//
|
||||
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||
func (b *BtcWallet) GetBestBlock() (*chainhash.Hash, int32, error) {
|
||||
switch backend := b.chain.(type) {
|
||||
|
||||
case *chain.NeutrinoClient:
|
||||
header, height, err := backend.CS.BlockHeaders.ChainTip()
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
blockHash := header.BlockHash()
|
||||
return &blockHash, int32(height), nil
|
||||
|
||||
case *chain.RPCClient:
|
||||
return backend.GetBestBlock()
|
||||
|
||||
default:
|
||||
return nil, -1, fmt.Errorf("unknown backend")
|
||||
}
|
||||
return b.chain.GetBestBlock()
|
||||
}
|
||||
|
||||
// GetUtxo returns the original output referenced by the passed outpoint.
|
||||
@ -61,10 +49,19 @@ func (b *BtcWallet) GetUtxo(op *wire.OutPoint, heightHint uint32) (*wire.TxOut,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if spendReport != nil && spendReport.SpendingTx != nil {
|
||||
// If the spend report is nil, then the output was not found in
|
||||
// the rescan.
|
||||
if spendReport == nil {
|
||||
return nil, ErrOutputNotFound
|
||||
}
|
||||
|
||||
// If the spending transaction is populated in the spend report,
|
||||
// this signals that the output has already been spent.
|
||||
if spendReport.SpendingTx != nil {
|
||||
return nil, ErrOutputSpent
|
||||
}
|
||||
|
||||
// Otherwise, the output is assumed to be in the UTXO.
|
||||
return spendReport.Output, nil
|
||||
|
||||
case *chain.RPCClient:
|
||||
@ -87,6 +84,26 @@ func (b *BtcWallet) GetUtxo(op *wire.OutPoint, heightHint uint32) (*wire.TxOut,
|
||||
PkScript: pkScript,
|
||||
}, nil
|
||||
|
||||
case *chain.BitcoindClient:
|
||||
txout, err := backend.GetTxOut(&op.Hash, op.Index, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if txout == nil {
|
||||
return nil, ErrOutputSpent
|
||||
}
|
||||
|
||||
pkScript, err := hex.DecodeString(txout.ScriptPubKey.Hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wire.TxOut{
|
||||
// Sadly, gettxout returns the output value in BTC
|
||||
// instead of satoshis.
|
||||
Value: int64(txout.Value * 1e8),
|
||||
PkScript: pkScript,
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown backend")
|
||||
}
|
||||
@ -96,27 +113,7 @@ func (b *BtcWallet) GetUtxo(op *wire.OutPoint, heightHint uint32) (*wire.TxOut,
|
||||
//
|
||||
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||
func (b *BtcWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
|
||||
switch backend := b.chain.(type) {
|
||||
|
||||
case *chain.NeutrinoClient:
|
||||
block, err := backend.CS.GetBlockFromNetwork(*blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block.MsgBlock(), nil
|
||||
|
||||
case *chain.RPCClient:
|
||||
block, err := backend.GetBlock(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown backend")
|
||||
}
|
||||
return b.chain.GetBlock(blockHash)
|
||||
}
|
||||
|
||||
// GetBlockHash returns the hash of the block in the best blockchain at the
|
||||
@ -124,29 +121,7 @@ func (b *BtcWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error)
|
||||
//
|
||||
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||
func (b *BtcWallet) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
|
||||
switch backend := b.chain.(type) {
|
||||
|
||||
case *chain.NeutrinoClient:
|
||||
height := uint32(blockHeight)
|
||||
blockHeader, err := backend.CS.BlockHeaders.FetchHeaderByHeight(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockHash := blockHeader.BlockHash()
|
||||
return &blockHash, nil
|
||||
|
||||
case *chain.RPCClient:
|
||||
blockHash, err := backend.GetBlockHash(blockHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blockHash, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown backend")
|
||||
}
|
||||
return b.chain.GetBlockHash(blockHeight)
|
||||
}
|
||||
|
||||
// A compile time check to ensure that BtcWallet implements the BlockChainIO
|
||||
|
@ -119,6 +119,17 @@ func New(cfg Config) (*BtcWallet, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BackEnd returns the underlying ChainService's name as a string.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) BackEnd() string {
|
||||
if b.chain != nil {
|
||||
return b.chain.BackEnd()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Start initializes the underlying rpc connection, the wallet itself, and
|
||||
// begins syncing to the current available blockchain state.
|
||||
//
|
||||
@ -668,22 +679,9 @@ func (b *BtcWallet) IsSynced() (bool, error) {
|
||||
|
||||
// Next, query the chain backend to grab the info about the tip of the
|
||||
// main chain.
|
||||
switch backend := b.cfg.ChainSource.(type) {
|
||||
case *chain.NeutrinoClient:
|
||||
header, height, err := backend.CS.BlockHeaders.ChainTip()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
bh := header.BlockHash()
|
||||
bestHash = &bh
|
||||
bestHeight = int32(height)
|
||||
|
||||
case *chain.RPCClient:
|
||||
bestHash, bestHeight, err = backend.GetBestBlock()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
bestHash, bestHeight, err = b.cfg.ChainSource.GetBestBlock()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If the wallet hasn't yet fully synced to the node's best chain tip,
|
||||
@ -696,21 +694,9 @@ func (b *BtcWallet) IsSynced() (bool, error) {
|
||||
// still may not yet be synced as the chain backend may still be
|
||||
// catching up to the main chain. So we'll grab the block header in
|
||||
// order to make a guess based on the current time stamp.
|
||||
var blockHeader *wire.BlockHeader
|
||||
switch backend := b.cfg.ChainSource.(type) {
|
||||
|
||||
case *chain.NeutrinoClient:
|
||||
bh, _, err := backend.CS.BlockHeaders.FetchHeader(bestHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
blockHeader = bh
|
||||
|
||||
case *chain.RPCClient:
|
||||
blockHeader, err = backend.GetBlockHeader(bestHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
blockHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If the timestamp no the best header is more than 2 hours in the
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/roasbeef/btcwallet/chain"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -36,6 +37,7 @@ func init() {
|
||||
driver := &lnwallet.WalletDriver{
|
||||
WalletType: walletType,
|
||||
New: createNewWallet,
|
||||
BackEnds: chain.BackEnds,
|
||||
}
|
||||
|
||||
if err := lnwallet.RegisterWallet(driver); err != nil {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/roasbeef/btcd/blockchain"
|
||||
"github.com/roasbeef/btcd/rpcclient"
|
||||
"github.com/roasbeef/btcutil"
|
||||
@ -200,3 +202,145 @@ func (b *BtcdFeeEstimator) fetchEstimatePerByte(confTarget uint32) (btcutil.Amou
|
||||
// A compile-time assertion to ensure that BtcdFeeEstimator implements the
|
||||
// FeeEstimator interface.
|
||||
var _ FeeEstimator = (*BtcdFeeEstimator)(nil)
|
||||
|
||||
// BitcoindFeeEstimator is an implementation of the FeeEstimator interface
|
||||
// backed by the RPC interface of an active bitcoind node. This implementation
|
||||
// will proxy any fee estimation requests to bitcoind's RPC interace.
|
||||
type BitcoindFeeEstimator struct {
|
||||
// fallBackFeeRate is the fall back fee rate in satoshis per byte that
|
||||
// is returned if the fee estimator does not yet have enough data to
|
||||
// actually produce fee estimates.
|
||||
fallBackFeeRate btcutil.Amount
|
||||
|
||||
bitcoindConn *rpcclient.Client
|
||||
}
|
||||
|
||||
// NewBitcoindFeeEstimator creates a new BitcoindFeeEstimator given a fully
|
||||
// populated rpc config that is able to successfully connect and authenticate
|
||||
// with the bitcoind node, and also a fall back fee rate. The fallback fee rate
|
||||
// is used in the occasion that the estimator has insufficient data, or returns
|
||||
// zero for a fee estimate.
|
||||
func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig,
|
||||
fallBackFeeRate btcutil.Amount) (*BitcoindFeeEstimator, error) {
|
||||
|
||||
rpcConfig.DisableConnectOnNew = true
|
||||
rpcConfig.DisableAutoReconnect = false
|
||||
rpcConfig.DisableTLS = true
|
||||
rpcConfig.HTTPPostMode = true
|
||||
chainConn, err := rpcclient.New(&rpcConfig, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BitcoindFeeEstimator{
|
||||
fallBackFeeRate: fallBackFeeRate,
|
||||
bitcoindConn: chainConn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start signals the FeeEstimator to start any processes or goroutines
|
||||
// it needs to perform its duty.
|
||||
//
|
||||
// NOTE: This method is part of the FeeEstimator interface.
|
||||
func (b *BitcoindFeeEstimator) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops any spawned goroutines and cleans up the resources used
|
||||
// by the fee estimator.
|
||||
//
|
||||
// NOTE: This method is part of the FeeEstimator interface.
|
||||
func (b *BitcoindFeeEstimator) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EstimateFeePerByte takes in a target for the number of blocks until an
|
||||
// initial confirmation and returns the estimated fee expressed in
|
||||
// satoshis/byte.
|
||||
func (b *BitcoindFeeEstimator) EstimateFeePerByte(numBlocks uint32) (btcutil.Amount, error) {
|
||||
feeEstimate, err := b.fetchEstimatePerByte(numBlocks)
|
||||
switch {
|
||||
// If the estimator doesn't have enough data, or returns an error, then
|
||||
// to return a proper value, then we'll return the default fall back
|
||||
// fee rate.
|
||||
case err != nil:
|
||||
walletLog.Errorf("unable to query estimator: %v", err)
|
||||
fallthrough
|
||||
|
||||
case feeEstimate == 0:
|
||||
return b.fallBackFeeRate, nil
|
||||
}
|
||||
|
||||
return feeEstimate, nil
|
||||
}
|
||||
|
||||
// EstimateFeePerWeight takes in a target for the number of blocks until an
|
||||
// initial confirmation and returns the estimated fee expressed in
|
||||
// satoshis/weight.
|
||||
func (b *BitcoindFeeEstimator) EstimateFeePerWeight(numBlocks uint32) (btcutil.Amount, error) {
|
||||
feePerByte, err := b.EstimateFeePerByte(numBlocks)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// We'll scale down the fee per byte to fee per weight, as for each raw
|
||||
// byte, there's 1/4 unit of weight mapped to it.
|
||||
satWeight := feePerByte / blockchain.WitnessScaleFactor
|
||||
|
||||
// If this ends up scaling down to a zero sat/weight amount, then we'll
|
||||
// use the default fallback fee rate.
|
||||
// TODO(aakselrod): maybe use the per-byte rate if it's non-zero?
|
||||
// Otherwise, we can return a higher sat/byte than sat/weight.
|
||||
if satWeight == 0 {
|
||||
return b.fallBackFeeRate / blockchain.WitnessScaleFactor, nil
|
||||
}
|
||||
|
||||
return satWeight, nil
|
||||
}
|
||||
|
||||
// fetchEstimate returns a fee estimate for a transaction be be confirmed in
|
||||
// confTarget blocks. The estimate is returned in sat/byte.
|
||||
func (b *BitcoindFeeEstimator) fetchEstimatePerByte(confTarget uint32) (btcutil.Amount, error) {
|
||||
// First, we'll send an "estimatesmartfee" command as a raw request,
|
||||
// since it isn't supported by btcd but is available in bitcoind.
|
||||
target, err := json.Marshal(uint64(confTarget))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// TODO: Allow selection of economical/conservative modifiers.
|
||||
resp, err := b.bitcoindConn.RawRequest("estimatesmartfee",
|
||||
[]json.RawMessage{target})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Next, we'll parse the response to get the BTC per KB.
|
||||
feeEstimate := struct {
|
||||
Feerate float64 `json:"feerate"`
|
||||
}{}
|
||||
err = json.Unmarshal(resp, &feeEstimate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Next, we'll convert the returned value to satoshis, as it's
|
||||
// currently returned in BTC.
|
||||
satPerKB, err := btcutil.NewAmount(feeEstimate.Feerate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// The value returned is expressed in fees per KB, while we want
|
||||
// fee-per-byte, so we'll divide by 1024 to map to satoshis-per-byte
|
||||
// before returning the estimate.
|
||||
satPerByte := satPerKB / 1024
|
||||
|
||||
walletLog.Debugf("Returning %v sat/byte for conf target of %v",
|
||||
int64(satPerByte), confTarget)
|
||||
|
||||
return satPerByte, nil
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that BitcoindFeeEstimator implements the
|
||||
// FeeEstimator interface.
|
||||
var _ FeeEstimator = (*BitcoindFeeEstimator)(nil)
|
||||
|
@ -207,6 +207,11 @@ type WalletController interface {
|
||||
// Stop signals the wallet for shutdown. Shutdown may entail closing
|
||||
// any active sockets, database handles, stopping goroutines, etc.
|
||||
Stop() error
|
||||
|
||||
// BackEnd returns a name for the wallet's backing chain service,
|
||||
// which could be e.g. btcd, bitcoind, neutrino, or another consensus
|
||||
// service.
|
||||
BackEnd() string
|
||||
}
|
||||
|
||||
// BlockChainIO is a dedicated source which will be used to obtain queries
|
||||
@ -288,6 +293,10 @@ type WalletDriver struct {
|
||||
// initialization flexibility, thereby accommodating several potential
|
||||
// WalletController implementations.
|
||||
New func(args ...interface{}) (WalletController, error)
|
||||
|
||||
// BackEnds returns a list of available chain service drivers for the
|
||||
// wallet driver. This could be e.g. bitcoind, btcd, neutrino, etc.
|
||||
BackEnds func() []string
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -5,8 +5,10 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@ -17,7 +19,10 @@ import (
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
"github.com/lightninglabs/neutrino"
|
||||
"github.com/roasbeef/btcwallet/chain"
|
||||
"github.com/roasbeef/btcwallet/walletdb"
|
||||
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
|
||||
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
||||
@ -25,10 +30,10 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/roasbeef/btcd/btcjson"
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
"github.com/roasbeef/btcd/rpcclient"
|
||||
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
|
||||
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcd/integration/rpctest"
|
||||
@ -76,7 +81,7 @@ var (
|
||||
0x69, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
|
||||
}
|
||||
|
||||
netParams = &chaincfg.SimNetParams
|
||||
netParams = &chaincfg.RegressionNetParams
|
||||
chainHash = netParams.GenesisHash
|
||||
|
||||
_, alicePub = btcec.PrivKeyFromBytes(btcec.S256(), testHdSeed[:])
|
||||
@ -145,6 +150,12 @@ func calcStaticFee(numHTLCs int) btcutil.Amount {
|
||||
func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
numOutputs, btcPerOutput int) error {
|
||||
|
||||
// For initial neutrino connection, wait a second.
|
||||
// TODO(aakselrod): Eliminate the need for this.
|
||||
switch w.BackEnd() {
|
||||
case "neutrino":
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
// Using the mining node, spend from a coinbase output numOutputs to
|
||||
// give us btcPerOutput with each output.
|
||||
satoshiPerOutput := int64(btcPerOutput * 1e8)
|
||||
@ -188,6 +199,7 @@ func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
|
||||
// Wait until the wallet has finished syncing up to the main chain.
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
timeout := time.After(30 * time.Second)
|
||||
|
||||
for range ticker.C {
|
||||
balance, err := w.ConfirmedBalance(1, false)
|
||||
@ -197,6 +209,17 @@ func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
if balance == expectedBalance {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-timeout:
|
||||
synced, err := w.IsSynced()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("timed out after 30 seconds "+
|
||||
"waiting for balance %v, current balance %v, "+
|
||||
"synced: %t", expectedBalance, balance, synced)
|
||||
default:
|
||||
}
|
||||
}
|
||||
ticker.Stop()
|
||||
|
||||
@ -222,7 +245,7 @@ func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
|
||||
WalletController: wc,
|
||||
Signer: signer,
|
||||
ChainIO: bio,
|
||||
FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 250},
|
||||
FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 10},
|
||||
DefaultConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: 500,
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) * 100,
|
||||
@ -343,6 +366,9 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
bobFundingSigs, bobCommitSig,
|
||||
)
|
||||
if err != nil {
|
||||
for _, in := range aliceChanReservation.FinalFundingTx().TxIn {
|
||||
fmt.Println(in.PreviousOutPoint.String())
|
||||
}
|
||||
t.Fatalf("unable to consume alice's sigs: %v", err)
|
||||
}
|
||||
_, err = bobChanReservation.CompleteReservation(
|
||||
@ -384,6 +410,10 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
|
||||
// Mine a single block, the funding transaction should be included
|
||||
// within this block.
|
||||
err = waitForMempoolTx(miner, &fundingSha)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
blockHashes, err := miner.Node.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate block: %v", err)
|
||||
@ -402,6 +432,16 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
|
||||
assertReservationDeleted(aliceChanReservation, t)
|
||||
assertReservationDeleted(bobChanReservation, t)
|
||||
|
||||
// Wait for wallets to catch up to prevent issues in subsequent tests.
|
||||
err = waitForWalletSync(miner, alice)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync alice: %v", err)
|
||||
}
|
||||
err = waitForWalletSync(miner, bob)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync bob: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
|
||||
@ -759,6 +799,10 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
|
||||
// Mine a single block, the funding transaction should be included
|
||||
// within this block.
|
||||
err = waitForMempoolTx(miner, &fundingSha)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
blockHashes, err := miner.Node.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate block: %v", err)
|
||||
@ -768,7 +812,8 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to find block: %v", err)
|
||||
}
|
||||
if len(block.Transactions) != 2 {
|
||||
t.Fatalf("funding transaction wasn't mined: %v", err)
|
||||
t.Fatalf("funding transaction wasn't mined: %d",
|
||||
len(block.Transactions))
|
||||
}
|
||||
blockTx := block.Transactions[1]
|
||||
if blockTx.TxHash() != fundingSha {
|
||||
@ -815,8 +860,10 @@ func testListTransactionDetails(miner *rpctest.Harness,
|
||||
}
|
||||
|
||||
// Next, fetch all the current transaction details.
|
||||
// TODO(roasbeef): use ntfn client here instead?
|
||||
time.Sleep(time.Second * 2)
|
||||
err = waitForWalletSync(miner, alice)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't sync Alice's wallet: %v", err)
|
||||
}
|
||||
txDetails, err := alice.ListTransactionDetails()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch tx details: %v", err)
|
||||
@ -905,6 +952,10 @@ func testListTransactionDetails(miner *rpctest.Harness,
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create burn tx: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(miner, burnTXID)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
burnBlock, err := miner.Node.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mine block: %v", err)
|
||||
@ -912,7 +963,10 @@ func testListTransactionDetails(miner *rpctest.Harness,
|
||||
|
||||
// Fetch the transaction details again, the new transaction should be
|
||||
// shown as debiting from the wallet's balance.
|
||||
time.Sleep(time.Second * 2)
|
||||
err = waitForWalletSync(miner, alice)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't sync Alice's wallet: %v", err)
|
||||
}
|
||||
txDetails, err = alice.ListTransactionDetails()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch tx details: %v", err)
|
||||
@ -955,7 +1009,7 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
// implementation of the WalletController.
|
||||
txClient, err := alice.SubscribeTransactions()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate tx subscription: %v", err)
|
||||
t.Skipf("unable to generate tx subscription: %v", err)
|
||||
}
|
||||
defer txClient.Cancel()
|
||||
|
||||
@ -964,25 +1018,33 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
numTxns = 3
|
||||
)
|
||||
unconfirmedNtfns := make(chan struct{})
|
||||
go func() {
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txDetail := <-txClient.UnconfirmedTransactions()
|
||||
if txDetail.NumConfirmations != 0 {
|
||||
t.Fatalf("incorrect number of confs, expected %v got %v",
|
||||
0, txDetail.NumConfirmations)
|
||||
switch alice.BackEnd() {
|
||||
case "neutrino":
|
||||
// Neutrino doesn't listen for unconfirmed transactions.
|
||||
default:
|
||||
go func() {
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txDetail := <-txClient.UnconfirmedTransactions()
|
||||
if txDetail.NumConfirmations != 0 {
|
||||
t.Fatalf("incorrect number of confs, "+
|
||||
"expected %v got %v", 0,
|
||||
txDetail.NumConfirmations)
|
||||
}
|
||||
if txDetail.Value != outputAmt {
|
||||
t.Fatalf("incorrect output amt, "+
|
||||
"expected %v got %v", outputAmt,
|
||||
txDetail.Value)
|
||||
}
|
||||
if txDetail.BlockHash != nil {
|
||||
t.Fatalf("block hash should be nil, "+
|
||||
"is instead %v",
|
||||
txDetail.BlockHash)
|
||||
}
|
||||
}
|
||||
if txDetail.Value != outputAmt {
|
||||
t.Fatalf("incorrect output amt, expected %v got %v",
|
||||
outputAmt, txDetail.Value)
|
||||
}
|
||||
if txDetail.BlockHash != nil {
|
||||
t.Fatalf("block hash should be nil, is instead %v",
|
||||
txDetail.BlockHash)
|
||||
}
|
||||
}
|
||||
|
||||
close(unconfirmedNtfns)
|
||||
}()
|
||||
close(unconfirmedNtfns)
|
||||
}()
|
||||
}
|
||||
|
||||
// Next, fetch a fresh address from the wallet, create 3 new outputs
|
||||
// with the pkScript.
|
||||
@ -1000,17 +1062,27 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
Value: outputAmt,
|
||||
PkScript: script,
|
||||
}
|
||||
if _, err := miner.SendOutputs([]*wire.TxOut{output}, 10); err != nil {
|
||||
txid, err := miner.SendOutputs([]*wire.TxOut{output}, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send coinbase: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// We should receive a notification for all three transactions
|
||||
// generated above.
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("transactions not received after 3 seconds")
|
||||
case <-unconfirmedNtfns: // Fall through on successs
|
||||
switch alice.BackEnd() {
|
||||
case "neutrino":
|
||||
// Neutrino doesn't listen for on unconfirmed transactions.
|
||||
default:
|
||||
// We should receive a notification for all three transactions
|
||||
// generated above.
|
||||
select {
|
||||
case <-time.After(time.Second * 10):
|
||||
t.Fatalf("transactions not received after 10 seconds")
|
||||
case <-unconfirmedNtfns: // Fall through on successs
|
||||
}
|
||||
}
|
||||
|
||||
confirmedNtfns := make(chan struct{})
|
||||
@ -1018,12 +1090,12 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txDetail := <-txClient.ConfirmedTransactions()
|
||||
if txDetail.NumConfirmations != 1 {
|
||||
t.Fatalf("incorrect number of confs, expected %v got %v",
|
||||
1, txDetail.NumConfirmations)
|
||||
t.Fatalf("incorrect number of confs for %s, expected %v got %v",
|
||||
txDetail.Hash, 1, txDetail.NumConfirmations)
|
||||
}
|
||||
if txDetail.Value != outputAmt {
|
||||
t.Fatalf("incorrect output amt, expected %v got %v",
|
||||
outputAmt, txDetail.Value)
|
||||
t.Fatalf("incorrect output amt, expected %v got %v in txid %s",
|
||||
outputAmt, txDetail.Value, txDetail.Hash)
|
||||
}
|
||||
}
|
||||
close(confirmedNtfns)
|
||||
@ -1039,7 +1111,7 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
// since they should be mined in the next block.
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("transactions not received after 3 seconds")
|
||||
t.Fatalf("transactions not received after 5 seconds")
|
||||
case <-confirmedNtfns: // Fall through on success
|
||||
}
|
||||
}
|
||||
@ -1088,7 +1160,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness,
|
||||
// generate a regular p2wkh from that.
|
||||
pubkeyHash := btcutil.Hash160(tweakedKey.SerializeCompressed())
|
||||
keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubkeyHash,
|
||||
&chaincfg.SimNetParams)
|
||||
&chaincfg.RegressionNetParams)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create addr: %v", err)
|
||||
}
|
||||
@ -1110,6 +1182,10 @@ func testSignOutputUsingTweaks(r *rpctest.Harness,
|
||||
|
||||
// Query for the transaction generated above so we can located
|
||||
// the index of our output.
|
||||
err = waitForMempoolTx(r, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
tx, err := r.Node.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for tx: %v", err)
|
||||
@ -1197,7 +1273,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
}
|
||||
|
||||
// Give wallet time to catch up.
|
||||
err = waitForWalletSync(w)
|
||||
err = waitForWalletSync(r, w)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync wallet: %v", err)
|
||||
}
|
||||
@ -1222,16 +1298,21 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
Value: 1e8,
|
||||
PkScript: script,
|
||||
}
|
||||
if _, err = w.SendOutputs([]*wire.TxOut{output}, 10); err != nil {
|
||||
txid, err := w.SendOutputs([]*wire.TxOut{output}, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send outputs: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(r, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
_, err = r.Node.Generate(50)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate blocks on passed node: %v", err)
|
||||
}
|
||||
|
||||
// Give wallet time to catch up.
|
||||
err = waitForWalletSync(w)
|
||||
err = waitForWalletSync(r, w)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync wallet: %v", err)
|
||||
}
|
||||
@ -1277,32 +1358,34 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
// one block on the passed miner and two on the created miner,
|
||||
// connecting them, and waiting for them to sync.
|
||||
for i := 0; i < 5; i++ {
|
||||
peers, err := r2.Node.GetPeerInfo()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get peer info: %v", err)
|
||||
}
|
||||
numPeers := len(peers)
|
||||
err = r2.Node.AddNode(r.P2PAddress(), rpcclient.ANRemove)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to disconnect mining nodes: %v", err)
|
||||
}
|
||||
// Wait for disconnection
|
||||
timeout := time.After(30 * time.Second)
|
||||
for true {
|
||||
stillConnected := true
|
||||
var peers []btcjson.GetPeerInfoResult
|
||||
for stillConnected {
|
||||
// Allow for timeout
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout waiting for miner disconnect")
|
||||
default:
|
||||
}
|
||||
err = r2.Node.AddNode(r.P2PAddress(), rpcclient.ANRemove)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to disconnect mining nodes: %v",
|
||||
err)
|
||||
}
|
||||
peers, err = r2.Node.GetPeerInfo()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get peer info: %v", err)
|
||||
}
|
||||
if len(peers) < numPeers {
|
||||
break
|
||||
stillConnected = false
|
||||
for _, peer := range peers {
|
||||
if peer.Addr == r.P2PAddress() {
|
||||
stillConnected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
_, err = r.Node.Generate(2)
|
||||
if err != nil {
|
||||
@ -1318,8 +1401,16 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
// Step 5: Reconnect the miners and wait for them to synchronize.
|
||||
err = r2.Node.AddNode(r.P2PAddress(), rpcclient.ANAdd)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect mining nodes together: %v",
|
||||
err)
|
||||
switch err := err.(type) {
|
||||
case *btcjson.RPCError:
|
||||
if err.Code != -8 {
|
||||
t.Fatalf("unable to connect mining "+
|
||||
"nodes together: %v", err)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unable to connect mining nodes "+
|
||||
"together: %v", err)
|
||||
}
|
||||
}
|
||||
err = rpctest.JoinNodes([]*rpctest.Harness{r2, r},
|
||||
rpctest.Blocks)
|
||||
@ -1328,7 +1419,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
}
|
||||
|
||||
// Give wallet time to catch up.
|
||||
err = waitForWalletSync(w)
|
||||
err = waitForWalletSync(r, w)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync wallet: %v", err)
|
||||
}
|
||||
@ -1405,21 +1496,78 @@ func clearWalletStates(a, b *lnwallet.LightningWallet) error {
|
||||
return b.Cfg.Database.Wipe()
|
||||
}
|
||||
|
||||
func waitForWalletSync(w *lnwallet.LightningWallet) error {
|
||||
var synced bool
|
||||
func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error {
|
||||
var found bool
|
||||
var tx *btcutil.Tx
|
||||
var err error
|
||||
timeout := time.After(10 * time.Second)
|
||||
for !synced {
|
||||
synced, err = w.IsSynced()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for !found {
|
||||
// Do a short wait
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timeout after 10s")
|
||||
default:
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Check for the harness' knowledge of the txid
|
||||
tx, err = r.Node.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case *btcjson.RPCError:
|
||||
if e.Code == btcjson.ErrRPCNoTxInfo {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
}
|
||||
return err
|
||||
}
|
||||
if tx != nil && tx.MsgTx().TxHash() == *txid {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error {
|
||||
var synced bool
|
||||
var err error
|
||||
var bestHash, knownHash *chainhash.Hash
|
||||
var bestHeight, knownHeight int32
|
||||
timeout := time.After(10 * time.Second)
|
||||
for !synced {
|
||||
// Do a short wait
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timeout after 10s")
|
||||
default:
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Check whether the chain source of the wallet is caught up to
|
||||
// the harness it's supposed to be catching up to.
|
||||
bestHash, bestHeight, err = r.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
knownHash, knownHeight, err = w.Cfg.ChainIO.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if knownHeight != bestHeight {
|
||||
continue
|
||||
}
|
||||
if *knownHash != *bestHash {
|
||||
return fmt.Errorf("hash at height %d doesn't match: "+
|
||||
"expected %s, got %s", bestHeight, bestHash,
|
||||
knownHash)
|
||||
}
|
||||
|
||||
// Check for synchronization.
|
||||
synced, err = w.IsSynced()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1454,7 +1602,7 @@ func TestLightningWallet(t *testing.T) {
|
||||
}
|
||||
|
||||
// Next mine enough blocks in order for segwit and the CSV package
|
||||
// soft-fork to activate on SimNet.
|
||||
// soft-fork to activate on RegNet.
|
||||
numBlocks := netParams.MinerConfirmationWindow * 2
|
||||
if _, err := miningNode.Node.Generate(numBlocks); err != nil {
|
||||
t.Fatalf("unable to generate blocks: %v", err)
|
||||
@ -1470,6 +1618,22 @@ func TestLightningWallet(t *testing.T) {
|
||||
t.Fatalf("unable to start notifier: %v", err)
|
||||
}
|
||||
|
||||
for _, walletDriver := range lnwallet.RegisteredWallets() {
|
||||
for _, backEnd := range walletDriver.BackEnds() {
|
||||
runTests(t, walletDriver, backEnd, miningNode,
|
||||
rpcConfig, chainNotifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runTests runs all of the tests for a single interface implementation and
|
||||
// chain back-end combination. This makes it easier to use `defer` as well as
|
||||
// factoring out the test logic from the loop which cycles through the
|
||||
// interface implementations.
|
||||
func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
|
||||
backEnd string, miningNode *rpctest.Harness,
|
||||
rpcConfig rpcclient.ConnConfig,
|
||||
chainNotifier *btcdnotify.BtcdNotifier) {
|
||||
var (
|
||||
bio lnwallet.BlockChainIO
|
||||
|
||||
@ -1478,107 +1642,230 @@ func TestLightningWallet(t *testing.T) {
|
||||
|
||||
aliceWalletController lnwallet.WalletController
|
||||
bobWalletController lnwallet.WalletController
|
||||
|
||||
feeEstimator lnwallet.FeeEstimator
|
||||
)
|
||||
for _, walletDriver := range lnwallet.RegisteredWallets() {
|
||||
tempTestDirAlice, err := ioutil.TempDir("", "lnwallet")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempTestDirAlice)
|
||||
|
||||
tempTestDirBob, err := ioutil.TempDir("", "lnwallet")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempTestDirBob)
|
||||
tempTestDirAlice, err := ioutil.TempDir("", "lnwallet")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempTestDirAlice)
|
||||
|
||||
walletType := walletDriver.WalletType
|
||||
switch walletType {
|
||||
case "btcwallet":
|
||||
aliceChainRPC, err := chain.NewRPCClient(netParams,
|
||||
tempTestDirBob, err := ioutil.TempDir("", "lnwallet")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempTestDirBob)
|
||||
|
||||
walletType := walletDriver.WalletType
|
||||
switch walletType {
|
||||
case "btcwallet":
|
||||
var aliceClient, bobClient chain.Interface
|
||||
switch backEnd {
|
||||
case "btcd":
|
||||
feeEstimator, err = lnwallet.NewBtcdFeeEstimator(
|
||||
rpcConfig, 250)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create btcd fee estimator: %v",
|
||||
err)
|
||||
}
|
||||
aliceClient, err = chain.NewRPCClient(netParams,
|
||||
rpcConfig.Host, rpcConfig.User, rpcConfig.Pass,
|
||||
rpcConfig.Certificates, false, 20)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make chain rpc: %v", err)
|
||||
}
|
||||
aliceWalletConfig := &btcwallet.Config{
|
||||
PrivatePass: []byte("alice-pass"),
|
||||
HdSeed: aliceHDSeed[:],
|
||||
DataDir: tempTestDirAlice,
|
||||
NetParams: netParams,
|
||||
ChainSource: aliceChainRPC,
|
||||
FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 250},
|
||||
}
|
||||
aliceWalletController, err = walletDriver.New(aliceWalletConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create btcwallet: %v", err)
|
||||
}
|
||||
aliceSigner = aliceWalletController.(*btcwallet.BtcWallet)
|
||||
|
||||
bobChainRPC, err := chain.NewRPCClient(netParams,
|
||||
bobClient, err = chain.NewRPCClient(netParams,
|
||||
rpcConfig.Host, rpcConfig.User, rpcConfig.Pass,
|
||||
rpcConfig.Certificates, false, 20)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make chain rpc: %v", err)
|
||||
}
|
||||
bobWalletConfig := &btcwallet.Config{
|
||||
PrivatePass: []byte("bob-pass"),
|
||||
HdSeed: bobHDSeed[:],
|
||||
DataDir: tempTestDirBob,
|
||||
NetParams: netParams,
|
||||
ChainSource: bobChainRPC,
|
||||
FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 250},
|
||||
}
|
||||
bobWalletController, err = walletDriver.New(bobWalletConfig)
|
||||
case "neutrino":
|
||||
feeEstimator = lnwallet.StaticFeeEstimator{FeeRate: 250}
|
||||
// Set some package-level variable to speed up
|
||||
// operation for tests.
|
||||
neutrino.WaitForMoreCFHeaders = time.Millisecond * 100
|
||||
neutrino.BanDuration = time.Millisecond * 100
|
||||
neutrino.QueryTimeout = time.Millisecond * 500
|
||||
neutrino.QueryNumRetries = 2
|
||||
// Start Alice - open a database, start a neutrino
|
||||
// instance, and initialize a btcwallet driver for it.
|
||||
aliceDB, err := walletdb.Create("bdb",
|
||||
tempTestDirAlice+"/neutrino.db")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create btcwallet: %v", err)
|
||||
t.Fatalf("unable to create DB: %v", err)
|
||||
}
|
||||
defer aliceDB.Close()
|
||||
aliceChain, err := neutrino.NewChainService(
|
||||
neutrino.Config{
|
||||
DataDir: tempTestDirAlice,
|
||||
Database: aliceDB,
|
||||
Namespace: []byte("alice"),
|
||||
ChainParams: *netParams,
|
||||
ConnectPeers: []string{
|
||||
miningNode.P2PAddress(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make neutrino: %v", err)
|
||||
}
|
||||
aliceChain.Start()
|
||||
defer aliceChain.Stop()
|
||||
aliceClient = chain.NewNeutrinoClient(aliceChain)
|
||||
|
||||
// Start Bob - open a database, start a neutrino
|
||||
// instance, and initialize a btcwallet driver for it.
|
||||
bobDB, err := walletdb.Create("bdb",
|
||||
tempTestDirBob+"/neutrino.db")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create DB: %v", err)
|
||||
}
|
||||
defer bobDB.Close()
|
||||
bobChain, err := neutrino.NewChainService(
|
||||
neutrino.Config{
|
||||
DataDir: tempTestDirBob,
|
||||
Database: bobDB,
|
||||
Namespace: []byte("bob"),
|
||||
ChainParams: *netParams,
|
||||
ConnectPeers: []string{
|
||||
miningNode.P2PAddress(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make neutrino: %v", err)
|
||||
}
|
||||
bobChain.Start()
|
||||
defer bobChain.Stop()
|
||||
bobClient = chain.NewNeutrinoClient(bobChain)
|
||||
case "bitcoind":
|
||||
feeEstimator, err = lnwallet.NewBitcoindFeeEstimator(
|
||||
rpcConfig, 250)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create bitcoind fee estimator: %v",
|
||||
err)
|
||||
}
|
||||
// Start a bitcoind instance.
|
||||
tempBitcoindDir, err := ioutil.TempDir("", "bitcoind")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
zmqPath := "ipc:///" + tempBitcoindDir + "/weks.socket"
|
||||
defer os.RemoveAll(tempBitcoindDir)
|
||||
rpcPort := rand.Int()%(65536-1024) + 1024
|
||||
bitcoind := exec.Command(
|
||||
"bitcoind",
|
||||
"-datadir="+tempBitcoindDir,
|
||||
"-regtest",
|
||||
"-connect="+miningNode.P2PAddress(),
|
||||
"-txindex",
|
||||
"-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6f"+
|
||||
"d$507c670e800a95284294edb5773b05544b"+
|
||||
"220110063096c221be9933c82d38e1",
|
||||
fmt.Sprintf("-rpcport=%d", rpcPort),
|
||||
"-disablewallet",
|
||||
"-zmqpubrawblock="+zmqPath,
|
||||
"-zmqpubrawtx="+zmqPath,
|
||||
)
|
||||
err = bitcoind.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't start bitcoind: %v", err)
|
||||
}
|
||||
defer bitcoind.Wait()
|
||||
defer bitcoind.Process.Kill()
|
||||
|
||||
// Start an Alice btcwallet bitcoind back end instance.
|
||||
aliceClient, err = chain.NewBitcoindClient(netParams,
|
||||
fmt.Sprintf("127.0.0.1:%d", rpcPort), "weks",
|
||||
"weks", zmqPath, 100*time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't start alice client: %v", err)
|
||||
}
|
||||
|
||||
// Start a Bob btcwallet bitcoind back end instance.
|
||||
bobClient, err = chain.NewBitcoindClient(netParams,
|
||||
fmt.Sprintf("127.0.0.1:%d", rpcPort), "weks",
|
||||
"weks", zmqPath, 100*time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't start bob client: %v", err)
|
||||
}
|
||||
bobSigner = bobWalletController.(*btcwallet.BtcWallet)
|
||||
bio = bobWalletController.(*btcwallet.BtcWallet)
|
||||
default:
|
||||
// TODO(roasbeef): add neutrino case
|
||||
t.Fatalf("unknown wallet driver: %v", walletType)
|
||||
t.Fatalf("unknown chain driver: %v", backEnd)
|
||||
}
|
||||
|
||||
// Funding via 20 outputs with 4BTC each.
|
||||
alice, err := createTestWallet(tempTestDirAlice, miningNode,
|
||||
netParams, chainNotifier, aliceWalletController,
|
||||
aliceSigner, bio)
|
||||
aliceWalletConfig := &btcwallet.Config{
|
||||
PrivatePass: []byte("alice-pass"),
|
||||
HdSeed: aliceHDSeed[:],
|
||||
DataDir: tempTestDirAlice,
|
||||
NetParams: netParams,
|
||||
ChainSource: aliceClient,
|
||||
FeeEstimator: feeEstimator,
|
||||
}
|
||||
aliceWalletController, err = walletDriver.New(aliceWalletConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test ln wallet: %v", err)
|
||||
t.Fatalf("unable to create btcwallet: %v", err)
|
||||
}
|
||||
defer alice.Shutdown()
|
||||
aliceSigner = aliceWalletController.(*btcwallet.BtcWallet)
|
||||
|
||||
bob, err := createTestWallet(tempTestDirBob, miningNode,
|
||||
netParams, chainNotifier, bobWalletController,
|
||||
bobSigner, bio)
|
||||
bobWalletConfig := &btcwallet.Config{
|
||||
PrivatePass: []byte("bob-pass"),
|
||||
HdSeed: bobHDSeed[:],
|
||||
DataDir: tempTestDirBob,
|
||||
NetParams: netParams,
|
||||
ChainSource: bobClient,
|
||||
FeeEstimator: feeEstimator,
|
||||
}
|
||||
bobWalletController, err = walletDriver.New(bobWalletConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test ln wallet: %v", err)
|
||||
t.Fatalf("unable to create btcwallet: %v", err)
|
||||
}
|
||||
defer bob.Shutdown()
|
||||
bobSigner = bobWalletController.(*btcwallet.BtcWallet)
|
||||
bio = bobWalletController.(*btcwallet.BtcWallet)
|
||||
default:
|
||||
t.Fatalf("unknown wallet driver: %v", walletType)
|
||||
}
|
||||
|
||||
// Both wallets should now have 80BTC available for spending.
|
||||
assertProperBalance(t, alice, 1, 80)
|
||||
assertProperBalance(t, bob, 1, 80)
|
||||
// Funding via 20 outputs with 4BTC each.
|
||||
alice, err := createTestWallet(tempTestDirAlice, miningNode, netParams,
|
||||
chainNotifier, aliceWalletController, aliceSigner, bio)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test ln wallet: %v", err)
|
||||
}
|
||||
defer alice.Shutdown()
|
||||
|
||||
// Execute every test, clearing possibly mutated wallet state
|
||||
// after each step.
|
||||
for _, walletTest := range walletTests {
|
||||
testName := fmt.Sprintf("%v:%v", walletType,
|
||||
walletTest.name)
|
||||
success := t.Run(testName, func(t *testing.T) {
|
||||
walletTest.test(miningNode, alice, bob, t)
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
bob, err := createTestWallet(tempTestDirBob, miningNode, netParams,
|
||||
chainNotifier, bobWalletController, bobSigner, bio)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test ln wallet: %v", err)
|
||||
}
|
||||
defer bob.Shutdown()
|
||||
|
||||
// TODO(roasbeef): possible reset mining node's
|
||||
// chainstate to initial level, cleanly wipe buckets
|
||||
if err := clearWalletStates(alice, bob); err != nil &&
|
||||
err != bolt.ErrBucketNotFound {
|
||||
t.Fatalf("unable to wipe wallet state: %v", err)
|
||||
}
|
||||
// Both wallets should now have 80BTC available for
|
||||
// spending.
|
||||
assertProperBalance(t, alice, 1, 80)
|
||||
assertProperBalance(t, bob, 1, 80)
|
||||
|
||||
// Execute every test, clearing possibly mutated
|
||||
// wallet state after each step.
|
||||
for _, walletTest := range walletTests {
|
||||
testName := fmt.Sprintf("%v/%v:%v", walletType, backEnd,
|
||||
walletTest.name)
|
||||
success := t.Run(testName, func(t *testing.T) {
|
||||
walletTest.test(miningNode, alice, bob, t)
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
|
||||
// TODO(roasbeef): possible reset mining
|
||||
// node's chainstate to initial level, cleanly
|
||||
// wipe buckets
|
||||
if err := clearWalletStates(alice, bob); err !=
|
||||
nil && err != bolt.ErrBucketNotFound {
|
||||
t.Fatalf("unable to wipe wallet state: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
mock.go
5
mock.go
@ -183,6 +183,11 @@ type mockWalletController struct {
|
||||
publishedTransactions chan *wire.MsgTx
|
||||
}
|
||||
|
||||
// BackEnd returns "mock" to signify a mock wallet controller.
|
||||
func (*mockWalletController) BackEnd() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
// FetchInputInfo will be called to get info about the inputs to the funding
|
||||
// transaction.
|
||||
func (*mockWalletController) FetchInputInfo(
|
||||
|
6
pilot.go
6
pilot.go
@ -89,8 +89,12 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(halseth): make configurable?
|
||||
minHtlc := lnwire.NewMSatFromSatoshis(1)
|
||||
|
||||
updateStream, errChan := c.server.OpenChannel(-1, target, amt, 0,
|
||||
feePerWeight, false)
|
||||
minHtlc, feePerWeight, false)
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
|
460
routing/chainview/bitcoind.go
Normal file
460
routing/chainview/bitcoind.go
Normal file
@ -0,0 +1,460 @@
|
||||
package chainview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/roasbeef/btcd/btcjson"
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
"github.com/roasbeef/btcd/rpcclient"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/roasbeef/btcutil"
|
||||
"github.com/roasbeef/btcwallet/chain"
|
||||
"github.com/roasbeef/btcwallet/wtxmgr"
|
||||
)
|
||||
|
||||
// BitcoindFilteredChainView is an implementation of the FilteredChainView
|
||||
// interface which is backed by bitcoind.
|
||||
type BitcoindFilteredChainView struct {
|
||||
started int32
|
||||
stopped int32
|
||||
|
||||
// bestHeight is the height of the latest block added to the
|
||||
// blockQueue from the onFilteredConnectedMethod. It is used to
|
||||
// determine up to what height we would need to rescan in case
|
||||
// of a filter update.
|
||||
bestHeightMtx sync.Mutex
|
||||
bestHeight uint32
|
||||
|
||||
// TODO: Factor out common logic between bitcoind and btcd into a
|
||||
// NodeFilteredView interface.
|
||||
chainClient *chain.BitcoindClient
|
||||
|
||||
// blockEventQueue is the ordered queue used to keep the order
|
||||
// of connected and disconnected blocks sent to the reader of the
|
||||
// chainView.
|
||||
blockQueue *blockEventQueue
|
||||
|
||||
// filterUpdates is a channel in which updates to the utxo filter
|
||||
// attached to this instance are sent over.
|
||||
filterUpdates chan filterUpdate
|
||||
|
||||
// chainFilter is the set of utox's that we're currently watching
|
||||
// spends for within the chain.
|
||||
filterMtx sync.RWMutex
|
||||
chainFilter map[wire.OutPoint]struct{}
|
||||
|
||||
// filterBlockReqs is a channel in which requests to filter select
|
||||
// blocks will be sent over.
|
||||
filterBlockReqs chan *filterBlockReq
|
||||
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// A compile time check to ensure BitcoindFilteredChainView implements the
|
||||
// chainview.FilteredChainView.
|
||||
var _ FilteredChainView = (*BitcoindFilteredChainView)(nil)
|
||||
|
||||
// NewBitcoindFilteredChainView creates a new instance of a FilteredChainView
|
||||
// from RPC credentials and a ZMQ socket address for a bitcoind instance.
|
||||
func NewBitcoindFilteredChainView(config rpcclient.ConnConfig,
|
||||
zmqConnect string, params chaincfg.Params) (*BitcoindFilteredChainView,
|
||||
error) {
|
||||
chainView := &BitcoindFilteredChainView{
|
||||
chainFilter: make(map[wire.OutPoint]struct{}),
|
||||
filterUpdates: make(chan filterUpdate),
|
||||
filterBlockReqs: make(chan *filterBlockReq),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
chainConn, err := chain.NewBitcoindClient(¶ms, config.Host,
|
||||
config.User, config.Pass, zmqConnect, 100*time.Millisecond)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chainView.chainClient = chainConn
|
||||
|
||||
chainView.blockQueue = newBlockEventQueue()
|
||||
|
||||
return chainView, nil
|
||||
}
|
||||
|
||||
// Start starts all goroutines necessary for normal operation.
|
||||
//
|
||||
// NOTE: This is part of the FilteredChainView interface.
|
||||
func (b *BitcoindFilteredChainView) Start() error {
|
||||
// Already started?
|
||||
if atomic.AddInt32(&b.started, 1) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("FilteredChainView starting")
|
||||
|
||||
err := b.chainClient.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, bestHeight, err := b.chainClient.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.bestHeightMtx.Lock()
|
||||
b.bestHeight = uint32(bestHeight)
|
||||
b.bestHeightMtx.Unlock()
|
||||
|
||||
b.blockQueue.Start()
|
||||
|
||||
b.wg.Add(1)
|
||||
go b.chainFilterer()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops all goroutines which we launched by the prior call to the Start
|
||||
// method.
|
||||
//
|
||||
// NOTE: This is part of the FilteredChainView interface.
|
||||
func (b *BitcoindFilteredChainView) Stop() error {
|
||||
// Already shutting down?
|
||||
if atomic.AddInt32(&b.stopped, 1) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown the rpc client, this gracefully disconnects from bitcoind's
|
||||
// zmq socket, and cleans up all related resources.
|
||||
b.chainClient.Stop()
|
||||
|
||||
b.blockQueue.Stop()
|
||||
|
||||
log.Infof("FilteredChainView stopping")
|
||||
|
||||
close(b.quit)
|
||||
b.wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// onFilteredBlockConnected is called for each block that's connected to the
|
||||
// end of the main chain. Based on our current chain filter, the block may or
|
||||
// may not include any relevant transactions.
|
||||
func (b *BitcoindFilteredChainView) onFilteredBlockConnected(height int32,
|
||||
hash chainhash.Hash, txns []*wtxmgr.TxRecord) {
|
||||
|
||||
mtxs := make([]*wire.MsgTx, len(txns))
|
||||
for i, tx := range txns {
|
||||
mtxs[i] = &tx.MsgTx
|
||||
|
||||
for _, txIn := range mtxs[i].TxIn {
|
||||
// We can delete this outpoint from the chainFilter, as
|
||||
// we just received a block where it was spent. In case
|
||||
// of a reorg, this outpoint might get "un-spent", but
|
||||
// that's okay since it would never be wise to consider
|
||||
// the channel open again (since a spending transaction
|
||||
// exists on the network).
|
||||
b.filterMtx.Lock()
|
||||
delete(b.chainFilter, txIn.PreviousOutPoint)
|
||||
b.filterMtx.Unlock()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// We record the height of the last connected block added to the
|
||||
// blockQueue such that we can scan up to this height in case of
|
||||
// a rescan. It must be protected by a mutex since a filter update
|
||||
// might be trying to read it concurrently.
|
||||
b.bestHeightMtx.Lock()
|
||||
b.bestHeight = uint32(height)
|
||||
b.bestHeightMtx.Unlock()
|
||||
|
||||
block := &FilteredBlock{
|
||||
Hash: hash,
|
||||
Height: uint32(height),
|
||||
Transactions: mtxs,
|
||||
}
|
||||
|
||||
b.blockQueue.Add(&blockEvent{
|
||||
eventType: connected,
|
||||
block: block,
|
||||
})
|
||||
}
|
||||
|
||||
// onFilteredBlockDisconnected is a callback which is executed once a block is
|
||||
// disconnected from the end of the main chain.
|
||||
func (b *BitcoindFilteredChainView) onFilteredBlockDisconnected(height int32,
|
||||
hash chainhash.Hash) {
|
||||
|
||||
log.Debugf("got disconnected block at height %d: %v", height,
|
||||
hash)
|
||||
|
||||
filteredBlock := &FilteredBlock{
|
||||
Hash: hash,
|
||||
Height: uint32(height),
|
||||
}
|
||||
|
||||
b.blockQueue.Add(&blockEvent{
|
||||
eventType: disconnected,
|
||||
block: filteredBlock,
|
||||
})
|
||||
}
|
||||
|
||||
// FilterBlock takes a block hash, and returns a FilteredBlocks which is the
|
||||
// result of applying the current registered UTXO sub-set on the block
|
||||
// corresponding to that block hash. If any watched UTOX's are spent by the
|
||||
// selected lock, then the internal chainFilter will also be updated.
|
||||
//
|
||||
// NOTE: This is part of the FilteredChainView interface.
|
||||
func (b *BitcoindFilteredChainView) FilterBlock(blockHash *chainhash.Hash) (*FilteredBlock, error) {
|
||||
req := &filterBlockReq{
|
||||
blockHash: blockHash,
|
||||
resp: make(chan *FilteredBlock, 1),
|
||||
err: make(chan error, 1),
|
||||
}
|
||||
|
||||
select {
|
||||
case b.filterBlockReqs <- req:
|
||||
case <-b.quit:
|
||||
return nil, fmt.Errorf("FilteredChainView shutting down")
|
||||
}
|
||||
|
||||
return <-req.resp, <-req.err
|
||||
}
|
||||
|
||||
// chainFilterer is the primary goroutine which: listens for new blocks coming
|
||||
// and dispatches the relevent FilteredBlock notifications, updates the filter
|
||||
// due to requests by callers, and finally is able to preform targeted block
|
||||
// filtration.
|
||||
//
|
||||
// TODO(roasbeef): change to use loadfilter RPC's
|
||||
func (b *BitcoindFilteredChainView) chainFilterer() {
|
||||
defer b.wg.Done()
|
||||
|
||||
// filterBlock is a helper funciton that scans the given block, and
|
||||
// notes which transactions spend outputs which are currently being
|
||||
// watched. Additionally, the chain filter will also be updated by
|
||||
// removing any spent outputs.
|
||||
filterBlock := func(blk *wire.MsgBlock) []*wire.MsgTx {
|
||||
var filteredTxns []*wire.MsgTx
|
||||
for _, tx := range blk.Transactions {
|
||||
for _, txIn := range tx.TxIn {
|
||||
prevOp := txIn.PreviousOutPoint
|
||||
if _, ok := b.chainFilter[prevOp]; ok {
|
||||
filteredTxns = append(filteredTxns, tx)
|
||||
|
||||
b.filterMtx.Lock()
|
||||
delete(b.chainFilter, prevOp)
|
||||
b.filterMtx.Unlock()
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredTxns
|
||||
}
|
||||
|
||||
decodeJSONBlock := func(block *btcjson.RescannedBlock,
|
||||
height uint32) (*FilteredBlock, error) {
|
||||
hash, err := chainhash.NewHashFromStr(block.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
txs := make([]*wire.MsgTx, 0, len(block.Transactions))
|
||||
for _, str := range block.Transactions {
|
||||
b, err := hex.DecodeString(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx := &wire.MsgTx{}
|
||||
err = tx.Deserialize(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
return &FilteredBlock{
|
||||
Hash: *hash,
|
||||
Height: height,
|
||||
Transactions: txs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
// The caller has just sent an update to the current chain
|
||||
// filter, so we'll apply the update, possibly rewinding our
|
||||
// state partially.
|
||||
case update := <-b.filterUpdates:
|
||||
|
||||
// First, we'll add all the new UTXO's to the set of
|
||||
// watched UTXO's, eliminating any duplicates in the
|
||||
// process.
|
||||
log.Debugf("Updating chain filter with new UTXO's: %v",
|
||||
update.newUtxos)
|
||||
for _, newOp := range update.newUtxos {
|
||||
b.filterMtx.Lock()
|
||||
b.chainFilter[newOp] = struct{}{}
|
||||
b.filterMtx.Unlock()
|
||||
}
|
||||
|
||||
// Apply the new TX filter to the chain client, which
|
||||
// will cause all following notifications from and
|
||||
// calls to it return blocks filtered with the new
|
||||
// filter.
|
||||
b.chainClient.LoadTxFilter(false, []btcutil.Address{},
|
||||
update.newUtxos)
|
||||
|
||||
// All blocks gotten after we loaded the filter will
|
||||
// have the filter applied, but we will need to rescan
|
||||
// the blocks up to the height of the block we last
|
||||
// added to the blockQueue.
|
||||
b.bestHeightMtx.Lock()
|
||||
bestHeight := b.bestHeight
|
||||
b.bestHeightMtx.Unlock()
|
||||
|
||||
// If the update height matches our best known height,
|
||||
// then we don't need to do any rewinding.
|
||||
if update.updateHeight == bestHeight {
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, we'll rewind the state to ensure the
|
||||
// caller doesn't miss any relevant notifications.
|
||||
// Starting from the height _after_ the update height,
|
||||
// we'll walk forwards, rescanning one block at a time
|
||||
// with the chain client applying the newly loaded
|
||||
// filter to each block.
|
||||
for i := update.updateHeight + 1; i < bestHeight+1; i++ {
|
||||
blockHash, err := b.chainClient.GetBlockHash(int64(i))
|
||||
if err != nil {
|
||||
log.Warnf("Unable to get block hash "+
|
||||
"for block at height %d: %v",
|
||||
i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// To avoid dealing with the case where a reorg
|
||||
// is happening while we rescan, we scan one
|
||||
// block at a time, skipping blocks that might
|
||||
// have gone missing.
|
||||
rescanned, err := b.chainClient.RescanBlocks(
|
||||
[]chainhash.Hash{*blockHash})
|
||||
if err != nil {
|
||||
log.Warnf("Unable to rescan block "+
|
||||
"with hash %v at height %d: %v",
|
||||
blockHash, i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// If no block was returned from the rescan, it
|
||||
// means no matching transactions were found.
|
||||
if len(rescanned) != 1 {
|
||||
log.Tracef("rescan of block %v at "+
|
||||
"height=%d yielded no "+
|
||||
"transactions", blockHash, i)
|
||||
continue
|
||||
}
|
||||
decoded, err := decodeJSONBlock(
|
||||
&rescanned[0], i)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to decode block: %v",
|
||||
err)
|
||||
continue
|
||||
}
|
||||
b.blockQueue.Add(&blockEvent{
|
||||
eventType: connected,
|
||||
block: decoded,
|
||||
})
|
||||
}
|
||||
|
||||
// We've received a new request to manually filter a block.
|
||||
case req := <-b.filterBlockReqs:
|
||||
// First we'll fetch the block itself as well as some
|
||||
// additional information including its height.
|
||||
block, err := b.chainClient.GetBlock(req.blockHash)
|
||||
if err != nil {
|
||||
req.err <- err
|
||||
req.resp <- nil
|
||||
continue
|
||||
}
|
||||
header, err := b.chainClient.GetBlockHeaderVerbose(
|
||||
req.blockHash)
|
||||
if err != nil {
|
||||
req.err <- err
|
||||
req.resp <- nil
|
||||
continue
|
||||
}
|
||||
|
||||
// Once we have this info, we can directly filter the
|
||||
// block and dispatch the proper notification.
|
||||
req.resp <- &FilteredBlock{
|
||||
Hash: *req.blockHash,
|
||||
Height: uint32(header.Height),
|
||||
Transactions: filterBlock(block),
|
||||
}
|
||||
req.err <- err
|
||||
|
||||
// We've received a new event from the chain client.
|
||||
case event := <-b.chainClient.Notifications():
|
||||
switch e := event.(type) {
|
||||
case chain.FilteredBlockConnected:
|
||||
b.onFilteredBlockConnected(e.Block.Height,
|
||||
e.Block.Hash, e.RelevantTxs)
|
||||
case chain.BlockDisconnected:
|
||||
b.onFilteredBlockDisconnected(e.Height, e.Hash)
|
||||
}
|
||||
|
||||
case <-b.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateFilter updates the UTXO filter which is to be consulted when creating
|
||||
// FilteredBlocks to be sent to subscribed clients. This method is cumulative
|
||||
// meaning repeated calls to this method should _expand_ the size of the UTXO
|
||||
// sub-set currently being watched. If the set updateHeight is _lower_ than
|
||||
// the best known height of the implementation, then the state should be
|
||||
// rewound to ensure all relevant notifications are dispatched.
|
||||
//
|
||||
// NOTE: This is part of the FilteredChainView interface.
|
||||
func (b *BitcoindFilteredChainView) UpdateFilter(ops []wire.OutPoint, updateHeight uint32) error {
|
||||
select {
|
||||
|
||||
case b.filterUpdates <- filterUpdate{
|
||||
newUtxos: ops,
|
||||
updateHeight: updateHeight,
|
||||
}:
|
||||
return nil
|
||||
|
||||
case <-b.quit:
|
||||
return fmt.Errorf("chain filter shutting down")
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredBlocks returns the channel that filtered blocks are to be sent over.
|
||||
// Each time a block is connected to the end of a main chain, and appropriate
|
||||
// FilteredBlock which contains the transactions which mutate our watched UTXO
|
||||
// set is to be returned.
|
||||
//
|
||||
// NOTE: This is part of the FilteredChainView interface.
|
||||
func (b *BitcoindFilteredChainView) FilteredBlocks() <-chan *FilteredBlock {
|
||||
return b.blockQueue.newBlocks
|
||||
}
|
||||
|
||||
// DisconnectedBlocks returns a receive only channel which will be sent upon
|
||||
// with the empty filtered blocks of blocks which are disconnected from the
|
||||
// main chain in the case of a re-org.
|
||||
//
|
||||
// NOTE: This is part of the FilteredChainView interface.
|
||||
func (b *BitcoindFilteredChainView) DisconnectedBlocks() <-chan *FilteredBlock {
|
||||
return b.blockQueue.staleBlocks
|
||||
}
|
@ -4,13 +4,16 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightninglabs/neutrino"
|
||||
"github.com/ltcsuite/ltcd/btcjson"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
@ -25,7 +28,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
netParams = &chaincfg.SimNetParams
|
||||
netParams = &chaincfg.RegressionNetParams
|
||||
|
||||
testPrivKey = []byte{
|
||||
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
|
||||
@ -42,6 +45,39 @@ var (
|
||||
testScript, _ = txscript.PayToAddrScript(testAddr)
|
||||
)
|
||||
|
||||
func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error {
|
||||
var found bool
|
||||
var tx *btcutil.Tx
|
||||
var err error
|
||||
timeout := time.After(10 * time.Second)
|
||||
for !found {
|
||||
// Do a short wait
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timeout after 10s")
|
||||
default:
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Check for the harness' knowledge of the txid
|
||||
tx, err = r.Node.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case *btcjson.RPCError:
|
||||
if e.Code == btcjson.ErrRPCNoTxInfo {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
}
|
||||
return err
|
||||
}
|
||||
if tx != nil && tx.MsgTx().TxHash() == *txid {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTestTXID(miner *rpctest.Harness) (*chainhash.Hash, error) {
|
||||
script, err := txscript.PayToAddrScript(testAddr)
|
||||
if err != nil {
|
||||
@ -131,11 +167,19 @@ func testFilterBlockNotifications(node *rpctest.Harness,
|
||||
// private key that we generated above.
|
||||
txid1, err := getTestTXID(node)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid")
|
||||
t.Fatalf("unable to get test txid: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(node, txid1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid in mempool: %v", err)
|
||||
}
|
||||
txid2, err := getTestTXID(node)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid")
|
||||
t.Fatalf("unable to get test txid: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(node, txid2)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid in mempool: %v", err)
|
||||
}
|
||||
|
||||
blockChan := chainView.FilteredBlocks()
|
||||
@ -218,6 +262,10 @@ func testFilterBlockNotifications(node *rpctest.Harness,
|
||||
if err != nil {
|
||||
t.Fatalf("unable to broadcast transaction: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(node, spendTxid1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get spending txid in mempool: %v", err)
|
||||
}
|
||||
newBlockHashes, err = node.Node.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate block: %v", err)
|
||||
@ -240,6 +288,10 @@ func testFilterBlockNotifications(node *rpctest.Harness,
|
||||
if err != nil {
|
||||
t.Fatalf("unable to broadcast transaction: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(node, spendTxid2)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get spending txid in mempool: %v", err)
|
||||
}
|
||||
newBlockHashes, err = node.Node.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate block: %v", err)
|
||||
@ -264,6 +316,10 @@ func testUpdateFilterBackTrack(node *rpctest.Harness,
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid")
|
||||
}
|
||||
err = waitForMempoolTx(node, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid in mempool: %v", err)
|
||||
}
|
||||
|
||||
// Next we'll mine a block confirming the output generated above.
|
||||
initBlockHashes, err := node.Node.Generate(1)
|
||||
@ -306,6 +362,10 @@ func testUpdateFilterBackTrack(node *rpctest.Harness,
|
||||
if err != nil {
|
||||
t.Fatalf("unable to broadcast transaction: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(node, spendTxid)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get spending txid in mempool: %v", err)
|
||||
}
|
||||
newBlockHashes, err := node.Node.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate block: %v", err)
|
||||
@ -352,10 +412,18 @@ func testFilterSingleBlock(node *rpctest.Harness, chainView FilteredChainView,
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid")
|
||||
}
|
||||
err = waitForMempoolTx(node, txid1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid in mempool: %v", err)
|
||||
}
|
||||
txid2, err := getTestTXID(node)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid")
|
||||
}
|
||||
err = waitForMempoolTx(node, txid2)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get test txid in mempool: %v", err)
|
||||
}
|
||||
|
||||
blockChan := chainView.FilteredBlocks()
|
||||
|
||||
@ -671,7 +739,7 @@ var chainViewTests = []testCase{
|
||||
test: testUpdateFilterBackTrack,
|
||||
},
|
||||
{
|
||||
name: "fitler single block",
|
||||
name: "filter single block",
|
||||
test: testFilterSingleBlock,
|
||||
},
|
||||
{
|
||||
@ -684,6 +752,68 @@ var interfaceImpls = []struct {
|
||||
name string
|
||||
chainViewInit chainViewInitFunc
|
||||
}{
|
||||
{
|
||||
name: "bitcoind_zmq",
|
||||
chainViewInit: func(_ rpcclient.ConnConfig, p2pAddr string) (func(), FilteredChainView, error) {
|
||||
// Start a bitcoind instance.
|
||||
tempBitcoindDir, err := ioutil.TempDir("", "bitcoind")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
zmqPath := "ipc:///" + tempBitcoindDir + "/weks.socket"
|
||||
cleanUp1 := func() {
|
||||
os.RemoveAll(tempBitcoindDir)
|
||||
}
|
||||
rpcPort := rand.Int()%(65536-1024) + 1024
|
||||
bitcoind := exec.Command(
|
||||
"bitcoind",
|
||||
"-datadir="+tempBitcoindDir,
|
||||
"-regtest",
|
||||
"-connect="+p2pAddr,
|
||||
"-txindex",
|
||||
"-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6f"+
|
||||
"d$507c670e800a95284294edb5773b05544b"+
|
||||
"220110063096c221be9933c82d38e1",
|
||||
fmt.Sprintf("-rpcport=%d", rpcPort),
|
||||
"-disablewallet",
|
||||
"-zmqpubrawblock="+zmqPath,
|
||||
"-zmqpubrawtx="+zmqPath,
|
||||
)
|
||||
err = bitcoind.Start()
|
||||
if err != nil {
|
||||
cleanUp1()
|
||||
return nil, nil, err
|
||||
}
|
||||
cleanUp2 := func() {
|
||||
bitcoind.Process.Kill()
|
||||
bitcoind.Wait()
|
||||
cleanUp1()
|
||||
}
|
||||
|
||||
// Wait for the bitcoind instance to start up.
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// Start the FilteredChainView implementation instance.
|
||||
config := rpcclient.ConnConfig{
|
||||
Host: fmt.Sprintf(
|
||||
"127.0.0.1:%d", rpcPort),
|
||||
User: "weks",
|
||||
Pass: "weks",
|
||||
DisableAutoReconnect: false,
|
||||
DisableConnectOnNew: true,
|
||||
DisableTLS: true,
|
||||
HTTPPostMode: true,
|
||||
}
|
||||
|
||||
chainView, err := NewBitcoindFilteredChainView(config,
|
||||
zmqPath, chaincfg.RegressionNetParams)
|
||||
if err != nil {
|
||||
cleanUp2()
|
||||
return nil, nil, err
|
||||
}
|
||||
return cleanUp2, chainView, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "p2p_neutrino",
|
||||
chainViewInit: func(_ rpcclient.ConnConfig, p2pAddr string) (func(), FilteredChainView, error) {
|
||||
|
@ -94,6 +94,18 @@ type FeeSchema struct {
|
||||
FeeRate uint32
|
||||
}
|
||||
|
||||
// ChannelPolicy holds the parameters that determine the policy we enforce
|
||||
// when fowarding payments on a channel. These parameters are communicated
|
||||
// to the rest of the network in ChannelUpdate messages.
|
||||
type ChannelPolicy struct {
|
||||
// FeeSchema holds the fee configuration for a channel.
|
||||
FeeSchema
|
||||
|
||||
// TimeLockDelta is the required HTLC timelock delta to be used
|
||||
// when forwarding payments.
|
||||
TimeLockDelta uint32
|
||||
}
|
||||
|
||||
// Config defines the configuration for the ChannelRouter. ALL elements within
|
||||
// the configuration MUST be non-nil for the ChannelRouter to carry out its
|
||||
// duties.
|
||||
|
55
rpcserver.go
55
rpcserver.go
@ -551,6 +551,7 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
|
||||
|
||||
localFundingAmt := btcutil.Amount(in.LocalFundingAmount)
|
||||
remoteInitialBalance := btcutil.Amount(in.PushSat)
|
||||
minHtlc := lnwire.MilliSatoshi(in.MinHtlcMsat)
|
||||
|
||||
// Ensure that the initial balance of the remote party (if pushing
|
||||
// satoshis) does not exceed the amount the local party has requested
|
||||
@ -627,7 +628,7 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
|
||||
updateChan, errChan := r.server.OpenChannel(
|
||||
in.TargetPeerId, nodePubKey, localFundingAmt,
|
||||
lnwire.NewMSatFromSatoshis(remoteInitialBalance),
|
||||
feePerByte, in.Private,
|
||||
minHtlc, feePerByte, in.Private,
|
||||
)
|
||||
|
||||
var outpoint wire.OutPoint
|
||||
@ -722,6 +723,7 @@ func (r *rpcServer) OpenChannelSync(ctx context.Context,
|
||||
|
||||
localFundingAmt := btcutil.Amount(in.LocalFundingAmount)
|
||||
remoteInitialBalance := btcutil.Amount(in.PushSat)
|
||||
minHtlc := lnwire.MilliSatoshi(in.MinHtlcMsat)
|
||||
|
||||
// Ensure that the initial balance of the remote party (if pushing
|
||||
// satoshis) does not exceed the amount the local party has requested
|
||||
@ -746,7 +748,7 @@ func (r *rpcServer) OpenChannelSync(ctx context.Context,
|
||||
updateChan, errChan := r.server.OpenChannel(
|
||||
in.TargetPeerId, nodepubKey, localFundingAmt,
|
||||
lnwire.NewMSatFromSatoshis(remoteInitialBalance),
|
||||
feePerByte, in.Private,
|
||||
minHtlc, feePerByte, in.Private,
|
||||
)
|
||||
|
||||
select {
|
||||
@ -2083,7 +2085,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
|
||||
zpay32.CLTVExpiry(invoice.CltvExpiry))
|
||||
default:
|
||||
// TODO(roasbeef): assumes set delta between versions
|
||||
defaultDelta := defaultBitcoinForwardingPolicy.TimeLockDelta
|
||||
defaultDelta := cfg.Bitcoin.TimeLockDelta
|
||||
options = append(options, zpay32.CLTVExpiry(uint64(defaultDelta)))
|
||||
}
|
||||
|
||||
@ -3192,13 +3194,13 @@ func (r *rpcServer) FeeReport(ctx context.Context,
|
||||
// 0.000001, or 0.0001%.
|
||||
const minFeeRate = 1e-6
|
||||
|
||||
// UpdateFees allows the caller to update the fee schedule for all channels
|
||||
// globally, or a particular channel.
|
||||
func (r *rpcServer) UpdateFees(ctx context.Context,
|
||||
req *lnrpc.FeeUpdateRequest) (*lnrpc.FeeUpdateResponse, error) {
|
||||
// UpdateChannelPolicy allows the caller to update the channel forwarding policy
|
||||
// for all channels globally, or a particular channel.
|
||||
func (r *rpcServer) UpdateChannelPolicy(ctx context.Context,
|
||||
req *lnrpc.PolicyUpdateRequest) (*lnrpc.PolicyUpdateResponse, error) {
|
||||
|
||||
if r.authSvc != nil {
|
||||
if err := macaroons.ValidateMacaroon(ctx, "udpatefees",
|
||||
if err := macaroons.ValidateMacaroon(ctx, "updatechannelpolicy",
|
||||
r.authSvc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -3208,11 +3210,11 @@ func (r *rpcServer) UpdateFees(ctx context.Context,
|
||||
switch scope := req.Scope.(type) {
|
||||
// If the request is targeting all active channels, then we don't need
|
||||
// target any channels by their channel point.
|
||||
case *lnrpc.FeeUpdateRequest_Global:
|
||||
case *lnrpc.PolicyUpdateRequest_Global:
|
||||
|
||||
// Otherwise, we're targeting an individual channel by its channel
|
||||
// point.
|
||||
case *lnrpc.FeeUpdateRequest_ChanPoint:
|
||||
case *lnrpc.PolicyUpdateRequest_ChanPoint:
|
||||
txid, err := chainhash.NewHash(scope.ChanPoint.FundingTxid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -3226,12 +3228,19 @@ func (r *rpcServer) UpdateFees(ctx context.Context,
|
||||
}
|
||||
|
||||
// As a sanity check, we'll ensure that the passed fee rate is below
|
||||
// 1e-6, or the lowest allowed fee rate.
|
||||
// 1e-6, or the lowest allowed fee rate, and that the passed timelock
|
||||
// is large enough.
|
||||
if req.FeeRate < minFeeRate {
|
||||
return nil, fmt.Errorf("fee rate of %v is too small, min fee "+
|
||||
"rate is %v", req.FeeRate, minFeeRate)
|
||||
}
|
||||
|
||||
if req.TimeLockDelta < minTimeLockDelta {
|
||||
return nil, fmt.Errorf("time lock delta of %v is too small, "+
|
||||
"minimum supported is %v", req.TimeLockDelta,
|
||||
minTimeLockDelta)
|
||||
}
|
||||
|
||||
// We'll also need to convert the floating point fee rate we accept
|
||||
// over RPC to the fixed point rate that we use within the protocol. We
|
||||
// do this by multiplying the passed fee rate by the fee base. This
|
||||
@ -3244,16 +3253,21 @@ func (r *rpcServer) UpdateFees(ctx context.Context,
|
||||
FeeRate: feeRateFixed,
|
||||
}
|
||||
|
||||
rpcsLog.Tracef("[updatefees] updating fee schedule base_fee=%v, "+
|
||||
"rate_float=%v, rate_fixed=%v, targets=%v",
|
||||
req.BaseFeeMsat, req.FeeRate, feeRateFixed,
|
||||
chanPolicy := routing.ChannelPolicy{
|
||||
FeeSchema: feeSchema,
|
||||
TimeLockDelta: req.TimeLockDelta,
|
||||
}
|
||||
|
||||
rpcsLog.Tracef("[updatechanpolicy] updating channel policy base_fee=%v, "+
|
||||
"rate_float=%v, rate_fixed=%v, time_lock_delta: %v, targets=%v",
|
||||
req.BaseFeeMsat, req.FeeRate, feeRateFixed, req.TimeLockDelta,
|
||||
spew.Sdump(targetChans))
|
||||
|
||||
// With the scope resolved, we'll now send this to the
|
||||
// AuthenticatedGossiper so it can propagate the new fee schema for out
|
||||
// AuthenticatedGossiper so it can propagate the new policy for our
|
||||
// target channel(s).
|
||||
err := r.server.authGossiper.PropagateFeeUpdate(
|
||||
feeSchema, targetChans...,
|
||||
err := r.server.authGossiper.PropagateChanPolicyUpdate(
|
||||
chanPolicy, targetChans...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -3265,8 +3279,9 @@ func (r *rpcServer) UpdateFees(ctx context.Context,
|
||||
// We create a partially policy as the logic won't overwrite a valid
|
||||
// sub-policy with a "nil" one.
|
||||
p := htlcswitch.ForwardingPolicy{
|
||||
BaseFee: baseFeeMsat,
|
||||
FeeRate: lnwire.MilliSatoshi(feeRateFixed),
|
||||
BaseFee: baseFeeMsat,
|
||||
FeeRate: lnwire.MilliSatoshi(feeRateFixed),
|
||||
TimeLockDelta: req.TimeLockDelta,
|
||||
}
|
||||
err = r.server.htlcSwitch.UpdateForwardingPolicies(p, targetChans...)
|
||||
if err != nil {
|
||||
@ -3276,5 +3291,5 @@ func (r *rpcServer) UpdateFees(ctx context.Context,
|
||||
rpcsLog.Warnf("Unable to update link fees: %v", err)
|
||||
}
|
||||
|
||||
return &lnrpc.FeeUpdateResponse{}, nil
|
||||
return &lnrpc.PolicyUpdateResponse{}, nil
|
||||
}
|
||||
|
153
sample-lnd.conf
153
sample-lnd.conf
@ -97,29 +97,6 @@
|
||||
; active.
|
||||
bitcoin.active=1
|
||||
|
||||
; The host that your local btcd daemon is listening on. This MUST be set if
|
||||
; neutrino mode isn't active.
|
||||
; bitcoin.rpchost=localhost
|
||||
|
||||
; Username for RPC connections to btcd. This only needs to be set if neutrino
|
||||
; mode isn't active. By default, lnd will attempt to automatically obtain the
|
||||
; credentials, so this likely won't need to be set (other than for simnet mode).
|
||||
; bitcoin.rpcuser=kek
|
||||
|
||||
; Password for RPC connections to btcd. This only needs to be set if neutrino
|
||||
; mode isn't active. By default, lnd will attempt to automatically obtain the
|
||||
; credentials, so this likely won't need to be set (other than for simnet mode).
|
||||
; bitcoin.rpcpass=kek
|
||||
|
||||
; File containing the daemon's certificate file. This only needs to be set if
|
||||
; the node isn't on the same host as lnd.
|
||||
; bitcoin.rpccert=~/.btcd/rpc.cert
|
||||
|
||||
; The raw bytes of the daemon's PEM-encoded certificate chain which will be used
|
||||
; to authenticate the RPC connection. This only needs to be set if the btcd
|
||||
; node is on a remote host.
|
||||
; bitcoin.rawrpccert=
|
||||
|
||||
; Use Bitcoin's test network.
|
||||
; bitcoin.testnet=1
|
||||
;
|
||||
@ -129,6 +106,77 @@ bitcoin.simnet=1
|
||||
; Use Bitcoin's regression test network
|
||||
; bitcoin.regtest=false
|
||||
|
||||
; Use the btcd back-end
|
||||
bitcoin.node=btcd
|
||||
|
||||
; Use the bitcoind back-end
|
||||
; bitcoin.node=bitcoind
|
||||
|
||||
; Use the neutrino (light client) back-end
|
||||
; bitcoin.node=neutrino
|
||||
|
||||
|
||||
[Btcd]
|
||||
|
||||
; The host that your local btcd daemon is listening on. By default, this
|
||||
; setting is assumed to be localhost with the default port for the current
|
||||
; network.
|
||||
; btcd.rpchost=localhost
|
||||
|
||||
; Username for RPC connections to btcd. By default, lnd will attempt to
|
||||
; automatically obtain the credentials, so this likely won't need to be set
|
||||
; (other than for simnet mode).
|
||||
; btcd.rpcuser=kek
|
||||
|
||||
; Password for RPC connections to btcd. By default, lnd will attempt to
|
||||
; automatically obtain the credentials, so this likely won't need to be set
|
||||
; (other than for simnet mode).
|
||||
; btcd.rpcpass=kek
|
||||
|
||||
; File containing the daemon's certificate file. This only needs to be set if
|
||||
; the node isn't on the same host as lnd.
|
||||
; btcd.rpccert=~/.btcd/rpc.cert
|
||||
|
||||
; The raw bytes of the daemon's PEM-encoded certificate chain which will be used
|
||||
; to authenticate the RPC connection. This only needs to be set if the btcd
|
||||
; node is on a remote host.
|
||||
; btcd.rawrpccert=
|
||||
|
||||
|
||||
[Bitcoind]
|
||||
|
||||
; The host that your local bitcoind daemon is listening on. By default, this
|
||||
; setting is assumed to be localhost with the default port for the current
|
||||
; network.
|
||||
; bitcoind.rpchost=localhost
|
||||
|
||||
; Username for RPC connections to bitcoind. By default, lnd will attempt to
|
||||
; automatically obtain the credentials, so this likely won't need to be set
|
||||
; (other than for a remote bitcoind instance).
|
||||
; bitcoind.rpcuser=kek
|
||||
|
||||
; Password for RPC connections to bitcoind. By default, lnd will attempt to
|
||||
; automatically obtain the credentials, so this likely won't need to be set
|
||||
; (other than for a remote bitcoind instance).
|
||||
; bitcoind.rpcpass=kek
|
||||
|
||||
; ZMQ socket which sends rawblock and (optionally) rawtx notifications from
|
||||
; bitcoind. By default, lnd will attempt to automatically obtain this
|
||||
; information, so this likely won't need to be set (other than for a remote
|
||||
; bitcoind instance).
|
||||
; bitcoind.zmqpath=tcp://127.0.0.1:18501
|
||||
|
||||
|
||||
[neutrino]
|
||||
|
||||
; Connect only to the specified peers at startup. This creates a persistent
|
||||
; connection to a target peer. This is recommended as there aren't many
|
||||
; neutrino compliant full nodes on the test network yet.
|
||||
;neutrino.connect=
|
||||
|
||||
; Add a peer to connect with at startup.
|
||||
;neutrino.addpeer=
|
||||
|
||||
|
||||
[Litecoin]
|
||||
|
||||
@ -136,27 +184,6 @@ bitcoin.simnet=1
|
||||
; active.
|
||||
; litecoin.active=1
|
||||
|
||||
; The host that your local ltcd daemon is listening on. This MUST be set if
|
||||
; neutrino mode isn't active.
|
||||
; litecoin.rpchost=localhost
|
||||
|
||||
; Username for RPC connections to ltcd. This only needs to be set if neutrino
|
||||
; mode isn't active.
|
||||
; litecoin.rpcuser=
|
||||
|
||||
; Password for RPC connections to ltcd. This only needs to be set if neutrino
|
||||
; mode isn't active.
|
||||
; litecoin.rpcpass=
|
||||
|
||||
; File containing the daemon's certificate file. This only needs to be set if
|
||||
; the node isn't on the same host as lnd.
|
||||
; litecoin.rpccert=~/.btcd/rpc.cert
|
||||
|
||||
; The raw bytes of the daemon's PEM-encoded certificate chain which will be used
|
||||
; to authenticate the RPC connection. This only needs to be set if the ltcd
|
||||
; node is on a remote host.
|
||||
; litecoin.rawrpccert=
|
||||
|
||||
; Use Bitcoin's test network.
|
||||
; litecoin.testnet=1
|
||||
;
|
||||
@ -166,21 +193,37 @@ litecoin.simnet=1
|
||||
; Use Bitcoin's regression test network
|
||||
; litecoin.regtest=false
|
||||
|
||||
; Use the ltcd back-end (marked as btcd). No other back-end type is currently
|
||||
; supported for litecoin.
|
||||
litecoin.node=btcd
|
||||
|
||||
[neutrino]
|
||||
|
||||
; If the light client mode should be active or not. This mode requires less
|
||||
; disk space as it doesn't require one to have full-node locally. Instead,
|
||||
; neutrino will connect to the P2P network for all of lnd's needs.
|
||||
neutrino.active=false
|
||||
[Ltcd]
|
||||
|
||||
; Connect only to the specified peers at startup. This creates a persistent
|
||||
; connection to a target peer. This is recommend as there aren't many neutrino
|
||||
; compliant full nodes on the test network yet.
|
||||
;neutrino.connect=
|
||||
; The host that your local ltcd daemon is listening on. By default, this
|
||||
; setting is assumed to be localhost with the default port for the current
|
||||
; network.
|
||||
; ltcd.rpchost=localhost
|
||||
|
||||
; Username for RPC connections to ltcd. By default, lnd will attempt to
|
||||
; automatically obtain the credentials, so this likely won't need to be set
|
||||
; (other than for simnet mode).
|
||||
; ltcd.rpcuser=kek
|
||||
|
||||
; Password for RPC connections to ltcd. By default, lnd will attempt to
|
||||
; automatically obtain the credentials, so this likely won't need to be set
|
||||
; (other than for simnet mode).
|
||||
; ltcd.rpcpass=kek
|
||||
|
||||
; File containing the daemon's certificate file. This only needs to be set if
|
||||
; the node isn't on the same host as lnd.
|
||||
; ltcd.rpccert=~/.ltcd/rpc.cert
|
||||
|
||||
; The raw bytes of the daemon's PEM-encoded certificate chain which will be used
|
||||
; to authenticate the RPC connection. This only needs to be set if the ltcd
|
||||
; node is on a remote host.
|
||||
; ltcd.rawrpccert=
|
||||
|
||||
; Add a peer to connect with at startup.
|
||||
;neutrino.addpeer=
|
||||
|
||||
[autopilot]
|
||||
|
||||
|
@ -1495,6 +1495,8 @@ type openChanReq struct {
|
||||
|
||||
private bool
|
||||
|
||||
minHtlc lnwire.MilliSatoshi
|
||||
|
||||
// TODO(roasbeef): add ability to specify channel constraints as well
|
||||
|
||||
updates chan *lnrpc.OpenStatusUpdate
|
||||
@ -1612,6 +1614,7 @@ func (s *server) DisconnectPeer(pubKey *btcec.PublicKey) error {
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func (s *server) OpenChannel(peerID int32, nodeKey *btcec.PublicKey,
|
||||
localAmt btcutil.Amount, pushAmt lnwire.MilliSatoshi,
|
||||
minHtlc lnwire.MilliSatoshi,
|
||||
fundingFeePerByte btcutil.Amount,
|
||||
private bool) (chan *lnrpc.OpenStatusUpdate, chan error) {
|
||||
|
||||
@ -1674,6 +1677,7 @@ func (s *server) OpenChannel(peerID int32, nodeKey *btcec.PublicKey,
|
||||
fundingFeePerWeight: fundingFeePerWeight,
|
||||
pushAmt: pushAmt,
|
||||
private: private,
|
||||
minHtlc: minHtlc,
|
||||
updates: updateChan,
|
||||
err: errChan,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user