Merge pull request #2313 from Roasbeef/static-chan-backups

multi: implement new safe static channel backup and recovery scheme, RPCs, and cli commands
This commit is contained in:
Olaoluwa Osuntokun 2019-04-01 16:22:37 -07:00 committed by GitHub
commit c37ea68ba6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 4738 additions and 1360 deletions

@ -108,7 +108,7 @@ type chainControl struct {
signer input.Signer
keyRing keychain.KeyRing
keyRing keychain.SecretKeyRing
wc lnwallet.WalletController

@ -76,6 +76,8 @@ func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
return ErrNoBackupFileExists
}
log.Infof("Updating backup file at %v", b.fileName)
// If the old back up file still exists, then we'll delete it before
// proceeding.
if _, err := os.Stat(b.tempFileName); err == nil {
@ -94,17 +96,17 @@ func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
var err error
b.tempFile, err = os.Create(b.tempFileName)
if err != nil {
return err
return fmt.Errorf("unable to create temp file: %v", err)
}
// With the file created, we'll write the new packed multi backup and
// remove the temporary file all together once this method exits.
_, err = b.tempFile.Write([]byte(newBackup))
if err != nil {
return err
return fmt.Errorf("unable to write backup to temp file: %v", err)
}
if err := b.tempFile.Sync(); err != nil {
return err
return fmt.Errorf("unable to sync temp file: %v", err)
}
defer os.Remove(b.tempFileName)

@ -19,6 +19,11 @@ const (
// backup. The serialized format for this version is simply: version ||
// numBackups || SCBs...
DefaultMultiVersion = 0
// NilMultiSizePacked is the size of a "nil" packed Multi (45 bytes).
// This consists of the 24 byte chacha nonce, the 16 byte MAC, one byte
// for the version, and 4 bytes to signal zero entries.
NilMultiSizePacked = 24 + 16 + 1 + 4
)
// Multi is a form of static channel backup that is amenable to being

@ -2,6 +2,7 @@ package chanbackup
import (
"bytes"
"fmt"
"net"
"sync"
"sync/atomic"
@ -47,9 +48,9 @@ type ChannelEvent struct {
// ChannelSubscription represents an intent to be notified of any updates to
// the primary channel state.
type ChannelSubscription struct {
// ChanUpdates is a read-only channel that will be sent upon once the
// primary channel state is updated.
ChanUpdates <-chan ChannelEvent
// ChanUpdates is a channel that will be sent upon once the primary
// channel state is updated.
ChanUpdates chan ChannelEvent
// Cancel is a closure that allows the caller to cancel their
// subscription and free up any resources allocated.
@ -160,6 +161,36 @@ func (s *SubSwapper) Stop() error {
return nil
}
// updateBackupFile updates the backup file in place given the current state of
// the SubSwapper.
func (s *SubSwapper) updateBackupFile() error {
// With our updated channel state obtained, we'll create a new multi
// from our series of singles.
var newMulti Multi
for _, backup := range s.backupState {
newMulti.StaticBackups = append(
newMulti.StaticBackups, backup,
)
}
// Now that our multi has been assembled, we'll attempt to pack
// (encrypt+encode) the new channel state to our target reader.
var b bytes.Buffer
err := newMulti.PackToWriter(&b, s.keyRing)
if err != nil {
return fmt.Errorf("unable to pack multi backup: %v", err)
}
// Finally, we'll swap out the old backup for this new one in a single
// atomic step.
err = s.Swapper.UpdateAndSwap(PackedMulti(b.Bytes()))
if err != nil {
return fmt.Errorf("unable to update multi backup: %v", err)
}
return nil
}
// backupFileUpdater is the primary goroutine of the SubSwapper which is
// responsible for listening for changes to the channel, and updating the
// persistent multi backup state with a new packed multi of the latest channel
@ -172,6 +203,12 @@ func (s *SubSwapper) backupUpdater() {
log.Debugf("SubSwapper's backupUpdater is active!")
// Before we enter our main loop, we'll update the on-disk state with
// the latest Single state, as nodes may have new advertised addresses.
if err := s.updateBackupFile(); err != nil {
log.Errorf("Unable to refresh backup file: %v", err)
}
for {
select {
// The channel state has been modified! We'll evaluate all
@ -183,7 +220,7 @@ func (s *SubSwapper) backupUpdater() {
// For all new open channels, we'll create a new SCB
// given the required information.
for _, newChan := range chanUpdate.NewChans {
log.Debugf("Adding chanenl %v to backup state",
log.Debugf("Adding channel %v to backup state",
newChan.FundingOutpoint)
s.backupState[newChan.FundingOutpoint] = NewSingle(
@ -204,41 +241,20 @@ func (s *SubSwapper) backupUpdater() {
newStateSize := len(s.backupState)
// With our updated channel state obtained, we'll
// create a new multi from our series of singles.
var newMulti Multi
for _, backup := range s.backupState {
newMulti.StaticBackups = append(
newMulti.StaticBackups, backup,
)
}
// Now that our multi has been assembled, we'll attempt
// to pack (encrypt+encode) the new channel state to
// our target reader.
var b bytes.Buffer
err := newMulti.PackToWriter(&b, s.keyRing)
if err != nil {
log.Errorf("unable to pack multi backup: %v",
err)
continue
}
log.Infof("Updating on-disk multi SCB backup: "+
"num_old_chans=%v, num_new_chans=%v",
oldStateSize, newStateSize)
// Finally, we'll swap out the old backup for this new
// one in a single atomic step.
err = s.Swapper.UpdateAndSwap(
PackedMulti(b.Bytes()),
)
if err != nil {
log.Errorf("unable to update multi "+
"backup: %v", err)
continue
// With out new state constructed, we'll, atomically
// update the on-disk backup state.
if err := s.updateBackupFile(); err != nil {
log.Errorf("unable to update backup file: %v",
err)
}
// TODO(roasbeef): refresh periodically on a time basis due to
// possible addr changes from node
// Exit at once if a quit signal is detected.
case <-s.quit:
return

@ -131,17 +131,23 @@ func TestSubSwapperIdempotentStartStop(t *testing.T) {
keyRing := &mockKeyRing{}
var (
swapper mockSwapper
chanNotifier mockChannelNotifier
)
var chanNotifier mockChannelNotifier
subSwapper, err := NewSubSwapper(nil, &chanNotifier, keyRing, &swapper)
swapper := newMockSwapper()
subSwapper, err := NewSubSwapper(nil, &chanNotifier, keyRing, swapper)
if err != nil {
t.Fatalf("unable to init subSwapper: %v", err)
}
subSwapper.Start()
if err := subSwapper.Start(); err != nil {
t.Fatalf("unable to start swapper: %v", err)
}
// The swapper should write the initial channel state as soon as it's
// active.
backupSet := make(map[wire.OutPoint]Single)
assertExpectedBackupSwap(t, swapper, subSwapper, keyRing, backupSet)
subSwapper.Start()
subSwapper.Stop()
@ -188,6 +194,10 @@ func TestSubSwapperUpdater(t *testing.T) {
}
defer subSwapper.Stop()
// The swapper should write the initial channel state as soon as it's
// active.
assertExpectedBackupSwap(t, swapper, subSwapper, keyRing, backupSet)
// Now that the sub-swapper is active, we'll notify to add a brand new
// channel to the channel state.
newChannel, err := genRandomOpenChannelShell()

@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
@ -40,6 +41,11 @@ type Single struct {
// pack the single backup.
Version SingleBackupVersion
// IsInitiator is true if we were the initiator of the channel, and
// false otherwise. We'll need to know this information in order to
// properly re-derive the state hint information.
IsInitiator bool
// ChainHash is a hash which represents the blockchain that this
// channel will be opened within. This value is typically the genesis
// hash. In the case that the original chain went through a contentious
@ -66,16 +72,29 @@ type Single struct {
// authenticated connection for the stored identity public key.
Addresses []net.Addr
// CsvDelay is the local CSV delay used within the channel. We may need
// this value to reconstruct our script to recover the funds on-chain
// after a force close.
CsvDelay uint16
// Capacity is the size of the original channel.
Capacity btcutil.Amount
// PaymentBasePoint describes how to derive base public that's used to
// deriving the key used within the non-delayed pay-to-self output on
// the commitment transaction for a node. With this information, we can
// re-derive the private key needed to sweep the funds on-chain.
PaymentBasePoint keychain.KeyLocator
// LocalChanCfg is our local channel configuration. It contains all the
// information we need to re-derive the keys we used within the
// channel. Most importantly, it allows to derive the base public
// that's used to deriving the key used within the non-delayed
// pay-to-self output on the commitment transaction for a node. With
// this information, we can re-derive the private key needed to sweep
// the funds on-chain.
//
// NOTE: Of the items in the ChannelConstraints, we only write the CSV
// delay.
LocalChanCfg channeldb.ChannelConfig
// RemoteChanCfg is the remote channel confirmation. We store this as
// well since we'll need some of their keys to re-derive things like
// the state hint obfuscator which will allow us to recognize the state
// their broadcast on chain.
//
// NOTE: Of the items in the ChannelConstraints, we only write the CSV
// delay.
RemoteChanCfg channeldb.ChannelConfig
// ShaChainRootDesc describes how to derive the private key that was
// used as the shachain root for this channel.
@ -88,8 +107,6 @@ type Single struct {
func NewSingle(channel *channeldb.OpenChannel,
nodeAddrs []net.Addr) Single {
chanCfg := channel.LocalChanCfg
// TODO(roasbeef): update after we start to store the KeyLoc for
// shachain root
@ -105,13 +122,16 @@ func NewSingle(channel *channeldb.OpenChannel,
_, shaChainPoint := btcec.PrivKeyFromBytes(btcec.S256(), b.Bytes())
return Single{
ChainHash: channel.ChainHash,
FundingOutpoint: channel.FundingOutpoint,
ShortChannelID: channel.ShortChannelID,
RemoteNodePub: channel.IdentityPub,
Addresses: nodeAddrs,
CsvDelay: chanCfg.CsvDelay,
PaymentBasePoint: chanCfg.PaymentBasePoint.KeyLocator,
Version: DefaultSingleVersion,
IsInitiator: channel.IsInitiator,
ChainHash: channel.ChainHash,
FundingOutpoint: channel.FundingOutpoint,
ShortChannelID: channel.ShortChannelID,
RemoteNodePub: channel.IdentityPub,
Addresses: nodeAddrs,
Capacity: channel.Capacity,
LocalChanCfg: channel.LocalChanCfg,
RemoteChanCfg: channel.RemoteChanCfg,
ShaChainRootDesc: keychain.KeyDescriptor{
PubKey: shaChainPoint,
KeyLocator: keychain.KeyLocator{
@ -150,14 +170,39 @@ func (s *Single) Serialize(w io.Writer) error {
var singleBytes bytes.Buffer
if err := lnwire.WriteElements(
&singleBytes,
s.IsInitiator,
s.ChainHash[:],
s.FundingOutpoint,
s.ShortChannelID,
s.RemoteNodePub,
s.Addresses,
s.CsvDelay,
uint32(s.PaymentBasePoint.Family),
s.PaymentBasePoint.Index,
s.Capacity,
s.LocalChanCfg.CsvDelay,
// We only need to write out the KeyLocator portion of the
// local channel config.
uint32(s.LocalChanCfg.MultiSigKey.Family),
s.LocalChanCfg.MultiSigKey.Index,
uint32(s.LocalChanCfg.RevocationBasePoint.Family),
s.LocalChanCfg.RevocationBasePoint.Index,
uint32(s.LocalChanCfg.PaymentBasePoint.Family),
s.LocalChanCfg.PaymentBasePoint.Index,
uint32(s.LocalChanCfg.DelayBasePoint.Family),
s.LocalChanCfg.DelayBasePoint.Index,
uint32(s.LocalChanCfg.HtlcBasePoint.Family),
s.LocalChanCfg.HtlcBasePoint.Index,
s.RemoteChanCfg.CsvDelay,
// We only need to write out the raw pubkey for the remote
// channel config.
s.RemoteChanCfg.MultiSigKey.PubKey,
s.RemoteChanCfg.RevocationBasePoint.PubKey,
s.RemoteChanCfg.PaymentBasePoint.PubKey,
s.RemoteChanCfg.DelayBasePoint.PubKey,
s.RemoteChanCfg.HtlcBasePoint.PubKey,
shaChainPub[:],
uint32(s.ShaChainRootDesc.KeyLocator.Family),
s.ShaChainRootDesc.KeyLocator.Index,
@ -201,6 +246,49 @@ func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
return encryptPayloadToWriter(rawBytes, w, keyRing)
}
// readLocalKeyDesc reads a KeyDescriptor encoded within an unpacked Single.
// For local KeyDescs, we only write out the KeyLocator information as we can
// re-derive the pubkey from it.
func readLocalKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
var keyDesc keychain.KeyDescriptor
var keyFam uint32
if err := lnwire.ReadElements(r, &keyFam); err != nil {
return keyDesc, err
}
keyDesc.Family = keychain.KeyFamily(keyFam)
if err := lnwire.ReadElements(r, &keyDesc.Index); err != nil {
return keyDesc, err
}
return keyDesc, nil
}
// readRemoteKeyDesc reads a remote KeyDescriptor encoded within an unpacked
// Single. For remote KeyDescs, we write out only the PubKey since we don't
// actually have the KeyLocator data.
func readRemoteKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
var (
keyDesc keychain.KeyDescriptor
pub [33]byte
)
_, err := io.ReadFull(r, pub[:])
if err != nil {
return keyDesc, nil
}
keyDesc.PubKey, err = btcec.ParsePubKey(pub[:], btcec.S256())
if err != nil {
return keyDesc, nil
}
keyDesc.PubKey.Curve = nil
return keyDesc, nil
}
// Deserialize attempts to read the raw plaintext serialized SCB from the
// passed io.Reader. If the method is successful, then the target
// StaticChannelBackup will be fully populated.
@ -228,20 +316,59 @@ func (s *Single) Deserialize(r io.Reader) error {
}
err = lnwire.ReadElements(
r, s.ChainHash[:], &s.FundingOutpoint, &s.ShortChannelID,
&s.RemoteNodePub, &s.Addresses, &s.CsvDelay,
r, &s.IsInitiator, s.ChainHash[:], &s.FundingOutpoint,
&s.ShortChannelID, &s.RemoteNodePub, &s.Addresses, &s.Capacity,
)
if err != nil {
return err
}
var keyFam uint32
if err := lnwire.ReadElements(r, &keyFam); err != nil {
err = lnwire.ReadElements(r, &s.LocalChanCfg.CsvDelay)
if err != nil {
return err
}
s.LocalChanCfg.MultiSigKey, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.RevocationBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.PaymentBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.DelayBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.HtlcBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.PaymentBasePoint.Family = keychain.KeyFamily(keyFam)
err = lnwire.ReadElements(r, &s.PaymentBasePoint.Index)
err = lnwire.ReadElements(r, &s.RemoteChanCfg.CsvDelay)
if err != nil {
return err
}
s.RemoteChanCfg.MultiSigKey, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.RevocationBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.PaymentBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.DelayBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.HtlcBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
@ -256,7 +383,7 @@ func (s *Single) Deserialize(r io.Reader) error {
}
// Since this field is optional, we'll check to see if the pubkey has
// ben specified or not.
// been specified or not.
if !bytes.Equal(shaChainPub[:], zeroPub[:]) {
s.ShaChainRootDesc.PubKey, err = btcec.ParsePubKey(
shaChainPub[:], btcec.S256(),

@ -42,6 +42,10 @@ func assertSingleEqual(t *testing.T, a, b Single) {
t.Fatalf("versions don't match: %v vs %v", a.Version,
b.Version)
}
if a.IsInitiator != b.IsInitiator {
t.Fatalf("initiators don't match: %v vs %v", a.IsInitiator,
b.IsInitiator)
}
if a.ChainHash != b.ChainHash {
t.Fatalf("chainhash doesn't match: %v vs %v", a.ChainHash,
b.ChainHash)
@ -54,24 +58,29 @@ func assertSingleEqual(t *testing.T, a, b Single) {
t.Fatalf("chan id doesn't match: %v vs %v",
a.ShortChannelID, b.ShortChannelID)
}
if a.Capacity != b.Capacity {
t.Fatalf("capacity doesn't match: %v vs %v",
a.Capacity, b.Capacity)
}
if !a.RemoteNodePub.IsEqual(b.RemoteNodePub) {
t.Fatalf("node pubs don't match %x vs %x",
a.RemoteNodePub.SerializeCompressed(),
b.RemoteNodePub.SerializeCompressed())
}
if a.CsvDelay != b.CsvDelay {
t.Fatalf("csv delay doesn't match: %v vs %v", a.CsvDelay,
b.CsvDelay)
if !reflect.DeepEqual(a.LocalChanCfg, b.LocalChanCfg) {
t.Fatalf("local chan config doesn't match: %v vs %v",
spew.Sdump(a.LocalChanCfg),
spew.Sdump(b.LocalChanCfg))
}
if !reflect.DeepEqual(a.PaymentBasePoint, b.PaymentBasePoint) {
t.Fatalf("base point doesn't match: %v vs %v",
spew.Sdump(a.PaymentBasePoint),
spew.Sdump(b.PaymentBasePoint))
if !reflect.DeepEqual(a.RemoteChanCfg, b.RemoteChanCfg) {
t.Fatalf("remote chan config doesn't match: %v vs %v",
spew.Sdump(a.RemoteChanCfg),
spew.Sdump(b.RemoteChanCfg))
}
if !reflect.DeepEqual(a.ShaChainRootDesc, b.ShaChainRootDesc) {
t.Fatalf("sha chain point doesn't match: %v vs %v",
spew.Sdump(a.PaymentBasePoint),
spew.Sdump(b.PaymentBasePoint))
spew.Sdump(a.ShaChainRootDesc),
spew.Sdump(b.ShaChainRootDesc))
}
if len(a.Addresses) != len(b.Addresses) {
@ -110,8 +119,14 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) {
shaChainProducer := shachain.NewRevocationProducer(shaChainRoot)
var isInitiator bool
if rand.Int63()%2 == 0 {
isInitiator = true
}
return &channeldb.OpenChannel{
ChainHash: chainHash,
IsInitiator: isInitiator,
FundingOutpoint: chanPoint,
ShortChannelID: lnwire.NewShortChanIDFromInt(
uint64(rand.Int63()),
@ -121,12 +136,56 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) {
ChannelConstraints: channeldb.ChannelConstraints{
CsvDelay: uint16(rand.Int63()),
},
MultiSigKey: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
RevocationBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
PaymentBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
DelayBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
HtlcBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(rand.Int63()),
Index: uint32(rand.Int63()),
},
},
},
RemoteChanCfg: channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
CsvDelay: uint16(rand.Int63()),
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: pub,
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: pub,
},
},
RevocationProducer: shaChainProducer,
}, nil

145
channel_notifier.go Normal file

@ -0,0 +1,145 @@
package main
import (
"fmt"
"net"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channelnotifier"
)
// addrSource is an interface that allow us to get the addresses for a target
// node. We'll need this in order to be able to properly proxy the
// notifications to create SCBs.
type addrSource interface {
// AddrsForNode returns all known addresses for the target node public
// key.
AddrsForNode(nodePub *btcec.PublicKey) ([]net.Addr, error)
}
// channelNotifier is an implementation of the chanbackup.ChannelNotifier
// interface using the existing channelnotifier.ChannelNotifier struct. This
// implementation allows us to satisfy all the dependencies of the
// chanbackup.SubSwapper struct.
type channelNotifier struct {
// chanNotifier is the based channel notifier that we'll proxy requests
// from.
chanNotifier *channelnotifier.ChannelNotifier
// addrs is an implementation of the addrSource interface that allows
// us to get the latest set of addresses for a given node. We'll need
// this to be able to create an SCB for new channels.
addrs addrSource
}
// SubscribeChans requests a new channel subscription relative to the initial
// set of known channels. We use the knownChans as a synchronization point to
// ensure that the chanbackup.SubSwapper does not miss any channel open or
// close events in the period between when it's created, and when it requests
// the channel subscription.
//
// NOTE: This is part of the chanbackup.ChannelNotifier interface.
func (c *channelNotifier) SubscribeChans(startingChans map[wire.OutPoint]struct{}) (
*chanbackup.ChannelSubscription, error) {
ltndLog.Infof("Channel backup proxy channel notifier starting")
// TODO(roasbeef): read existing set of chans and diff
quit := make(chan struct{})
chanUpdates := make(chan chanbackup.ChannelEvent, 1)
// In order to adhere to the interface, we'll proxy the events from the
// channel notifier to the sub-swapper in a format it understands.
go func() {
// First, we'll subscribe to the primary channel notifier so we can
// obtain events for new opened/closed channels.
chanSubscription, err := c.chanNotifier.SubscribeChannelEvents()
if err != nil {
panic(fmt.Sprintf("unable to subscribe to chans: %v",
err))
}
defer chanSubscription.Cancel()
for {
select {
// A new event has been sent by the chanNotifier, we'll
// filter out the events we actually care about and
// send them to the sub-swapper.
case e := <-chanSubscription.Updates():
// TODO(roasbeef): batch dispatch ntnfs
switch event := e.(type) {
// A new channel has been opened, we'll obtain
// the node address, then send to the
// sub-swapper.
case channelnotifier.OpenChannelEvent:
nodeAddrs, err := c.addrs.AddrsForNode(
event.Channel.IdentityPub,
)
if err != nil {
pub := event.Channel.IdentityPub
ltndLog.Errorf("unable to "+
"fetch addrs for %x: %v",
pub.SerializeCompressed(),
err)
}
channel := event.Channel
chanEvent := chanbackup.ChannelEvent{
NewChans: []chanbackup.ChannelWithAddrs{
{
OpenChannel: channel,
Addrs: nodeAddrs,
},
},
}
select {
case chanUpdates <- chanEvent:
case <-quit:
return
}
// An existing channel has been closed, we'll
// send only the chanPoint of the closed
// channel to the sub-swapper.
case channelnotifier.ClosedChannelEvent:
chanPoint := event.CloseSummary.ChanPoint
chanEvent := chanbackup.ChannelEvent{
ClosedChans: []wire.OutPoint{
chanPoint,
},
}
select {
case chanUpdates <- chanEvent:
case <-quit:
return
}
}
// The cancel method has been called, signalling us to
// exit
case <-quit:
return
}
}
}()
return &chanbackup.ChannelSubscription{
ChanUpdates: chanUpdates,
Cancel: func() {
close(quit)
},
}, nil
}
// A compile-time constraint to ensure channelNotifier implements
// chanbackup.ChannelNotifier.
var _ chanbackup.ChannelNotifier = (*channelNotifier)(nil)

@ -2425,7 +2425,9 @@ func putChanInfo(chanBucket *bbolt.Bucket, channel *OpenChannel) error {
}
// For single funder channels that we initiated, write the funding txn.
if channel.ChanType == SingleFunder && channel.IsInitiator {
if channel.ChanType == SingleFunder && channel.IsInitiator &&
!channel.hasChanStatus(ChanStatusRestored) {
if err := WriteElement(&w, channel.FundingTxn); err != nil {
return err
}
@ -2545,7 +2547,9 @@ func fetchChanInfo(chanBucket *bbolt.Bucket, channel *OpenChannel) error {
}
// For single funder channels that we initiated, read the funding txn.
if channel.ChanType == SingleFunder && channel.IsInitiator {
if channel.ChanType == SingleFunder && channel.IsInitiator &&
!channel.hasChanStatus(ChanStatusRestored) {
if err := ReadElement(r, &channel.FundingTxn); err != nil {
return err
}

@ -906,6 +906,12 @@ func (d *DB) RestoreChannelShells(channelShells ...*ChannelShell) error {
for _, channelShell := range channelShells {
channel := channelShell.Chan
// When we make a channel, we mark that the channel has
// been restored, this will signal to other sub-systems
// to not attempt to use the channel as if it was a
// regular one.
channel.chanStatus |= ChanStatusRestored
// First, we'll attempt to create a new open channel
// and link node for this channel. If the channel
// already exists, then in order to ensure this method
@ -930,6 +936,7 @@ func (d *DB) RestoreChannelShells(channelShells ...*ChannelShell) error {
ChannelID: channel.ShortChannelID.ToUint64(),
ChainHash: channel.ChainHash,
ChannelPoint: channel.FundingOutpoint,
Capacity: channel.Capacity,
}
nodes := tx.Bucket(nodeBucket)
@ -958,7 +965,7 @@ func (d *DB) RestoreChannelShells(channelShells ...*ChannelShell) error {
// With the edge info shell constructed, we'll now add
// it to the graph.
err = chanGraph.addChannelEdge(tx, &edgeInfo)
if err != nil {
if err != nil && err != ErrEdgeAlreadyExist {
return err
}
@ -1011,7 +1018,9 @@ func (d *DB) AddrsForNode(nodePub *btcec.PublicKey) ([]net.Addr, error) {
}
compressedPubKey := nodePub.SerializeCompressed()
graphNode, err = fetchLightningNode(nodes, compressedPubKey)
if err != nil {
if err != nil && err != ErrGraphNodeNotFound {
// If the node isn't found, then that's OK, as we still
// have the link node data.
return err
}

187
chanrestore.go Normal file

@ -0,0 +1,187 @@
package main
import (
"fmt"
"net"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
)
// chanDBRestorer is an implementation of the chanbackup.ChannelRestorer
// interface that is able to properly map a Single backup, into a
// channeldb.ChannelShell which is required to fully restore a channel. We also
// need the secret key chain in order obtain the prior shachain root so we can
// verify the DLP protocol as initiated by the remote node.
type chanDBRestorer struct {
db *channeldb.DB
secretKeys keychain.SecretKeyRing
chainArb *contractcourt.ChainArbitrator
}
// openChannelShell maps the static channel back up into an open channel
// "shell". We say shell as this doesn't include all the information required
// to continue to use the channel, only the minimal amount of information to
// insert this shell channel back into the database.
func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) (
*channeldb.ChannelShell, error) {
// First, we'll also need to obtain the private key for the shachain
// root from the encoded public key.
//
// TODO(roasbeef): now adds req for hardware signers to impl
// shachain...
privKey, err := c.secretKeys.DerivePrivKey(backup.ShaChainRootDesc)
if err != nil {
return nil, nil
}
revRoot, err := chainhash.NewHash(privKey.Serialize())
if err != nil {
return nil, err
}
shaChainProducer := shachain.NewRevocationProducer(*revRoot)
// Each of the keys in our local channel config only have their
// locators populate, so we'll re-derive the raw key now as we'll need
// it in order to carry out the DLP protocol.
backup.LocalChanCfg.MultiSigKey, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.MultiSigKey.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive multi sig key: %v", err)
}
backup.LocalChanCfg.RevocationBasePoint, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.RevocationBasePoint.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive revocation key: %v", err)
}
backup.LocalChanCfg.PaymentBasePoint, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.PaymentBasePoint.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive payment key: %v", err)
}
backup.LocalChanCfg.DelayBasePoint, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.DelayBasePoint.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive delay key: %v", err)
}
backup.LocalChanCfg.HtlcBasePoint, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.HtlcBasePoint.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive htlc key: %v", err)
}
chanShell := channeldb.ChannelShell{
NodeAddrs: backup.Addresses,
Chan: &channeldb.OpenChannel{
ChainHash: backup.ChainHash,
IsInitiator: backup.IsInitiator,
Capacity: backup.Capacity,
FundingOutpoint: backup.FundingOutpoint,
ShortChannelID: backup.ShortChannelID,
IdentityPub: backup.RemoteNodePub,
IsPending: false,
LocalChanCfg: backup.LocalChanCfg,
RemoteChanCfg: backup.RemoteChanCfg,
RemoteCurrentRevocation: backup.RemoteNodePub,
RevocationStore: shachain.NewRevocationStore(),
RevocationProducer: shaChainProducer,
},
}
return &chanShell, nil
}
// RestoreChansFromSingles attempts to map the set of single channel backups to
// channel shells that will be stored persistently. Once these shells have been
// stored on disk, we'll be able to connect to the channel peer an execute the
// data loss recovery protocol.
//
// NOTE: Part of the chanbackup.ChannelRestorer interface.
func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) error {
channelShells := make([]*channeldb.ChannelShell, 0, len(backups))
for _, backup := range backups {
chanShell, err := c.openChannelShell(backup)
if err != nil {
return err
}
channelShells = append(channelShells, chanShell)
}
ltndLog.Infof("Inserting %v SCB channel shells into DB",
len(channelShells))
// Now that we have all the backups mapped into a series of Singles,
// we'll insert them all into the database.
if err := c.db.RestoreChannelShells(channelShells...); err != nil {
return err
}
ltndLog.Infof("Informing chain watchers of new restored channels")
// Finally, we'll need to inform the chain arbitrator of these new
// channels so we'll properly watch for their ultimate closure on chain
// and sweep them via the DLP.
for _, restoredChannel := range channelShells {
err := c.chainArb.WatchNewChannel(restoredChannel.Chan)
if err != nil {
return err
}
}
return nil
}
// A compile-time constraint to ensure chanDBRestorer implements
// chanbackup.ChannelRestorer.
var _ chanbackup.ChannelRestorer = (*chanDBRestorer)(nil)
// ConnectPeer attempts to connect to the target node at the set of available
// addresses. Once this method returns with a non-nil error, the connector
// should attempt to persistently connect to the target peer in the background
// as a persistent attempt.
//
// NOTE: Part of the chanbackup.PeerConnector interface.
func (s *server) ConnectPeer(nodePub *btcec.PublicKey, addrs []net.Addr) error {
// For each of the known addresses, we'll attempt to launch a
// persistent connection to the (pub, addr) pair. In the event that any
// of them connect, all the other stale requests will be cancelled.
for _, addr := range addrs {
netAddr := &lnwire.NetAddress{
IdentityKey: nodePub,
Address: addr,
}
ltndLog.Infof("Attempting to connect to %v for SCB restore "+
"DLP", netAddr)
// Attempt to connect to the peer using this full address. If
// we're unable to connect to them, then we'll try the next
// address in place of it.
if err := s.ConnectToPeer(netAddr, true); err != nil {
ltndLog.Errorf("unable to connect to %v to "+
"complete SCB restore: %v", netAddr, err)
continue
}
// If we connected no problem, then we can exit early as our
// job here is done.
return nil
}
return fmt.Errorf("unable to connect to peer %x for SCB restore",
nodePub.SerializeCompressed())
}

@ -17,6 +17,7 @@ import (
"syscall"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/lightningnetwork/lnd/lnrpc"
@ -1324,7 +1325,29 @@ var createCommand = cli.Command{
was provided by the user. This should be written down as it can be used
to potentially recover all on-chain funds, and most off-chain funds as
well.
Finally, it's also possible to use this command and a set of static
channel backups to trigger a recover attempt for the provided Static
Channel Backups. Only one of the three parameters will be accepted. See
the restorechanbackup command for further details w.r.t the format
accepted.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "single_backup",
Usage: "a hex encoded single channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_backup",
Usage: "a hex encoded multi-channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_file",
Usage: "the path to a multi-channel back up file",
},
},
Action: actionDecorator(create),
}
@ -1566,6 +1589,30 @@ mnemonicCheck:
fmt.Println("\n!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
"RESTORE THE WALLET!!!")
// We'll also check to see if they provided any static channel backups,
// if so, then we'll also tack these onto the final innit wallet
// request.
var chanBackups *lnrpc.ChanBackupSnapshot
backups, err := parseChanBackups(ctx)
if err != nil {
return fmt.Errorf("unable to parse chan "+
"backups: %v", err)
}
if backups != nil {
switch {
case backups.GetChanBackups() != nil:
singleBackup := backups.GetChanBackups()
chanBackups.SingleChanBackups = singleBackup
case backups.GetMultiChanBackup() != nil:
multiBackup := backups.GetMultiChanBackup()
chanBackups.MultiChanBackup = &lnrpc.MultiChanBackup{
MultiChanBackup: multiBackup,
}
}
}
// With either the user's prior cipher seed, or a newly generated one,
// we'll go ahead and initialize the wallet.
req := &lnrpc.InitWalletRequest{
@ -1573,6 +1620,7 @@ mnemonicCheck:
CipherSeedMnemonic: cipherSeedMnemonic,
AezeedPassphrase: aezeedPass,
RecoveryWindow: recoveryWindow,
ChannelBackups: chanBackups,
}
if _, err := client.InitWallet(ctxb, req); err != nil {
return err
@ -1646,6 +1694,8 @@ func unlock(ctx *cli.Context) error {
fmt.Println("\nlnd successfully unlocked!")
// TODO(roasbeef): add ability to accept hex single and multi backups
return nil
}
@ -3328,6 +3378,31 @@ var updateChannelPolicyCommand = cli.Command{
Action: actionDecorator(updateChannelPolicy),
}
func parseChanPoint(s string) (*lnrpc.ChannelPoint, error) {
split := strings.Split(s, ":")
if len(split) != 2 {
return nil, fmt.Errorf("expecting chan_point to be in format of: " +
"txid:index")
}
index, err := strconv.ParseInt(split[1], 10, 32)
if err != nil {
return nil, fmt.Errorf("unable to decode output index: %v", err)
}
txid, err := chainhash.NewHashFromStr(split[0])
if err != nil {
return nil, fmt.Errorf("unable to parse hex string: %v", err)
}
return &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: txid[:],
},
OutputIndex: uint32(index),
}, nil
}
func updateChannelPolicy(ctx *cli.Context) error {
ctxb := context.Background()
client, cleanUp := getClient(ctx)
@ -3396,22 +3471,9 @@ func updateChannelPolicy(ctx *cli.Context) error {
}
if chanPointStr != "" {
split := strings.Split(chanPointStr, ":")
if len(split) != 2 {
return fmt.Errorf("expecting chan_point to be in format of: " +
"txid:index")
}
index, err := strconv.ParseInt(split[1], 10, 32)
chanPoint, err = parseChanPoint(chanPointStr)
if err != nil {
return fmt.Errorf("unable to decode output index: %v", err)
}
chanPoint = &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: split[0],
},
OutputIndex: uint32(index),
return fmt.Errorf("unable to parse chan point: %v", err)
}
}
@ -3554,3 +3616,378 @@ func forwardingHistory(ctx *cli.Context) error {
printRespJSON(resp)
return nil
}
var exportChanBackupCommand = cli.Command{
Name: "exportchanbackup",
Category: "Channels",
Usage: "Obtain a static channel back up for a selected channels, " +
"or all known channels",
ArgsUsage: "[chan_point] [--all] [--output_file]",
Description: `
This command allows a user to export a Static Channel Backup (SCB) for
as selected channel. SCB's are encrypted backups of a channel's initial
state that are encrypted with a key derived from the seed of a user.In
the case of partial or complete data loss, the SCB will allow the user
to reclaim settled funds in the channel at its final state. The
exported channel backups can be restored at a later time using the
restorechanbackup command.
This command will return one of two types of channel backups depending
on the set of passed arguments:
* If a target channel point is specified, then a single channel
backup containing only the information for that channel will be
returned.
* If the --all flag is passed, then a multi-channel backup will be
returned. A multi backup is a single encrypted blob (displayed in
hex encoding) that contains several channels in a single cipher
text.
Both of the backup types can be restored using the restorechanbackup
command.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "chan_point",
Usage: "the target channel to obtain an SCB for",
},
cli.BoolFlag{
Name: "all",
Usage: "if specified, then a multi backup of all " +
"active channels will be returned",
},
cli.StringFlag{
Name: "output_file",
Usage: `
if specified, then rather than printing a JSON output
of the static channel backup, a serialized version of
the backup (either Single or Multi) will be written to
the target file, this is the same format used by lnd in
its channels.backup file `,
},
},
Action: actionDecorator(exportChanBackup),
}
func exportChanBackup(ctx *cli.Context) error {
ctxb := context.Background()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Show command help if no arguments provided
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "exportchanbackup")
return nil
}
var (
err error
chanPointStr string
)
args := ctx.Args()
switch {
case ctx.IsSet("chan_point"):
chanPointStr = ctx.String("chan_point")
case args.Present():
chanPointStr = args.First()
case !ctx.IsSet("all"):
return fmt.Errorf("must specify chan_point if --all isn't set")
}
if chanPointStr != "" {
chanPointRPC, err := parseChanPoint(chanPointStr)
if err != nil {
return err
}
chanBackup, err := client.ExportChannelBackup(
ctxb, &lnrpc.ExportChannelBackupRequest{
ChanPoint: chanPointRPC,
},
)
if err != nil {
return err
}
txid, err := chainhash.NewHash(
chanPointRPC.GetFundingTxidBytes(),
)
if err != nil {
return err
}
chanPoint := wire.OutPoint{
Hash: *txid,
Index: chanPointRPC.OutputIndex,
}
printJSON(struct {
ChanPoint string `json:"chan_point"`
ChanBackup string `json:"chan_backup"`
}{
ChanPoint: chanPoint.String(),
ChanBackup: hex.EncodeToString(
chanBackup.ChanBackup,
),
},
)
return nil
}
if !ctx.IsSet("all") {
return fmt.Errorf("if a channel isn't specified, -all must be")
}
chanBackup, err := client.ExportAllChannelBackups(
ctxb, &lnrpc.ChanBackupExportRequest{},
)
if err != nil {
return err
}
if ctx.IsSet("output_file") {
return ioutil.WriteFile(
ctx.String("output_file"),
chanBackup.MultiChanBackup.MultiChanBackup,
0666,
)
}
// TODO(roasbeef): support for export | restore ?
var chanPoints []string
for _, chanPoint := range chanBackup.MultiChanBackup.ChanPoints {
txid, err := chainhash.NewHash(chanPoint.GetFundingTxidBytes())
if err != nil {
return err
}
chanPoints = append(chanPoints, wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}.String())
}
printJSON(struct {
ChanPoints []string `json:"chan_points"`
MultiChanBackup string `json:"multi_chan_backup"`
}{
ChanPoints: chanPoints,
MultiChanBackup: hex.EncodeToString(
chanBackup.MultiChanBackup.MultiChanBackup,
),
},
)
return nil
}
var verifyChanBackupCommand = cli.Command{
Name: "verifychanbackup",
Category: "Channels",
Usage: "Verify an existing channel backup",
ArgsUsage: "[--single_backup] [--multi_backup] [--multi_file=]",
Description: `
This command allows a user to verify an existing Single or Multi channel
backup for integrity. This is useful when a user has a backup, but is
unsure as to if it's valid or for the target node.
The command will accept backups in one of three forms:
* A single channel packed SCB, which can be obtained from
exportchanbackup. This should be passed in hex encoded format.
* A packed multi-channel SCB, which couples several individual
static channel backups in single blob.
* A file path which points to a packed multi-channel backup within a
file, using the same format that lnd does in its channels.backup
file.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "single_backup",
Usage: "a hex encoded single channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_backup",
Usage: "a hex encoded multi-channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_file",
Usage: "the path to a multi-channel back up file",
},
},
Action: actionDecorator(verifyChanBackup),
}
func verifyChanBackup(ctx *cli.Context) error {
ctxb := context.Background()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Show command help if no arguments provided
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "verifychanbackup")
return nil
}
backups, err := parseChanBackups(ctx)
if err != nil {
return err
}
verifyReq := lnrpc.ChanBackupSnapshot{}
if backups.GetChanBackups() != nil {
verifyReq.SingleChanBackups = backups.GetChanBackups()
}
if backups.GetMultiChanBackup() != nil {
verifyReq.MultiChanBackup = &lnrpc.MultiChanBackup{
MultiChanBackup: backups.GetMultiChanBackup(),
}
}
resp, err := client.VerifyChanBackup(ctxb, &verifyReq)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var restoreChanBackupCommand = cli.Command{
Name: "restorechanbackup",
Category: "Channels",
Usage: "Restore an existing single or multi-channel static channel " +
"backup",
ArgsUsage: "[--single_backup] [--multi_backup] [--multi_file=",
Description: `
Allows a suer to restore a Static Channel Backup (SCB) that was
obtained either via the exportchanbackup command, or from lnd's
automatically manged channels.backup file. This command should be used
if a user is attempting to restore a channel due to data loss on a
running node restored with the same seed as the node that created the
channel. If successful, this command will allows the user to recover
the settled funds stored in the recovered channels.
The command will accept backups in one of three forms:
* A single channel packed SCB, which can be obtained from
exportchanbackup. This should be passed in hex encoded format.
* A packed multi-channel SCB, which couples several individual
static channel backups in single blob.
* A file path which points to a packed multi-channel backup within a
file, using the same format that lnd does in its channels.backup
file.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "single_backup",
Usage: "a hex encoded single channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_backup",
Usage: "a hex encoded multi-channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_file",
Usage: "the path to a multi-channel back up file",
},
},
Action: actionDecorator(restoreChanBackup),
}
func parseChanBackups(ctx *cli.Context) (*lnrpc.RestoreChanBackupRequest, error) {
switch {
case ctx.IsSet("single_backup"):
packedBackup, err := hex.DecodeString(
ctx.String("single_backup"),
)
if err != nil {
return nil, fmt.Errorf("unable to decode single packed "+
"backup: %v", err)
}
return &lnrpc.RestoreChanBackupRequest{
Backup: &lnrpc.RestoreChanBackupRequest_ChanBackups{
ChanBackups: &lnrpc.ChannelBackups{
ChanBackups: []*lnrpc.ChannelBackup{
{
ChanBackup: packedBackup,
},
},
},
},
}, nil
case ctx.IsSet("multi_backup"):
packedMulti, err := hex.DecodeString(
ctx.String("multi_backup"),
)
if err != nil {
return nil, fmt.Errorf("unable to decode multi packed "+
"backup: %v", err)
}
return &lnrpc.RestoreChanBackupRequest{
Backup: &lnrpc.RestoreChanBackupRequest_MultiChanBackup{
MultiChanBackup: packedMulti,
},
}, nil
case ctx.IsSet("multi_file"):
packedMulti, err := ioutil.ReadFile(ctx.String("multi_file"))
if err != nil {
return nil, fmt.Errorf("unable to decode multi packed "+
"backup: %v", err)
}
return &lnrpc.RestoreChanBackupRequest{
Backup: &lnrpc.RestoreChanBackupRequest_MultiChanBackup{
MultiChanBackup: packedMulti,
},
}, nil
default:
return nil, nil
}
}
func restoreChanBackup(ctx *cli.Context) error {
ctxb := context.Background()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Show command help if no arguments provided
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "restorechanbackup")
return nil
}
var req lnrpc.RestoreChanBackupRequest
backups, err := parseChanBackups(ctx)
if err != nil {
return err
}
req.Backup = backups.Backup
_, err = client.RestoreChannelBackups(ctxb, &req)
if err != nil {
return fmt.Errorf("unable to restore chan backups: %v", err)
}
return nil
}

@ -295,6 +295,9 @@ func main() {
feeReportCommand,
updateChannelPolicyCommand,
forwardingHistoryCommand,
exportChanBackupCommand,
verifyChanBackupCommand,
restoreChanBackupCommand,
}
// Add any extra autopilot commands determined by build flags.

@ -22,6 +22,7 @@ import (
"github.com/btcsuite/btcutil"
flags "github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
@ -210,10 +211,11 @@ type config struct {
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65535"`
DebugHTLC bool `long:"debughtlc" description:"Activate the debug htlc mode. With the debug HTLC mode, all payments sent use a pre-determined R-Hash. Additionally, all HTLCs sent to a node with the debug HTLC R-Hash are immediately settled in the next available state transition."`
UnsafeDisconnect bool `long:"unsafe-disconnect" description:"Allows the rpcserver to intentionally disconnect from peers with open channels. USED FOR TESTING ONLY."`
UnsafeReplay bool `long:"unsafe-replay" description:"Causes a link to replay the adds on its commitment txn after starting up, this enables testing of the sphinx replay logic."`
MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
DebugHTLC bool `long:"debughtlc" description:"Activate the debug htlc mode. With the debug HTLC mode, all payments sent use a pre-determined R-Hash. Additionally, all HTLCs sent to a node with the debug HTLC R-Hash are immediately settled in the next available state transition."`
UnsafeDisconnect bool `long:"unsafe-disconnect" description:"Allows the rpcserver to intentionally disconnect from peers with open channels. USED FOR TESTING ONLY."`
UnsafeReplay bool `long:"unsafe-replay" description:"Causes a link to replay the adds on its commitment txn after starting up, this enables testing of the sphinx replay logic."`
MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
BackupFilePath string `long:"backupfilepath" description:"The target location of the channel backup file"`
Bitcoin *chainConfig `group:"Bitcoin" namespace:"bitcoin"`
BtcdMode *btcdConfig `group:"btcd" namespace:"btcd"`
@ -814,7 +816,7 @@ func loadConfig() (*config, error) {
}
// We'll now construct the network directory which will be where we
// store all the data specifc to this chain/network.
// store all the data specific to this chain/network.
networkDir = filepath.Join(
cfg.DataDir, defaultChainSubDirname,
registeredChains.PrimaryChain().String(),
@ -840,6 +842,14 @@ func loadConfig() (*config, error) {
)
}
// Similarly, if a custom back up file path wasn't specified, then
// we'll update the file location to match our set network directory.
if cfg.BackupFilePath == "" {
cfg.BackupFilePath = filepath.Join(
networkDir, chanbackup.DefaultBackupFileName,
)
}
// Append the network type to the log directory so it is "namespaced"
// per network in the same fashion as the data directory.
cfg.LogDir = filepath.Join(cfg.LogDir,

@ -283,22 +283,22 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
c.cfg.chanState.FundingOutpoint)
select {
// We've detected a spend of the channel onchain! Depending on
// the type of spend, we'll act accordingly , so we'll examine
// the spending transaction to determine what we should do.
// We've detected a spend of the channel onchain! Depending on the type
// of spend, we'll act accordingly , so we'll examine the spending
// transaction to determine what we should do.
//
// TODO(Roasbeef): need to be able to ensure this only triggers
// on confirmation, to ensure if multiple txns are broadcast, we
// act on the one that's timestamped
case commitSpend, ok := <-spendNtfn.Spend:
// If the channel was closed, then this means that the
// notifier exited, so we will as well.
// If the channel was closed, then this means that the notifier
// exited, so we will as well.
if !ok {
return
}
// Otherwise, the remote party might have broadcast a
// prior revoked state...!!!
// Otherwise, the remote party might have broadcast a prior
// revoked state...!!!
commitTxBroadcast := commitSpend.SpendingTx
localCommit, remoteCommit, err := c.cfg.chanState.LatestCommitments()
@ -308,9 +308,9 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
return
}
// We'll not retrieve the latest sate of the revocation
// store so we can populate the information within the
// channel state object that we have.
// We'll not retrieve the latest sate of the revocation store
// so we can populate the information within the channel state
// object that we have.
//
// TODO(roasbeef): mutation is bad mkay
_, err = c.cfg.chanState.RemoteRevocationStore()
@ -320,30 +320,45 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
return
}
// If this is our commitment transaction, then we can
// exit here as we don't have any further processing we
// need to do (we can't cheat ourselves :p).
commitmentHash := localCommit.CommitTx.TxHash()
isOurCommitment := commitSpend.SpenderTxHash.IsEqual(
&commitmentHash,
// If this channel has been recovered, then we'll modify our
// behavior as it isn't possible for us to close out the
// channel off-chain ourselves. It can only be the remote party
// force closing, or a cooperative closure we signed off on
// before losing data getting confirmed in the chain.
isRecoveredChan := c.cfg.chanState.HasChanStatus(
channeldb.ChanStatusRestored,
)
if isOurCommitment {
if err := c.dispatchLocalForceClose(
commitSpend, *localCommit,
); err != nil {
log.Errorf("unable to handle local"+
"close for chan_point=%v: %v",
c.cfg.chanState.FundingOutpoint, err)
// If we're not recovering this channel, and this is our
// commitment transaction, then we can exit here as we don't
// have any further processing we need to do (we can't cheat
// ourselves :p).
if !isRecoveredChan {
commitmentHash := localCommit.CommitTx.TxHash()
isOurCommitment := commitSpend.SpenderTxHash.IsEqual(
&commitmentHash,
)
if isOurCommitment {
if err := c.dispatchLocalForceClose(
commitSpend, *localCommit,
); err != nil {
log.Errorf("unable to handle local"+
"close for chan_point=%v: %v",
c.cfg.chanState.FundingOutpoint, err)
}
return
}
return
}
// Next, we'll check to see if this is a cooperative
// channel closure or not. This is characterized by
// having an input sequence number that's finalized.
// This won't happen with regular commitment
// transactions due to the state hint encoding scheme.
// Next, we'll check to see if this is a cooperative channel
// closure or not. This is characterized by having an input
// sequence number that's finalized. This won't happen with
// regular commitment transactions due to the state hint
// encoding scheme.
if commitTxBroadcast.TxIn[0].Sequence == wire.MaxTxInSequenceNum {
// TODO(roasbeef): rare but possible, need itest case
// for
err := c.dispatchCooperativeClose(commitSpend)
if err != nil {
log.Errorf("unable to handle co op close: %v", err)
@ -371,12 +386,12 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
}
switch {
// If state number spending transaction matches the
// current latest state, then they've initiated a
// unilateral close. So we'll trigger the unilateral
// close signal so subscribers can clean up the state
// as necessary.
case broadcastStateNum == remoteStateNum:
// If state number spending transaction matches the current
// latest state, then they've initiated a unilateral close. So
// we'll trigger the unilateral close signal so subscribers can
// clean up the state as necessary.
case broadcastStateNum == remoteStateNum && !isRecoveredChan:
err := c.dispatchRemoteForceClose(
commitSpend, *remoteCommit,
c.cfg.chanState.RemoteCurrentRevocation,
@ -387,14 +402,13 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
c.cfg.chanState.FundingOutpoint, err)
}
// We'll also handle the case of the remote party
// broadcasting their commitment transaction which is
// one height above ours. This case can arise when we
// initiate a state transition, but the remote party
// has a fail crash _after_ accepting the new state,
// but _before_ sending their signature to us.
// We'll also handle the case of the remote party broadcasting
// their commitment transaction which is one height above ours.
// This case can arise when we initiate a state transition, but
// the remote party has a fail crash _after_ accepting the new
// state, but _before_ sending their signature to us.
case broadcastStateNum == remoteStateNum+1 &&
remoteChainTip != nil:
remoteChainTip != nil && !isRecoveredChan:
err := c.dispatchRemoteForceClose(
commitSpend, remoteChainTip.Commitment,
@ -410,8 +424,12 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
// known state for them, and they don't have a pending
// commitment (we write them to disk before sending out), then
// this means that we've lost data. In this case, we'll enter
// the DLP protocol.
case broadcastStateNum > remoteStateNum:
// the DLP protocol. Otherwise, if we've recovered our channel
// state from scratch, then we don't know what the precise
// current state is, so we assume either the remote party
// forced closed or we've been breached. In the latter case,
// our tower will take care of us.
case broadcastStateNum > remoteStateNum || isRecoveredChan:
log.Warnf("Remote node broadcast state #%v, "+
"which is more than 1 beyond best known "+
"state #%v!!! Attempting recovery...",
@ -476,12 +494,11 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
c.cfg.chanState.FundingOutpoint, err)
}
// If the state number broadcast is lower than the
// remote node's current un-revoked height, then
// THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT
// WITHIN THE PAYMENT CHANNEL. Therefore we close the
// signal indicating a revoked broadcast to allow
// subscribers to swiftly dispatch justice!!!
// If the state number broadcast is lower than the remote
// node's current un-revoked height, then THEY'RE ATTEMPTING TO
// VIOLATE THE CONTRACT LAID OUT WITHIN THE PAYMENT CHANNEL.
// Therefore we close the signal indicating a revoked broadcast
// to allow subscribers to swiftly dispatch justice!!!
case broadcastStateNum < remoteStateNum:
err := c.dispatchContractBreach(
commitSpend, remoteCommit,
@ -494,8 +511,8 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
}
}
// Now that a spend has been detected, we've done our
// job, so we'll exit immediately.
// Now that a spend has been detected, we've done our job, so
// we'll exit immediately.
return
// The chainWatcher has been signalled to exit, so we'll do so now.
@ -564,7 +581,10 @@ func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDet
}
// Attempt to add a channel sync message to the close summary.
chanSync, err := lnwallet.ChanSyncMsg(c.cfg.chanState)
chanSync, err := lnwallet.ChanSyncMsg(
c.cfg.chanState,
c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored),
)
if err != nil {
log.Errorf("ChannelPoint(%v): unable to create channel sync "+
"message: %v", c.cfg.chanState.FundingOutpoint, err)
@ -641,7 +661,10 @@ func (c *chainWatcher) dispatchLocalForceClose(
}
// Attempt to add a channel sync message to the close summary.
chanSync, err := lnwallet.ChanSyncMsg(c.cfg.chanState)
chanSync, err := lnwallet.ChanSyncMsg(
c.cfg.chanState,
c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored),
)
if err != nil {
log.Errorf("ChannelPoint(%v): unable to create channel sync "+
"message: %v", c.cfg.chanState.FundingOutpoint, err)
@ -819,7 +842,10 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail
}
// Attempt to add a channel sync message to the close summary.
chanSync, err := lnwallet.ChanSyncMsg(c.cfg.chanState)
chanSync, err := lnwallet.ChanSyncMsg(
c.cfg.chanState,
c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored),
)
if err != nil {
log.Errorf("ChannelPoint(%v): unable to create channel sync "+
"message: %v", c.cfg.chanState.FundingOutpoint, err)

@ -562,7 +562,11 @@ func (l *channelLink) syncChanStates() error {
// First, we'll generate our ChanSync message to send to the other
// side. Based on this message, the remote party will decide if they
// need to retransmit any data or not.
localChanSyncMsg, err := lnwallet.ChanSyncMsg(l.channel.State())
chanState := l.channel.State()
localChanSyncMsg, err := lnwallet.ChanSyncMsg(
chanState,
chanState.HasChanStatus(channeldb.ChanStatusRestored),
)
if err != nil {
return fmt.Errorf("unable to generate chan sync message for "+
"ChannelPoint(%v)", l.channel.ChannelPoint())

48
lnd.go

@ -217,18 +217,21 @@ func lndMain() error {
}
var (
privateWalletPw = lnwallet.DefaultPrivatePassphrase
publicWalletPw = lnwallet.DefaultPublicPassphrase
birthday = time.Now()
recoveryWindow uint32
unlockedWallet *wallet.Wallet
walletInitParams WalletUnlockParams
privateWalletPw = lnwallet.DefaultPrivatePassphrase
publicWalletPw = lnwallet.DefaultPublicPassphrase
)
// If the user didn't request a seed, then we'll manually assume a
// wallet birthday of now, as otherwise the seed would've specified
// this information.
walletInitParams.Birthday = time.Now()
// We wait until the user provides a password over RPC. In case lnd is
// started with the --noseedbackup flag, we use the default password
// for wallet encryption.
if !cfg.NoSeedBackup {
walletInitParams, err := waitForWalletPassword(
params, err := waitForWalletPassword(
cfg.RPCListeners, cfg.RESTListeners, serverOpts,
proxyOpts, tlsConf,
)
@ -236,16 +239,14 @@ func lndMain() error {
return err
}
walletInitParams = *params
privateWalletPw = walletInitParams.Password
publicWalletPw = walletInitParams.Password
birthday = walletInitParams.Birthday
recoveryWindow = walletInitParams.RecoveryWindow
unlockedWallet = walletInitParams.Wallet
if recoveryWindow > 0 {
if walletInitParams.RecoveryWindow > 0 {
ltndLog.Infof("Wallet recovery mode enabled with "+
"address lookahead of %d addresses",
recoveryWindow)
walletInitParams.RecoveryWindow)
}
}
@ -264,7 +265,7 @@ func lndMain() error {
// Try to unlock the macaroon store with the private password.
err = macaroonService.CreateUnlock(&privateWalletPw)
if err != nil {
srvrLog.Error(err)
srvrLog.Errorf("unable to unlock macaroons: %v", err)
return err
}
@ -288,8 +289,9 @@ func lndMain() error {
// instances of the pertinent interfaces required to operate the
// Lightning Network Daemon.
activeChainControl, chainCleanUp, err := newChainControlFromConfig(
cfg, chanDB, privateWalletPw, publicWalletPw, birthday,
recoveryWindow, unlockedWallet, neutrinoCS,
cfg, chanDB, privateWalletPw, publicWalletPw,
walletInitParams.Birthday, walletInitParams.RecoveryWindow,
walletInitParams.Wallet, neutrinoCS,
)
if err != nil {
fmt.Printf("unable to create chain control: %v\n", err)
@ -327,6 +329,7 @@ func lndMain() error {
// connections.
server, err := newServer(
cfg.Listeners, chanDB, activeChainControl, idPrivKey,
walletInitParams.ChansToRestore,
)
if err != nil {
srvrLog.Errorf("unable to create server: %v\n", err)
@ -672,6 +675,10 @@ type WalletUnlockParams struct {
// later when lnd actually uses it). Because unlocking involves scrypt
// which is resource intensive, we want to avoid doing it twice.
Wallet *wallet.Wallet
// ChansToRestore a set of static channel backups that should be
// restored before the main server instance starts up.
ChansToRestore walletunlocker.ChannelsToRecover
}
// waitForWalletPassword will spin up gRPC and REST endpoints for the
@ -825,24 +832,23 @@ func waitForWalletPassword(grpcEndpoints, restEndpoints []net.Addr,
return nil, err
}
walletInitParams := &WalletUnlockParams{
return &WalletUnlockParams{
Password: password,
Birthday: birthday,
RecoveryWindow: recoveryWindow,
Wallet: newWallet,
}
return walletInitParams, nil
ChansToRestore: initMsg.ChanBackups,
}, nil
// The wallet has already been created in the past, and is simply being
// unlocked. So we'll just return these passphrases.
case unlockMsg := <-pwService.UnlockMsgs:
walletInitParams := &WalletUnlockParams{
return &WalletUnlockParams{
Password: unlockMsg.Passphrase,
RecoveryWindow: unlockMsg.RecoveryWindow,
Wallet: unlockMsg.Wallet,
}
return walletInitParams, nil
ChansToRestore: unlockMsg.ChanBackups,
}, nil
case <-signal.ShutdownChannel():
return nil, fmt.Errorf("shutting down")

@ -15,6 +15,7 @@ import (
"path/filepath"
"reflect"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
@ -28,6 +29,7 @@ import (
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lnwire"
@ -122,6 +124,18 @@ func assertTxInBlock(t *harnessTest, block *wire.MsgBlock, txid *chainhash.Hash)
t.Fatalf("tx was not included in block")
}
func rpcPointToWirePoint(t *harnessTest, chanPoint *lnrpc.ChannelPoint) wire.OutPoint {
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
return wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}
}
// mineBlocks mine 'num' of blocks and check that blocks are present in
// node blockchain. numTxs should be set to the number of transactions
// (excluding the coinbase) we expect to be included in the first mined block.
@ -192,14 +206,10 @@ func openChannelAndAssert(ctx context.Context, t *harnessTest,
if err != nil {
t.Fatalf("error while waiting for channel open: %v", err)
}
txidHash, err := getChanPointFundingTxid(fundingChanPoint)
fundingTxID, err := getChanPointFundingTxid(fundingChanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
fundingTxID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
assertTxInBlock(t, block, fundingTxID)
// The channel should be listed in the peer information returned by
@ -297,14 +307,10 @@ func assertChannelClosed(ctx context.Context, t *harnessTest,
fundingChanPoint *lnrpc.ChannelPoint,
closeUpdates lnrpc.Lightning_CloseChannelClient) *chainhash.Hash {
txidHash, err := getChanPointFundingTxid(fundingChanPoint)
txid, err := getChanPointFundingTxid(fundingChanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to convert to chainhash: %v", err)
}
chanPointStr := fmt.Sprintf("%v:%v", txid, fundingChanPoint.OutputIndex)
// At this point, the channel should now be marked as being in the
@ -369,12 +375,7 @@ func assertChannelClosed(ctx context.Context, t *harnessTest,
func waitForChannelPendingForceClose(ctx context.Context,
node *lntest.HarnessNode, fundingChanPoint *lnrpc.ChannelPoint) error {
txidHash, err := getChanPointFundingTxid(fundingChanPoint)
if err != nil {
return err
}
txid, err := chainhash.NewHash(txidHash)
txid, err := getChanPointFundingTxid(fundingChanPoint)
if err != nil {
return err
}
@ -584,7 +585,9 @@ func completePaymentRequests(ctx context.Context, client lnrpc.LightningClient,
}
for _, payReq := range paymentRequests {
sendReq := &lnrpc.SendRequest{PaymentRequest: payReq}
sendReq := &lnrpc.SendRequest{
PaymentRequest: payReq,
}
err := payStream.Send(sendReq)
if err != nil {
return err
@ -709,7 +712,7 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
// Restore Carol, passing in the password, mnemonic, and
// desired recovery window.
node, err := net.RestoreNodeWithSeed(
"Carol", nil, password, mnemonic, recoveryWindow,
"Carol", nil, password, mnemonic, recoveryWindow, nil,
)
if err != nil {
t.Fatalf("unable to restore node: %v", err)
@ -1020,11 +1023,7 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
// txStr returns the string representation of the channel's funding transaction.
func txStr(chanPoint *lnrpc.ChannelPoint) string {
txidHash, err := getChanPointFundingTxid(chanPoint)
if err != nil {
return ""
}
fundingTxID, err := chainhash.NewHash(txidHash)
fundingTxID, err := getChanPointFundingTxid(chanPoint)
if err != nil {
return ""
}
@ -2693,14 +2692,10 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
// Compute the outpoint of the channel, which we will use repeatedly to
// locate the pending channel information in the rpc responses.
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
op := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -3920,14 +3915,10 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointAlice)
txidHash, err := getChanPointFundingTxid(chanPointAlice)
aliceChanTXID, err := getChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
@ -3963,14 +3954,10 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
},
)
networkChans = append(networkChans, chanPointDave)
txidHash, err = getChanPointFundingTxid(chanPointDave)
daveChanTXID, err := getChanPointFundingTxid(chanPointDave)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
daveChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
daveFundPoint := wire.OutPoint{
Hash: *daveChanTXID,
Index: chanPointDave.OutputIndex,
@ -4002,14 +3989,10 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointCarol)
txidHash, err = getChanPointFundingTxid(chanPointCarol)
carolChanTXID, err := getChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
@ -4020,14 +4003,10 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -4214,14 +4193,10 @@ func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointAlice)
txidHash, err := getChanPointFundingTxid(chanPointAlice)
aliceChanTXID, err := getChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
@ -4232,14 +4207,10 @@ func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
nodeNames := []string{"Alice", "Bob"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -4359,14 +4330,10 @@ func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointAlice)
txidHash, err := getChanPointFundingTxid(chanPointAlice)
aliceChanTXID, err := getChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
@ -4398,14 +4365,10 @@ func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
},
)
networkChans = append(networkChans, chanPointBob)
txidHash, err = getChanPointFundingTxid(chanPointBob)
bobChanTXID, err := getChanPointFundingTxid(chanPointBob)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
bobChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
bobFundPoint := wire.OutPoint{
Hash: *bobChanTXID,
Index: chanPointBob.OutputIndex,
@ -4416,14 +4379,10 @@ func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
nodeNames := []string{"Alice", "Bob", "Carol"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -4805,14 +4764,10 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointAlice)
txidHash, err := getChanPointFundingTxid(chanPointAlice)
aliceChanTXID, err := getChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
@ -4842,14 +4797,10 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) {
},
)
networkChans = append(networkChans, chanPointDave)
txidHash, err = getChanPointFundingTxid(chanPointDave)
daveChanTXID, err := getChanPointFundingTxid(chanPointDave)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
daveChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
daveFundPoint := wire.OutPoint{
Hash: *daveChanTXID,
Index: chanPointDave.OutputIndex,
@ -4881,14 +4832,10 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointCarol)
txidHash, err = getChanPointFundingTxid(chanPointCarol)
carolChanTXID, err := getChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
@ -4900,14 +4847,10 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) {
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -4949,14 +4892,10 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) {
if err != nil {
t.Fatalf("error while waiting for channel open: %v", err)
}
txidHash, err = getChanPointFundingTxid(chanPointPrivate)
fundingTxID, err := getChanPointFundingTxid(chanPointPrivate)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
fundingTxID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
assertTxInBlock(t, block, fundingTxID)
// The channel should be listed in the peer information returned by
@ -5392,14 +5331,10 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest)
}
// Retrieve Alice's funding outpoint.
txidHash, err := getChanPointFundingTxid(chanPointAlice)
aliceChanTXID, err := getChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
@ -5445,14 +5380,10 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest)
}
// Retrieve Bob's funding outpoint.
txidHash, err = getChanPointFundingTxid(chanPointBob)
bobChanTXID, err := getChanPointFundingTxid(chanPointBob)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
bobChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
bobFundPoint := wire.OutPoint{
Hash: *bobChanTXID,
Index: chanPointBob.OutputIndex,
@ -5504,14 +5435,10 @@ func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest)
}
// Retrieve Carol's funding point.
txidHash, err = getChanPointFundingTxid(chanPointCarol)
carolChanTXID, err := getChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
@ -6123,14 +6050,10 @@ func testMaxPendingChannels(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("error while waiting for channel open: %v", err)
}
txidHash, err := getChanPointFundingTxid(fundingChanPoint)
fundingTxID, err := getChanPointFundingTxid(fundingChanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
fundingTxID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
// Ensure that the funding transaction enters a block, and is
// properly advertised by Alice.
@ -7619,10 +7542,117 @@ func assertNumPendingChannels(t *harnessTest, node *lntest.HarnessNode,
}
}
// assertDLPExecuted asserts that Dave is a node that has recovered their state
// form scratch. Carol should then force close on chain, with Dave sweeping his
// funds immediately, and Carol sweeping her fund after her CSV delay is up. If
// the blankSlate value is true, then this means that Dave won't need to sweep
// on chain as he has no funds in the channel.
func assertDLPExecuted(net *lntest.NetworkHarness, t *harnessTest,
carol *lntest.HarnessNode, carolStartingBalance int64,
dave *lntest.HarnessNode, daveStartingBalance int64) {
// Upon reconnection, the nodes should detect that Dave is out of sync.
// Carol should force close the channel using her latest commitment.
ctxb := context.Background()
forceClose, err := waitForTxInMempool(
net.Miner.Node, minerMempoolTimeout,
)
if err != nil {
t.Fatalf("unable to find Carol's force close tx in mempool: %v",
err)
}
// Channel should be in the state "waiting close" for Carol since she
// broadcasted the force close tx.
assertNumPendingChannels(t, carol, 1, 0)
// Dave should also consider the channel "waiting close", as he noticed
// the channel was out of sync, and is now waiting for a force close to
// hit the chain.
assertNumPendingChannels(t, dave, 1, 0)
// Restart Dave to make sure he is able to sweep the funds after
// shutdown.
if err := net.RestartNode(dave, nil); err != nil {
t.Fatalf("Node restart failed: %v", err)
}
// Generate a single block, which should confirm the closing tx.
block := mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, forceClose)
// Dave should sweep his funds immediately, as they are not timelocked.
daveSweep, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout)
if err != nil {
t.Fatalf("unable to find Dave's sweep tx in mempool: %v", err)
}
// Dave should consider the channel pending force close (since he is
// waiting for his sweep to confirm).
assertNumPendingChannels(t, dave, 0, 1)
// Carol is considering it "pending force close", as we must wait
// before she can sweep her outputs.
assertNumPendingChannels(t, carol, 0, 1)
// Mine the sweep tx.
block = mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, daveSweep)
// Now Dave should consider the channel fully closed.
assertNumPendingChannels(t, dave, 0, 0)
// We query Dave's balance to make sure it increased after the channel
// closed. This checks that he was able to sweep the funds he had in
// the channel.
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
balReq := &lnrpc.WalletBalanceRequest{}
daveBalResp, err := dave.WalletBalance(ctxt, balReq)
if err != nil {
t.Fatalf("unable to get dave's balance: %v", err)
}
daveBalance := daveBalResp.ConfirmedBalance
if daveBalance <= daveStartingBalance {
t.Fatalf("expected dave to have balance above %d, "+
"instead had %v", daveStartingBalance, daveBalance)
}
// After the Carol's output matures, she should also reclaim her funds.
mineBlocks(t, net, defaultCSV-1, 0)
carolSweep, err := waitForTxInMempool(
net.Miner.Node, minerMempoolTimeout,
)
if err != nil {
t.Fatalf("unable to find Carol's sweep tx in mempool: %v", err)
}
block = mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, carolSweep)
// Now the channel should be fully closed also from Carol's POV.
assertNumPendingChannels(t, carol, 0, 0)
// Make sure Carol got her balance back.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
carolBalResp, err := carol.WalletBalance(ctxt, balReq)
if err != nil {
t.Fatalf("unable to get carol's balance: %v", err)
}
carolBalance := carolBalResp.ConfirmedBalance
if carolBalance <= carolStartingBalance {
t.Fatalf("expected carol to have balance above %d, "+
"instead had %v", carolStartingBalance,
carolBalance)
}
assertNodeNumChannels(t, dave, 0)
assertNodeNumChannels(t, carol, 0)
}
// testDataLossProtection tests that if one of the nodes in a channel
// relationship lost state, they will detect this during channel sync, and the
// up-to-date party will force close the channel, giving the outdated party the
// oppurtunity to sweep its output.
// opportunity to sweep its output.
func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
const (
@ -7818,8 +7848,8 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("unable to time travel dave: %v", err)
}
// We a´make a note of the nodes' current on-chain balances, to make
// sure they are able to retrieve the channel funds eventually,
// We make a note of the nodes' current on-chain balances, to make sure
// they are able to retrieve the channel funds eventually,
balReq := &lnrpc.WalletBalanceRequest{}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
carolBalResp, err := carol.WalletBalance(ctxt, balReq)
@ -7833,98 +7863,13 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("unable to restart dave: %v", err)
}
// Upon reconnection, the nodes should detect that Dave is out of sync.
// Carol should force close the channel using her latest commitment.
forceClose, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout)
if err != nil {
t.Fatalf("unable to find Carol's force close tx in mempool: %v",
err)
}
// Assert that once Dave comes up, they reconnect, Carol force closes
// on chain, and both of them properly carry out the DLP protocol.
assertDLPExecuted(
net, t, carol, carolStartingBalance, dave, daveStartingBalance,
)
// Channel should be in the state "waiting close" for Carol since she
// broadcasted the force close tx.
assertNumPendingChannels(t, carol, 1, 0)
// Dave should also consider the channel "waiting close", as he noticed
// the channel was out of sync, and is now waiting for a force close to
// hit the chain.
assertNumPendingChannels(t, dave, 1, 0)
// Restart Dave to make sure he is able to sweep the funds after
// shutdown.
if err := net.RestartNode(dave, nil); err != nil {
t.Fatalf("Node restart failed: %v", err)
}
// Generate a single block, which should confirm the closing tx.
block := mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, forceClose)
// Dave should sweep his funds immediately, as they are not timelocked.
daveSweep, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout)
if err != nil {
t.Fatalf("unable to find Dave's sweep tx in mempool: %v", err)
}
// Dave should consider the channel pending force close (since he is
// waiting for his sweep to confirm).
assertNumPendingChannels(t, dave, 0, 1)
// Carol is considering it "pending force close", as whe must wait
// before she can sweep her outputs.
assertNumPendingChannels(t, carol, 0, 1)
// Mine the sweep tx.
block = mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, daveSweep)
// Now Dave should consider the channel fully closed.
assertNumPendingChannels(t, dave, 0, 0)
// We query Dave's balance to make sure it increased after the channel
// closed. This checks that he was able to sweep the funds he had in
// the channel.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
daveBalResp, err := dave.WalletBalance(ctxt, balReq)
if err != nil {
t.Fatalf("unable to get dave's balance: %v", err)
}
daveBalance := daveBalResp.ConfirmedBalance
if daveBalance <= daveStartingBalance {
t.Fatalf("expected dave to have balance above %d, intead had %v",
daveStartingBalance, daveBalance)
}
// After the Carol's output matures, she should also reclaim her funds.
mineBlocks(t, net, defaultCSV-1, 0)
carolSweep, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout)
if err != nil {
t.Fatalf("unable to find Carol's sweep tx in mempool: %v", err)
}
block = mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, carolSweep)
// Now the channel should be fully closed also from Carol's POV.
assertNumPendingChannels(t, carol, 0, 0)
// Make sure Carol got her balance back.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
carolBalResp, err = carol.WalletBalance(ctxt, balReq)
if err != nil {
t.Fatalf("unable to get carol's balance: %v", err)
}
carolBalance := carolBalResp.ConfirmedBalance
if carolBalance <= carolStartingBalance {
t.Fatalf("expected carol to have balance above %d, "+
"instead had %v", carolStartingBalance,
carolBalance)
}
assertNodeNumChannels(t, dave, 0)
assertNodeNumChannels(t, carol, 0)
// As a second part of this test, we will test the the scenario where a
// As a second part of this test, we will test the scenario where a
// channel is closed while Dave is offline, loses his state and comes
// back online. In this case the node should attempt to resync the
// channel, and the peer should resend a channel sync message for the
@ -7957,11 +7902,11 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) {
// Mine enough blocks for Carol to sweep her funds.
mineBlocks(t, net, defaultCSV, 0)
carolSweep, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout)
carolSweep, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout)
if err != nil {
t.Fatalf("unable to find Carol's sweep tx in mempool: %v", err)
}
block = mineBlocks(t, net, 1, 1)[0]
block := mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, carolSweep)
// Now the channel should be fully closed also from Carol's POV.
@ -7973,7 +7918,7 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) {
if err != nil {
t.Fatalf("unable to get carol's balance: %v", err)
}
carolBalance = carolBalResp.ConfirmedBalance
carolBalance := carolBalResp.ConfirmedBalance
if carolBalance <= carolStartingBalance {
t.Fatalf("expected carol to have balance above %d, "+
"instead had %v", carolStartingBalance,
@ -8001,12 +7946,12 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) {
assertNodeNumChannels(t, dave, 0)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
daveBalResp, err = dave.WalletBalance(ctxt, balReq)
daveBalResp, err := dave.WalletBalance(ctxt, balReq)
if err != nil {
t.Fatalf("unable to get dave's balance: %v", err)
}
daveBalance = daveBalResp.ConfirmedBalance
daveBalance := daveBalResp.ConfirmedBalance
if daveBalance <= daveStartingBalance {
t.Fatalf("expected dave to have balance above %d, intead had %v",
daveStartingBalance, daveBalance)
@ -8543,7 +8488,7 @@ out:
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
if !bytes.Equal(closedChanTxid, chanPointTxid) {
if !bytes.Equal(closedChanTxid[:], chanPointTxid[:]) {
t.Fatalf("channel point hash mismatch: "+
"expected %v, got %v", chanPointTxid,
closedChanTxid)
@ -9471,14 +9416,10 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest) {
}
// Bob's force close transaction should now be found in the mempool.
txidHash, err := getChanPointFundingTxid(bobChanPoint)
bobFundingTxid, err := getChanPointFundingTxid(bobChanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
bobFundingTxid, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
assertSpendingTxInMempool(
t, net.Miner.Node, minerMempoolTimeout, wire.OutPoint{
Hash: *bobFundingTxid,
@ -9702,16 +9643,11 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest)
t.Fatalf("expected transaction not found in mempool: %v", err)
}
txidHash, err := getChanPointFundingTxid(bobChanPoint)
bobFundingTxid, err := getChanPointFundingTxid(bobChanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
bobFundingTxid, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundingPoint := wire.OutPoint{
Hash: *bobFundingTxid,
Index: bobChanPoint.OutputIndex,
@ -10440,14 +10376,10 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest)
if err != nil {
t.Fatalf("transactions not found in mempool: %v", err)
}
txidHash, err := getChanPointFundingTxid(bobChanPoint)
bobFundingTxid, err := getChanPointFundingTxid(bobChanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
bobFundingTxid, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundingPoint := wire.OutPoint{
Hash: *bobFundingTxid,
Index: bobChanPoint.OutputIndex,
@ -10795,14 +10727,10 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
if err != nil {
t.Fatalf("transactions not found in mempool: %v", err)
}
txidHash, err := getChanPointFundingTxid(bobChanPoint)
bobFundingTxid, err := getChanPointFundingTxid(bobChanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
bobFundingTxid, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundingPoint := wire.OutPoint{
Hash: *bobFundingTxid,
Index: bobChanPoint.OutputIndex,
@ -11005,14 +10933,10 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointAlice)
txidHash, err := getChanPointFundingTxid(chanPointAlice)
aliceChanTXID, err := getChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
@ -11049,14 +10973,10 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) {
},
)
networkChans = append(networkChans, chanPointDave)
txidHash, err = getChanPointFundingTxid(chanPointDave)
daveChanTXID, err := getChanPointFundingTxid(chanPointDave)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
daveChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
daveFundPoint := wire.OutPoint{
Hash: *daveChanTXID,
Index: chanPointDave.OutputIndex,
@ -11090,14 +11010,10 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointCarol)
txidHash, err = getChanPointFundingTxid(chanPointCarol)
carolChanTXID, err := getChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
@ -11108,14 +11024,10 @@ func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) {
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -11344,14 +11256,10 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointAlice)
txidHash, err := getChanPointFundingTxid(chanPointAlice)
aliceChanTXID, err := getChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
@ -11388,14 +11296,10 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) {
},
)
networkChans = append(networkChans, chanPointDave)
txidHash, err = getChanPointFundingTxid(chanPointDave)
daveChanTXID, err := getChanPointFundingTxid(chanPointDave)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
daveChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
daveFundPoint := wire.OutPoint{
Hash: *daveChanTXID,
Index: chanPointDave.OutputIndex,
@ -11429,14 +11333,10 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) {
)
networkChans = append(networkChans, chanPointCarol)
txidHash, err = getChanPointFundingTxid(chanPointCarol)
carolChanTXID, err := getChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
@ -11447,14 +11347,10 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) {
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -11690,14 +11586,10 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness
)
networkChans = append(networkChans, chanPointAlice)
txidHash, err := getChanPointFundingTxid(chanPointAlice)
aliceChanTXID, err := getChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
@ -11735,14 +11627,10 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness
)
networkChans = append(networkChans, chanPointDave)
txidHash, err = getChanPointFundingTxid(chanPointDave)
daveChanTXID, err := getChanPointFundingTxid(chanPointDave)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
daveChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
daveFundPoint := wire.OutPoint{
Hash: *daveChanTXID,
Index: chanPointDave.OutputIndex,
@ -11776,14 +11664,10 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness
)
networkChans = append(networkChans, chanPointCarol)
txidHash, err = getChanPointFundingTxid(chanPointCarol)
carolChanTXID, err := getChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
@ -11794,14 +11678,10 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -12043,14 +11923,10 @@ func testSwitchOfflineDeliveryOutgoingOffline(
)
networkChans = append(networkChans, chanPointAlice)
txidHash, err := getChanPointFundingTxid(chanPointAlice)
aliceChanTXID, err := getChanPointFundingTxid(chanPointAlice)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
aliceChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
aliceFundPoint := wire.OutPoint{
Hash: *aliceChanTXID,
Index: chanPointAlice.OutputIndex,
@ -12087,14 +11963,10 @@ func testSwitchOfflineDeliveryOutgoingOffline(
},
)
networkChans = append(networkChans, chanPointDave)
txidHash, err = getChanPointFundingTxid(chanPointDave)
daveChanTXID, err := getChanPointFundingTxid(chanPointDave)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
daveChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
daveFundPoint := wire.OutPoint{
Hash: *daveChanTXID,
Index: chanPointDave.OutputIndex,
@ -12126,14 +11998,10 @@ func testSwitchOfflineDeliveryOutgoingOffline(
)
networkChans = append(networkChans, chanPointCarol)
txidHash, err = getChanPointFundingTxid(chanPointCarol)
carolChanTXID, err := getChanPointFundingTxid(chanPointCarol)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
carolChanTXID, err := chainhash.NewHash(txidHash)
if err != nil {
t.Fatalf("unable to create sha hash: %v", err)
}
carolFundPoint := wire.OutPoint{
Hash: *carolChanTXID,
Index: chanPointCarol.OutputIndex,
@ -12144,14 +12012,10 @@ func testSwitchOfflineDeliveryOutgoingOffline(
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -12269,8 +12133,8 @@ func testSwitchOfflineDeliveryOutgoingOffline(
t.Fatalf("unable to restart alice: %v", err)
}
// Ensure that Dave is reconnected to Alice before waiting for the htlcs
// to clear.
// Ensure that Dave is reconnected to Alice before waiting for the
// htlcs to clear.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.EnsureConnected(ctxt, dave, net.Alice)
if err != nil {
@ -12299,8 +12163,8 @@ func testSwitchOfflineDeliveryOutgoingOffline(
// At this point, all channels (minus Carol, who is shutdown) should
// show a shift of 5k satoshis towards Carol. The order of asserts
// corresponds to increasing of time is needed to embed the HTLC in
// commitment transaction, in channel Bob->Alice->David, order is David,
// Alice, Bob.
// commitment transaction, in channel Bob->Alice->David, order is
// David, Alice, Bob.
assertAmountPaid(t, "Alice(local) => Dave(remote)", dave,
daveFundPoint, int64(0), amountPaid+(baseFee*numPayments))
assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice,
@ -12396,14 +12260,10 @@ func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) {
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
point := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -12613,14 +12473,10 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) {
}
for _, chanPoint := range networkChans {
for i, node := range nodes {
txidHash, err := getChanPointFundingTxid(chanPoint)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
t.Fatalf("unable to get txid: %v", err)
}
txid, e := chainhash.NewHash(txidHash)
if e != nil {
t.Fatalf("unable to create sha hash: %v", e)
}
outpoint := wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
@ -13247,6 +13103,745 @@ func testSweepAllCoins(net *lntest.NetworkHarness, t *harnessTest) {
}
}
// testChannelBackupUpdates tests that both the streaming channel update RPC,
// and the on-disk channels.backup are updated each time a channel is
// opened/closed.
func testChannelBackupUpdates(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// First, we'll make a temp directory that we'll use to store our
// backup file, so we can check in on it during the test easily.
backupDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unable to create backup dir: %v", err)
}
defer os.RemoveAll(backupDir)
// First, we'll create a new node, Carol. We'll also create a temporary
// file that Carol will use to store her channel backups.
backupFilePath := filepath.Join(
backupDir, chanbackup.DefaultBackupFileName,
)
carolArgs := fmt.Sprintf("--backupfilepath=%v", backupFilePath)
carol, err := net.NewNode("carol", []string{carolArgs})
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, carol)
// Next, we'll register for streaming notifications for changes to the
// backup file.
backupStream, err := carol.SubscribeChannelBackups(
ctxb, &lnrpc.ChannelBackupSubscription{},
)
if err != nil {
t.Fatalf("unable to create backup stream: %v", err)
}
// We'll use this goroutine to proxy any updates to a channel we can
// easily use below.
var wg sync.WaitGroup
backupUpdates := make(chan *lnrpc.ChanBackupSnapshot)
streamErr := make(chan error)
streamQuit := make(chan struct{})
wg.Add(1)
go func() {
defer wg.Done()
for {
snapshot, err := backupStream.Recv()
if err != nil {
select {
case streamErr <- err:
case <-streamQuit:
return
}
}
select {
case backupUpdates <- snapshot:
case <-streamQuit:
return
}
}
}()
defer close(streamQuit)
// With Carol up, we'll now connect her to Alice, and open a channel
// between them.
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil {
t.Fatalf("unable to connect carol to alice: %v", err)
}
// Next, we'll open two channels between Alice and Carol back to back.
var chanPoints []*lnrpc.ChannelPoint
numChans := 2
chanAmt := btcutil.Amount(1000000)
for i := 0; i < numChans; i++ {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
chanPoint := openChannelAndAssert(
ctxt, t, net, net.Alice, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
chanPoints = append(chanPoints, chanPoint)
}
// Using this helper function, we'll maintain a pointer to the latest
// channel backup so we can compare it to the on disk state.
var currentBackup *lnrpc.ChanBackupSnapshot
assertBackupNtfns := func(numNtfns int) {
for i := 0; i < numNtfns; i++ {
select {
case err := <-streamErr:
t.Fatalf("error with backup stream: %v", err)
case currentBackup = <-backupUpdates:
case <-time.After(time.Second * 5):
t.Fatalf("didn't receive channel backup "+
"notification %v", i+1)
}
}
}
// assertBackupFileState is a helper function that we'll use to compare
// the on disk back up file to our currentBackup pointer above.
assertBackupFileState := func() {
err := lntest.WaitNoError(func() error {
packedBackup, err := ioutil.ReadFile(backupFilePath)
if err != nil {
return fmt.Errorf("unable to read backup "+
"file: %v", err)
}
// As each back up file will be encrypted with a fresh
// nonce, we can't compare them directly, so instead
// we'll compare the length which is a proxy for the
// number of channels that the multi-backup contains.
rawBackup := currentBackup.MultiChanBackup.MultiChanBackup
if len(rawBackup) != len(packedBackup) {
return fmt.Errorf("backup files don't match: "+
"expected %x got %x", rawBackup, packedBackup)
}
// Additionally, we'll assert that both backups up
// returned are valid.
for i, backup := range [][]byte{rawBackup, packedBackup} {
snapshot := &lnrpc.ChanBackupSnapshot{
MultiChanBackup: &lnrpc.MultiChanBackup{
MultiChanBackup: backup,
},
}
resp, err := carol.VerifyChanBackup(ctxb, snapshot)
if err != nil {
return fmt.Errorf("unable to verify "+
"back up: %v", err)
}
if !resp.SinglesValid || !resp.MultiValid {
return fmt.Errorf("backup #%v is "+
"invalid", i)
}
}
return nil
}, time.Second*15)
if err != nil {
t.Fatalf("backup state invalid: %v", err)
}
}
// As these two channels were just open, we should've got two
// notifications for channel backups.
assertBackupNtfns(2)
// The on disk file should also exactly match the latest backup that we
// have.
assertBackupFileState()
// Next, we'll close the channels one by one. After each channel
// closure, we should get a notification, and the on-disk state should
// match this state as well.
for i := 0; i < numChans; i++ {
// To ensure force closes also trigger an update, we'll force
// close half of the channels.
forceClose := i%2 == 0
chanPoint := chanPoints[i]
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(
ctxt, t, net, net.Alice, chanPoint, forceClose,
)
// We should get a single notification after closing, and the
// on-disk state should match this latest notifications.
assertBackupNtfns(1)
assertBackupFileState()
// If we force closed the channel, then we'll mine enough
// blocks to ensure all outputs have been swept.
if forceClose {
cleanupForceClose(t, net, net.Alice, chanPoint)
}
}
}
// testExportChannelBackup tests that we're able to properly export either a
// targeted channel's backup, or export backups of all the currents open
// channels.
func testExportChannelBackup(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// First, we'll create our primary test node: Carol. We'll use Carol to
// open channels and also export backups that we'll examine throughout
// the test.
carol, err := net.NewNode("carol", nil)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, carol)
// With Carol up, we'll now connect her to Alice, and open a channel
// between them.
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil {
t.Fatalf("unable to connect carol to alice: %v", err)
}
// Next, we'll open two channels between Alice and Carol back to back.
var chanPoints []*lnrpc.ChannelPoint
numChans := 2
chanAmt := btcutil.Amount(1000000)
for i := 0; i < numChans; i++ {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
chanPoint := openChannelAndAssert(
ctxt, t, net, net.Alice, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
chanPoints = append(chanPoints, chanPoint)
}
// Now that the channels are open, we should be able to fetch the
// backups of each of the channels.
for _, chanPoint := range chanPoints {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
req := &lnrpc.ExportChannelBackupRequest{
ChanPoint: chanPoint,
}
chanBackup, err := carol.ExportChannelBackup(ctxt, req)
if err != nil {
t.Fatalf("unable to fetch backup for channel %v: %v",
chanPoint, err)
}
// The returned backup should be full populated. Since it's
// encrypted, we can't assert any more than that atm.
if len(chanBackup.ChanBackup) == 0 {
t.Fatalf("obtained empty backup for channel: %v", chanPoint)
}
// The specified chanPoint in the response should match our
// requested chanPoint.
if chanBackup.ChanPoint.String() != chanPoint.String() {
t.Fatalf("chanPoint mismatched: expected %v, got %v",
chanPoint.String(),
chanBackup.ChanPoint.String())
}
}
// Before we proceed, we'll make two utility methods we'll use below
// for our primary assertions.
assertNumSingleBackups := func(numSingles int) {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
req := &lnrpc.ChanBackupExportRequest{}
chanSnapshot, err := carol.ExportAllChannelBackups(ctxt, req)
if err != nil {
t.Fatalf("unable to export channel backup: %v", err)
}
if chanSnapshot.SingleChanBackups == nil {
t.Fatalf("single chan backups not populated")
}
backups := chanSnapshot.SingleChanBackups.ChanBackups
if len(backups) != numSingles {
t.Fatalf("expected %v singles, got %v", len(backups),
numSingles)
}
}
assertMultiBackupFound := func() func(bool, map[wire.OutPoint]struct{}) {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
req := &lnrpc.ChanBackupExportRequest{}
chanSnapshot, err := carol.ExportAllChannelBackups(ctxt, req)
if err != nil {
t.Fatalf("unable to export channel backup: %v", err)
}
return func(found bool, chanPoints map[wire.OutPoint]struct{}) {
switch {
case found && chanSnapshot.MultiChanBackup == nil:
t.Fatalf("multi-backup not present")
case !found && chanSnapshot.MultiChanBackup != nil &&
(len(chanSnapshot.MultiChanBackup.MultiChanBackup) !=
chanbackup.NilMultiSizePacked):
t.Fatalf("found multi-backup when non should " +
"be found")
}
if !found {
return
}
backedUpChans := chanSnapshot.MultiChanBackup.ChanPoints
if len(chanPoints) != len(backedUpChans) {
t.Fatalf("expected %v chans got %v", len(chanPoints),
len(backedUpChans))
}
for _, chanPoint := range backedUpChans {
wirePoint := rpcPointToWirePoint(t, chanPoint)
if _, ok := chanPoints[wirePoint]; !ok {
t.Fatalf("unexpected backup: %v", wirePoint)
}
}
}
}
chans := make(map[wire.OutPoint]struct{})
for _, chanPoint := range chanPoints {
chans[rpcPointToWirePoint(t, chanPoint)] = struct{}{}
}
// We should have exactly two single channel backups contained, and we
// should also have a multi-channel backup.
assertNumSingleBackups(2)
assertMultiBackupFound()(true, chans)
// We'll now close each channel on by one. After we close a channel, we
// shouldn't be able to find that channel as a backup still. We should
// also have one less single written to disk.
for i, chanPoint := range chanPoints {
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(
ctxt, t, net, net.Alice, chanPoint, false,
)
assertNumSingleBackups(len(chanPoints) - i - 1)
delete(chans, rpcPointToWirePoint(t, chanPoint))
assertMultiBackupFound()(true, chans)
}
// At this point we shouldn't have any single or multi-chan backups at
// all.
assertNumSingleBackups(0)
assertMultiBackupFound()(false, nil)
}
// nodeRestorer is a function closure that allows each chanRestoreTestCase to
// control exactly *how* the prior node is restored. This might be using an
// backup obtained over RPC, or the file system, etc.
type nodeRestorer func() (*lntest.HarnessNode, error)
// chanRestoreTestCase describes a test case for an end to end SCB restoration
// work flow. One node will start from scratch using an existing SCB. At the
// end of the est, both nodes should be made whole via the DLP protocol.
type chanRestoreTestCase struct {
// name is the name of the target test case.
name string
// channelsUpdated is false then this means that no updates
// have taken place within the channel before restore.
// Otherwise, HTLCs will be settled between the two parties
// before restoration modifying the balance beyond the initial
// allocation.
channelsUpdated bool
// initiator signals if Dave should be the one that opens the
// channel to Alice, or if it should be the other way around.
initiator bool
// private signals if the channel from Dave to Carol should be
// private or not.
private bool
// restoreMethod takes an old node, then returns a function
// closure that'll return the same node, but with its state
// restored via a custom method. We use this to abstract away
// _how_ a node is restored from our assertions once the node
// has been fully restored itself.
restoreMethod func(oldNode *lntest.HarnessNode,
backupFilePath string,
mnemonic []string) (nodeRestorer, error)
}
// testChanRestoreScenario executes a chanRestoreTestCase from end to end,
// ensuring that after Dave restores his channel state according to the
// testCase, the DLP protocol is executed properly and both nodes are made
// whole.
func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness,
testCase *chanRestoreTestCase, password []byte) {
const (
chanAmt = btcutil.Amount(10000000)
pushAmt = btcutil.Amount(5000000)
)
ctxb := context.Background()
// First, we'll create a brand new node we'll use within the test. If
// we have a custom backup file specified, then we'll also create that
// for use.
dave, mnemonic, err := net.NewNodeWithSeed(
"dave", nil, password,
)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, dave)
carol, err := net.NewNode("carol", nil)
if err != nil {
t.Fatalf("unable to make new node: %v", err)
}
defer shutdownAndAssert(net, t, carol)
// Now that our new node is created, we'll give him some coins it can
// use to open channels with Carol.
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, dave)
if err != nil {
t.Fatalf("unable to send coins to dave: %v", err)
}
var from, to *lntest.HarnessNode
if testCase.initiator {
from, to = dave, carol
} else {
from, to = carol, dave
}
// Next, we'll connect Dave to Carol, and open a new channel to her
// with a portion pushed.
if err := net.ConnectNodes(ctxt, dave, carol); err != nil {
t.Fatalf("unable to connect dave to carol: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPoint := openChannelAndAssert(
ctxt, t, net, from, to,
lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
Private: testCase.private,
},
)
// Wait for both sides to see the opened channel.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("dave didn't report channel: %v", err)
}
err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("carol didn't report channel: %v", err)
}
// If both parties should start with existing channel updates, then
// we'll send+settle an HTLC between 'from' and 'to' now.
if testCase.channelsUpdated {
invoice := &lnrpc.Invoice{
Memo: "testing",
Value: 10000,
}
invoiceResp, err := to.AddInvoice(ctxt, invoice)
if err != nil {
t.Fatalf("unable to add invoice: %v", err)
}
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err = completePaymentRequests(
ctxt, from, []string{invoiceResp.PaymentRequest},
true,
)
if err != nil {
t.Fatalf("unable to complete payments: %v", err)
}
}
// Before we start the recovery, we'll record the balances of both
// Carol and Dave to ensure they both sweep their coins at the end.
balReq := &lnrpc.WalletBalanceRequest{}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
carolBalResp, err := carol.WalletBalance(ctxt, balReq)
if err != nil {
t.Fatalf("unable to get carol's balance: %v", err)
}
carolStartingBalance := carolBalResp.ConfirmedBalance
daveBalance, err := dave.WalletBalance(ctxt, balReq)
if err != nil {
t.Fatalf("unable to get carol's balance: %v", err)
}
daveStartingBalance := daveBalance.ConfirmedBalance
// At this point, we'll now execute the restore method to give us the
// new node we should attempt our assertions against.
backupFilePath := dave.ChanBackupPath()
restoredNodeFunc, err := testCase.restoreMethod(
dave, backupFilePath, mnemonic,
)
if err != nil {
t.Fatalf("unable to prep node restoration: %v", err)
}
// TODO(roasbeef): assert recovery state in channel
// Now that we're able to make our restored now, we'll shutdown the old
// Dave node as we'll be storing it shortly below.
shutdownAndAssert(net, t, dave)
// Next, we'll make a new Dave and start the bulk of our recovery
// workflow.
dave, err = restoredNodeFunc()
if err != nil {
t.Fatalf("unable to restore node: %v", err)
}
// Now that we have our new node up, we expect that it'll re-connect to
// Carol automatically based on the restored backup.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.EnsureConnected(ctxt, dave, carol)
if err != nil {
t.Fatalf("node didn't connect after recovery: %v", err)
}
// TODO(roasbeef): move dave restarts?
// Now we'll assert that both sides properly execute the DLP protocol.
// We grab their balances now to ensure that they're made whole at the
// end of the protocol.
assertDLPExecuted(
net, t, carol, carolStartingBalance, dave, daveStartingBalance,
)
}
// chanRestoreViaRPC is a helper test method that returns a nodeRestorer
// instance which will restore the target node from a password+seed, then
// trigger a SCB restore using the RPC interface.
func chanRestoreViaRPC(net *lntest.NetworkHarness,
password []byte, mnemonic []string,
multi []byte) (nodeRestorer, error) {
backup := &lnrpc.RestoreChanBackupRequest_MultiChanBackup{
MultiChanBackup: multi,
}
ctxb := context.Background()
return func() (*lntest.HarnessNode, error) {
newNode, err := net.RestoreNodeWithSeed(
"dave", nil, password, mnemonic, 1000, nil,
)
if err != nil {
return nil, fmt.Errorf("unable to "+
"restore node: %v", err)
}
_, err = newNode.RestoreChannelBackups(
ctxb, &lnrpc.RestoreChanBackupRequest{
Backup: backup,
},
)
if err != nil {
return nil, fmt.Errorf("unable "+
"to restore backups: %v", err)
}
return newNode, nil
}, nil
}
// testChannelBackupRestore tests that we're able to recover from, and initiate
// the DLP protocol via: the RPC restore command, restoring on unlock, and
// restoring from initial wallet creation. We'll also alternate between
// restoring form the on disk file, and restoring from the exported RPC command
// as well.
func testChannelBackupRestore(net *lntest.NetworkHarness, t *harnessTest) {
password := []byte("El Psy Kongroo")
ctxb := context.Background()
var testCases = []chanRestoreTestCase{
// Restore from backups obtained via the RPC interface. Dave
// was the initiator, of the non-advertised channel.
{
name: "restore from RPC backup",
channelsUpdated: false,
initiator: true,
private: false,
restoreMethod: func(oldNode *lntest.HarnessNode,
backupFilePath string,
mnemonic []string) (nodeRestorer, error) {
// For this restoration method, we'll grab the
// current multi-channel backup from the old
// node, and use it to restore a new node
// within the closure.
req := &lnrpc.ChanBackupExportRequest{}
chanBackup, err := oldNode.ExportAllChannelBackups(
ctxb, req,
)
if err != nil {
return nil, fmt.Errorf("unable to obtain "+
"channel backup: %v", err)
}
multi := chanBackup.MultiChanBackup.MultiChanBackup
// In our nodeRestorer function, we'll restore
// the node from seed, then manually recover
// the channel backup.
return chanRestoreViaRPC(
net, password, mnemonic, multi,
)
},
},
// Restore the backup from the on-disk file, using the RPC
// interface.
{
name: "restore from backup file",
initiator: true,
private: false,
restoreMethod: func(oldNode *lntest.HarnessNode,
backupFilePath string,
mnemonic []string) (nodeRestorer, error) {
// Read the entire Multi backup stored within
// this node's chaannels.backup file.
multi, err := ioutil.ReadFile(backupFilePath)
if err != nil {
return nil, err
}
// Now that we have Dave's backup file, we'll
// create a new nodeRestorer that will restore
// using the on-disk channels.backup.
return chanRestoreViaRPC(
net, password, mnemonic, multi,
)
},
},
// Restore the backup as part of node initialization with the
// prior mnemonic and new backup seed.
{
name: "restore during creation",
initiator: true,
private: false,
restoreMethod: func(oldNode *lntest.HarnessNode,
backupFilePath string,
mnemonic []string) (nodeRestorer, error) {
// First, fetch the current backup state as is,
// to obtain our latest Multi.
chanBackup, err := oldNode.ExportAllChannelBackups(
ctxb, &lnrpc.ChanBackupExportRequest{},
)
if err != nil {
return nil, fmt.Errorf("unable to obtain "+
"channel backup: %v", err)
}
backupSnapshot := &lnrpc.ChanBackupSnapshot{
MultiChanBackup: chanBackup.MultiChanBackup,
}
// Create a new nodeRestorer that will restore
// the node using the Multi backup we just
// obtained above.
return func() (*lntest.HarnessNode, error) {
return net.RestoreNodeWithSeed(
"dave", nil, password,
mnemonic, 1000, backupSnapshot,
)
}, nil
},
},
// Restore the backup once the node has already been
// re-created, using the Unlock call.
{
name: "restore during unlock",
initiator: true,
private: false,
restoreMethod: func(oldNode *lntest.HarnessNode,
backupFilePath string,
mnemonic []string) (nodeRestorer, error) {
// First, fetch the current backup state as is,
// to obtain our latest Multi.
chanBackup, err := oldNode.ExportAllChannelBackups(
ctxb, &lnrpc.ChanBackupExportRequest{},
)
if err != nil {
return nil, fmt.Errorf("unable to obtain "+
"channel backup: %v", err)
}
backupSnapshot := &lnrpc.ChanBackupSnapshot{
MultiChanBackup: chanBackup.MultiChanBackup,
}
// Create a new nodeRestorer that will restore
// the node with its seed, but no channel
// backup, shutdown this initialized node, then
// restart it again using Unlock.
return func() (*lntest.HarnessNode, error) {
newNode, err := net.RestoreNodeWithSeed(
"dave", nil, password,
mnemonic, 1000, nil,
)
if err != nil {
return nil, err
}
err = net.RestartNode(
newNode, nil, backupSnapshot,
)
if err != nil {
return nil, err
}
return newNode, nil
}, nil
},
},
}
// TODO(roasbeef): online vs offline close?
// TODO(roasbeef): need to re-trigger the on-disk file once the node
// ann is updated?
for _, testCase := range testCases {
success := t.t.Run(testCase.name, func(_ *testing.T) {
testChanRestoreScenario(t, net, &testCase, password)
})
if !success {
break
}
}
}
type testCase struct {
name string
test func(net *lntest.NetworkHarness, t *harnessTest)
@ -13474,6 +14069,18 @@ var testsCases = []*testCase{
name: "send update disable channel",
test: testSendUpdateDisableChannel,
},
{
name: "streaming channel backup update",
test: testChannelBackupUpdates,
},
{
name: "export channel backup",
test: testExportChannelBackup,
},
{
name: "channel backup restore",
test: testChannelBackupRestore,
},
}
// TestLightningNetworkDaemon performs a series of integration tests amongst a

@ -55,7 +55,7 @@ func (x AddressType) String() string {
return proto.EnumName(AddressType_name, int32(x))
}
func (AddressType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{0}
return fileDescriptor_rpc_189cdc2900530099, []int{0}
}
type ChannelCloseSummary_ClosureType int32
@ -90,7 +90,7 @@ func (x ChannelCloseSummary_ClosureType) String() string {
return proto.EnumName(ChannelCloseSummary_ClosureType_name, int32(x))
}
func (ChannelCloseSummary_ClosureType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{41, 0}
return fileDescriptor_rpc_189cdc2900530099, []int{41, 0}
}
type ChannelEventUpdate_UpdateType int32
@ -119,7 +119,7 @@ func (x ChannelEventUpdate_UpdateType) String() string {
return proto.EnumName(ChannelEventUpdate_UpdateType_name, int32(x))
}
func (ChannelEventUpdate_UpdateType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{62, 0}
return fileDescriptor_rpc_189cdc2900530099, []int{62, 0}
}
type Invoice_InvoiceState int32
@ -148,7 +148,7 @@ func (x Invoice_InvoiceState) String() string {
return proto.EnumName(Invoice_InvoiceState_name, int32(x))
}
func (Invoice_InvoiceState) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{92, 0}
return fileDescriptor_rpc_189cdc2900530099, []int{92, 0}
}
type GenSeedRequest struct {
@ -169,7 +169,7 @@ func (m *GenSeedRequest) Reset() { *m = GenSeedRequest{} }
func (m *GenSeedRequest) String() string { return proto.CompactTextString(m) }
func (*GenSeedRequest) ProtoMessage() {}
func (*GenSeedRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{0}
return fileDescriptor_rpc_189cdc2900530099, []int{0}
}
func (m *GenSeedRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GenSeedRequest.Unmarshal(m, b)
@ -224,7 +224,7 @@ func (m *GenSeedResponse) Reset() { *m = GenSeedResponse{} }
func (m *GenSeedResponse) String() string { return proto.CompactTextString(m) }
func (*GenSeedResponse) ProtoMessage() {}
func (*GenSeedResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{1}
return fileDescriptor_rpc_189cdc2900530099, []int{1}
}
func (m *GenSeedResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GenSeedResponse.Unmarshal(m, b)
@ -276,20 +276,28 @@ type InitWalletRequest struct {
// *
// recovery_window is an optional argument specifying the address lookahead
// when restoring a wallet seed. The recovery window applies to each
// invdividual branch of the BIP44 derivation paths. Supplying a recovery
// individual branch of the BIP44 derivation paths. Supplying a recovery
// window of zero indicates that no addresses should be recovered, such after
// the first initialization of the wallet.
RecoveryWindow int32 `protobuf:"varint,4,opt,name=recovery_window,json=recoveryWindow,proto3" json:"recovery_window,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
RecoveryWindow int32 `protobuf:"varint,4,opt,name=recovery_window,json=recoveryWindow,proto3" json:"recovery_window,omitempty"`
// *
// channel_backups is an optional argument that allows clients to recover the
// settled funds within a set of channels. This should be populated if the
// user was unable to close out all channels and sweep funds before partial or
// total data loss occurred. If specified, then after on-chain recovery of
// funds, lnd begin to carry out the data loss recovery protocol in order to
// recover the funds in each channel from a remote force closed transaction.
ChannelBackups *ChanBackupSnapshot `protobuf:"bytes,5,opt,name=channel_backups,json=channelBackups,proto3" json:"channel_backups,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *InitWalletRequest) Reset() { *m = InitWalletRequest{} }
func (m *InitWalletRequest) String() string { return proto.CompactTextString(m) }
func (*InitWalletRequest) ProtoMessage() {}
func (*InitWalletRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{2}
return fileDescriptor_rpc_189cdc2900530099, []int{2}
}
func (m *InitWalletRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_InitWalletRequest.Unmarshal(m, b)
@ -337,6 +345,13 @@ func (m *InitWalletRequest) GetRecoveryWindow() int32 {
return 0
}
func (m *InitWalletRequest) GetChannelBackups() *ChanBackupSnapshot {
if m != nil {
return m.ChannelBackups
}
return nil
}
type InitWalletResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@ -347,7 +362,7 @@ func (m *InitWalletResponse) Reset() { *m = InitWalletResponse{} }
func (m *InitWalletResponse) String() string { return proto.CompactTextString(m) }
func (*InitWalletResponse) ProtoMessage() {}
func (*InitWalletResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{3}
return fileDescriptor_rpc_189cdc2900530099, []int{3}
}
func (m *InitWalletResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_InitWalletResponse.Unmarshal(m, b)
@ -379,17 +394,25 @@ type UnlockWalletRequest struct {
// invdividual branch of the BIP44 derivation paths. Supplying a recovery
// window of zero indicates that no addresses should be recovered, such after
// the first initialization of the wallet.
RecoveryWindow int32 `protobuf:"varint,2,opt,name=recovery_window,json=recoveryWindow,proto3" json:"recovery_window,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
RecoveryWindow int32 `protobuf:"varint,2,opt,name=recovery_window,json=recoveryWindow,proto3" json:"recovery_window,omitempty"`
// *
// channel_backups is an optional argument that allows clients to recover the
// settled funds within a set of channels. This should be populated if the
// user was unable to close out all channels and sweep funds before partial or
// total data loss occurred. If specified, then after on-chain recovery of
// funds, lnd begin to carry out the data loss recovery protocol in order to
// recover the funds in each channel from a remote force closed transaction.
ChannelBackups *ChanBackupSnapshot `protobuf:"bytes,3,opt,name=channel_backups,json=channelBackups,proto3" json:"channel_backups,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UnlockWalletRequest) Reset() { *m = UnlockWalletRequest{} }
func (m *UnlockWalletRequest) String() string { return proto.CompactTextString(m) }
func (*UnlockWalletRequest) ProtoMessage() {}
func (*UnlockWalletRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{4}
return fileDescriptor_rpc_189cdc2900530099, []int{4}
}
func (m *UnlockWalletRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UnlockWalletRequest.Unmarshal(m, b)
@ -423,6 +446,13 @@ func (m *UnlockWalletRequest) GetRecoveryWindow() int32 {
return 0
}
func (m *UnlockWalletRequest) GetChannelBackups() *ChanBackupSnapshot {
if m != nil {
return m.ChannelBackups
}
return nil
}
type UnlockWalletResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@ -433,7 +463,7 @@ func (m *UnlockWalletResponse) Reset() { *m = UnlockWalletResponse{} }
func (m *UnlockWalletResponse) String() string { return proto.CompactTextString(m) }
func (*UnlockWalletResponse) ProtoMessage() {}
func (*UnlockWalletResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{5}
return fileDescriptor_rpc_189cdc2900530099, []int{5}
}
func (m *UnlockWalletResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UnlockWalletResponse.Unmarshal(m, b)
@ -471,7 +501,7 @@ func (m *ChangePasswordRequest) Reset() { *m = ChangePasswordRequest{} }
func (m *ChangePasswordRequest) String() string { return proto.CompactTextString(m) }
func (*ChangePasswordRequest) ProtoMessage() {}
func (*ChangePasswordRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{6}
return fileDescriptor_rpc_189cdc2900530099, []int{6}
}
func (m *ChangePasswordRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChangePasswordRequest.Unmarshal(m, b)
@ -515,7 +545,7 @@ func (m *ChangePasswordResponse) Reset() { *m = ChangePasswordResponse{}
func (m *ChangePasswordResponse) String() string { return proto.CompactTextString(m) }
func (*ChangePasswordResponse) ProtoMessage() {}
func (*ChangePasswordResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{7}
return fileDescriptor_rpc_189cdc2900530099, []int{7}
}
func (m *ChangePasswordResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChangePasswordResponse.Unmarshal(m, b)
@ -557,7 +587,7 @@ func (m *Utxo) Reset() { *m = Utxo{} }
func (m *Utxo) String() string { return proto.CompactTextString(m) }
func (*Utxo) ProtoMessage() {}
func (*Utxo) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{8}
return fileDescriptor_rpc_189cdc2900530099, []int{8}
}
func (m *Utxo) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Utxo.Unmarshal(m, b)
@ -645,7 +675,7 @@ func (m *Transaction) Reset() { *m = Transaction{} }
func (m *Transaction) String() string { return proto.CompactTextString(m) }
func (*Transaction) ProtoMessage() {}
func (*Transaction) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{9}
return fileDescriptor_rpc_189cdc2900530099, []int{9}
}
func (m *Transaction) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Transaction.Unmarshal(m, b)
@ -731,7 +761,7 @@ func (m *GetTransactionsRequest) Reset() { *m = GetTransactionsRequest{}
func (m *GetTransactionsRequest) String() string { return proto.CompactTextString(m) }
func (*GetTransactionsRequest) ProtoMessage() {}
func (*GetTransactionsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{10}
return fileDescriptor_rpc_189cdc2900530099, []int{10}
}
func (m *GetTransactionsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GetTransactionsRequest.Unmarshal(m, b)
@ -763,7 +793,7 @@ func (m *TransactionDetails) Reset() { *m = TransactionDetails{} }
func (m *TransactionDetails) String() string { return proto.CompactTextString(m) }
func (*TransactionDetails) ProtoMessage() {}
func (*TransactionDetails) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{11}
return fileDescriptor_rpc_189cdc2900530099, []int{11}
}
func (m *TransactionDetails) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_TransactionDetails.Unmarshal(m, b)
@ -804,7 +834,7 @@ func (m *FeeLimit) Reset() { *m = FeeLimit{} }
func (m *FeeLimit) String() string { return proto.CompactTextString(m) }
func (*FeeLimit) ProtoMessage() {}
func (*FeeLimit) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{12}
return fileDescriptor_rpc_189cdc2900530099, []int{12}
}
func (m *FeeLimit) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_FeeLimit.Unmarshal(m, b)
@ -968,7 +998,7 @@ func (m *SendRequest) Reset() { *m = SendRequest{} }
func (m *SendRequest) String() string { return proto.CompactTextString(m) }
func (*SendRequest) ProtoMessage() {}
func (*SendRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{13}
return fileDescriptor_rpc_189cdc2900530099, []int{13}
}
func (m *SendRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SendRequest.Unmarshal(m, b)
@ -1072,7 +1102,7 @@ func (m *SendResponse) Reset() { *m = SendResponse{} }
func (m *SendResponse) String() string { return proto.CompactTextString(m) }
func (*SendResponse) ProtoMessage() {}
func (*SendResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{14}
return fileDescriptor_rpc_189cdc2900530099, []int{14}
}
func (m *SendResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SendResponse.Unmarshal(m, b)
@ -1142,7 +1172,7 @@ func (m *SendToRouteRequest) Reset() { *m = SendToRouteRequest{} }
func (m *SendToRouteRequest) String() string { return proto.CompactTextString(m) }
func (*SendToRouteRequest) ProtoMessage() {}
func (*SendToRouteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{15}
return fileDescriptor_rpc_189cdc2900530099, []int{15}
}
func (m *SendToRouteRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SendToRouteRequest.Unmarshal(m, b)
@ -1207,7 +1237,7 @@ func (m *ChannelPoint) Reset() { *m = ChannelPoint{} }
func (m *ChannelPoint) String() string { return proto.CompactTextString(m) }
func (*ChannelPoint) ProtoMessage() {}
func (*ChannelPoint) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{16}
return fileDescriptor_rpc_189cdc2900530099, []int{16}
}
func (m *ChannelPoint) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelPoint.Unmarshal(m, b)
@ -1353,7 +1383,7 @@ func (m *OutPoint) Reset() { *m = OutPoint{} }
func (m *OutPoint) String() string { return proto.CompactTextString(m) }
func (*OutPoint) ProtoMessage() {}
func (*OutPoint) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{17}
return fileDescriptor_rpc_189cdc2900530099, []int{17}
}
func (m *OutPoint) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_OutPoint.Unmarshal(m, b)
@ -1408,7 +1438,7 @@ func (m *LightningAddress) Reset() { *m = LightningAddress{} }
func (m *LightningAddress) String() string { return proto.CompactTextString(m) }
func (*LightningAddress) ProtoMessage() {}
func (*LightningAddress) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{18}
return fileDescriptor_rpc_189cdc2900530099, []int{18}
}
func (m *LightningAddress) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LightningAddress.Unmarshal(m, b)
@ -1456,7 +1486,7 @@ func (m *EstimateFeeRequest) Reset() { *m = EstimateFeeRequest{} }
func (m *EstimateFeeRequest) String() string { return proto.CompactTextString(m) }
func (*EstimateFeeRequest) ProtoMessage() {}
func (*EstimateFeeRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{19}
return fileDescriptor_rpc_189cdc2900530099, []int{19}
}
func (m *EstimateFeeRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_EstimateFeeRequest.Unmarshal(m, b)
@ -1504,7 +1534,7 @@ func (m *EstimateFeeResponse) Reset() { *m = EstimateFeeResponse{} }
func (m *EstimateFeeResponse) String() string { return proto.CompactTextString(m) }
func (*EstimateFeeResponse) ProtoMessage() {}
func (*EstimateFeeResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{20}
return fileDescriptor_rpc_189cdc2900530099, []int{20}
}
func (m *EstimateFeeResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_EstimateFeeResponse.Unmarshal(m, b)
@ -1554,7 +1584,7 @@ func (m *SendManyRequest) Reset() { *m = SendManyRequest{} }
func (m *SendManyRequest) String() string { return proto.CompactTextString(m) }
func (*SendManyRequest) ProtoMessage() {}
func (*SendManyRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{21}
return fileDescriptor_rpc_189cdc2900530099, []int{21}
}
func (m *SendManyRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SendManyRequest.Unmarshal(m, b)
@ -1607,7 +1637,7 @@ func (m *SendManyResponse) Reset() { *m = SendManyResponse{} }
func (m *SendManyResponse) String() string { return proto.CompactTextString(m) }
func (*SendManyResponse) ProtoMessage() {}
func (*SendManyResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{22}
return fileDescriptor_rpc_189cdc2900530099, []int{22}
}
func (m *SendManyResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SendManyResponse.Unmarshal(m, b)
@ -1657,7 +1687,7 @@ func (m *SendCoinsRequest) Reset() { *m = SendCoinsRequest{} }
func (m *SendCoinsRequest) String() string { return proto.CompactTextString(m) }
func (*SendCoinsRequest) ProtoMessage() {}
func (*SendCoinsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{23}
return fileDescriptor_rpc_189cdc2900530099, []int{23}
}
func (m *SendCoinsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SendCoinsRequest.Unmarshal(m, b)
@ -1724,7 +1754,7 @@ func (m *SendCoinsResponse) Reset() { *m = SendCoinsResponse{} }
func (m *SendCoinsResponse) String() string { return proto.CompactTextString(m) }
func (*SendCoinsResponse) ProtoMessage() {}
func (*SendCoinsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{24}
return fileDescriptor_rpc_189cdc2900530099, []int{24}
}
func (m *SendCoinsResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SendCoinsResponse.Unmarshal(m, b)
@ -1765,7 +1795,7 @@ func (m *ListUnspentRequest) Reset() { *m = ListUnspentRequest{} }
func (m *ListUnspentRequest) String() string { return proto.CompactTextString(m) }
func (*ListUnspentRequest) ProtoMessage() {}
func (*ListUnspentRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{25}
return fileDescriptor_rpc_189cdc2900530099, []int{25}
}
func (m *ListUnspentRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListUnspentRequest.Unmarshal(m, b)
@ -1811,7 +1841,7 @@ func (m *ListUnspentResponse) Reset() { *m = ListUnspentResponse{} }
func (m *ListUnspentResponse) String() string { return proto.CompactTextString(m) }
func (*ListUnspentResponse) ProtoMessage() {}
func (*ListUnspentResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{26}
return fileDescriptor_rpc_189cdc2900530099, []int{26}
}
func (m *ListUnspentResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListUnspentResponse.Unmarshal(m, b)
@ -1850,7 +1880,7 @@ func (m *NewAddressRequest) Reset() { *m = NewAddressRequest{} }
func (m *NewAddressRequest) String() string { return proto.CompactTextString(m) }
func (*NewAddressRequest) ProtoMessage() {}
func (*NewAddressRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{27}
return fileDescriptor_rpc_189cdc2900530099, []int{27}
}
func (m *NewAddressRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NewAddressRequest.Unmarshal(m, b)
@ -1889,7 +1919,7 @@ func (m *NewAddressResponse) Reset() { *m = NewAddressResponse{} }
func (m *NewAddressResponse) String() string { return proto.CompactTextString(m) }
func (*NewAddressResponse) ProtoMessage() {}
func (*NewAddressResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{28}
return fileDescriptor_rpc_189cdc2900530099, []int{28}
}
func (m *NewAddressResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NewAddressResponse.Unmarshal(m, b)
@ -1928,7 +1958,7 @@ func (m *SignMessageRequest) Reset() { *m = SignMessageRequest{} }
func (m *SignMessageRequest) String() string { return proto.CompactTextString(m) }
func (*SignMessageRequest) ProtoMessage() {}
func (*SignMessageRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{29}
return fileDescriptor_rpc_189cdc2900530099, []int{29}
}
func (m *SignMessageRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SignMessageRequest.Unmarshal(m, b)
@ -1967,7 +1997,7 @@ func (m *SignMessageResponse) Reset() { *m = SignMessageResponse{} }
func (m *SignMessageResponse) String() string { return proto.CompactTextString(m) }
func (*SignMessageResponse) ProtoMessage() {}
func (*SignMessageResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{30}
return fileDescriptor_rpc_189cdc2900530099, []int{30}
}
func (m *SignMessageResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SignMessageResponse.Unmarshal(m, b)
@ -2008,7 +2038,7 @@ func (m *VerifyMessageRequest) Reset() { *m = VerifyMessageRequest{} }
func (m *VerifyMessageRequest) String() string { return proto.CompactTextString(m) }
func (*VerifyMessageRequest) ProtoMessage() {}
func (*VerifyMessageRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{31}
return fileDescriptor_rpc_189cdc2900530099, []int{31}
}
func (m *VerifyMessageRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_VerifyMessageRequest.Unmarshal(m, b)
@ -2056,7 +2086,7 @@ func (m *VerifyMessageResponse) Reset() { *m = VerifyMessageResponse{} }
func (m *VerifyMessageResponse) String() string { return proto.CompactTextString(m) }
func (*VerifyMessageResponse) ProtoMessage() {}
func (*VerifyMessageResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{32}
return fileDescriptor_rpc_189cdc2900530099, []int{32}
}
func (m *VerifyMessageResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_VerifyMessageResponse.Unmarshal(m, b)
@ -2105,7 +2135,7 @@ func (m *ConnectPeerRequest) Reset() { *m = ConnectPeerRequest{} }
func (m *ConnectPeerRequest) String() string { return proto.CompactTextString(m) }
func (*ConnectPeerRequest) ProtoMessage() {}
func (*ConnectPeerRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{33}
return fileDescriptor_rpc_189cdc2900530099, []int{33}
}
func (m *ConnectPeerRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ConnectPeerRequest.Unmarshal(m, b)
@ -2149,7 +2179,7 @@ func (m *ConnectPeerResponse) Reset() { *m = ConnectPeerResponse{} }
func (m *ConnectPeerResponse) String() string { return proto.CompactTextString(m) }
func (*ConnectPeerResponse) ProtoMessage() {}
func (*ConnectPeerResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{34}
return fileDescriptor_rpc_189cdc2900530099, []int{34}
}
func (m *ConnectPeerResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ConnectPeerResponse.Unmarshal(m, b)
@ -2181,7 +2211,7 @@ func (m *DisconnectPeerRequest) Reset() { *m = DisconnectPeerRequest{} }
func (m *DisconnectPeerRequest) String() string { return proto.CompactTextString(m) }
func (*DisconnectPeerRequest) ProtoMessage() {}
func (*DisconnectPeerRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{35}
return fileDescriptor_rpc_189cdc2900530099, []int{35}
}
func (m *DisconnectPeerRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DisconnectPeerRequest.Unmarshal(m, b)
@ -2218,7 +2248,7 @@ func (m *DisconnectPeerResponse) Reset() { *m = DisconnectPeerResponse{}
func (m *DisconnectPeerResponse) String() string { return proto.CompactTextString(m) }
func (*DisconnectPeerResponse) ProtoMessage() {}
func (*DisconnectPeerResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{36}
return fileDescriptor_rpc_189cdc2900530099, []int{36}
}
func (m *DisconnectPeerResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DisconnectPeerResponse.Unmarshal(m, b)
@ -2252,7 +2282,7 @@ func (m *HTLC) Reset() { *m = HTLC{} }
func (m *HTLC) String() string { return proto.CompactTextString(m) }
func (*HTLC) ProtoMessage() {}
func (*HTLC) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{37}
return fileDescriptor_rpc_189cdc2900530099, []int{37}
}
func (m *HTLC) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HTLC.Unmarshal(m, b)
@ -2349,14 +2379,15 @@ type Channel struct {
// The list of active, uncleared HTLCs currently pending within the channel.
PendingHtlcs []*HTLC `protobuf:"bytes,15,rep,name=pending_htlcs,proto3" json:"pending_htlcs,omitempty"`
// *
// The CSV delay expressed in relative blocks. If the channel is force
// closed, we'll need to wait for this many blocks before we can regain our
// funds.
// The CSV delay expressed in relative blocks. If the channel is force closed,
// we will need to wait for this many blocks before we can regain our funds.
CsvDelay uint32 `protobuf:"varint,16,opt,name=csv_delay,proto3" json:"csv_delay,omitempty"`
// / Whether this channel is advertised to the network or not.
Private bool `protobuf:"varint,17,opt,name=private,proto3" json:"private,omitempty"`
// / True if we were the ones that created the channel.
Initiator bool `protobuf:"varint,18,opt,name=initiator,proto3" json:"initiator,omitempty"`
Initiator bool `protobuf:"varint,18,opt,name=initiator,proto3" json:"initiator,omitempty"`
// / A set of flags showing the current state of the cahnnel.
ChanStatusFlags string `protobuf:"bytes,19,opt,name=chan_status_flags,proto3" json:"chan_status_flags,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -2366,7 +2397,7 @@ func (m *Channel) Reset() { *m = Channel{} }
func (m *Channel) String() string { return proto.CompactTextString(m) }
func (*Channel) ProtoMessage() {}
func (*Channel) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{38}
return fileDescriptor_rpc_189cdc2900530099, []int{38}
}
func (m *Channel) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Channel.Unmarshal(m, b)
@ -2512,6 +2543,13 @@ func (m *Channel) GetInitiator() bool {
return false
}
func (m *Channel) GetChanStatusFlags() string {
if m != nil {
return m.ChanStatusFlags
}
return ""
}
type ListChannelsRequest struct {
ActiveOnly bool `protobuf:"varint,1,opt,name=active_only,json=activeOnly,proto3" json:"active_only,omitempty"`
InactiveOnly bool `protobuf:"varint,2,opt,name=inactive_only,json=inactiveOnly,proto3" json:"inactive_only,omitempty"`
@ -2526,7 +2564,7 @@ func (m *ListChannelsRequest) Reset() { *m = ListChannelsRequest{} }
func (m *ListChannelsRequest) String() string { return proto.CompactTextString(m) }
func (*ListChannelsRequest) ProtoMessage() {}
func (*ListChannelsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{39}
return fileDescriptor_rpc_189cdc2900530099, []int{39}
}
func (m *ListChannelsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListChannelsRequest.Unmarshal(m, b)
@ -2586,7 +2624,7 @@ func (m *ListChannelsResponse) Reset() { *m = ListChannelsResponse{} }
func (m *ListChannelsResponse) String() string { return proto.CompactTextString(m) }
func (*ListChannelsResponse) ProtoMessage() {}
func (*ListChannelsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{40}
return fileDescriptor_rpc_189cdc2900530099, []int{40}
}
func (m *ListChannelsResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListChannelsResponse.Unmarshal(m, b)
@ -2643,7 +2681,7 @@ func (m *ChannelCloseSummary) Reset() { *m = ChannelCloseSummary{} }
func (m *ChannelCloseSummary) String() string { return proto.CompactTextString(m) }
func (*ChannelCloseSummary) ProtoMessage() {}
func (*ChannelCloseSummary) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{41}
return fileDescriptor_rpc_189cdc2900530099, []int{41}
}
func (m *ChannelCloseSummary) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelCloseSummary.Unmarshal(m, b)
@ -2749,7 +2787,7 @@ func (m *ClosedChannelsRequest) Reset() { *m = ClosedChannelsRequest{} }
func (m *ClosedChannelsRequest) String() string { return proto.CompactTextString(m) }
func (*ClosedChannelsRequest) ProtoMessage() {}
func (*ClosedChannelsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{42}
return fileDescriptor_rpc_189cdc2900530099, []int{42}
}
func (m *ClosedChannelsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ClosedChannelsRequest.Unmarshal(m, b)
@ -2822,7 +2860,7 @@ func (m *ClosedChannelsResponse) Reset() { *m = ClosedChannelsResponse{}
func (m *ClosedChannelsResponse) String() string { return proto.CompactTextString(m) }
func (*ClosedChannelsResponse) ProtoMessage() {}
func (*ClosedChannelsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{43}
return fileDescriptor_rpc_189cdc2900530099, []int{43}
}
func (m *ClosedChannelsResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ClosedChannelsResponse.Unmarshal(m, b)
@ -2875,7 +2913,7 @@ func (m *Peer) Reset() { *m = Peer{} }
func (m *Peer) String() string { return proto.CompactTextString(m) }
func (*Peer) ProtoMessage() {}
func (*Peer) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{44}
return fileDescriptor_rpc_189cdc2900530099, []int{44}
}
func (m *Peer) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Peer.Unmarshal(m, b)
@ -2961,7 +2999,7 @@ func (m *ListPeersRequest) Reset() { *m = ListPeersRequest{} }
func (m *ListPeersRequest) String() string { return proto.CompactTextString(m) }
func (*ListPeersRequest) ProtoMessage() {}
func (*ListPeersRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{45}
return fileDescriptor_rpc_189cdc2900530099, []int{45}
}
func (m *ListPeersRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListPeersRequest.Unmarshal(m, b)
@ -2993,7 +3031,7 @@ func (m *ListPeersResponse) Reset() { *m = ListPeersResponse{} }
func (m *ListPeersResponse) String() string { return proto.CompactTextString(m) }
func (*ListPeersResponse) ProtoMessage() {}
func (*ListPeersResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{46}
return fileDescriptor_rpc_189cdc2900530099, []int{46}
}
func (m *ListPeersResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListPeersResponse.Unmarshal(m, b)
@ -3030,7 +3068,7 @@ func (m *GetInfoRequest) Reset() { *m = GetInfoRequest{} }
func (m *GetInfoRequest) String() string { return proto.CompactTextString(m) }
func (*GetInfoRequest) ProtoMessage() {}
func (*GetInfoRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{47}
return fileDescriptor_rpc_189cdc2900530099, []int{47}
}
func (m *GetInfoRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GetInfoRequest.Unmarshal(m, b)
@ -3090,7 +3128,7 @@ func (m *GetInfoResponse) Reset() { *m = GetInfoResponse{} }
func (m *GetInfoResponse) String() string { return proto.CompactTextString(m) }
func (*GetInfoResponse) ProtoMessage() {}
func (*GetInfoResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{48}
return fileDescriptor_rpc_189cdc2900530099, []int{48}
}
func (m *GetInfoResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GetInfoResponse.Unmarshal(m, b)
@ -3223,7 +3261,7 @@ func (m *Chain) Reset() { *m = Chain{} }
func (m *Chain) String() string { return proto.CompactTextString(m) }
func (*Chain) ProtoMessage() {}
func (*Chain) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{49}
return fileDescriptor_rpc_189cdc2900530099, []int{49}
}
func (m *Chain) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Chain.Unmarshal(m, b)
@ -3270,7 +3308,7 @@ func (m *ConfirmationUpdate) Reset() { *m = ConfirmationUpdate{} }
func (m *ConfirmationUpdate) String() string { return proto.CompactTextString(m) }
func (*ConfirmationUpdate) ProtoMessage() {}
func (*ConfirmationUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{50}
return fileDescriptor_rpc_189cdc2900530099, []int{50}
}
func (m *ConfirmationUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ConfirmationUpdate.Unmarshal(m, b)
@ -3322,7 +3360,7 @@ func (m *ChannelOpenUpdate) Reset() { *m = ChannelOpenUpdate{} }
func (m *ChannelOpenUpdate) String() string { return proto.CompactTextString(m) }
func (*ChannelOpenUpdate) ProtoMessage() {}
func (*ChannelOpenUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{51}
return fileDescriptor_rpc_189cdc2900530099, []int{51}
}
func (m *ChannelOpenUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelOpenUpdate.Unmarshal(m, b)
@ -3361,7 +3399,7 @@ func (m *ChannelCloseUpdate) Reset() { *m = ChannelCloseUpdate{} }
func (m *ChannelCloseUpdate) String() string { return proto.CompactTextString(m) }
func (*ChannelCloseUpdate) ProtoMessage() {}
func (*ChannelCloseUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{52}
return fileDescriptor_rpc_189cdc2900530099, []int{52}
}
func (m *ChannelCloseUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelCloseUpdate.Unmarshal(m, b)
@ -3416,7 +3454,7 @@ func (m *CloseChannelRequest) Reset() { *m = CloseChannelRequest{} }
func (m *CloseChannelRequest) String() string { return proto.CompactTextString(m) }
func (*CloseChannelRequest) ProtoMessage() {}
func (*CloseChannelRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{53}
return fileDescriptor_rpc_189cdc2900530099, []int{53}
}
func (m *CloseChannelRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CloseChannelRequest.Unmarshal(m, b)
@ -3478,7 +3516,7 @@ func (m *CloseStatusUpdate) Reset() { *m = CloseStatusUpdate{} }
func (m *CloseStatusUpdate) String() string { return proto.CompactTextString(m) }
func (*CloseStatusUpdate) ProtoMessage() {}
func (*CloseStatusUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{54}
return fileDescriptor_rpc_189cdc2900530099, []int{54}
}
func (m *CloseStatusUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CloseStatusUpdate.Unmarshal(m, b)
@ -3621,7 +3659,7 @@ func (m *PendingUpdate) Reset() { *m = PendingUpdate{} }
func (m *PendingUpdate) String() string { return proto.CompactTextString(m) }
func (*PendingUpdate) ProtoMessage() {}
func (*PendingUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{55}
return fileDescriptor_rpc_189cdc2900530099, []int{55}
}
func (m *PendingUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PendingUpdate.Unmarshal(m, b)
@ -3687,7 +3725,7 @@ func (m *OpenChannelRequest) Reset() { *m = OpenChannelRequest{} }
func (m *OpenChannelRequest) String() string { return proto.CompactTextString(m) }
func (*OpenChannelRequest) ProtoMessage() {}
func (*OpenChannelRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{56}
return fileDescriptor_rpc_189cdc2900530099, []int{56}
}
func (m *OpenChannelRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_OpenChannelRequest.Unmarshal(m, b)
@ -3798,7 +3836,7 @@ func (m *OpenStatusUpdate) Reset() { *m = OpenStatusUpdate{} }
func (m *OpenStatusUpdate) String() string { return proto.CompactTextString(m) }
func (*OpenStatusUpdate) ProtoMessage() {}
func (*OpenStatusUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{57}
return fileDescriptor_rpc_189cdc2900530099, []int{57}
}
func (m *OpenStatusUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_OpenStatusUpdate.Unmarshal(m, b)
@ -3954,7 +3992,7 @@ func (m *PendingHTLC) Reset() { *m = PendingHTLC{} }
func (m *PendingHTLC) String() string { return proto.CompactTextString(m) }
func (*PendingHTLC) ProtoMessage() {}
func (*PendingHTLC) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{58}
return fileDescriptor_rpc_189cdc2900530099, []int{58}
}
func (m *PendingHTLC) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PendingHTLC.Unmarshal(m, b)
@ -4026,7 +4064,7 @@ func (m *PendingChannelsRequest) Reset() { *m = PendingChannelsRequest{}
func (m *PendingChannelsRequest) String() string { return proto.CompactTextString(m) }
func (*PendingChannelsRequest) ProtoMessage() {}
func (*PendingChannelsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{59}
return fileDescriptor_rpc_189cdc2900530099, []int{59}
}
func (m *PendingChannelsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PendingChannelsRequest.Unmarshal(m, b)
@ -4066,7 +4104,7 @@ func (m *PendingChannelsResponse) Reset() { *m = PendingChannelsResponse
func (m *PendingChannelsResponse) String() string { return proto.CompactTextString(m) }
func (*PendingChannelsResponse) ProtoMessage() {}
func (*PendingChannelsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{60}
return fileDescriptor_rpc_189cdc2900530099, []int{60}
}
func (m *PendingChannelsResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PendingChannelsResponse.Unmarshal(m, b)
@ -4138,7 +4176,7 @@ func (m *PendingChannelsResponse_PendingChannel) Reset() {
func (m *PendingChannelsResponse_PendingChannel) String() string { return proto.CompactTextString(m) }
func (*PendingChannelsResponse_PendingChannel) ProtoMessage() {}
func (*PendingChannelsResponse_PendingChannel) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{60, 0}
return fileDescriptor_rpc_189cdc2900530099, []int{60, 0}
}
func (m *PendingChannelsResponse_PendingChannel) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PendingChannelsResponse_PendingChannel.Unmarshal(m, b)
@ -4225,7 +4263,7 @@ func (m *PendingChannelsResponse_PendingOpenChannel) String() string {
}
func (*PendingChannelsResponse_PendingOpenChannel) ProtoMessage() {}
func (*PendingChannelsResponse_PendingOpenChannel) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{60, 1}
return fileDescriptor_rpc_189cdc2900530099, []int{60, 1}
}
func (m *PendingChannelsResponse_PendingOpenChannel) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PendingChannelsResponse_PendingOpenChannel.Unmarshal(m, b)
@ -4298,7 +4336,7 @@ func (m *PendingChannelsResponse_WaitingCloseChannel) String() string {
}
func (*PendingChannelsResponse_WaitingCloseChannel) ProtoMessage() {}
func (*PendingChannelsResponse_WaitingCloseChannel) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{60, 2}
return fileDescriptor_rpc_189cdc2900530099, []int{60, 2}
}
func (m *PendingChannelsResponse_WaitingCloseChannel) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PendingChannelsResponse_WaitingCloseChannel.Unmarshal(m, b)
@ -4346,7 +4384,7 @@ func (m *PendingChannelsResponse_ClosedChannel) Reset() { *m = PendingCh
func (m *PendingChannelsResponse_ClosedChannel) String() string { return proto.CompactTextString(m) }
func (*PendingChannelsResponse_ClosedChannel) ProtoMessage() {}
func (*PendingChannelsResponse_ClosedChannel) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{60, 3}
return fileDescriptor_rpc_189cdc2900530099, []int{60, 3}
}
func (m *PendingChannelsResponse_ClosedChannel) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PendingChannelsResponse_ClosedChannel.Unmarshal(m, b)
@ -4410,7 +4448,7 @@ func (m *PendingChannelsResponse_ForceClosedChannel) String() string {
}
func (*PendingChannelsResponse_ForceClosedChannel) ProtoMessage() {}
func (*PendingChannelsResponse_ForceClosedChannel) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{60, 4}
return fileDescriptor_rpc_189cdc2900530099, []int{60, 4}
}
func (m *PendingChannelsResponse_ForceClosedChannel) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PendingChannelsResponse_ForceClosedChannel.Unmarshal(m, b)
@ -4489,7 +4527,7 @@ func (m *ChannelEventSubscription) Reset() { *m = ChannelEventSubscripti
func (m *ChannelEventSubscription) String() string { return proto.CompactTextString(m) }
func (*ChannelEventSubscription) ProtoMessage() {}
func (*ChannelEventSubscription) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{61}
return fileDescriptor_rpc_189cdc2900530099, []int{61}
}
func (m *ChannelEventSubscription) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelEventSubscription.Unmarshal(m, b)
@ -4526,7 +4564,7 @@ func (m *ChannelEventUpdate) Reset() { *m = ChannelEventUpdate{} }
func (m *ChannelEventUpdate) String() string { return proto.CompactTextString(m) }
func (*ChannelEventUpdate) ProtoMessage() {}
func (*ChannelEventUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{62}
return fileDescriptor_rpc_189cdc2900530099, []int{62}
}
func (m *ChannelEventUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelEventUpdate.Unmarshal(m, b)
@ -4738,7 +4776,7 @@ func (m *WalletBalanceRequest) Reset() { *m = WalletBalanceRequest{} }
func (m *WalletBalanceRequest) String() string { return proto.CompactTextString(m) }
func (*WalletBalanceRequest) ProtoMessage() {}
func (*WalletBalanceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{63}
return fileDescriptor_rpc_189cdc2900530099, []int{63}
}
func (m *WalletBalanceRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_WalletBalanceRequest.Unmarshal(m, b)
@ -4774,7 +4812,7 @@ func (m *WalletBalanceResponse) Reset() { *m = WalletBalanceResponse{} }
func (m *WalletBalanceResponse) String() string { return proto.CompactTextString(m) }
func (*WalletBalanceResponse) ProtoMessage() {}
func (*WalletBalanceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{64}
return fileDescriptor_rpc_189cdc2900530099, []int{64}
}
func (m *WalletBalanceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_WalletBalanceResponse.Unmarshal(m, b)
@ -4825,7 +4863,7 @@ func (m *ChannelBalanceRequest) Reset() { *m = ChannelBalanceRequest{} }
func (m *ChannelBalanceRequest) String() string { return proto.CompactTextString(m) }
func (*ChannelBalanceRequest) ProtoMessage() {}
func (*ChannelBalanceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{65}
return fileDescriptor_rpc_189cdc2900530099, []int{65}
}
func (m *ChannelBalanceRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelBalanceRequest.Unmarshal(m, b)
@ -4859,7 +4897,7 @@ func (m *ChannelBalanceResponse) Reset() { *m = ChannelBalanceResponse{}
func (m *ChannelBalanceResponse) String() string { return proto.CompactTextString(m) }
func (*ChannelBalanceResponse) ProtoMessage() {}
func (*ChannelBalanceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{66}
return fileDescriptor_rpc_189cdc2900530099, []int{66}
}
func (m *ChannelBalanceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelBalanceResponse.Unmarshal(m, b)
@ -4929,7 +4967,7 @@ func (m *QueryRoutesRequest) Reset() { *m = QueryRoutesRequest{} }
func (m *QueryRoutesRequest) String() string { return proto.CompactTextString(m) }
func (*QueryRoutesRequest) ProtoMessage() {}
func (*QueryRoutesRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{67}
return fileDescriptor_rpc_189cdc2900530099, []int{67}
}
func (m *QueryRoutesRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QueryRoutesRequest.Unmarshal(m, b)
@ -5024,7 +5062,7 @@ func (m *EdgeLocator) Reset() { *m = EdgeLocator{} }
func (m *EdgeLocator) String() string { return proto.CompactTextString(m) }
func (*EdgeLocator) ProtoMessage() {}
func (*EdgeLocator) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{68}
return fileDescriptor_rpc_189cdc2900530099, []int{68}
}
func (m *EdgeLocator) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_EdgeLocator.Unmarshal(m, b)
@ -5069,7 +5107,7 @@ func (m *QueryRoutesResponse) Reset() { *m = QueryRoutesResponse{} }
func (m *QueryRoutesResponse) String() string { return proto.CompactTextString(m) }
func (*QueryRoutesResponse) ProtoMessage() {}
func (*QueryRoutesResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{69}
return fileDescriptor_rpc_189cdc2900530099, []int{69}
}
func (m *QueryRoutesResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QueryRoutesResponse.Unmarshal(m, b)
@ -5121,7 +5159,7 @@ func (m *Hop) Reset() { *m = Hop{} }
func (m *Hop) String() string { return proto.CompactTextString(m) }
func (*Hop) ProtoMessage() {}
func (*Hop) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{70}
return fileDescriptor_rpc_189cdc2900530099, []int{70}
}
func (m *Hop) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Hop.Unmarshal(m, b)
@ -5242,7 +5280,7 @@ func (m *Route) Reset() { *m = Route{} }
func (m *Route) String() string { return proto.CompactTextString(m) }
func (*Route) ProtoMessage() {}
func (*Route) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{71}
return fileDescriptor_rpc_189cdc2900530099, []int{71}
}
func (m *Route) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Route.Unmarshal(m, b)
@ -5318,7 +5356,7 @@ func (m *NodeInfoRequest) Reset() { *m = NodeInfoRequest{} }
func (m *NodeInfoRequest) String() string { return proto.CompactTextString(m) }
func (*NodeInfoRequest) ProtoMessage() {}
func (*NodeInfoRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{72}
return fileDescriptor_rpc_189cdc2900530099, []int{72}
}
func (m *NodeInfoRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NodeInfoRequest.Unmarshal(m, b)
@ -5363,7 +5401,7 @@ func (m *NodeInfo) Reset() { *m = NodeInfo{} }
func (m *NodeInfo) String() string { return proto.CompactTextString(m) }
func (*NodeInfo) ProtoMessage() {}
func (*NodeInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{73}
return fileDescriptor_rpc_189cdc2900530099, []int{73}
}
func (m *NodeInfo) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NodeInfo.Unmarshal(m, b)
@ -5424,7 +5462,7 @@ func (m *LightningNode) Reset() { *m = LightningNode{} }
func (m *LightningNode) String() string { return proto.CompactTextString(m) }
func (*LightningNode) ProtoMessage() {}
func (*LightningNode) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{74}
return fileDescriptor_rpc_189cdc2900530099, []int{74}
}
func (m *LightningNode) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LightningNode.Unmarshal(m, b)
@ -5491,7 +5529,7 @@ func (m *NodeAddress) Reset() { *m = NodeAddress{} }
func (m *NodeAddress) String() string { return proto.CompactTextString(m) }
func (*NodeAddress) ProtoMessage() {}
func (*NodeAddress) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{75}
return fileDescriptor_rpc_189cdc2900530099, []int{75}
}
func (m *NodeAddress) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NodeAddress.Unmarshal(m, b)
@ -5541,7 +5579,7 @@ func (m *RoutingPolicy) Reset() { *m = RoutingPolicy{} }
func (m *RoutingPolicy) String() string { return proto.CompactTextString(m) }
func (*RoutingPolicy) ProtoMessage() {}
func (*RoutingPolicy) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{76}
return fileDescriptor_rpc_189cdc2900530099, []int{76}
}
func (m *RoutingPolicy) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RoutingPolicy.Unmarshal(m, b)
@ -5631,7 +5669,7 @@ func (m *ChannelEdge) Reset() { *m = ChannelEdge{} }
func (m *ChannelEdge) String() string { return proto.CompactTextString(m) }
func (*ChannelEdge) ProtoMessage() {}
func (*ChannelEdge) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{77}
return fileDescriptor_rpc_189cdc2900530099, []int{77}
}
func (m *ChannelEdge) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelEdge.Unmarshal(m, b)
@ -5722,7 +5760,7 @@ func (m *ChannelGraphRequest) Reset() { *m = ChannelGraphRequest{} }
func (m *ChannelGraphRequest) String() string { return proto.CompactTextString(m) }
func (*ChannelGraphRequest) ProtoMessage() {}
func (*ChannelGraphRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{78}
return fileDescriptor_rpc_189cdc2900530099, []int{78}
}
func (m *ChannelGraphRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelGraphRequest.Unmarshal(m, b)
@ -5764,7 +5802,7 @@ func (m *ChannelGraph) Reset() { *m = ChannelGraph{} }
func (m *ChannelGraph) String() string { return proto.CompactTextString(m) }
func (*ChannelGraph) ProtoMessage() {}
func (*ChannelGraph) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{79}
return fileDescriptor_rpc_189cdc2900530099, []int{79}
}
func (m *ChannelGraph) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelGraph.Unmarshal(m, b)
@ -5813,7 +5851,7 @@ func (m *ChanInfoRequest) Reset() { *m = ChanInfoRequest{} }
func (m *ChanInfoRequest) String() string { return proto.CompactTextString(m) }
func (*ChanInfoRequest) ProtoMessage() {}
func (*ChanInfoRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{80}
return fileDescriptor_rpc_189cdc2900530099, []int{80}
}
func (m *ChanInfoRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChanInfoRequest.Unmarshal(m, b)
@ -5850,7 +5888,7 @@ func (m *NetworkInfoRequest) Reset() { *m = NetworkInfoRequest{} }
func (m *NetworkInfoRequest) String() string { return proto.CompactTextString(m) }
func (*NetworkInfoRequest) ProtoMessage() {}
func (*NetworkInfoRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{81}
return fileDescriptor_rpc_189cdc2900530099, []int{81}
}
func (m *NetworkInfoRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NetworkInfoRequest.Unmarshal(m, b)
@ -5890,7 +5928,7 @@ func (m *NetworkInfo) Reset() { *m = NetworkInfo{} }
func (m *NetworkInfo) String() string { return proto.CompactTextString(m) }
func (*NetworkInfo) ProtoMessage() {}
func (*NetworkInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{82}
return fileDescriptor_rpc_189cdc2900530099, []int{82}
}
func (m *NetworkInfo) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NetworkInfo.Unmarshal(m, b)
@ -5990,7 +6028,7 @@ func (m *StopRequest) Reset() { *m = StopRequest{} }
func (m *StopRequest) String() string { return proto.CompactTextString(m) }
func (*StopRequest) ProtoMessage() {}
func (*StopRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{83}
return fileDescriptor_rpc_189cdc2900530099, []int{83}
}
func (m *StopRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StopRequest.Unmarshal(m, b)
@ -6020,7 +6058,7 @@ func (m *StopResponse) Reset() { *m = StopResponse{} }
func (m *StopResponse) String() string { return proto.CompactTextString(m) }
func (*StopResponse) ProtoMessage() {}
func (*StopResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{84}
return fileDescriptor_rpc_189cdc2900530099, []int{84}
}
func (m *StopResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StopResponse.Unmarshal(m, b)
@ -6050,7 +6088,7 @@ func (m *GraphTopologySubscription) Reset() { *m = GraphTopologySubscrip
func (m *GraphTopologySubscription) String() string { return proto.CompactTextString(m) }
func (*GraphTopologySubscription) ProtoMessage() {}
func (*GraphTopologySubscription) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{85}
return fileDescriptor_rpc_189cdc2900530099, []int{85}
}
func (m *GraphTopologySubscription) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GraphTopologySubscription.Unmarshal(m, b)
@ -6083,7 +6121,7 @@ func (m *GraphTopologyUpdate) Reset() { *m = GraphTopologyUpdate{} }
func (m *GraphTopologyUpdate) String() string { return proto.CompactTextString(m) }
func (*GraphTopologyUpdate) ProtoMessage() {}
func (*GraphTopologyUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{86}
return fileDescriptor_rpc_189cdc2900530099, []int{86}
}
func (m *GraphTopologyUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GraphTopologyUpdate.Unmarshal(m, b)
@ -6138,7 +6176,7 @@ func (m *NodeUpdate) Reset() { *m = NodeUpdate{} }
func (m *NodeUpdate) String() string { return proto.CompactTextString(m) }
func (*NodeUpdate) ProtoMessage() {}
func (*NodeUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{87}
return fileDescriptor_rpc_189cdc2900530099, []int{87}
}
func (m *NodeUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_NodeUpdate.Unmarshal(m, b)
@ -6206,7 +6244,7 @@ func (m *ChannelEdgeUpdate) Reset() { *m = ChannelEdgeUpdate{} }
func (m *ChannelEdgeUpdate) String() string { return proto.CompactTextString(m) }
func (*ChannelEdgeUpdate) ProtoMessage() {}
func (*ChannelEdgeUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{88}
return fileDescriptor_rpc_189cdc2900530099, []int{88}
}
func (m *ChannelEdgeUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelEdgeUpdate.Unmarshal(m, b)
@ -6286,7 +6324,7 @@ func (m *ClosedChannelUpdate) Reset() { *m = ClosedChannelUpdate{} }
func (m *ClosedChannelUpdate) String() string { return proto.CompactTextString(m) }
func (*ClosedChannelUpdate) ProtoMessage() {}
func (*ClosedChannelUpdate) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{89}
return fileDescriptor_rpc_189cdc2900530099, []int{89}
}
func (m *ClosedChannelUpdate) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ClosedChannelUpdate.Unmarshal(m, b)
@ -6356,7 +6394,7 @@ func (m *HopHint) Reset() { *m = HopHint{} }
func (m *HopHint) String() string { return proto.CompactTextString(m) }
func (*HopHint) ProtoMessage() {}
func (*HopHint) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{90}
return fileDescriptor_rpc_189cdc2900530099, []int{90}
}
func (m *HopHint) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HopHint.Unmarshal(m, b)
@ -6425,7 +6463,7 @@ func (m *RouteHint) Reset() { *m = RouteHint{} }
func (m *RouteHint) String() string { return proto.CompactTextString(m) }
func (*RouteHint) ProtoMessage() {}
func (*RouteHint) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{91}
return fileDescriptor_rpc_189cdc2900530099, []int{91}
}
func (m *RouteHint) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RouteHint.Unmarshal(m, b)
@ -6540,7 +6578,7 @@ func (m *Invoice) Reset() { *m = Invoice{} }
func (m *Invoice) String() string { return proto.CompactTextString(m) }
func (*Invoice) ProtoMessage() {}
func (*Invoice) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{92}
return fileDescriptor_rpc_189cdc2900530099, []int{92}
}
func (m *Invoice) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Invoice.Unmarshal(m, b)
@ -6732,7 +6770,7 @@ func (m *AddInvoiceResponse) Reset() { *m = AddInvoiceResponse{} }
func (m *AddInvoiceResponse) String() string { return proto.CompactTextString(m) }
func (*AddInvoiceResponse) ProtoMessage() {}
func (*AddInvoiceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{93}
return fileDescriptor_rpc_189cdc2900530099, []int{93}
}
func (m *AddInvoiceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AddInvoiceResponse.Unmarshal(m, b)
@ -6789,7 +6827,7 @@ func (m *PaymentHash) Reset() { *m = PaymentHash{} }
func (m *PaymentHash) String() string { return proto.CompactTextString(m) }
func (*PaymentHash) ProtoMessage() {}
func (*PaymentHash) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{94}
return fileDescriptor_rpc_189cdc2900530099, []int{94}
}
func (m *PaymentHash) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PaymentHash.Unmarshal(m, b)
@ -6845,7 +6883,7 @@ func (m *ListInvoiceRequest) Reset() { *m = ListInvoiceRequest{} }
func (m *ListInvoiceRequest) String() string { return proto.CompactTextString(m) }
func (*ListInvoiceRequest) ProtoMessage() {}
func (*ListInvoiceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{95}
return fileDescriptor_rpc_189cdc2900530099, []int{95}
}
func (m *ListInvoiceRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListInvoiceRequest.Unmarshal(m, b)
@ -6915,7 +6953,7 @@ func (m *ListInvoiceResponse) Reset() { *m = ListInvoiceResponse{} }
func (m *ListInvoiceResponse) String() string { return proto.CompactTextString(m) }
func (*ListInvoiceResponse) ProtoMessage() {}
func (*ListInvoiceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{96}
return fileDescriptor_rpc_189cdc2900530099, []int{96}
}
func (m *ListInvoiceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListInvoiceResponse.Unmarshal(m, b)
@ -6978,7 +7016,7 @@ func (m *InvoiceSubscription) Reset() { *m = InvoiceSubscription{} }
func (m *InvoiceSubscription) String() string { return proto.CompactTextString(m) }
func (*InvoiceSubscription) ProtoMessage() {}
func (*InvoiceSubscription) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{97}
return fileDescriptor_rpc_189cdc2900530099, []int{97}
}
func (m *InvoiceSubscription) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_InvoiceSubscription.Unmarshal(m, b)
@ -7038,7 +7076,7 @@ func (m *Payment) Reset() { *m = Payment{} }
func (m *Payment) String() string { return proto.CompactTextString(m) }
func (*Payment) ProtoMessage() {}
func (*Payment) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{98}
return fileDescriptor_rpc_189cdc2900530099, []int{98}
}
func (m *Payment) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Payment.Unmarshal(m, b)
@ -7125,7 +7163,7 @@ func (m *ListPaymentsRequest) Reset() { *m = ListPaymentsRequest{} }
func (m *ListPaymentsRequest) String() string { return proto.CompactTextString(m) }
func (*ListPaymentsRequest) ProtoMessage() {}
func (*ListPaymentsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{99}
return fileDescriptor_rpc_189cdc2900530099, []int{99}
}
func (m *ListPaymentsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListPaymentsRequest.Unmarshal(m, b)
@ -7157,7 +7195,7 @@ func (m *ListPaymentsResponse) Reset() { *m = ListPaymentsResponse{} }
func (m *ListPaymentsResponse) String() string { return proto.CompactTextString(m) }
func (*ListPaymentsResponse) ProtoMessage() {}
func (*ListPaymentsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{100}
return fileDescriptor_rpc_189cdc2900530099, []int{100}
}
func (m *ListPaymentsResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ListPaymentsResponse.Unmarshal(m, b)
@ -7194,7 +7232,7 @@ func (m *DeleteAllPaymentsRequest) Reset() { *m = DeleteAllPaymentsReque
func (m *DeleteAllPaymentsRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteAllPaymentsRequest) ProtoMessage() {}
func (*DeleteAllPaymentsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{101}
return fileDescriptor_rpc_189cdc2900530099, []int{101}
}
func (m *DeleteAllPaymentsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DeleteAllPaymentsRequest.Unmarshal(m, b)
@ -7224,7 +7262,7 @@ func (m *DeleteAllPaymentsResponse) Reset() { *m = DeleteAllPaymentsResp
func (m *DeleteAllPaymentsResponse) String() string { return proto.CompactTextString(m) }
func (*DeleteAllPaymentsResponse) ProtoMessage() {}
func (*DeleteAllPaymentsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{102}
return fileDescriptor_rpc_189cdc2900530099, []int{102}
}
func (m *DeleteAllPaymentsResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DeleteAllPaymentsResponse.Unmarshal(m, b)
@ -7255,7 +7293,7 @@ func (m *AbandonChannelRequest) Reset() { *m = AbandonChannelRequest{} }
func (m *AbandonChannelRequest) String() string { return proto.CompactTextString(m) }
func (*AbandonChannelRequest) ProtoMessage() {}
func (*AbandonChannelRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{103}
return fileDescriptor_rpc_189cdc2900530099, []int{103}
}
func (m *AbandonChannelRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AbandonChannelRequest.Unmarshal(m, b)
@ -7292,7 +7330,7 @@ func (m *AbandonChannelResponse) Reset() { *m = AbandonChannelResponse{}
func (m *AbandonChannelResponse) String() string { return proto.CompactTextString(m) }
func (*AbandonChannelResponse) ProtoMessage() {}
func (*AbandonChannelResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{104}
return fileDescriptor_rpc_189cdc2900530099, []int{104}
}
func (m *AbandonChannelResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_AbandonChannelResponse.Unmarshal(m, b)
@ -7324,7 +7362,7 @@ func (m *DebugLevelRequest) Reset() { *m = DebugLevelRequest{} }
func (m *DebugLevelRequest) String() string { return proto.CompactTextString(m) }
func (*DebugLevelRequest) ProtoMessage() {}
func (*DebugLevelRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{105}
return fileDescriptor_rpc_189cdc2900530099, []int{105}
}
func (m *DebugLevelRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DebugLevelRequest.Unmarshal(m, b)
@ -7369,7 +7407,7 @@ func (m *DebugLevelResponse) Reset() { *m = DebugLevelResponse{} }
func (m *DebugLevelResponse) String() string { return proto.CompactTextString(m) }
func (*DebugLevelResponse) ProtoMessage() {}
func (*DebugLevelResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{106}
return fileDescriptor_rpc_189cdc2900530099, []int{106}
}
func (m *DebugLevelResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DebugLevelResponse.Unmarshal(m, b)
@ -7408,7 +7446,7 @@ func (m *PayReqString) Reset() { *m = PayReqString{} }
func (m *PayReqString) String() string { return proto.CompactTextString(m) }
func (*PayReqString) ProtoMessage() {}
func (*PayReqString) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{107}
return fileDescriptor_rpc_189cdc2900530099, []int{107}
}
func (m *PayReqString) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PayReqString.Unmarshal(m, b)
@ -7455,7 +7493,7 @@ func (m *PayReq) Reset() { *m = PayReq{} }
func (m *PayReq) String() string { return proto.CompactTextString(m) }
func (*PayReq) ProtoMessage() {}
func (*PayReq) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{108}
return fileDescriptor_rpc_189cdc2900530099, []int{108}
}
func (m *PayReq) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PayReq.Unmarshal(m, b)
@ -7555,7 +7593,7 @@ func (m *FeeReportRequest) Reset() { *m = FeeReportRequest{} }
func (m *FeeReportRequest) String() string { return proto.CompactTextString(m) }
func (*FeeReportRequest) ProtoMessage() {}
func (*FeeReportRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{109}
return fileDescriptor_rpc_189cdc2900530099, []int{109}
}
func (m *FeeReportRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_FeeReportRequest.Unmarshal(m, b)
@ -7593,7 +7631,7 @@ func (m *ChannelFeeReport) Reset() { *m = ChannelFeeReport{} }
func (m *ChannelFeeReport) String() string { return proto.CompactTextString(m) }
func (*ChannelFeeReport) ProtoMessage() {}
func (*ChannelFeeReport) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{110}
return fileDescriptor_rpc_189cdc2900530099, []int{110}
}
func (m *ChannelFeeReport) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelFeeReport.Unmarshal(m, b)
@ -7659,7 +7697,7 @@ func (m *FeeReportResponse) Reset() { *m = FeeReportResponse{} }
func (m *FeeReportResponse) String() string { return proto.CompactTextString(m) }
func (*FeeReportResponse) ProtoMessage() {}
func (*FeeReportResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{111}
return fileDescriptor_rpc_189cdc2900530099, []int{111}
}
func (m *FeeReportResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_FeeReportResponse.Unmarshal(m, b)
@ -7727,7 +7765,7 @@ func (m *PolicyUpdateRequest) Reset() { *m = PolicyUpdateRequest{} }
func (m *PolicyUpdateRequest) String() string { return proto.CompactTextString(m) }
func (*PolicyUpdateRequest) ProtoMessage() {}
func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{112}
return fileDescriptor_rpc_189cdc2900530099, []int{112}
}
func (m *PolicyUpdateRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PolicyUpdateRequest.Unmarshal(m, b)
@ -7888,7 +7926,7 @@ func (m *PolicyUpdateResponse) Reset() { *m = PolicyUpdateResponse{} }
func (m *PolicyUpdateResponse) String() string { return proto.CompactTextString(m) }
func (*PolicyUpdateResponse) ProtoMessage() {}
func (*PolicyUpdateResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{113}
return fileDescriptor_rpc_189cdc2900530099, []int{113}
}
func (m *PolicyUpdateResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PolicyUpdateResponse.Unmarshal(m, b)
@ -7926,7 +7964,7 @@ func (m *ForwardingHistoryRequest) Reset() { *m = ForwardingHistoryReque
func (m *ForwardingHistoryRequest) String() string { return proto.CompactTextString(m) }
func (*ForwardingHistoryRequest) ProtoMessage() {}
func (*ForwardingHistoryRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{114}
return fileDescriptor_rpc_189cdc2900530099, []int{114}
}
func (m *ForwardingHistoryRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ForwardingHistoryRequest.Unmarshal(m, b)
@ -7998,7 +8036,7 @@ func (m *ForwardingEvent) Reset() { *m = ForwardingEvent{} }
func (m *ForwardingEvent) String() string { return proto.CompactTextString(m) }
func (*ForwardingEvent) ProtoMessage() {}
func (*ForwardingEvent) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{115}
return fileDescriptor_rpc_189cdc2900530099, []int{115}
}
func (m *ForwardingEvent) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ForwardingEvent.Unmarshal(m, b)
@ -8081,7 +8119,7 @@ func (m *ForwardingHistoryResponse) Reset() { *m = ForwardingHistoryResp
func (m *ForwardingHistoryResponse) String() string { return proto.CompactTextString(m) }
func (*ForwardingHistoryResponse) ProtoMessage() {}
func (*ForwardingHistoryResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_72c11ad9c9dfa44d, []int{116}
return fileDescriptor_rpc_189cdc2900530099, []int{116}
}
func (m *ForwardingHistoryResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ForwardingHistoryResponse.Unmarshal(m, b)
@ -8115,6 +8153,518 @@ func (m *ForwardingHistoryResponse) GetLastOffsetIndex() uint32 {
return 0
}
type ExportChannelBackupRequest struct {
// / The target chanenl point to obtain a back up for.
ChanPoint *ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ExportChannelBackupRequest) Reset() { *m = ExportChannelBackupRequest{} }
func (m *ExportChannelBackupRequest) String() string { return proto.CompactTextString(m) }
func (*ExportChannelBackupRequest) ProtoMessage() {}
func (*ExportChannelBackupRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{117}
}
func (m *ExportChannelBackupRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ExportChannelBackupRequest.Unmarshal(m, b)
}
func (m *ExportChannelBackupRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ExportChannelBackupRequest.Marshal(b, m, deterministic)
}
func (dst *ExportChannelBackupRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ExportChannelBackupRequest.Merge(dst, src)
}
func (m *ExportChannelBackupRequest) XXX_Size() int {
return xxx_messageInfo_ExportChannelBackupRequest.Size(m)
}
func (m *ExportChannelBackupRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ExportChannelBackupRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ExportChannelBackupRequest proto.InternalMessageInfo
func (m *ExportChannelBackupRequest) GetChanPoint() *ChannelPoint {
if m != nil {
return m.ChanPoint
}
return nil
}
type ChannelBackup struct {
// *
// Identifies the channel that this backup belongs to.
ChanPoint *ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,proto3" json:"chan_point,omitempty"`
// *
// Is an encrypted single-chan backup. this can be passed to
// RestoreChannelBackups, or the WalletUnlocker Innit and Unlock methods in
// order to trigger the recovery protocol.
ChanBackup []byte `protobuf:"bytes,2,opt,name=chan_backup,proto3" json:"chan_backup,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ChannelBackup) Reset() { *m = ChannelBackup{} }
func (m *ChannelBackup) String() string { return proto.CompactTextString(m) }
func (*ChannelBackup) ProtoMessage() {}
func (*ChannelBackup) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{118}
}
func (m *ChannelBackup) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelBackup.Unmarshal(m, b)
}
func (m *ChannelBackup) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ChannelBackup.Marshal(b, m, deterministic)
}
func (dst *ChannelBackup) XXX_Merge(src proto.Message) {
xxx_messageInfo_ChannelBackup.Merge(dst, src)
}
func (m *ChannelBackup) XXX_Size() int {
return xxx_messageInfo_ChannelBackup.Size(m)
}
func (m *ChannelBackup) XXX_DiscardUnknown() {
xxx_messageInfo_ChannelBackup.DiscardUnknown(m)
}
var xxx_messageInfo_ChannelBackup proto.InternalMessageInfo
func (m *ChannelBackup) GetChanPoint() *ChannelPoint {
if m != nil {
return m.ChanPoint
}
return nil
}
func (m *ChannelBackup) GetChanBackup() []byte {
if m != nil {
return m.ChanBackup
}
return nil
}
type MultiChanBackup struct {
// *
// Is the set of all channels that are included in this multi-channel backup.
ChanPoints []*ChannelPoint `protobuf:"bytes,1,rep,name=chan_points,proto3" json:"chan_points,omitempty"`
// *
// A single encrypted blob containing all the static channel backups of the
// channel listed above. This can be stored as a single file or blob, and
// safely be replaced with any prior/future versions.
MultiChanBackup []byte `protobuf:"bytes,2,opt,name=multi_chan_backup,proto3" json:"multi_chan_backup,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *MultiChanBackup) Reset() { *m = MultiChanBackup{} }
func (m *MultiChanBackup) String() string { return proto.CompactTextString(m) }
func (*MultiChanBackup) ProtoMessage() {}
func (*MultiChanBackup) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{119}
}
func (m *MultiChanBackup) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_MultiChanBackup.Unmarshal(m, b)
}
func (m *MultiChanBackup) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_MultiChanBackup.Marshal(b, m, deterministic)
}
func (dst *MultiChanBackup) XXX_Merge(src proto.Message) {
xxx_messageInfo_MultiChanBackup.Merge(dst, src)
}
func (m *MultiChanBackup) XXX_Size() int {
return xxx_messageInfo_MultiChanBackup.Size(m)
}
func (m *MultiChanBackup) XXX_DiscardUnknown() {
xxx_messageInfo_MultiChanBackup.DiscardUnknown(m)
}
var xxx_messageInfo_MultiChanBackup proto.InternalMessageInfo
func (m *MultiChanBackup) GetChanPoints() []*ChannelPoint {
if m != nil {
return m.ChanPoints
}
return nil
}
func (m *MultiChanBackup) GetMultiChanBackup() []byte {
if m != nil {
return m.MultiChanBackup
}
return nil
}
type ChanBackupExportRequest struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ChanBackupExportRequest) Reset() { *m = ChanBackupExportRequest{} }
func (m *ChanBackupExportRequest) String() string { return proto.CompactTextString(m) }
func (*ChanBackupExportRequest) ProtoMessage() {}
func (*ChanBackupExportRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{120}
}
func (m *ChanBackupExportRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChanBackupExportRequest.Unmarshal(m, b)
}
func (m *ChanBackupExportRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ChanBackupExportRequest.Marshal(b, m, deterministic)
}
func (dst *ChanBackupExportRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ChanBackupExportRequest.Merge(dst, src)
}
func (m *ChanBackupExportRequest) XXX_Size() int {
return xxx_messageInfo_ChanBackupExportRequest.Size(m)
}
func (m *ChanBackupExportRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ChanBackupExportRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ChanBackupExportRequest proto.InternalMessageInfo
type ChanBackupSnapshot struct {
// *
// The set of new channels that have been added since the last channel backup
// snapshot was requested.
SingleChanBackups *ChannelBackups `protobuf:"bytes,1,opt,name=single_chan_backups,proto3" json:"single_chan_backups,omitempty"`
// *
// A multi-channel backup that covers all open channels currently known to
// lnd.
MultiChanBackup *MultiChanBackup `protobuf:"bytes,2,opt,name=multi_chan_backup,proto3" json:"multi_chan_backup,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ChanBackupSnapshot) Reset() { *m = ChanBackupSnapshot{} }
func (m *ChanBackupSnapshot) String() string { return proto.CompactTextString(m) }
func (*ChanBackupSnapshot) ProtoMessage() {}
func (*ChanBackupSnapshot) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{121}
}
func (m *ChanBackupSnapshot) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChanBackupSnapshot.Unmarshal(m, b)
}
func (m *ChanBackupSnapshot) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ChanBackupSnapshot.Marshal(b, m, deterministic)
}
func (dst *ChanBackupSnapshot) XXX_Merge(src proto.Message) {
xxx_messageInfo_ChanBackupSnapshot.Merge(dst, src)
}
func (m *ChanBackupSnapshot) XXX_Size() int {
return xxx_messageInfo_ChanBackupSnapshot.Size(m)
}
func (m *ChanBackupSnapshot) XXX_DiscardUnknown() {
xxx_messageInfo_ChanBackupSnapshot.DiscardUnknown(m)
}
var xxx_messageInfo_ChanBackupSnapshot proto.InternalMessageInfo
func (m *ChanBackupSnapshot) GetSingleChanBackups() *ChannelBackups {
if m != nil {
return m.SingleChanBackups
}
return nil
}
func (m *ChanBackupSnapshot) GetMultiChanBackup() *MultiChanBackup {
if m != nil {
return m.MultiChanBackup
}
return nil
}
type ChannelBackups struct {
// *
// A set of single-chan static channel backups.
ChanBackups []*ChannelBackup `protobuf:"bytes,1,rep,name=chan_backups,proto3" json:"chan_backups,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ChannelBackups) Reset() { *m = ChannelBackups{} }
func (m *ChannelBackups) String() string { return proto.CompactTextString(m) }
func (*ChannelBackups) ProtoMessage() {}
func (*ChannelBackups) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{122}
}
func (m *ChannelBackups) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelBackups.Unmarshal(m, b)
}
func (m *ChannelBackups) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ChannelBackups.Marshal(b, m, deterministic)
}
func (dst *ChannelBackups) XXX_Merge(src proto.Message) {
xxx_messageInfo_ChannelBackups.Merge(dst, src)
}
func (m *ChannelBackups) XXX_Size() int {
return xxx_messageInfo_ChannelBackups.Size(m)
}
func (m *ChannelBackups) XXX_DiscardUnknown() {
xxx_messageInfo_ChannelBackups.DiscardUnknown(m)
}
var xxx_messageInfo_ChannelBackups proto.InternalMessageInfo
func (m *ChannelBackups) GetChanBackups() []*ChannelBackup {
if m != nil {
return m.ChanBackups
}
return nil
}
type RestoreChanBackupRequest struct {
// Types that are valid to be assigned to Backup:
// *RestoreChanBackupRequest_ChanBackups
// *RestoreChanBackupRequest_MultiChanBackup
Backup isRestoreChanBackupRequest_Backup `protobuf_oneof:"backup"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RestoreChanBackupRequest) Reset() { *m = RestoreChanBackupRequest{} }
func (m *RestoreChanBackupRequest) String() string { return proto.CompactTextString(m) }
func (*RestoreChanBackupRequest) ProtoMessage() {}
func (*RestoreChanBackupRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{123}
}
func (m *RestoreChanBackupRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RestoreChanBackupRequest.Unmarshal(m, b)
}
func (m *RestoreChanBackupRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RestoreChanBackupRequest.Marshal(b, m, deterministic)
}
func (dst *RestoreChanBackupRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_RestoreChanBackupRequest.Merge(dst, src)
}
func (m *RestoreChanBackupRequest) XXX_Size() int {
return xxx_messageInfo_RestoreChanBackupRequest.Size(m)
}
func (m *RestoreChanBackupRequest) XXX_DiscardUnknown() {
xxx_messageInfo_RestoreChanBackupRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RestoreChanBackupRequest proto.InternalMessageInfo
type isRestoreChanBackupRequest_Backup interface {
isRestoreChanBackupRequest_Backup()
}
type RestoreChanBackupRequest_ChanBackups struct {
ChanBackups *ChannelBackups `protobuf:"bytes,1,opt,name=chan_backups,proto3,oneof"`
}
type RestoreChanBackupRequest_MultiChanBackup struct {
MultiChanBackup []byte `protobuf:"bytes,2,opt,name=multi_chan_backup,proto3,oneof"`
}
func (*RestoreChanBackupRequest_ChanBackups) isRestoreChanBackupRequest_Backup() {}
func (*RestoreChanBackupRequest_MultiChanBackup) isRestoreChanBackupRequest_Backup() {}
func (m *RestoreChanBackupRequest) GetBackup() isRestoreChanBackupRequest_Backup {
if m != nil {
return m.Backup
}
return nil
}
func (m *RestoreChanBackupRequest) GetChanBackups() *ChannelBackups {
if x, ok := m.GetBackup().(*RestoreChanBackupRequest_ChanBackups); ok {
return x.ChanBackups
}
return nil
}
func (m *RestoreChanBackupRequest) GetMultiChanBackup() []byte {
if x, ok := m.GetBackup().(*RestoreChanBackupRequest_MultiChanBackup); ok {
return x.MultiChanBackup
}
return nil
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*RestoreChanBackupRequest) 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 _RestoreChanBackupRequest_OneofMarshaler, _RestoreChanBackupRequest_OneofUnmarshaler, _RestoreChanBackupRequest_OneofSizer, []interface{}{
(*RestoreChanBackupRequest_ChanBackups)(nil),
(*RestoreChanBackupRequest_MultiChanBackup)(nil),
}
}
func _RestoreChanBackupRequest_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
m := msg.(*RestoreChanBackupRequest)
// backup
switch x := m.Backup.(type) {
case *RestoreChanBackupRequest_ChanBackups:
b.EncodeVarint(1<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.ChanBackups); err != nil {
return err
}
case *RestoreChanBackupRequest_MultiChanBackup:
b.EncodeVarint(2<<3 | proto.WireBytes)
b.EncodeRawBytes(x.MultiChanBackup)
case nil:
default:
return fmt.Errorf("RestoreChanBackupRequest.Backup has unexpected type %T", x)
}
return nil
}
func _RestoreChanBackupRequest_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
m := msg.(*RestoreChanBackupRequest)
switch tag {
case 1: // backup.chan_backups
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(ChannelBackups)
err := b.DecodeMessage(msg)
m.Backup = &RestoreChanBackupRequest_ChanBackups{msg}
return true, err
case 2: // backup.multi_chan_backup
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeRawBytes(true)
m.Backup = &RestoreChanBackupRequest_MultiChanBackup{x}
return true, err
default:
return false, nil
}
}
func _RestoreChanBackupRequest_OneofSizer(msg proto.Message) (n int) {
m := msg.(*RestoreChanBackupRequest)
// backup
switch x := m.Backup.(type) {
case *RestoreChanBackupRequest_ChanBackups:
s := proto.Size(x.ChanBackups)
n += 1 // tag and wire
n += proto.SizeVarint(uint64(s))
n += s
case *RestoreChanBackupRequest_MultiChanBackup:
n += 1 // tag and wire
n += proto.SizeVarint(uint64(len(x.MultiChanBackup)))
n += len(x.MultiChanBackup)
case nil:
default:
panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
}
return n
}
type RestoreBackupResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RestoreBackupResponse) Reset() { *m = RestoreBackupResponse{} }
func (m *RestoreBackupResponse) String() string { return proto.CompactTextString(m) }
func (*RestoreBackupResponse) ProtoMessage() {}
func (*RestoreBackupResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{124}
}
func (m *RestoreBackupResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RestoreBackupResponse.Unmarshal(m, b)
}
func (m *RestoreBackupResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RestoreBackupResponse.Marshal(b, m, deterministic)
}
func (dst *RestoreBackupResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_RestoreBackupResponse.Merge(dst, src)
}
func (m *RestoreBackupResponse) XXX_Size() int {
return xxx_messageInfo_RestoreBackupResponse.Size(m)
}
func (m *RestoreBackupResponse) XXX_DiscardUnknown() {
xxx_messageInfo_RestoreBackupResponse.DiscardUnknown(m)
}
var xxx_messageInfo_RestoreBackupResponse proto.InternalMessageInfo
type ChannelBackupSubscription struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ChannelBackupSubscription) Reset() { *m = ChannelBackupSubscription{} }
func (m *ChannelBackupSubscription) String() string { return proto.CompactTextString(m) }
func (*ChannelBackupSubscription) ProtoMessage() {}
func (*ChannelBackupSubscription) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{125}
}
func (m *ChannelBackupSubscription) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ChannelBackupSubscription.Unmarshal(m, b)
}
func (m *ChannelBackupSubscription) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ChannelBackupSubscription.Marshal(b, m, deterministic)
}
func (dst *ChannelBackupSubscription) XXX_Merge(src proto.Message) {
xxx_messageInfo_ChannelBackupSubscription.Merge(dst, src)
}
func (m *ChannelBackupSubscription) XXX_Size() int {
return xxx_messageInfo_ChannelBackupSubscription.Size(m)
}
func (m *ChannelBackupSubscription) XXX_DiscardUnknown() {
xxx_messageInfo_ChannelBackupSubscription.DiscardUnknown(m)
}
var xxx_messageInfo_ChannelBackupSubscription proto.InternalMessageInfo
type VerifyChanBackupResponse struct {
SinglesValid bool `protobuf:"varint,1,opt,name=singles_valid,proto3" json:"singles_valid,omitempty"`
MultiValid bool `protobuf:"varint,2,opt,name=multi_valid,proto3" json:"multi_valid,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *VerifyChanBackupResponse) Reset() { *m = VerifyChanBackupResponse{} }
func (m *VerifyChanBackupResponse) String() string { return proto.CompactTextString(m) }
func (*VerifyChanBackupResponse) ProtoMessage() {}
func (*VerifyChanBackupResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_rpc_189cdc2900530099, []int{126}
}
func (m *VerifyChanBackupResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_VerifyChanBackupResponse.Unmarshal(m, b)
}
func (m *VerifyChanBackupResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_VerifyChanBackupResponse.Marshal(b, m, deterministic)
}
func (dst *VerifyChanBackupResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_VerifyChanBackupResponse.Merge(dst, src)
}
func (m *VerifyChanBackupResponse) XXX_Size() int {
return xxx_messageInfo_VerifyChanBackupResponse.Size(m)
}
func (m *VerifyChanBackupResponse) XXX_DiscardUnknown() {
xxx_messageInfo_VerifyChanBackupResponse.DiscardUnknown(m)
}
var xxx_messageInfo_VerifyChanBackupResponse proto.InternalMessageInfo
func (m *VerifyChanBackupResponse) GetSinglesValid() bool {
if m != nil {
return m.SinglesValid
}
return false
}
func (m *VerifyChanBackupResponse) GetMultiValid() bool {
if m != nil {
return m.MultiValid
}
return false
}
func init() {
proto.RegisterType((*GenSeedRequest)(nil), "lnrpc.GenSeedRequest")
proto.RegisterType((*GenSeedResponse)(nil), "lnrpc.GenSeedResponse")
@ -8240,6 +8790,16 @@ func init() {
proto.RegisterType((*ForwardingHistoryRequest)(nil), "lnrpc.ForwardingHistoryRequest")
proto.RegisterType((*ForwardingEvent)(nil), "lnrpc.ForwardingEvent")
proto.RegisterType((*ForwardingHistoryResponse)(nil), "lnrpc.ForwardingHistoryResponse")
proto.RegisterType((*ExportChannelBackupRequest)(nil), "lnrpc.ExportChannelBackupRequest")
proto.RegisterType((*ChannelBackup)(nil), "lnrpc.ChannelBackup")
proto.RegisterType((*MultiChanBackup)(nil), "lnrpc.MultiChanBackup")
proto.RegisterType((*ChanBackupExportRequest)(nil), "lnrpc.ChanBackupExportRequest")
proto.RegisterType((*ChanBackupSnapshot)(nil), "lnrpc.ChanBackupSnapshot")
proto.RegisterType((*ChannelBackups)(nil), "lnrpc.ChannelBackups")
proto.RegisterType((*RestoreChanBackupRequest)(nil), "lnrpc.RestoreChanBackupRequest")
proto.RegisterType((*RestoreBackupResponse)(nil), "lnrpc.RestoreBackupResponse")
proto.RegisterType((*ChannelBackupSubscription)(nil), "lnrpc.ChannelBackupSubscription")
proto.RegisterType((*VerifyChanBackupResponse)(nil), "lnrpc.VerifyChanBackupResponse")
proto.RegisterEnum("lnrpc.AddressType", AddressType_name, AddressType_value)
proto.RegisterEnum("lnrpc.ChannelCloseSummary_ClosureType", ChannelCloseSummary_ClosureType_name, ChannelCloseSummary_ClosureType_value)
proto.RegisterEnum("lnrpc.ChannelEventUpdate_UpdateType", ChannelEventUpdate_UpdateType_name, ChannelEventUpdate_UpdateType_value)
@ -8716,7 +9276,7 @@ type LightningClient interface {
UpdateChannelPolicy(ctx context.Context, in *PolicyUpdateRequest, opts ...grpc.CallOption) (*PolicyUpdateResponse, error)
// * lncli: `fwdinghistory`
// ForwardingHistory allows the caller to query the htlcswitch for a record of
// all HTLC's forwarded within the target time range, and integer offset
// all HTLCs forwarded within the target time range, and integer offset
// within that time range. If no time-range is specified, then the first chunk
// of the past 24 hrs of forwarding history are returned.
//
@ -8726,6 +9286,42 @@ type LightningClient interface {
// the index offset of the last entry. The index offset can be provided to the
// request to allow the caller to skip a series of records.
ForwardingHistory(ctx context.Context, in *ForwardingHistoryRequest, opts ...grpc.CallOption) (*ForwardingHistoryResponse, error)
// * lncli: `exportchanbackup`
// ExportChannelBackup attempts to return an encrypted static channel backup
// for the target channel identified by it channel point. The backup is
// encrypted with a key generated from the aezeed seed of the user. The
// returned backup can either be restored using the RestoreChannelBackup
// method once lnd is running, or via the InitWallet and UnlockWallet methods
// from the WalletUnlocker service.
ExportChannelBackup(ctx context.Context, in *ExportChannelBackupRequest, opts ...grpc.CallOption) (*ChannelBackup, error)
// *
// ExportAllChannelBackups returns static channel backups for all existing
// channels known to lnd. A set of regular singular static channel backups for
// each channel are returned. Additionally, a multi-channel backup is returned
// as well, which contains a single encrypted blob containing the backups of
// each channel.
ExportAllChannelBackups(ctx context.Context, in *ChanBackupExportRequest, opts ...grpc.CallOption) (*ChanBackupSnapshot, error)
// *
// VerifyChanBackup allows a caller to verify the integrity of a channel
// backup snapshot. This method will accept both a packed Single, and also a
// Packed multi. Two bools are returned which indicate if the passed Single
// (if present) is valid and also if the passed Multi (if present) is valid.
VerifyChanBackup(ctx context.Context, in *ChanBackupSnapshot, opts ...grpc.CallOption) (*VerifyChanBackupResponse, error)
// * lncli: `restorechanbackup`
// RestoreChannelBackups accepts a set of singular channel backups, or a
// single encrypted multi-chan backup and attempts to recover any funds
// remaining within the channel. If we are able to unpack the backup, then the
// new channel will be shown under listchannels, as well as pending channels.
RestoreChannelBackups(ctx context.Context, in *RestoreChanBackupRequest, opts ...grpc.CallOption) (*RestoreBackupResponse, error)
// *
// SubscribeChannelBackups allows a client to sub-subscribe to the most up to
// date information concerning the state of all channel backups. Each time a
// new channel is added, we return the new set of channels, along with a
// multi-chan backup containing the backup info for all channels. Each time a
// channel is closed, we send a new update, which contains new new chan back
// ups, but the updated set of encrypted multi-chan backups with the closed
// channel(s) removed.
SubscribeChannelBackups(ctx context.Context, in *ChannelBackupSubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelBackupsClient, error)
}
type lightningClient struct {
@ -9323,6 +9919,74 @@ func (c *lightningClient) ForwardingHistory(ctx context.Context, in *ForwardingH
return out, nil
}
func (c *lightningClient) ExportChannelBackup(ctx context.Context, in *ExportChannelBackupRequest, opts ...grpc.CallOption) (*ChannelBackup, error) {
out := new(ChannelBackup)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ExportChannelBackup", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ExportAllChannelBackups(ctx context.Context, in *ChanBackupExportRequest, opts ...grpc.CallOption) (*ChanBackupSnapshot, error) {
out := new(ChanBackupSnapshot)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ExportAllChannelBackups", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) VerifyChanBackup(ctx context.Context, in *ChanBackupSnapshot, opts ...grpc.CallOption) (*VerifyChanBackupResponse, error) {
out := new(VerifyChanBackupResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/VerifyChanBackup", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) RestoreChannelBackups(ctx context.Context, in *RestoreChanBackupRequest, opts ...grpc.CallOption) (*RestoreBackupResponse, error) {
out := new(RestoreBackupResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/RestoreChannelBackups", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SubscribeChannelBackups(ctx context.Context, in *ChannelBackupSubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelBackupsClient, error) {
stream, err := c.cc.NewStream(ctx, &_Lightning_serviceDesc.Streams[8], "/lnrpc.Lightning/SubscribeChannelBackups", opts...)
if err != nil {
return nil, err
}
x := &lightningSubscribeChannelBackupsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_SubscribeChannelBackupsClient interface {
Recv() (*ChanBackupSnapshot, error)
grpc.ClientStream
}
type lightningSubscribeChannelBackupsClient struct {
grpc.ClientStream
}
func (x *lightningSubscribeChannelBackupsClient) Recv() (*ChanBackupSnapshot, error) {
m := new(ChanBackupSnapshot)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// LightningServer is the server API for Lightning service.
type LightningServer interface {
// * lncli: `walletbalance`
@ -9564,7 +10228,7 @@ type LightningServer interface {
UpdateChannelPolicy(context.Context, *PolicyUpdateRequest) (*PolicyUpdateResponse, error)
// * lncli: `fwdinghistory`
// ForwardingHistory allows the caller to query the htlcswitch for a record of
// all HTLC's forwarded within the target time range, and integer offset
// all HTLCs forwarded within the target time range, and integer offset
// within that time range. If no time-range is specified, then the first chunk
// of the past 24 hrs of forwarding history are returned.
//
@ -9574,6 +10238,42 @@ type LightningServer interface {
// the index offset of the last entry. The index offset can be provided to the
// request to allow the caller to skip a series of records.
ForwardingHistory(context.Context, *ForwardingHistoryRequest) (*ForwardingHistoryResponse, error)
// * lncli: `exportchanbackup`
// ExportChannelBackup attempts to return an encrypted static channel backup
// for the target channel identified by it channel point. The backup is
// encrypted with a key generated from the aezeed seed of the user. The
// returned backup can either be restored using the RestoreChannelBackup
// method once lnd is running, or via the InitWallet and UnlockWallet methods
// from the WalletUnlocker service.
ExportChannelBackup(context.Context, *ExportChannelBackupRequest) (*ChannelBackup, error)
// *
// ExportAllChannelBackups returns static channel backups for all existing
// channels known to lnd. A set of regular singular static channel backups for
// each channel are returned. Additionally, a multi-channel backup is returned
// as well, which contains a single encrypted blob containing the backups of
// each channel.
ExportAllChannelBackups(context.Context, *ChanBackupExportRequest) (*ChanBackupSnapshot, error)
// *
// VerifyChanBackup allows a caller to verify the integrity of a channel
// backup snapshot. This method will accept both a packed Single, and also a
// Packed multi. Two bools are returned which indicate if the passed Single
// (if present) is valid and also if the passed Multi (if present) is valid.
VerifyChanBackup(context.Context, *ChanBackupSnapshot) (*VerifyChanBackupResponse, error)
// * lncli: `restorechanbackup`
// RestoreChannelBackups accepts a set of singular channel backups, or a
// single encrypted multi-chan backup and attempts to recover any funds
// remaining within the channel. If we are able to unpack the backup, then the
// new channel will be shown under listchannels, as well as pending channels.
RestoreChannelBackups(context.Context, *RestoreChanBackupRequest) (*RestoreBackupResponse, error)
// *
// SubscribeChannelBackups allows a client to sub-subscribe to the most up to
// date information concerning the state of all channel backups. Each time a
// new channel is added, we return the new set of channels, along with a
// multi-chan backup containing the backup info for all channels. Each time a
// channel is closed, we send a new update, which contains new new chan back
// ups, but the updated set of encrypted multi-chan backups with the closed
// channel(s) removed.
SubscribeChannelBackups(*ChannelBackupSubscription, Lightning_SubscribeChannelBackupsServer) error
}
func RegisterLightningServer(s *grpc.Server, srv LightningServer) {
@ -10424,6 +11124,99 @@ func _Lightning_ForwardingHistory_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler)
}
func _Lightning_ExportChannelBackup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ExportChannelBackupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ExportChannelBackup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ExportChannelBackup",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ExportChannelBackup(ctx, req.(*ExportChannelBackupRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ExportAllChannelBackups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChanBackupExportRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ExportAllChannelBackups(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ExportAllChannelBackups",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ExportAllChannelBackups(ctx, req.(*ChanBackupExportRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_VerifyChanBackup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChanBackupSnapshot)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).VerifyChanBackup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/VerifyChanBackup",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).VerifyChanBackup(ctx, req.(*ChanBackupSnapshot))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_RestoreChannelBackups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RestoreChanBackupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).RestoreChannelBackups(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/RestoreChannelBackups",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).RestoreChannelBackups(ctx, req.(*RestoreChanBackupRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SubscribeChannelBackups_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(ChannelBackupSubscription)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).SubscribeChannelBackups(m, &lightningSubscribeChannelBackupsServer{stream})
}
type Lightning_SubscribeChannelBackupsServer interface {
Send(*ChanBackupSnapshot) error
grpc.ServerStream
}
type lightningSubscribeChannelBackupsServer struct {
grpc.ServerStream
}
func (x *lightningSubscribeChannelBackupsServer) Send(m *ChanBackupSnapshot) error {
return x.ServerStream.SendMsg(m)
}
var _Lightning_serviceDesc = grpc.ServiceDesc{
ServiceName: "lnrpc.Lightning",
HandlerType: (*LightningServer)(nil),
@ -10576,6 +11369,22 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{
MethodName: "ForwardingHistory",
Handler: _Lightning_ForwardingHistory_Handler,
},
{
MethodName: "ExportChannelBackup",
Handler: _Lightning_ExportChannelBackup_Handler,
},
{
MethodName: "ExportAllChannelBackups",
Handler: _Lightning_ExportAllChannelBackups_Handler,
},
{
MethodName: "VerifyChanBackup",
Handler: _Lightning_VerifyChanBackup_Handler,
},
{
MethodName: "RestoreChannelBackups",
Handler: _Lightning_RestoreChannelBackups_Handler,
},
},
Streams: []grpc.StreamDesc{
{
@ -10620,467 +11429,496 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{
Handler: _Lightning_SubscribeChannelGraph_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeChannelBackups",
Handler: _Lightning_SubscribeChannelBackups_Handler,
ServerStreams: true,
},
},
Metadata: "rpc.proto",
}
func init() { proto.RegisterFile("rpc.proto", fileDescriptor_rpc_72c11ad9c9dfa44d) }
func init() { proto.RegisterFile("rpc.proto", fileDescriptor_rpc_189cdc2900530099) }
var fileDescriptor_rpc_72c11ad9c9dfa44d = []byte{
// 7268 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7c, 0x5f, 0x6c, 0x24, 0xd9,
0x59, 0xef, 0x54, 0xff, 0xb1, 0xbb, 0xbf, 0x6e, 0xb7, 0xdb, 0xc7, 0xff, 0x7a, 0x7a, 0x67, 0x67,
0x67, 0x2b, 0x73, 0x77, 0x1c, 0xef, 0xde, 0xf1, 0xec, 0x24, 0xd9, 0x4c, 0x76, 0x6f, 0xee, 0xbd,
0x1e, 0xdb, 0x33, 0x9e, 0xc4, 0xeb, 0x71, 0xca, 0x33, 0x19, 0xb2, 0x09, 0xea, 0x94, 0xbb, 0x8f,
0xdb, 0xb5, 0x53, 0x5d, 0xd5, 0xa9, 0xaa, 0xb6, 0xc7, 0x59, 0x46, 0x42, 0x08, 0x81, 0x84, 0x40,
0x08, 0x10, 0x12, 0x41, 0x41, 0x48, 0x01, 0x09, 0xc2, 0x1b, 0x0f, 0x41, 0x48, 0x10, 0x9e, 0x90,
0x90, 0x90, 0x10, 0x42, 0x79, 0x44, 0x42, 0x42, 0xf0, 0x02, 0x3c, 0x20, 0x90, 0x78, 0x44, 0x42,
0xe7, 0x3b, 0x7f, 0xea, 0x9c, 0xaa, 0xea, 0xf1, 0x6c, 0x12, 0x78, 0xb2, 0xcf, 0xef, 0x7c, 0x75,
0xfe, 0x7e, 0xff, 0xce, 0x77, 0xbe, 0xd3, 0x50, 0x8f, 0xc6, 0xfd, 0x9b, 0xe3, 0x28, 0x4c, 0x42,
0x52, 0xf5, 0x83, 0x68, 0xdc, 0xef, 0x5e, 0x19, 0x86, 0xe1, 0xd0, 0xa7, 0x1b, 0xee, 0xd8, 0xdb,
0x70, 0x83, 0x20, 0x4c, 0xdc, 0xc4, 0x0b, 0x83, 0x98, 0x13, 0xd9, 0x5f, 0x87, 0xd6, 0x7d, 0x1a,
0x1c, 0x52, 0x3a, 0x70, 0xe8, 0x37, 0x26, 0x34, 0x4e, 0xc8, 0x9b, 0xb0, 0xe0, 0xd2, 0x6f, 0x52,
0x3a, 0xe8, 0x8d, 0xdd, 0x38, 0x1e, 0x9f, 0x44, 0x6e, 0x4c, 0x3b, 0xd6, 0x35, 0x6b, 0xad, 0xe9,
0xb4, 0x79, 0xc5, 0x81, 0xc2, 0xc9, 0xeb, 0xd0, 0x8c, 0x19, 0x29, 0x0d, 0x92, 0x28, 0x1c, 0x9f,
0x77, 0x4a, 0x48, 0xd7, 0x60, 0xd8, 0x0e, 0x87, 0x6c, 0x1f, 0xe6, 0x55, 0x0f, 0xf1, 0x38, 0x0c,
0x62, 0x4a, 0x6e, 0xc1, 0x52, 0xdf, 0x1b, 0x9f, 0xd0, 0xa8, 0x87, 0x1f, 0x8f, 0x02, 0x3a, 0x0a,
0x03, 0xaf, 0xdf, 0xb1, 0xae, 0x95, 0xd7, 0xea, 0x0e, 0xe1, 0x75, 0xec, 0x8b, 0xf7, 0x45, 0x0d,
0xb9, 0x01, 0xf3, 0x34, 0xe0, 0x38, 0x1d, 0xe0, 0x57, 0xa2, 0xab, 0x56, 0x0a, 0xb3, 0x0f, 0xec,
0x3f, 0xb7, 0x60, 0xe1, 0x41, 0xe0, 0x25, 0x4f, 0x5c, 0xdf, 0xa7, 0x89, 0x9c, 0xd3, 0x0d, 0x98,
0x3f, 0x43, 0x00, 0xe7, 0x74, 0x16, 0x46, 0x03, 0x31, 0xa3, 0x16, 0x87, 0x0f, 0x04, 0x3a, 0x75,
0x64, 0xa5, 0xa9, 0x23, 0x2b, 0x5c, 0xae, 0xf2, 0x94, 0xe5, 0xba, 0x01, 0xf3, 0x11, 0xed, 0x87,
0xa7, 0x34, 0x3a, 0xef, 0x9d, 0x79, 0xc1, 0x20, 0x3c, 0xeb, 0x54, 0xae, 0x59, 0x6b, 0x55, 0xa7,
0x25, 0xe1, 0x27, 0x88, 0xda, 0x4b, 0x40, 0xf4, 0x59, 0xf0, 0x75, 0xb3, 0x87, 0xb0, 0xf8, 0x38,
0xf0, 0xc3, 0xfe, 0xd3, 0x1f, 0x72, 0x76, 0x05, 0xdd, 0x97, 0x0a, 0xbb, 0x5f, 0x81, 0x25, 0xb3,
0x23, 0x31, 0x00, 0x0a, 0xcb, 0x5b, 0x27, 0x6e, 0x30, 0xa4, 0xb2, 0x49, 0x39, 0x84, 0x4f, 0x42,
0xbb, 0x3f, 0x89, 0x22, 0x1a, 0xe4, 0xc6, 0x30, 0x2f, 0x70, 0x35, 0x88, 0xd7, 0xa1, 0x19, 0xd0,
0xb3, 0x94, 0x4c, 0xb0, 0x4c, 0x40, 0xcf, 0x24, 0x89, 0xdd, 0x81, 0x95, 0x6c, 0x37, 0x62, 0x00,
0x7f, 0x6f, 0x41, 0xe5, 0x71, 0xf2, 0x2c, 0x24, 0x37, 0xa1, 0x92, 0x9c, 0x8f, 0x39, 0x63, 0xb6,
0x6e, 0x93, 0x9b, 0xc8, 0xeb, 0x37, 0x37, 0x07, 0x83, 0x88, 0xc6, 0xf1, 0xa3, 0xf3, 0x31, 0x75,
0x9a, 0x2e, 0x2f, 0xf4, 0x18, 0x1d, 0xe9, 0xc0, 0xac, 0x28, 0x63, 0x87, 0x75, 0x47, 0x16, 0xc9,
0x55, 0x00, 0x77, 0x14, 0x4e, 0x82, 0xa4, 0x17, 0xbb, 0x09, 0xee, 0x5c, 0xd9, 0xd1, 0x10, 0x72,
0x05, 0xea, 0xe3, 0xa7, 0xbd, 0xb8, 0x1f, 0x79, 0xe3, 0x04, 0x77, 0xab, 0xee, 0xa4, 0x00, 0x79,
0x13, 0x6a, 0xe1, 0x24, 0x19, 0x87, 0x5e, 0x90, 0x74, 0xaa, 0xd7, 0xac, 0xb5, 0xc6, 0xed, 0x79,
0x31, 0x96, 0x87, 0x93, 0xe4, 0x80, 0xc1, 0x8e, 0x22, 0x20, 0xd7, 0x61, 0xae, 0x1f, 0x06, 0xc7,
0x5e, 0x34, 0xe2, 0x32, 0xd8, 0x99, 0xc1, 0xde, 0x4c, 0xd0, 0xfe, 0x56, 0x09, 0x1a, 0x8f, 0x22,
0x37, 0x88, 0xdd, 0x3e, 0x03, 0xd8, 0xd0, 0x93, 0x67, 0xbd, 0x13, 0x37, 0x3e, 0xc1, 0xd9, 0xd6,
0x1d, 0x59, 0x24, 0x2b, 0x30, 0xc3, 0x07, 0x8a, 0x73, 0x2a, 0x3b, 0xa2, 0x44, 0xde, 0x82, 0x85,
0x60, 0x32, 0xea, 0x99, 0x7d, 0x95, 0x71, 0xa7, 0xf3, 0x15, 0x6c, 0x01, 0x8e, 0xd8, 0x5e, 0xf3,
0x2e, 0xf8, 0x0c, 0x35, 0x84, 0xd8, 0xd0, 0x14, 0x25, 0xea, 0x0d, 0x4f, 0xf8, 0x34, 0xab, 0x8e,
0x81, 0xb1, 0x36, 0x12, 0x6f, 0x44, 0x7b, 0x71, 0xe2, 0x8e, 0xc6, 0x62, 0x5a, 0x1a, 0x82, 0xf5,
0x61, 0xe2, 0xfa, 0xbd, 0x63, 0x4a, 0xe3, 0xce, 0xac, 0xa8, 0x57, 0x08, 0x79, 0x03, 0x5a, 0x03,
0x1a, 0x27, 0x3d, 0xb1, 0x29, 0x34, 0xee, 0xd4, 0x50, 0xe2, 0x32, 0x28, 0xe3, 0x8c, 0xfb, 0x34,
0xd1, 0x56, 0x27, 0x16, 0x1c, 0x68, 0xef, 0x01, 0xd1, 0xe0, 0x6d, 0x9a, 0xb8, 0x9e, 0x1f, 0x93,
0x77, 0xa0, 0x99, 0x68, 0xc4, 0xa8, 0x61, 0x1a, 0x8a, 0x5d, 0xb4, 0x0f, 0x1c, 0x83, 0xce, 0xbe,
0x0f, 0xb5, 0x7b, 0x94, 0xee, 0x79, 0x23, 0x2f, 0x21, 0x2b, 0x50, 0x3d, 0xf6, 0x9e, 0x51, 0xce,
0xd0, 0xe5, 0xdd, 0x4b, 0x0e, 0x2f, 0x92, 0x2e, 0xcc, 0x8e, 0x69, 0xd4, 0xa7, 0x72, 0xf9, 0x77,
0x2f, 0x39, 0x12, 0xb8, 0x3b, 0x0b, 0x55, 0x9f, 0x7d, 0x6c, 0xff, 0x5b, 0x09, 0x1a, 0x87, 0x34,
0x50, 0x82, 0x42, 0xa0, 0xc2, 0xa6, 0x24, 0x84, 0x03, 0xff, 0x27, 0xaf, 0x41, 0x03, 0xa7, 0x19,
0x27, 0x91, 0x17, 0x0c, 0x05, 0x7f, 0x02, 0x83, 0x0e, 0x11, 0x21, 0x6d, 0x28, 0xbb, 0x23, 0xc9,
0x9b, 0xec, 0x5f, 0x26, 0x44, 0x63, 0xf7, 0x7c, 0xc4, 0xe4, 0x4d, 0xed, 0x5a, 0xd3, 0x69, 0x08,
0x6c, 0x97, 0x6d, 0xdb, 0x4d, 0x58, 0xd4, 0x49, 0x64, 0xeb, 0x55, 0x6c, 0x7d, 0x41, 0xa3, 0x14,
0x9d, 0xdc, 0x80, 0x79, 0x49, 0x1f, 0xf1, 0xc1, 0xe2, 0x3e, 0xd6, 0x9d, 0x96, 0x80, 0xe5, 0x14,
0xd6, 0xa0, 0x7d, 0xec, 0x05, 0xae, 0xdf, 0xeb, 0xfb, 0xc9, 0x69, 0x6f, 0x40, 0xfd, 0xc4, 0xc5,
0x1d, 0xad, 0x3a, 0x2d, 0xc4, 0xb7, 0xfc, 0xe4, 0x74, 0x9b, 0xa1, 0xe4, 0x2d, 0xa8, 0x1f, 0x53,
0xda, 0xc3, 0x95, 0xe8, 0xd4, 0x0c, 0xe9, 0x90, 0xab, 0xeb, 0xd4, 0x8e, 0xe5, 0x3a, 0xaf, 0x41,
0x3b, 0x9c, 0x24, 0xc3, 0xd0, 0x0b, 0x86, 0xbd, 0xfe, 0x89, 0x1b, 0xf4, 0xbc, 0x41, 0xa7, 0x7e,
0xcd, 0x5a, 0xab, 0x38, 0x2d, 0x89, 0x33, 0xad, 0xf0, 0x60, 0x40, 0x5e, 0x05, 0xc0, 0xbe, 0x79,
0xc3, 0x70, 0xcd, 0x5a, 0x9b, 0x73, 0xea, 0x0c, 0xc1, 0x86, 0xec, 0x3f, 0xb6, 0xa0, 0xc9, 0xd7,
0x5c, 0xd8, 0x9b, 0xeb, 0x30, 0x27, 0xa7, 0x46, 0xa3, 0x28, 0x8c, 0x84, 0x1c, 0x99, 0x20, 0x59,
0x87, 0xb6, 0x04, 0xc6, 0x11, 0xf5, 0x46, 0xee, 0x90, 0x0a, 0xe5, 0x94, 0xc3, 0xc9, 0xed, 0xb4,
0xc5, 0x28, 0x9c, 0x24, 0x5c, 0xe3, 0x37, 0x6e, 0x37, 0xc5, 0xec, 0x1c, 0x86, 0x39, 0x26, 0x09,
0x93, 0xa3, 0x82, 0x3d, 0x33, 0x30, 0xfb, 0x7b, 0x16, 0x10, 0x36, 0xf4, 0x47, 0x21, 0x6f, 0x42,
0x2c, 0x79, 0x76, 0xbb, 0xad, 0x97, 0xde, 0xee, 0xd2, 0xb4, 0xed, 0x5e, 0x83, 0x19, 0x1c, 0x16,
0x53, 0x0c, 0xe5, 0xec, 0xd0, 0xef, 0x96, 0x3a, 0x96, 0x23, 0xea, 0x89, 0x0d, 0x55, 0x3e, 0xc7,
0x4a, 0xc1, 0x1c, 0x79, 0x95, 0xfd, 0x1d, 0x0b, 0x9a, 0x6c, 0x73, 0x02, 0xea, 0xa3, 0xd2, 0x23,
0xb7, 0x80, 0x1c, 0x4f, 0x82, 0x01, 0xdb, 0xcb, 0xe4, 0x99, 0x37, 0xe8, 0x1d, 0x9d, 0xb3, 0xae,
0x70, 0xdc, 0xbb, 0x97, 0x9c, 0x82, 0x3a, 0xf2, 0x16, 0xb4, 0x0d, 0x34, 0x4e, 0x22, 0x3e, 0xfa,
0xdd, 0x4b, 0x4e, 0xae, 0x86, 0x2d, 0x26, 0x53, 0xab, 0x93, 0xa4, 0xe7, 0x05, 0x03, 0xfa, 0x0c,
0xd7, 0x7f, 0xce, 0x31, 0xb0, 0xbb, 0x2d, 0x68, 0xea, 0xdf, 0xd9, 0x1f, 0x42, 0x4d, 0x2a, 0x65,
0x54, 0x48, 0x99, 0x71, 0x39, 0x1a, 0x42, 0xba, 0x50, 0x33, 0x47, 0xe1, 0xd4, 0x3e, 0x4e, 0xdf,
0xf6, 0xff, 0x85, 0xf6, 0x1e, 0xd3, 0x8c, 0x81, 0x17, 0x0c, 0x85, 0x55, 0x62, 0xea, 0x7a, 0x3c,
0x39, 0x7a, 0x4a, 0xcf, 0x05, 0xff, 0x89, 0x12, 0xd3, 0x09, 0x27, 0x61, 0x9c, 0x88, 0x7e, 0xf0,
0x7f, 0xfb, 0x2f, 0x2c, 0x20, 0x3b, 0x71, 0xe2, 0x8d, 0xdc, 0x84, 0xde, 0xa3, 0x8a, 0x11, 0x1e,
0x42, 0x93, 0xb5, 0xf6, 0x28, 0xdc, 0xe4, 0x7a, 0x9f, 0xeb, 0xb3, 0x37, 0xc5, 0x96, 0xe4, 0x3f,
0xb8, 0xa9, 0x53, 0x33, 0x8f, 0xec, 0xdc, 0x31, 0x1a, 0x60, 0xba, 0x27, 0x71, 0xa3, 0x21, 0x4d,
0xd0, 0x28, 0x08, 0x77, 0x00, 0x38, 0xb4, 0x15, 0x06, 0xc7, 0xdd, 0xff, 0x07, 0x0b, 0xb9, 0x36,
0x98, 0x42, 0x4a, 0xa7, 0xc1, 0xfe, 0x25, 0x4b, 0x50, 0x3d, 0x75, 0xfd, 0x09, 0x15, 0x96, 0x88,
0x17, 0xde, 0x2d, 0xdd, 0xb1, 0xec, 0x3e, 0x2c, 0x1a, 0xe3, 0x12, 0x32, 0xd9, 0x81, 0x59, 0xa6,
0x1b, 0x98, 0xcd, 0x45, 0xbd, 0xea, 0xc8, 0x22, 0xb9, 0x0d, 0x4b, 0xc7, 0x94, 0x46, 0x6e, 0x82,
0xc5, 0xde, 0x98, 0x46, 0xb8, 0x27, 0xa2, 0xe5, 0xc2, 0x3a, 0xfb, 0x1f, 0x2c, 0x98, 0x67, 0x72,
0xf3, 0xbe, 0x1b, 0x9c, 0xcb, 0xb5, 0xda, 0x2b, 0x5c, 0xab, 0x35, 0xb1, 0x56, 0x19, 0xea, 0x8f,
0xbb, 0x50, 0xe5, 0xec, 0x42, 0x91, 0x6b, 0xd0, 0x34, 0x86, 0x5b, 0xe5, 0x46, 0x2e, 0x76, 0x93,
0x03, 0x1a, 0xdd, 0x3d, 0x4f, 0xe8, 0x8f, 0xbe, 0x94, 0x6f, 0x40, 0x3b, 0x1d, 0xb6, 0x58, 0x47,
0x02, 0x15, 0xc6, 0x98, 0xa2, 0x01, 0xfc, 0xdf, 0xfe, 0xb6, 0xc5, 0x09, 0xb7, 0x42, 0x4f, 0x19,
0x48, 0x46, 0xc8, 0xec, 0xa8, 0x24, 0x64, 0xff, 0x4f, 0x75, 0x20, 0x7e, 0xf4, 0xc9, 0x92, 0xcb,
0x50, 0x8b, 0x69, 0x30, 0xe8, 0xb9, 0xbe, 0x8f, 0x76, 0xa4, 0xe6, 0xcc, 0xb2, 0xf2, 0xa6, 0xef,
0xdb, 0x37, 0x60, 0x41, 0x1b, 0xdd, 0x0b, 0xe6, 0xb1, 0x0f, 0x64, 0xcf, 0x8b, 0x93, 0xc7, 0x41,
0x3c, 0xd6, 0xec, 0xcf, 0x2b, 0x50, 0x1f, 0x79, 0x01, 0x8e, 0x8c, 0x4b, 0x6e, 0xd5, 0xa9, 0x8d,
0xbc, 0x80, 0x8d, 0x2b, 0xc6, 0x4a, 0xf7, 0x99, 0xa8, 0x2c, 0x89, 0x4a, 0xf7, 0x19, 0x56, 0xda,
0x77, 0x60, 0xd1, 0x68, 0x4f, 0x74, 0xfd, 0x3a, 0x54, 0x27, 0xc9, 0xb3, 0x50, 0x7a, 0x07, 0x0d,
0xc1, 0x21, 0xcc, 0xcf, 0x74, 0x78, 0x8d, 0xfd, 0x1e, 0x2c, 0xec, 0xd3, 0x33, 0x21, 0xc8, 0x72,
0x20, 0x6f, 0x5c, 0xe8, 0x83, 0x62, 0xbd, 0x7d, 0x13, 0x88, 0xfe, 0x71, 0x2a, 0x00, 0xd2, 0x23,
0xb5, 0x0c, 0x8f, 0xd4, 0x7e, 0x03, 0xc8, 0xa1, 0x37, 0x0c, 0xde, 0xa7, 0x71, 0xec, 0x0e, 0x95,
0xe8, 0xb7, 0xa1, 0x3c, 0x8a, 0x87, 0x42, 0x55, 0xb1, 0x7f, 0xed, 0x4f, 0xc1, 0xa2, 0x41, 0x27,
0x1a, 0xbe, 0x02, 0xf5, 0xd8, 0x1b, 0x06, 0x6e, 0x32, 0x89, 0xa8, 0x68, 0x3a, 0x05, 0xec, 0x7b,
0xb0, 0xf4, 0x65, 0x1a, 0x79, 0xc7, 0xe7, 0x17, 0x35, 0x6f, 0xb6, 0x53, 0xca, 0xb6, 0xb3, 0x03,
0xcb, 0x99, 0x76, 0x44, 0xf7, 0x9c, 0x7d, 0xc5, 0x4e, 0xd6, 0x1c, 0x5e, 0xd0, 0x74, 0x5f, 0x49,
0xd7, 0x7d, 0xf6, 0x63, 0x20, 0x5b, 0x61, 0x10, 0xd0, 0x7e, 0x72, 0x40, 0x69, 0x94, 0x9e, 0x41,
0x53, 0x5e, 0x6d, 0xdc, 0x5e, 0x15, 0x2b, 0x9b, 0x55, 0xa8, 0x82, 0x89, 0x09, 0x54, 0xc6, 0x34,
0x1a, 0x61, 0xc3, 0x35, 0x07, 0xff, 0xb7, 0x97, 0x61, 0xd1, 0x68, 0x56, 0x1c, 0x1f, 0xde, 0x86,
0xe5, 0x6d, 0x2f, 0xee, 0xe7, 0x3b, 0xec, 0xc0, 0xec, 0x78, 0x72, 0xd4, 0x4b, 0x25, 0x51, 0x16,
0x99, 0xc7, 0x99, 0xfd, 0x44, 0x34, 0xf6, 0x73, 0x16, 0x54, 0x76, 0x1f, 0xed, 0x6d, 0x31, 0x5b,
0xe1, 0x05, 0xfd, 0x70, 0xc4, 0xec, 0x2d, 0x9f, 0xb4, 0x2a, 0x4f, 0x95, 0xb0, 0x2b, 0x50, 0x47,
0x33, 0xcd, 0x9c, 0x68, 0x71, 0x5c, 0x4c, 0x01, 0xe6, 0xc0, 0xd3, 0x67, 0x63, 0x2f, 0x42, 0x0f,
0x5d, 0xfa, 0xdd, 0x15, 0x34, 0x33, 0xf9, 0x0a, 0xfb, 0xdb, 0x55, 0x98, 0x15, 0xc6, 0x17, 0xfb,
0xeb, 0x27, 0xde, 0x29, 0x15, 0x23, 0x11, 0x25, 0xe6, 0x02, 0x45, 0x74, 0x14, 0x26, 0xb4, 0x67,
0x6c, 0x83, 0x09, 0xe2, 0x01, 0x85, 0x37, 0xd4, 0xe3, 0x47, 0x9a, 0x32, 0xa7, 0x32, 0x40, 0xb6,
0x58, 0xd2, 0x3f, 0xab, 0xa0, 0x7f, 0x26, 0x8b, 0x6c, 0x25, 0xfa, 0xee, 0xd8, 0xed, 0x7b, 0xc9,
0xb9, 0x50, 0x09, 0xaa, 0xcc, 0xda, 0xf6, 0xc3, 0xbe, 0xeb, 0xf7, 0x8e, 0x5c, 0xdf, 0x0d, 0xfa,
0x54, 0x1e, 0x7e, 0x0c, 0x90, 0x1d, 0x04, 0xc4, 0x90, 0x24, 0x19, 0x3f, 0x2c, 0x64, 0x50, 0x66,
0xbf, 0xfb, 0xe1, 0x68, 0xe4, 0x25, 0xec, 0xfc, 0x80, 0xbe, 0x65, 0xd9, 0xd1, 0x10, 0x7e, 0xd4,
0xc2, 0xd2, 0x19, 0x5f, 0xbd, 0xba, 0x3c, 0x6a, 0x69, 0x20, 0x6b, 0x85, 0x59, 0x1d, 0xa6, 0xc6,
0x9e, 0x9e, 0xa1, 0x23, 0x59, 0x76, 0x34, 0x84, 0xed, 0xc3, 0x24, 0x88, 0x69, 0x92, 0xf8, 0x74,
0xa0, 0x06, 0xd4, 0x40, 0xb2, 0x7c, 0x05, 0xb9, 0x05, 0x8b, 0xfc, 0x48, 0x13, 0xbb, 0x49, 0x18,
0x9f, 0x78, 0x71, 0x2f, 0x66, 0x87, 0x83, 0x26, 0xd2, 0x17, 0x55, 0x91, 0x3b, 0xb0, 0x9a, 0x81,
0x23, 0xda, 0xa7, 0xde, 0x29, 0x1d, 0x74, 0xe6, 0xf0, 0xab, 0x69, 0xd5, 0xe4, 0x1a, 0x34, 0xd8,
0x49, 0x6e, 0x32, 0x1e, 0xb8, 0xcc, 0x81, 0x69, 0xe1, 0x3e, 0xe8, 0x10, 0x79, 0x1b, 0xe6, 0xc6,
0x94, 0x7b, 0x3f, 0x27, 0x89, 0xdf, 0x8f, 0x3b, 0xf3, 0x86, 0x76, 0x63, 0x9c, 0xeb, 0x98, 0x14,
0x8c, 0x29, 0xfb, 0x31, 0xba, 0xf4, 0xee, 0x79, 0xa7, 0x2d, 0xdc, 0x6a, 0x09, 0xa0, 0x8c, 0x44,
0xde, 0xa9, 0x9b, 0xd0, 0xce, 0x02, 0x57, 0xe8, 0xa2, 0xc8, 0xbe, 0xf3, 0x02, 0x2f, 0xf1, 0xdc,
0x24, 0x8c, 0x3a, 0x04, 0xeb, 0x52, 0xc0, 0xfe, 0x6d, 0x8b, 0xab, 0x5d, 0xc1, 0xa2, 0x4a, 0x7d,
0xbe, 0x06, 0x0d, 0xce, 0x9c, 0xbd, 0x30, 0xf0, 0xcf, 0x05, 0xbf, 0x02, 0x87, 0x1e, 0x06, 0xfe,
0x39, 0xf9, 0x04, 0xcc, 0x79, 0x81, 0x4e, 0xc2, 0x25, 0xbc, 0x29, 0x41, 0x24, 0x7a, 0x0d, 0x1a,
0xe3, 0xc9, 0x91, 0xef, 0xf5, 0x39, 0x49, 0x99, 0xb7, 0xc2, 0x21, 0x24, 0x60, 0xbe, 0x33, 0x1f,
0x27, 0xa7, 0xa8, 0x20, 0x45, 0x43, 0x60, 0x8c, 0xc4, 0xbe, 0x0b, 0x4b, 0xe6, 0x00, 0x85, 0x2a,
0x5b, 0x87, 0x9a, 0xe0, 0xfc, 0xb8, 0xd3, 0xc0, 0xd5, 0x6b, 0x89, 0xd5, 0x13, 0xa4, 0x8e, 0xaa,
0xb7, 0xff, 0xa8, 0x02, 0x8b, 0x02, 0xdd, 0xf2, 0xc3, 0x98, 0x1e, 0x4e, 0x46, 0x23, 0x37, 0x2a,
0x10, 0x29, 0xeb, 0x02, 0x91, 0x2a, 0x99, 0x22, 0xc5, 0x18, 0xfd, 0xc4, 0xf5, 0x02, 0xee, 0xf8,
0x73, 0x79, 0xd4, 0x10, 0xb2, 0x06, 0xf3, 0x7d, 0x3f, 0x8c, 0xb9, 0x93, 0xab, 0x1f, 0xe1, 0xb3,
0x70, 0x5e, 0x05, 0x54, 0x8b, 0x54, 0x80, 0x2e, 0xc2, 0x33, 0x19, 0x11, 0xb6, 0xa1, 0xc9, 0x1a,
0xa5, 0x52, 0x23, 0xcd, 0x72, 0xc7, 0x57, 0xc7, 0xd8, 0x78, 0xb2, 0x02, 0xc3, 0xa5, 0x73, 0xbe,
0x48, 0x5c, 0xbc, 0x11, 0x45, 0x8d, 0xa7, 0x51, 0xd7, 0x85, 0xb8, 0xe4, 0xab, 0xc8, 0x3d, 0x76,
0xee, 0x63, 0x7d, 0xa1, 0xd9, 0x05, 0x34, 0xbb, 0x6f, 0x98, 0x3b, 0xa2, 0xaf, 0xfd, 0x4d, 0x56,
0x98, 0x44, 0x14, 0x4d, 0xb1, 0xf6, 0xa5, 0xfd, 0x0b, 0x16, 0x34, 0xb4, 0x3a, 0xb2, 0x0c, 0x0b,
0x5b, 0x0f, 0x1f, 0x1e, 0xec, 0x38, 0x9b, 0x8f, 0x1e, 0x7c, 0x79, 0xa7, 0xb7, 0xb5, 0xf7, 0xf0,
0x70, 0xa7, 0x7d, 0x89, 0xc1, 0x7b, 0x0f, 0xb7, 0x36, 0xf7, 0x7a, 0xf7, 0x1e, 0x3a, 0x5b, 0x12,
0xb6, 0xc8, 0x0a, 0x10, 0x67, 0xe7, 0xfd, 0x87, 0x8f, 0x76, 0x0c, 0xbc, 0x44, 0xda, 0xd0, 0xbc,
0xeb, 0xec, 0x6c, 0x6e, 0xed, 0x0a, 0xa4, 0x4c, 0x96, 0xa0, 0x7d, 0xef, 0xf1, 0xfe, 0xf6, 0x83,
0xfd, 0xfb, 0xbd, 0xad, 0xcd, 0xfd, 0xad, 0x9d, 0xbd, 0x9d, 0xed, 0x76, 0x85, 0xcc, 0x41, 0x7d,
0xf3, 0xee, 0xe6, 0xfe, 0xf6, 0xc3, 0xfd, 0x9d, 0xed, 0x76, 0xd5, 0xfe, 0x3b, 0x0b, 0x96, 0x71,
0xd4, 0x83, 0xac, 0x80, 0x5c, 0x83, 0x46, 0x3f, 0x0c, 0xc7, 0xcc, 0xdd, 0x4d, 0x15, 0xba, 0x0e,
0x31, 0xe6, 0xe7, 0xea, 0xf3, 0x38, 0x8c, 0xfa, 0x54, 0xc8, 0x07, 0x20, 0x74, 0x8f, 0x21, 0x8c,
0xf9, 0xc5, 0xf6, 0x72, 0x0a, 0x2e, 0x1e, 0x0d, 0x8e, 0x71, 0x92, 0x15, 0x98, 0x39, 0x8a, 0xa8,
0xdb, 0x3f, 0x11, 0x92, 0x21, 0x4a, 0xe4, 0x93, 0xe9, 0x79, 0xac, 0xcf, 0x56, 0xdf, 0xa7, 0x03,
0xe4, 0x98, 0x9a, 0x33, 0x2f, 0xf0, 0x2d, 0x01, 0x33, 0xf9, 0x77, 0x8f, 0xdc, 0x60, 0x10, 0x06,
0x74, 0x20, 0x9c, 0xbd, 0x14, 0xb0, 0x0f, 0x60, 0x25, 0x3b, 0x3f, 0x21, 0x5f, 0xef, 0x68, 0xf2,
0xc5, 0x7d, 0xaf, 0xee, 0xf4, 0xdd, 0xd4, 0x64, 0xed, 0x9f, 0x2d, 0xa8, 0x30, 0x53, 0x3c, 0xdd,
0x6c, 0xeb, 0xde, 0x55, 0x39, 0x17, 0xef, 0xc3, 0x23, 0x1e, 0x57, 0xce, 0xdc, 0x80, 0x69, 0x48,
0x5a, 0x1f, 0xd1, 0xfe, 0x29, 0xce, 0x58, 0xd5, 0x33, 0x84, 0x09, 0x08, 0x73, 0x7d, 0xf1, 0x6b,
0x21, 0x20, 0xb2, 0x2c, 0xeb, 0xf0, 0xcb, 0xd9, 0xb4, 0x0e, 0xbf, 0xeb, 0xc0, 0xac, 0x17, 0x1c,
0x85, 0x93, 0x60, 0x80, 0x02, 0x51, 0x73, 0x64, 0x11, 0x23, 0x8c, 0x28, 0xa8, 0xde, 0x48, 0xb2,
0x7f, 0x0a, 0xd8, 0x84, 0x9d, 0x24, 0x63, 0x74, 0x3d, 0x54, 0xb0, 0xeb, 0x1d, 0x58, 0xd0, 0xb0,
0xd4, 0x8d, 0x1d, 0x33, 0x20, 0xe3, 0xc6, 0xa2, 0xcf, 0xc2, 0x6b, 0xec, 0x36, 0xb4, 0xee, 0xd3,
0xe4, 0x41, 0x70, 0x1c, 0xca, 0x96, 0x7e, 0xaf, 0x02, 0xf3, 0x0a, 0x12, 0x0d, 0xad, 0xc1, 0xbc,
0x37, 0xa0, 0x41, 0xe2, 0x25, 0xe7, 0x3d, 0xe3, 0xc0, 0x9a, 0x85, 0x99, 0xaf, 0xe7, 0xfa, 0x9e,
0x2b, 0x63, 0xaa, 0xbc, 0xc0, 0x0e, 0x70, 0xcc, 0x10, 0x49, 0xdb, 0xa2, 0xb6, 0x98, 0x9f, 0x93,
0x0b, 0xeb, 0x98, 0x32, 0x60, 0xb8, 0xd0, 0xf6, 0xea, 0x13, 0xee, 0xf3, 0x14, 0x55, 0xb1, 0x55,
0xe3, 0x2d, 0xb1, 0x29, 0x57, 0xb9, 0xb1, 0x52, 0x40, 0x2e, 0x68, 0x39, 0xc3, 0x55, 0x55, 0x36,
0x68, 0xa9, 0x05, 0x3e, 0x6b, 0xb9, 0xc0, 0x27, 0x53, 0x65, 0xe7, 0x41, 0x9f, 0x0e, 0x7a, 0x49,
0xd8, 0x43, 0x95, 0x8b, 0xbb, 0x53, 0x73, 0xb2, 0x30, 0xb9, 0x02, 0xb3, 0x09, 0x8d, 0x93, 0x80,
0xf2, 0x68, 0x54, 0x0d, 0xe3, 0x27, 0x12, 0x62, 0x0e, 0xea, 0x24, 0xf2, 0xe2, 0x4e, 0x13, 0x43,
0x9a, 0xf8, 0x3f, 0xf9, 0x34, 0x2c, 0x1f, 0xd1, 0x38, 0xe9, 0x9d, 0x50, 0x77, 0x40, 0x23, 0xdc,
0x69, 0x1e, 0x3b, 0xe5, 0x76, 0xbf, 0xb8, 0x92, 0xf1, 0xd0, 0x29, 0x8d, 0x62, 0x2f, 0x0c, 0xd0,
0xe2, 0xd7, 0x1d, 0x59, 0x64, 0xed, 0xb1, 0xc9, 0x2b, 0x7b, 0xa9, 0x56, 0x70, 0x1e, 0x27, 0x5e,
0x5c, 0x49, 0xae, 0xc3, 0x0c, 0x4e, 0x20, 0xee, 0xb4, 0x8d, 0x20, 0xd0, 0x16, 0x03, 0x1d, 0x51,
0xf7, 0x85, 0x4a, 0xad, 0xd1, 0x6e, 0xda, 0x9f, 0x85, 0x2a, 0xc2, 0x6c, 0xd3, 0xf9, 0x62, 0x70,
0xa6, 0xe0, 0x05, 0x36, 0xb4, 0x80, 0x26, 0x67, 0x61, 0xf4, 0x54, 0x06, 0xd8, 0x45, 0xd1, 0xfe,
0x26, 0xba, 0xf8, 0x2a, 0xe0, 0xfc, 0x18, 0xfd, 0x13, 0x76, 0x50, 0xe3, 0x4b, 0x1d, 0x9f, 0xb8,
0xe2, 0xd4, 0x51, 0x43, 0xe0, 0xf0, 0xc4, 0x65, 0x6a, 0xcb, 0xd8, 0x3d, 0x7e, 0x90, 0x6b, 0x20,
0xb6, 0xcb, 0x37, 0xef, 0x3a, 0xb4, 0x64, 0x28, 0x3b, 0xee, 0xf9, 0xf4, 0x38, 0x91, 0x61, 0x98,
0x60, 0x32, 0xc2, 0xd3, 0xde, 0x1e, 0x3d, 0x4e, 0xec, 0x7d, 0x58, 0x10, 0xaa, 0xe4, 0xe1, 0x98,
0xca, 0xae, 0x3f, 0x57, 0x64, 0x92, 0x1b, 0xb7, 0x17, 0x4d, 0xdd, 0xc3, 0x83, 0xf7, 0x26, 0xa5,
0xed, 0x00, 0xd1, 0x55, 0x93, 0x68, 0x50, 0xd8, 0x45, 0x19, 0x68, 0x12, 0xd3, 0x31, 0x30, 0xb6,
0x3e, 0xf1, 0xa4, 0xdf, 0x97, 0x17, 0x10, 0xec, 0x38, 0xcc, 0x8b, 0xf6, 0xef, 0x5b, 0xb0, 0x88,
0xad, 0x49, 0xa7, 0x42, 0xa8, 0xff, 0x3b, 0x1f, 0x63, 0x98, 0xcd, 0xbe, 0x1e, 0x7c, 0x5b, 0x82,
0xaa, 0x6e, 0x10, 0x78, 0xe1, 0xe3, 0x1f, 0xea, 0x2b, 0xd9, 0x43, 0xbd, 0xfd, 0x1b, 0x16, 0x2c,
0x70, 0x9d, 0x9c, 0xb8, 0xc9, 0x24, 0x16, 0xd3, 0xff, 0x3f, 0x30, 0xc7, 0x8d, 0xab, 0x90, 0x6a,
0x31, 0xd0, 0x25, 0xa5, 0x80, 0x10, 0xe5, 0xc4, 0xbb, 0x97, 0x1c, 0x93, 0x98, 0xbc, 0x87, 0x0e,
0x4e, 0xd0, 0x43, 0x54, 0xc4, 0x51, 0x2f, 0x17, 0x98, 0x01, 0xf5, 0xbd, 0x46, 0x7e, 0xb7, 0x06,
0x33, 0xdc, 0xdf, 0xb5, 0xef, 0xc3, 0x9c, 0xd1, 0x91, 0x11, 0x50, 0x68, 0xf2, 0x80, 0x42, 0x2e,
0x72, 0x57, 0x2a, 0x88, 0xdc, 0xfd, 0x61, 0x19, 0x08, 0x63, 0x96, 0xcc, 0x6e, 0x30, 0x87, 0x3b,
0x1c, 0x18, 0xc7, 0xa7, 0xa6, 0xa3, 0x43, 0xe4, 0x26, 0x10, 0xad, 0x28, 0x03, 0xb0, 0xdc, 0xfa,
0x14, 0xd4, 0x30, 0x35, 0x29, 0x8c, 0xb7, 0x30, 0xb3, 0xe2, 0xa0, 0xc8, 0x97, 0xbd, 0xb0, 0x8e,
0x19, 0x98, 0xf1, 0x24, 0x3e, 0xc1, 0xb0, 0x99, 0x38, 0x60, 0xc9, 0x72, 0x76, 0x7f, 0x67, 0x2e,
0xdc, 0xdf, 0xd9, 0x5c, 0xd0, 0x46, 0x73, 0xf1, 0x6b, 0xa6, 0x8b, 0x7f, 0x1d, 0xe6, 0x46, 0xcc,
0xe5, 0x4c, 0xfc, 0x7e, 0x6f, 0xc4, 0x7a, 0x17, 0xe7, 0x29, 0x03, 0x24, 0xeb, 0xd0, 0x16, 0xee,
0x46, 0x7a, 0x8e, 0xe0, 0xe1, 0xf9, 0x1c, 0xce, 0xf4, 0x77, 0x1a, 0xc6, 0x69, 0xe0, 0x60, 0x53,
0x80, 0x9d, 0xbc, 0x62, 0xc6, 0x21, 0xbd, 0x49, 0x20, 0x6e, 0xab, 0xe8, 0x00, 0x4f, 0x52, 0x35,
0x27, 0x5f, 0x61, 0xff, 0xaa, 0x05, 0x6d, 0xb6, 0x67, 0x06, 0x5b, 0xbe, 0x0b, 0x28, 0x15, 0x2f,
0xc9, 0x95, 0x06, 0x2d, 0xb9, 0x03, 0x75, 0x2c, 0x87, 0x63, 0x1a, 0x08, 0x9e, 0xec, 0x98, 0x3c,
0x99, 0xea, 0x93, 0xdd, 0x4b, 0x4e, 0x4a, 0xac, 0x71, 0xe4, 0x5f, 0x5b, 0xd0, 0x10, 0xbd, 0xfc,
0xd0, 0x61, 0x82, 0xae, 0x76, 0xbd, 0xc8, 0x39, 0x29, 0xbd, 0x4d, 0x5c, 0x83, 0xf9, 0x91, 0x9b,
0x4c, 0x22, 0x66, 0x8f, 0x8d, 0x10, 0x41, 0x16, 0x66, 0xc6, 0x15, 0x55, 0x67, 0xdc, 0x4b, 0x3c,
0xbf, 0x27, 0x6b, 0xc5, 0x45, 0x5e, 0x51, 0x15, 0xd3, 0x20, 0x71, 0xe2, 0x0e, 0xa9, 0xb0, 0x9b,
0xbc, 0x60, 0x77, 0x60, 0x45, 0x4c, 0x28, 0xe3, 0xaa, 0xda, 0xdf, 0x6f, 0xc2, 0x6a, 0xae, 0x4a,
0xdd, 0xf6, 0x8b, 0xb3, 0xaf, 0xef, 0x8d, 0x8e, 0x42, 0xe5, 0xe7, 0x5b, 0xfa, 0xb1, 0xd8, 0xa8,
0x22, 0x43, 0x58, 0x96, 0x0e, 0x02, 0x5b, 0xd3, 0xd4, 0x98, 0x95, 0xd0, 0x4a, 0xbd, 0x6d, 0x6e,
0x61, 0xb6, 0x43, 0x89, 0xeb, 0x42, 0x5c, 0xdc, 0x1e, 0x39, 0x81, 0x8e, 0xf2, 0x44, 0x84, 0xb2,
0xd6, 0xbc, 0x15, 0xd6, 0xd7, 0x5b, 0x17, 0xf4, 0x65, 0x78, 0xb6, 0xce, 0xd4, 0xd6, 0xc8, 0x39,
0x5c, 0x95, 0x75, 0xa8, 0x8d, 0xf3, 0xfd, 0x55, 0x5e, 0x6a, 0x6e, 0xe8, 0xb3, 0x9b, 0x9d, 0x5e,
0xd0, 0x30, 0xf9, 0x10, 0x56, 0xce, 0x5c, 0x2f, 0x91, 0xc3, 0xd2, 0x7c, 0x83, 0x2a, 0x76, 0x79,
0xfb, 0x82, 0x2e, 0x9f, 0xf0, 0x8f, 0x0d, 0x13, 0x35, 0xa5, 0xc5, 0xee, 0x5f, 0x5a, 0xd0, 0x32,
0xdb, 0x61, 0x6c, 0x2a, 0x64, 0x5f, 0xea, 0x40, 0xe9, 0x4d, 0x66, 0xe0, 0xfc, 0x51, 0xb9, 0x54,
0x74, 0x54, 0xd6, 0x0f, 0xa8, 0xe5, 0x8b, 0x62, 0x4c, 0x95, 0x97, 0x8b, 0x31, 0x55, 0x8b, 0x62,
0x4c, 0xdd, 0xff, 0xb0, 0x80, 0xe4, 0x79, 0x89, 0xdc, 0xe7, 0x67, 0xf5, 0x80, 0xfa, 0x42, 0xa5,
0xfc, 0xef, 0x97, 0xe3, 0x47, 0xb9, 0x76, 0xf2, 0x6b, 0x26, 0x18, 0xfa, 0x4d, 0xbc, 0xee, 0xec,
0xcc, 0x39, 0x45, 0x55, 0x99, 0xa8, 0x57, 0xe5, 0xe2, 0xa8, 0x57, 0xf5, 0xe2, 0xa8, 0xd7, 0x4c,
0x36, 0xea, 0xd5, 0xfd, 0x59, 0x0b, 0x16, 0x0b, 0x36, 0xfd, 0xc7, 0x37, 0x71, 0xb6, 0x4d, 0x86,
0x2e, 0x28, 0x89, 0x6d, 0xd2, 0xc1, 0xee, 0x4f, 0xc1, 0x9c, 0xc1, 0xe8, 0x3f, 0xbe, 0xfe, 0xb3,
0xfe, 0x1a, 0xe7, 0x33, 0x03, 0xeb, 0xfe, 0x4b, 0x09, 0x48, 0x5e, 0xd8, 0xfe, 0x47, 0xc7, 0x90,
0x5f, 0xa7, 0x72, 0xc1, 0x3a, 0xfd, 0xb7, 0xda, 0x81, 0xb7, 0x60, 0x41, 0xa4, 0x06, 0x69, 0x11,
0x1a, 0xce, 0x31, 0xf9, 0x0a, 0xe6, 0xb1, 0x9a, 0x21, 0xc7, 0x9a, 0x91, 0x6e, 0xa1, 0x19, 0xc3,
0x4c, 0xe4, 0xd1, 0xee, 0x42, 0x47, 0xac, 0xd0, 0xce, 0x29, 0x0d, 0x92, 0xc3, 0xc9, 0x11, 0xcf,
0xaf, 0xf1, 0xc2, 0xc0, 0xfe, 0x5e, 0x59, 0x39, 0xdd, 0x58, 0x29, 0xcc, 0xfb, 0xa7, 0xa1, 0xa9,
0x2b, 0x73, 0xb1, 0x1d, 0x99, 0x00, 0x1d, 0x33, 0xec, 0x3a, 0x15, 0xd9, 0x86, 0x16, 0xaa, 0xac,
0x81, 0xfa, 0xae, 0x84, 0xdf, 0xbd, 0x20, 0xf0, 0xb0, 0x7b, 0xc9, 0xc9, 0x7c, 0x43, 0x3e, 0x0f,
0x2d, 0xf3, 0x28, 0x25, 0x7c, 0x84, 0x22, 0xdf, 0x9c, 0x7d, 0x6e, 0x12, 0x93, 0x4d, 0x68, 0x67,
0xcf, 0x62, 0xe2, 0x72, 0x7d, 0x4a, 0x03, 0x39, 0x72, 0x72, 0x47, 0xdc, 0x3d, 0x55, 0x31, 0x08,
0x76, 0xdd, 0xfc, 0x4c, 0x5b, 0xa6, 0x9b, 0xfc, 0x8f, 0x76, 0x1b, 0xf5, 0x35, 0x80, 0x14, 0x23,
0x6d, 0x68, 0x3e, 0x3c, 0xd8, 0xd9, 0xef, 0x6d, 0xed, 0x6e, 0xee, 0xef, 0xef, 0xec, 0xb5, 0x2f,
0x11, 0x02, 0x2d, 0x8c, 0x5f, 0x6d, 0x2b, 0xcc, 0x62, 0xd8, 0xe6, 0x16, 0x8f, 0x8d, 0x09, 0xac,
0x44, 0x96, 0xa0, 0xfd, 0x60, 0x3f, 0x83, 0x96, 0xef, 0xd6, 0x95, 0x7c, 0xd8, 0x2b, 0xb0, 0xc4,
0xd3, 0xc7, 0xee, 0x72, 0xf6, 0x90, 0xbe, 0xc2, 0x6f, 0x59, 0xb0, 0x9c, 0xa9, 0x48, 0xf3, 0x34,
0xb8, 0x3b, 0x60, 0xfa, 0x08, 0x26, 0xc8, 0x78, 0x52, 0x79, 0x7e, 0x19, 0x0d, 0x92, 0xaf, 0x60,
0x3c, 0xaf, 0x79, 0x8a, 0x19, 0x49, 0x2a, 0xaa, 0xb2, 0x57, 0x79, 0x92, 0x5b, 0x40, 0xfd, 0xcc,
0xc0, 0x8f, 0x79, 0x5a, 0x9a, 0x5e, 0x91, 0xde, 0xe5, 0x99, 0x43, 0x96, 0x45, 0xe6, 0xe4, 0x1b,
0xae, 0x87, 0x39, 0xde, 0xc2, 0x3a, 0xfb, 0xcf, 0x4a, 0x40, 0xbe, 0x34, 0xa1, 0xd1, 0x39, 0xa6,
0x58, 0xa8, 0x70, 0xe0, 0x6a, 0x36, 0xd8, 0x35, 0x33, 0x9e, 0x1c, 0x7d, 0x91, 0x9e, 0xcb, 0xf4,
0xa0, 0x92, 0x9e, 0x1e, 0x04, 0xec, 0x70, 0xac, 0x12, 0x3c, 0xac, 0xb5, 0x2a, 0x86, 0x24, 0xea,
0xc1, 0x64, 0xc4, 0x1b, 0x2d, 0xcc, 0xe2, 0xa9, 0x5c, 0x9c, 0xc5, 0x53, 0xbd, 0x28, 0x8b, 0xe7,
0x13, 0x30, 0xe7, 0x0d, 0x83, 0x90, 0xa9, 0x05, 0x66, 0xd8, 0xe3, 0xce, 0xcc, 0xb5, 0x32, 0x3b,
0x0c, 0x0b, 0x70, 0x9f, 0x61, 0xe4, 0xb3, 0x29, 0x11, 0x1d, 0x0c, 0x31, 0x23, 0x4c, 0x57, 0x14,
0x3b, 0x83, 0x21, 0xdd, 0x0b, 0xfb, 0x6e, 0x12, 0x46, 0xea, 0x43, 0x86, 0xc5, 0xec, 0xd4, 0x1f,
0x87, 0x13, 0xe6, 0xe6, 0xc8, 0xa5, 0xe0, 0x61, 0x9b, 0x26, 0x47, 0x0f, 0x70, 0x41, 0xec, 0xaf,
0x40, 0x43, 0x6b, 0x02, 0xd3, 0x85, 0x84, 0x0b, 0x21, 0xce, 0x83, 0x15, 0xee, 0xb1, 0x07, 0xd4,
0x7f, 0x30, 0x20, 0x6f, 0xc2, 0xc2, 0xc0, 0x8b, 0x28, 0x66, 0x7e, 0xf5, 0x22, 0x7a, 0x4a, 0xa3,
0x58, 0x9e, 0x9c, 0xdb, 0xaa, 0xc2, 0xe1, 0xb8, 0xfd, 0x1e, 0x2c, 0x1a, 0x5b, 0xa3, 0x38, 0x57,
0x66, 0xd3, 0x58, 0xf9, 0x6c, 0x1a, 0x99, 0x49, 0x63, 0xff, 0x7c, 0x09, 0xca, 0xbb, 0xe1, 0x58,
0x8f, 0xf6, 0x5b, 0x66, 0xb4, 0x5f, 0xb8, 0x40, 0x3d, 0xe5, 0xe1, 0x08, 0xcb, 0x68, 0x80, 0x64,
0x1d, 0x5a, 0xee, 0x28, 0xe9, 0x25, 0x21, 0x73, 0xf9, 0xce, 0xdc, 0x68, 0xc0, 0xd9, 0x19, 0xb7,
0x38, 0x53, 0x43, 0x96, 0xa0, 0xac, 0x7c, 0x05, 0x24, 0x60, 0x45, 0x76, 0xde, 0xc0, 0x7b, 0xc4,
0x73, 0x11, 0x39, 0x13, 0x25, 0x26, 0x2d, 0xe6, 0xf7, 0xfc, 0xb0, 0xc7, 0x35, 0x7e, 0x51, 0x15,
0x73, 0xc7, 0x18, 0x77, 0x20, 0x99, 0x08, 0x79, 0xca, 0xb2, 0x1e, 0x9e, 0xad, 0x99, 0xb7, 0xaa,
0xff, 0x64, 0x41, 0x15, 0xd7, 0x86, 0x59, 0x2f, 0x2e, 0xde, 0x2a, 0xe0, 0x8f, 0x6b, 0x32, 0xe7,
0x64, 0x61, 0x62, 0x1b, 0x39, 0x84, 0x25, 0x35, 0x21, 0x3d, 0x8f, 0xf0, 0x1a, 0xd4, 0x79, 0x49,
0xe5, 0xcb, 0x71, 0xbe, 0x57, 0x20, 0xb9, 0x0a, 0x95, 0x93, 0x70, 0x2c, 0xdd, 0x6d, 0x90, 0xb7,
0x61, 0xe1, 0xd8, 0x41, 0x3c, 0x1d, 0x0f, 0x6b, 0x8f, 0x4f, 0x8b, 0x3b, 0x51, 0x59, 0x98, 0xb9,
0x91, 0xaa, 0x59, 0x7d, 0x99, 0x32, 0xa8, 0xbd, 0x0e, 0xf3, 0x8c, 0xeb, 0xb5, 0xa8, 0xeb, 0x54,
0x51, 0xb6, 0x7f, 0xda, 0x82, 0x9a, 0x24, 0x26, 0x6b, 0x50, 0x61, 0x22, 0x94, 0x39, 0xb8, 0xaa,
0x5b, 0x70, 0x46, 0xe7, 0x20, 0x05, 0x73, 0x26, 0x30, 0x18, 0x96, 0x9e, 0x93, 0x64, 0x28, 0x2c,
0x3d, 0x06, 0xa8, 0xe1, 0x66, 0xbc, 0xe7, 0x0c, 0x6a, 0x7f, 0xd7, 0x82, 0x39, 0xa3, 0x0f, 0x72,
0x0d, 0x1a, 0xbe, 0x1b, 0x27, 0xe2, 0x66, 0x51, 0x6c, 0x8f, 0x0e, 0xe9, 0x1b, 0x5d, 0x32, 0xe3,
0xf0, 0x2a, 0x42, 0x5c, 0xd6, 0x23, 0xc4, 0xb7, 0xa0, 0x9e, 0x66, 0x7a, 0x56, 0x0c, 0xd9, 0x67,
0x3d, 0xca, 0xfb, 0xfd, 0x94, 0x08, 0x83, 0x8e, 0xa1, 0x1f, 0x46, 0xe2, 0xd2, 0x8a, 0x17, 0xec,
0xf7, 0xa0, 0xa1, 0xd1, 0xeb, 0x31, 0x48, 0xcb, 0x88, 0x41, 0xaa, 0xe4, 0x97, 0x52, 0x9a, 0xfc,
0x62, 0xff, 0xab, 0x05, 0x73, 0x8c, 0x07, 0xbd, 0x60, 0x78, 0x10, 0xfa, 0x5e, 0xff, 0x1c, 0xf7,
0x5e, 0xb2, 0x9b, 0x50, 0x89, 0x92, 0x17, 0x4d, 0x98, 0x71, 0xbd, 0x8c, 0x7c, 0x08, 0x11, 0x55,
0x65, 0x26, 0xc3, 0x4c, 0x02, 0x8e, 0xdc, 0x58, 0x88, 0x85, 0xf0, 0xda, 0x0c, 0x90, 0x49, 0x1a,
0x03, 0x30, 0x95, 0x69, 0xe4, 0xf9, 0xbe, 0xc7, 0x69, 0xb9, 0x4f, 0x5f, 0x54, 0xc5, 0xfa, 0x1c,
0x78, 0xb1, 0x7b, 0x94, 0x5e, 0xc4, 0xa8, 0x32, 0x86, 0x67, 0xdc, 0x67, 0x5a, 0x78, 0x66, 0x06,
0xf5, 0x8a, 0x09, 0xda, 0x7f, 0x52, 0x82, 0x86, 0x74, 0x11, 0x06, 0x43, 0x2a, 0xee, 0x16, 0x4d,
0xc5, 0xa8, 0x21, 0xb2, 0xde, 0x38, 0x8d, 0x69, 0x48, 0x96, 0x31, 0xca, 0x79, 0xc6, 0xb8, 0x02,
0x75, 0xc6, 0xa0, 0x6f, 0xe3, 0xb1, 0x4f, 0x24, 0x4f, 0x2b, 0x40, 0xd6, 0xde, 0xc6, 0xda, 0x6a,
0x5a, 0x8b, 0xc0, 0x0b, 0x6f, 0x22, 0xef, 0x40, 0x53, 0x34, 0x83, 0x3b, 0x87, 0x9a, 0x27, 0x15,
0x11, 0x63, 0x57, 0x1d, 0x83, 0x52, 0x7e, 0x79, 0x5b, 0x7e, 0x59, 0xbb, 0xe8, 0x4b, 0x49, 0x69,
0xdf, 0x57, 0x17, 0xbc, 0xf7, 0x23, 0x77, 0x7c, 0x22, 0x65, 0xf9, 0x16, 0x2c, 0x7a, 0x41, 0xdf,
0x9f, 0x0c, 0x68, 0x6f, 0x12, 0xb8, 0x41, 0x10, 0x4e, 0x82, 0x3e, 0x95, 0xd9, 0x2f, 0x45, 0x55,
0xf6, 0x40, 0xe5, 0x4a, 0x62, 0x43, 0x64, 0x1d, 0xaa, 0xdc, 0x54, 0x72, 0xdb, 0x51, 0x2c, 0xe8,
0x9c, 0x84, 0xac, 0x41, 0x95, 0x5b, 0xcc, 0x92, 0x21, 0x35, 0xda, 0xae, 0x3a, 0x9c, 0x80, 0xa9,
0x1d, 0x4c, 0x97, 0x35, 0xd5, 0x8e, 0x69, 0x77, 0x66, 0xfa, 0x98, 0x50, 0x6b, 0x2f, 0x01, 0xd9,
0xe7, 0x92, 0xa2, 0xdf, 0x0d, 0x7d, 0xbf, 0x0c, 0x0d, 0x0d, 0x66, 0x1a, 0x64, 0xc8, 0x06, 0xdc,
0x1b, 0x78, 0xee, 0x88, 0x26, 0x34, 0x12, 0xd2, 0x91, 0x41, 0x19, 0x9d, 0x7b, 0x3a, 0xec, 0x85,
0x93, 0xa4, 0x37, 0xa0, 0xc3, 0x88, 0x72, 0x6b, 0xca, 0x4c, 0x93, 0x81, 0x32, 0x3a, 0xc6, 0x9f,
0x1a, 0x1d, 0xe7, 0xa0, 0x0c, 0x2a, 0x6f, 0x7a, 0xf8, 0x1a, 0x55, 0xd2, 0x9b, 0x1e, 0xbe, 0x22,
0x59, 0xdd, 0x57, 0x2d, 0xd0, 0x7d, 0xef, 0xc0, 0x0a, 0xd7, 0x72, 0x42, 0x1f, 0xf4, 0x32, 0x8c,
0x35, 0xa5, 0x96, 0xac, 0x43, 0x9b, 0x8d, 0x59, 0x8a, 0x44, 0xec, 0x7d, 0x93, 0x47, 0x4d, 0x2d,
0x27, 0x87, 0x33, 0x5a, 0x0c, 0x5f, 0xea, 0xb4, 0xfc, 0xe6, 0x3b, 0x87, 0x23, 0xad, 0xfb, 0xcc,
0xa4, 0xad, 0x0b, 0xda, 0x0c, 0x4e, 0xee, 0xc0, 0xea, 0x88, 0x0e, 0x3c, 0xd7, 0x6c, 0x02, 0x23,
0xc0, 0x3c, 0x61, 0x65, 0x5a, 0xb5, 0x3d, 0x07, 0x8d, 0xc3, 0x24, 0x1c, 0xcb, 0xed, 0x6c, 0x41,
0x93, 0x17, 0x45, 0xfe, 0xd2, 0x2b, 0x70, 0x19, 0xf9, 0xef, 0x51, 0x38, 0x0e, 0xfd, 0x70, 0x78,
0x6e, 0x1c, 0xba, 0xfe, 0xca, 0x82, 0x45, 0xa3, 0x36, 0x3d, 0x75, 0x61, 0xbc, 0x46, 0x26, 0x9e,
0x70, 0x96, 0x5d, 0xd0, 0x94, 0x37, 0x27, 0xe4, 0xa1, 0xf1, 0xc7, 0x22, 0x17, 0x65, 0x13, 0xe6,
0xe5, 0xe8, 0xe4, 0x87, 0x9c, 0x7f, 0x3b, 0x79, 0xfe, 0x15, 0xdf, 0xb7, 0xc4, 0x07, 0xb2, 0x89,
0xcf, 0x8b, 0xdc, 0x03, 0x7e, 0x08, 0x93, 0xe1, 0x39, 0x75, 0x6c, 0xd3, 0x0f, 0xe9, 0x72, 0x04,
0x7d, 0x05, 0xc6, 0xf6, 0x2f, 0x5a, 0x00, 0xe9, 0xe8, 0xf0, 0xc6, 0x5a, 0x19, 0x20, 0xfe, 0xec,
0x48, 0x33, 0x36, 0xaf, 0x43, 0x53, 0xdd, 0x74, 0xa6, 0x36, 0xad, 0x21, 0x31, 0xe6, 0x73, 0xdf,
0x80, 0xf9, 0xa1, 0x1f, 0x1e, 0xa1, 0x43, 0x80, 0x09, 0x71, 0xb1, 0xc8, 0xe2, 0x6a, 0x71, 0xf8,
0x9e, 0x40, 0x53, 0x03, 0x58, 0xd1, 0x0c, 0xa0, 0xfd, 0x4b, 0x25, 0x75, 0x31, 0x95, 0xce, 0x79,
0xaa, 0x7c, 0x92, 0xdb, 0x39, 0x45, 0x3c, 0xe5, 0x1e, 0x08, 0xdd, 0xda, 0x83, 0x0b, 0xe3, 0x64,
0xef, 0x41, 0x2b, 0xe2, 0x9a, 0x4e, 0xaa, 0xc1, 0xca, 0x0b, 0xd4, 0xe0, 0x5c, 0x64, 0x58, 0xc9,
0x4f, 0x42, 0xdb, 0x1d, 0x9c, 0xd2, 0x28, 0xf1, 0x30, 0x52, 0x81, 0x2e, 0x0a, 0x57, 0xde, 0xf3,
0x1a, 0x8e, 0x9e, 0xc3, 0x0d, 0x98, 0x17, 0x99, 0x73, 0x8a, 0x52, 0xbc, 0x29, 0x48, 0x61, 0x46,
0x68, 0xff, 0x8e, 0xbc, 0x03, 0x33, 0xf7, 0x70, 0xfa, 0x8a, 0xe8, 0xb3, 0x2b, 0x65, 0x66, 0xf7,
0x09, 0x71, 0x1f, 0x35, 0x90, 0xe1, 0x90, 0xb2, 0x96, 0xa7, 0x32, 0x10, 0xf7, 0x87, 0xe6, 0x92,
0x56, 0x5e, 0x66, 0x49, 0xed, 0x1f, 0x58, 0x30, 0xbb, 0x1b, 0x8e, 0x77, 0x45, 0xc6, 0x0e, 0x0a,
0x82, 0x4a, 0x59, 0x95, 0xc5, 0x17, 0xe4, 0xf2, 0x14, 0x7a, 0x06, 0x73, 0x59, 0xcf, 0xe0, 0xff,
0xc3, 0x2b, 0x18, 0x8c, 0x8b, 0xc2, 0x71, 0x18, 0x31, 0x61, 0x74, 0x7d, 0xee, 0x06, 0x84, 0x41,
0x72, 0x22, 0x15, 0xe0, 0x8b, 0x48, 0xf0, 0x84, 0xcc, 0x4e, 0x75, 0xdc, 0xa9, 0x17, 0x9e, 0x0c,
0xd7, 0x8b, 0xf9, 0x0a, 0xfb, 0x73, 0x50, 0x47, 0x57, 0x1c, 0xa7, 0xf5, 0x16, 0xd4, 0x4f, 0xc2,
0x71, 0xef, 0xc4, 0x0b, 0x12, 0x29, 0xdc, 0xad, 0xd4, 0x47, 0xde, 0xc5, 0x05, 0x51, 0x04, 0xf6,
0xaf, 0xcf, 0xc0, 0xec, 0x83, 0xe0, 0x34, 0xf4, 0xfa, 0x78, 0xdf, 0x36, 0xa2, 0xa3, 0x50, 0x26,
0xf0, 0xb2, 0xff, 0xc9, 0x15, 0x98, 0xc5, 0x8c, 0xb5, 0x31, 0x67, 0xda, 0x26, 0xbf, 0x17, 0x17,
0x10, 0x73, 0x2f, 0xa2, 0xf4, 0xa9, 0x05, 0x17, 0x1f, 0x0d, 0x61, 0x87, 0x94, 0x48, 0x7f, 0x2a,
0x21, 0x4a, 0x69, 0x82, 0x74, 0x55, 0x4b, 0x90, 0x66, 0x7d, 0x89, 0x0c, 0x23, 0x9e, 0x82, 0xc2,
0xfb, 0x12, 0x10, 0x1e, 0xac, 0x22, 0xca, 0x83, 0xa9, 0xe8, 0xac, 0xcc, 0x8a, 0x83, 0x95, 0x0e,
0x32, 0x87, 0x86, 0x7f, 0xc0, 0x69, 0xb8, 0xfa, 0xd6, 0x21, 0xe6, 0x22, 0x66, 0x5f, 0xc9, 0xd4,
0x39, 0xef, 0x67, 0x60, 0xa6, 0xe3, 0x07, 0x54, 0x29, 0x54, 0x3e, 0x0f, 0xe0, 0xcf, 0x49, 0xb2,
0xb8, 0x76, 0x1c, 0xe3, 0xc9, 0x85, 0xf2, 0x38, 0xc6, 0x18, 0xc6, 0xf5, 0xfd, 0x23, 0xb7, 0xff,
0x14, 0x1f, 0x41, 0xe1, 0x0d, 0x58, 0xdd, 0x31, 0x41, 0xcc, 0x13, 0x4a, 0x77, 0x15, 0x33, 0x08,
0x2a, 0x8e, 0x0e, 0x91, 0xdb, 0xd0, 0xc0, 0x23, 0xa8, 0xd8, 0xd7, 0x16, 0xee, 0x6b, 0x5b, 0x3f,
0xa3, 0xe2, 0xce, 0xea, 0x44, 0xfa, 0x5d, 0xe0, 0x7c, 0x2e, 0xdd, 0xcf, 0x1d, 0x0c, 0xc4, 0x15,
0x6a, 0x9b, 0x1f, 0xa7, 0x15, 0xc0, 0xec, 0xb1, 0x58, 0x30, 0x4e, 0xb0, 0x80, 0x04, 0x06, 0x46,
0xae, 0x42, 0x8d, 0x1d, 0x8f, 0xc6, 0xae, 0x37, 0xc0, 0x7c, 0x41, 0x7e, 0x4a, 0x53, 0x18, 0x6b,
0x43, 0xfe, 0x8f, 0x86, 0x6e, 0x11, 0x57, 0xc5, 0xc0, 0xd8, 0xda, 0xa8, 0x32, 0x0a, 0xd3, 0x12,
0xdf, 0x51, 0x03, 0x24, 0x6f, 0xe3, 0x45, 0x56, 0x42, 0x3b, 0xcb, 0x18, 0x28, 0x7b, 0x45, 0xcc,
0x59, 0x30, 0xad, 0xfc, 0x7b, 0xc8, 0x48, 0x1c, 0x4e, 0x69, 0x6f, 0x42, 0x53, 0x87, 0x49, 0x0d,
0x2a, 0x0f, 0x0f, 0x76, 0xf6, 0xdb, 0x97, 0x48, 0x03, 0x66, 0x0f, 0x77, 0x1e, 0x3d, 0xda, 0xdb,
0xd9, 0x6e, 0x5b, 0xa4, 0x09, 0x35, 0x95, 0xd4, 0x55, 0x62, 0xa5, 0xcd, 0xad, 0xad, 0x9d, 0x83,
0x47, 0x3b, 0xdb, 0xed, 0xb2, 0x9d, 0x00, 0xd9, 0x1c, 0x0c, 0x44, 0x2b, 0x2a, 0x48, 0x90, 0xf2,
0xb3, 0x65, 0xf0, 0x73, 0x01, 0x4f, 0x95, 0x8a, 0x79, 0xea, 0x85, 0x2b, 0x6f, 0xef, 0x40, 0xe3,
0x40, 0x7b, 0x11, 0x84, 0xe2, 0x25, 0xdf, 0x02, 0x09, 0xb1, 0xd4, 0x10, 0x6d, 0x38, 0x25, 0x7d,
0x38, 0xf6, 0xef, 0x5a, 0x3c, 0xed, 0x5e, 0x0d, 0x9f, 0xf7, 0x6d, 0x43, 0x53, 0x45, 0xab, 0xd2,
0x7c, 0x4d, 0x03, 0x63, 0x34, 0x38, 0x94, 0x5e, 0x78, 0x7c, 0x1c, 0x53, 0x99, 0x5d, 0x65, 0x60,
0x4c, 0x2e, 0x98, 0x6f, 0xc6, 0xfc, 0x1c, 0x8f, 0xf7, 0x10, 0x8b, 0x2c, 0xab, 0x1c, 0xce, 0xb4,
0xbc, 0x08, 0xc8, 0xc8, 0xbc, 0x32, 0x55, 0x56, 0x69, 0xa5, 0xd9, 0x55, 0x5e, 0x87, 0x9a, 0x6a,
0xd7, 0x54, 0x60, 0x92, 0x52, 0xd5, 0x33, 0x45, 0x89, 0xa7, 0x15, 0x63, 0xd0, 0x5c, 0x69, 0xe7,
0x2b, 0xc8, 0x4d, 0x20, 0xc7, 0x5e, 0x94, 0x25, 0x2f, 0x23, 0x79, 0x41, 0x8d, 0xfd, 0x04, 0x16,
0x25, 0x23, 0x69, 0xae, 0x95, 0xb9, 0x89, 0xd6, 0x45, 0xe2, 0x53, 0xca, 0x8b, 0x8f, 0xfd, 0x9f,
0x16, 0xcc, 0x8a, 0x9d, 0xce, 0xbd, 0x2a, 0xe3, 0xfb, 0x6c, 0x60, 0xa4, 0x63, 0xbc, 0x28, 0x41,
0x59, 0x13, 0x4a, 0x33, 0xa7, 0x16, 0xcb, 0x45, 0x6a, 0x91, 0x40, 0x65, 0xec, 0x26, 0x27, 0x78,
0x52, 0xaf, 0x3b, 0xf8, 0x3f, 0x69, 0xf3, 0xb8, 0x12, 0x57, 0xc1, 0x18, 0x53, 0x2a, 0x7a, 0x3f,
0xc7, 0xad, 0x7d, 0xfe, 0xfd, 0xdc, 0x15, 0xa8, 0xe3, 0x00, 0x7a, 0x69, 0xd8, 0x28, 0x05, 0x18,
0xe7, 0xf2, 0x02, 0xca, 0xb5, 0x48, 0xee, 0x4e, 0x11, 0x7b, 0x99, 0xef, 0xbc, 0x58, 0x02, 0x75,
0x09, 0x2d, 0xd2, 0x78, 0x53, 0x38, 0xe5, 0x08, 0x31, 0x80, 0x2c, 0x47, 0x08, 0x52, 0x47, 0xd5,
0xdb, 0x5d, 0xe8, 0x6c, 0x53, 0x9f, 0x26, 0x74, 0xd3, 0xf7, 0xb3, 0xed, 0xbf, 0x02, 0x97, 0x0b,
0xea, 0x84, 0x37, 0xfd, 0x25, 0x58, 0xde, 0xe4, 0x29, 0x8f, 0x3f, 0xae, 0x34, 0x1e, 0xbb, 0x03,
0x2b, 0xd9, 0x26, 0x45, 0x67, 0xf7, 0x60, 0x61, 0x9b, 0x1e, 0x4d, 0x86, 0x7b, 0xf4, 0x34, 0xed,
0x88, 0x40, 0x25, 0x3e, 0x09, 0xcf, 0x84, 0x60, 0xe2, 0xff, 0xe4, 0x55, 0x00, 0x9f, 0xd1, 0xf4,
0xe2, 0x31, 0xed, 0xcb, 0x47, 0x1c, 0x88, 0x1c, 0x8e, 0x69, 0xdf, 0x7e, 0x07, 0x88, 0xde, 0x8e,
0x58, 0x2f, 0x66, 0x05, 0x27, 0x47, 0xbd, 0xf8, 0x3c, 0x4e, 0xe8, 0x48, 0xbe, 0x4e, 0xd1, 0x21,
0xfb, 0x06, 0x34, 0x0f, 0xdc, 0x73, 0x87, 0x7e, 0x43, 0x3c, 0x26, 0x5c, 0x85, 0xd9, 0xb1, 0x7b,
0xce, 0xd4, 0x94, 0x8a, 0x67, 0x61, 0xb5, 0xfd, 0xef, 0x25, 0x98, 0xe1, 0x94, 0xac, 0xd5, 0x01,
0x8d, 0x13, 0x2f, 0x40, 0xc6, 0x92, 0xad, 0x6a, 0x50, 0x8e, 0x95, 0x4b, 0x05, 0xac, 0x2c, 0x4e,
0x7b, 0x32, 0x21, 0x5e, 0xf0, 0xab, 0x81, 0x31, 0xe6, 0x4a, 0xf3, 0xe9, 0x78, 0x40, 0x25, 0x05,
0x32, 0xa1, 0xcf, 0xd4, 0xd6, 0xf2, 0xf1, 0x49, 0x29, 0x15, 0x9c, 0xab, 0x43, 0x85, 0x16, 0x7d,
0x96, 0x33, 0x78, 0xce, 0xa2, 0xe7, 0x2c, 0x77, 0xed, 0x25, 0x2c, 0x37, 0x3f, 0x02, 0xbe, 0xc8,
0x72, 0xc3, 0x4b, 0x58, 0x6e, 0x9b, 0x40, 0x1b, 0x5f, 0xda, 0x31, 0xdf, 0x50, 0xf2, 0xee, 0xb7,
0x2c, 0x68, 0x0b, 0x2e, 0x52, 0x75, 0xe4, 0x75, 0xc3, 0x07, 0x2e, 0x4c, 0x4c, 0xbf, 0x0e, 0x73,
0xe8, 0x99, 0xaa, 0x18, 0xaf, 0x08, 0x48, 0x1b, 0x20, 0x9b, 0x87, 0xbc, 0x3f, 0x1e, 0x79, 0xbe,
0xd8, 0x14, 0x1d, 0x92, 0x61, 0xe2, 0xc8, 0x15, 0x79, 0x65, 0x96, 0xa3, 0xca, 0xf6, 0x9f, 0x5a,
0xb0, 0xa0, 0x0d, 0x58, 0x70, 0xe1, 0x7b, 0x20, 0xa5, 0x81, 0x07, 0x7c, 0xb9, 0xe4, 0xae, 0x9a,
0x62, 0x93, 0x7e, 0x66, 0x10, 0xe3, 0x66, 0xba, 0xe7, 0x38, 0xc0, 0x78, 0x32, 0x12, 0x4a, 0x54,
0x87, 0x18, 0x23, 0x9d, 0x51, 0xfa, 0x54, 0x91, 0x70, 0x35, 0x6e, 0x60, 0x18, 0x55, 0x63, 0x1e,
0xb5, 0x22, 0xaa, 0x88, 0xa8, 0x9a, 0x0e, 0xda, 0x7f, 0x6b, 0xc1, 0x22, 0x3f, 0x1a, 0x89, 0x83,
0xa7, 0x7a, 0x53, 0x34, 0xc3, 0xcf, 0x82, 0x5c, 0x22, 0x77, 0x2f, 0x39, 0xa2, 0x4c, 0x3e, 0xf3,
0x92, 0xc7, 0x39, 0x95, 0xec, 0x36, 0x65, 0x2f, 0xca, 0x45, 0x7b, 0xf1, 0x82, 0x95, 0x2e, 0x0a,
0x70, 0x56, 0x0b, 0x03, 0x9c, 0x77, 0x67, 0xa1, 0x1a, 0xf7, 0xc3, 0x31, 0xb5, 0x57, 0x60, 0xc9,
0x9c, 0x9c, 0x50, 0x41, 0xdf, 0xb1, 0xa0, 0x73, 0x8f, 0x5f, 0x04, 0x78, 0xc1, 0x70, 0xd7, 0x8b,
0x93, 0x30, 0x52, 0x4f, 0x2f, 0xaf, 0x02, 0xc4, 0x89, 0x1b, 0x25, 0x3c, 0xa5, 0x59, 0x04, 0x16,
0x53, 0x84, 0x8d, 0x91, 0x06, 0x03, 0x5e, 0xcb, 0xf7, 0x46, 0x95, 0x73, 0x3e, 0x84, 0x38, 0xbc,
0x19, 0x96, 0xf8, 0x0d, 0x9e, 0xfc, 0xc9, 0x7c, 0x05, 0x7a, 0x8a, 0x7a, 0x9d, 0x9f, 0x8a, 0x32,
0xa8, 0xfd, 0x37, 0x16, 0xcc, 0xa7, 0x83, 0xc4, 0x6b, 0x51, 0x53, 0x3b, 0x08, 0xf3, 0x9b, 0x6a,
0x07, 0x19, 0xf2, 0xf4, 0x98, 0x3d, 0x16, 0x63, 0xd3, 0x10, 0x94, 0x58, 0x51, 0x0a, 0x27, 0xd2,
0xc1, 0xd1, 0x21, 0x9e, 0xca, 0xc5, 0x3c, 0x01, 0xe1, 0xd5, 0x88, 0x12, 0x66, 0xa4, 0x8f, 0x12,
0xfc, 0x8a, 0x07, 0x67, 0x65, 0x51, 0x9a, 0xd2, 0x59, 0x44, 0xd1, 0x94, 0xea, 0x97, 0x2a, 0x35,
0xbe, 0x3e, 0xb2, 0x6c, 0xff, 0xb2, 0x05, 0x97, 0x0b, 0x16, 0x5e, 0x48, 0xcd, 0x36, 0x2c, 0x1c,
0xab, 0x4a, 0xb9, 0x38, 0x5c, 0x74, 0x56, 0xe4, 0xa5, 0x9d, 0xb9, 0x20, 0x4e, 0xfe, 0x03, 0xe5,
0x17, 0xf1, 0xe5, 0x36, 0x92, 0x25, 0xf3, 0x15, 0xeb, 0xcf, 0xa1, 0xa1, 0x3d, 0x7a, 0x24, 0xab,
0xb0, 0xf8, 0xe4, 0xc1, 0xa3, 0xfd, 0x9d, 0xc3, 0xc3, 0xde, 0xc1, 0xe3, 0xbb, 0x5f, 0xdc, 0xf9,
0x4a, 0x6f, 0x77, 0xf3, 0x70, 0xb7, 0x7d, 0x89, 0xac, 0x00, 0xd9, 0xdf, 0x39, 0x7c, 0xb4, 0xb3,
0x6d, 0xe0, 0x16, 0xb9, 0x0a, 0xdd, 0xc7, 0xfb, 0x8f, 0x0f, 0x77, 0xb6, 0x7b, 0x45, 0xdf, 0x95,
0xc8, 0xab, 0x70, 0x59, 0xd4, 0x17, 0x7c, 0x5e, 0xbe, 0xfd, 0x2b, 0x65, 0x68, 0xf1, 0xfb, 0x64,
0xfe, 0x9b, 0x25, 0x34, 0x22, 0xef, 0xc3, 0xac, 0xf8, 0xcd, 0x19, 0xb2, 0x2c, 0x66, 0x6d, 0xfe,
0xca, 0x4d, 0x77, 0x25, 0x0b, 0x0b, 0xb6, 0x5e, 0xfc, 0x99, 0x1f, 0xfc, 0xe3, 0xaf, 0x95, 0xe6,
0x48, 0x63, 0xe3, 0xf4, 0xed, 0x8d, 0x21, 0x0d, 0x62, 0xd6, 0xc6, 0xd7, 0x00, 0xd2, 0x5f, 0x63,
0x21, 0x1d, 0xe5, 0x4e, 0x66, 0x7e, 0x66, 0xa6, 0x7b, 0xb9, 0xa0, 0x46, 0xb4, 0x7b, 0x19, 0xdb,
0x5d, 0xb4, 0x5b, 0xac, 0x5d, 0x2f, 0xf0, 0x12, 0xfe, 0xd3, 0x2c, 0xef, 0x5a, 0xeb, 0x64, 0x00,
0x4d, 0xfd, 0xc7, 0x56, 0x88, 0x8c, 0x69, 0x15, 0xfc, 0xd4, 0x4b, 0xf7, 0x95, 0xc2, 0x3a, 0x19,
0xd0, 0xc3, 0x3e, 0x96, 0xed, 0x36, 0xeb, 0x63, 0x82, 0x14, 0x69, 0x2f, 0x3e, 0xb4, 0xcc, 0xdf,
0x54, 0x21, 0x57, 0x34, 0x8d, 0x93, 0xfb, 0x45, 0x97, 0xee, 0xab, 0x53, 0x6a, 0x45, 0x5f, 0xaf,
0x62, 0x5f, 0xab, 0x36, 0x61, 0x7d, 0xf5, 0x91, 0x46, 0xfe, 0xa2, 0xcb, 0xbb, 0xd6, 0xfa, 0xed,
0x3f, 0x78, 0x1d, 0xea, 0x2a, 0x7e, 0x4d, 0x3e, 0x84, 0x39, 0xe3, 0xc2, 0x9f, 0xc8, 0x69, 0x14,
0xe5, 0x07, 0x74, 0xaf, 0x14, 0x57, 0x8a, 0x8e, 0xaf, 0x62, 0xc7, 0x1d, 0xb2, 0xc2, 0x3a, 0x16,
0x37, 0xe6, 0x1b, 0x98, 0xba, 0xc2, 0xf3, 0xd0, 0x9f, 0xf2, 0x79, 0xa6, 0x97, 0xf4, 0xc6, 0x3c,
0x73, 0x97, 0xfa, 0xc6, 0x3c, 0xf3, 0x37, 0xfb, 0xf6, 0x15, 0xec, 0x6e, 0x85, 0x2c, 0xe9, 0xdd,
0xa9, 0xb8, 0x32, 0xc5, 0xc7, 0x13, 0xfa, 0xcf, 0x91, 0x90, 0x57, 0x15, 0x63, 0x15, 0xfd, 0x4c,
0x89, 0x62, 0x91, 0xfc, 0x6f, 0x95, 0xd8, 0x1d, 0xec, 0x8a, 0x10, 0xdc, 0x3e, 0xfd, 0xd7, 0x48,
0xc8, 0x11, 0x34, 0xb4, 0x27, 0xf4, 0xe4, 0xf2, 0xd4, 0xe7, 0xfe, 0xdd, 0x6e, 0x51, 0x55, 0xd1,
0x54, 0xf4, 0xf6, 0x37, 0x98, 0xca, 0xf9, 0x2a, 0xd4, 0xd5, 0xa3, 0x6c, 0xb2, 0xaa, 0x3d, 0x92,
0xd7, 0x1f, 0x91, 0x77, 0x3b, 0xf9, 0x8a, 0x22, 0xe6, 0xd3, 0x5b, 0x67, 0xcc, 0xf7, 0x04, 0x1a,
0xda, 0xc3, 0x6b, 0x35, 0x81, 0xfc, 0xe3, 0x6e, 0x35, 0x81, 0x82, 0x77, 0xda, 0xf6, 0x02, 0x76,
0xd1, 0x20, 0x75, 0xe4, 0xef, 0xe4, 0x59, 0x18, 0x93, 0x3d, 0x58, 0x16, 0x67, 0xab, 0x23, 0xfa,
0x71, 0xb6, 0xa1, 0xe0, 0x17, 0x60, 0x6e, 0x59, 0xe4, 0x3d, 0xa8, 0xc9, 0xf7, 0xf5, 0x64, 0xa5,
0xf8, 0x77, 0x02, 0xba, 0xab, 0x39, 0x5c, 0x68, 0xde, 0xaf, 0x00, 0xa4, 0xaf, 0xbc, 0x95, 0x92,
0xc8, 0xbd, 0x1a, 0x57, 0x1c, 0x90, 0x7f, 0x12, 0x6e, 0xaf, 0xe0, 0x04, 0xdb, 0x04, 0x95, 0x44,
0x40, 0xcf, 0xe4, 0x93, 0xa5, 0xaf, 0x43, 0x43, 0x7b, 0xe8, 0xad, 0x96, 0x2f, 0xff, 0x48, 0x5c,
0x2d, 0x5f, 0xc1, 0xbb, 0x70, 0xbb, 0x8b, 0xad, 0x2f, 0xd9, 0xf3, 0xac, 0xf5, 0xd8, 0x1b, 0x06,
0x23, 0x4e, 0xc0, 0x36, 0xe8, 0x04, 0xe6, 0x8c, 0xd7, 0xdc, 0x4a, 0x42, 0x8b, 0xde, 0x8a, 0x2b,
0x09, 0x2d, 0x7c, 0x00, 0x2e, 0xf9, 0xcc, 0x5e, 0x60, 0xfd, 0x9c, 0x22, 0x89, 0xd6, 0xd3, 0x07,
0xd0, 0xd0, 0x5e, 0x66, 0xab, 0xb9, 0xe4, 0x1f, 0x81, 0xab, 0xb9, 0x14, 0x3d, 0xe4, 0x5e, 0xc2,
0x3e, 0x5a, 0x36, 0xb2, 0x02, 0xbe, 0xf8, 0x61, 0x6d, 0x7f, 0x08, 0x2d, 0xf3, 0xad, 0xb6, 0x92,
0xfd, 0xc2, 0x57, 0xdf, 0x4a, 0xf6, 0xa7, 0x3c, 0xf0, 0x16, 0x2c, 0xbd, 0xbe, 0xa8, 0x3a, 0xd9,
0xf8, 0x48, 0xdc, 0x6b, 0x3f, 0x27, 0x5f, 0x62, 0x0a, 0x4e, 0x3c, 0xc1, 0x22, 0xab, 0x1a, 0xd7,
0xea, 0x0f, 0xb5, 0x94, 0xbc, 0xe4, 0x5e, 0x6b, 0x99, 0xcc, 0xcc, 0xdf, 0x2c, 0xa1, 0xd5, 0xc2,
0xa7, 0x58, 0x9a, 0xd5, 0xd2, 0x5f, 0x6b, 0x69, 0x56, 0xcb, 0x78, 0xb1, 0x95, 0xb5, 0x5a, 0x89,
0xc7, 0xda, 0x08, 0x60, 0x3e, 0x93, 0x94, 0xa8, 0xa4, 0xa2, 0x38, 0x8b, 0xbb, 0x7b, 0xf5, 0xc5,
0xb9, 0x8c, 0xa6, 0x06, 0x91, 0x4a, 0x70, 0x43, 0xe6, 0xcc, 0xff, 0x24, 0x34, 0xf5, 0x57, 0xb4,
0x44, 0x17, 0xe5, 0x6c, 0x4f, 0xaf, 0x14, 0xd6, 0x99, 0x9b, 0x4b, 0x9a, 0x7a, 0x37, 0xe4, 0xcb,
0xb0, 0xa2, 0x44, 0x5d, 0xcf, 0x73, 0x8b, 0xc9, 0x6b, 0x05, 0xd9, 0x6f, 0x7a, 0xc4, 0xa5, 0x7b,
0x79, 0x6a, 0x7a, 0xdc, 0x2d, 0x8b, 0x31, 0x8d, 0xf9, 0x3c, 0x31, 0x35, 0x18, 0x45, 0xaf, 0x32,
0x53, 0x83, 0x51, 0xf8, 0xa6, 0x51, 0x32, 0x0d, 0x59, 0x34, 0xd6, 0x88, 0x5f, 0x3d, 0x90, 0x0f,
0x60, 0x5e, 0xcb, 0x24, 0x3e, 0x3c, 0x0f, 0xfa, 0x4a, 0x00, 0xf2, 0x4f, 0x4e, 0xba, 0x45, 0x47,
0x02, 0x7b, 0x15, 0xdb, 0x5f, 0xb0, 0x8d, 0xc5, 0x61, 0xcc, 0xbf, 0x05, 0x0d, 0x3d, 0x4b, 0xf9,
0x05, 0xed, 0xae, 0x6a, 0x55, 0xfa, 0x8b, 0x89, 0x5b, 0x16, 0xf9, 0x4d, 0x0b, 0x9a, 0x46, 0xce,
0xaf, 0x71, 0xc1, 0x96, 0x69, 0xa7, 0xa3, 0xd7, 0xe9, 0x0d, 0xd9, 0x0e, 0x0e, 0x72, 0x6f, 0xfd,
0x0b, 0xc6, 0x22, 0x7c, 0x64, 0x1c, 0x2d, 0x6f, 0x66, 0x7f, 0xd3, 0xe7, 0x79, 0x96, 0x40, 0x7f,
0x96, 0xf3, 0xfc, 0x96, 0x45, 0xbe, 0x6b, 0x41, 0xcb, 0x0c, 0x88, 0xa8, 0xad, 0x2a, 0x0c, 0xbd,
0xa8, 0xad, 0x9a, 0x12, 0x45, 0xf9, 0x00, 0x47, 0xf9, 0x68, 0xdd, 0x31, 0x46, 0x29, 0x1e, 0xae,
0xfe, 0x68, 0xa3, 0x25, 0xef, 0xf2, 0x9f, 0xfd, 0x92, 0x51, 0x3a, 0xa2, 0x59, 0x8d, 0xec, 0xf6,
0xea, 0x3f, 0x55, 0xb5, 0x66, 0xdd, 0xb2, 0xc8, 0xd7, 0xf9, 0x6f, 0xd9, 0x88, 0x6f, 0x91, 0x4b,
0x5e, 0xf6, 0x7b, 0xfb, 0x3a, 0xce, 0xe9, 0xaa, 0x7d, 0xd9, 0x98, 0x53, 0xd6, 0x1e, 0x6f, 0xf2,
0xd1, 0x89, 0x5f, 0x99, 0x4a, 0x0d, 0x4a, 0xee, 0x97, 0xa7, 0xa6, 0x0f, 0x72, 0xc4, 0x07, 0x29,
0xc8, 0x0d, 0x56, 0x7e, 0xc9, 0x66, 0xec, 0x75, 0x1c, 0xeb, 0x75, 0xfb, 0xb5, 0xa9, 0x63, 0xdd,
0xc0, 0xb0, 0x06, 0x1b, 0xf1, 0x01, 0x40, 0x1a, 0x51, 0x27, 0x99, 0x88, 0xae, 0x12, 0xf0, 0x7c,
0xd0, 0xdd, 0x94, 0x17, 0x19, 0xf8, 0x65, 0x2d, 0x7e, 0x95, 0xab, 0xab, 0x07, 0x32, 0x16, 0xac,
0x3b, 0x25, 0x66, 0xe8, 0xdb, 0x70, 0x4a, 0xb2, 0xed, 0x1b, 0xca, 0x4a, 0x05, 0x96, 0x1f, 0xc3,
0xdc, 0x5e, 0x18, 0x3e, 0x9d, 0x8c, 0xd5, 0xed, 0x98, 0x19, 0x71, 0xdc, 0x75, 0xe3, 0x93, 0x6e,
0x66, 0x16, 0xf6, 0x35, 0x6c, 0xaa, 0x4b, 0x3a, 0x5a, 0x53, 0x1b, 0x1f, 0xa5, 0x11, 0xfb, 0xe7,
0xc4, 0x85, 0x05, 0xa5, 0x03, 0xd5, 0xc0, 0xbb, 0x66, 0x33, 0x86, 0xe6, 0xcb, 0x76, 0x61, 0x78,
0xcf, 0x72, 0xb4, 0x1b, 0xb1, 0x6c, 0xf3, 0x96, 0x45, 0x0e, 0xa0, 0xb9, 0x4d, 0xfb, 0xe1, 0x80,
0x8a, 0xb0, 0xdd, 0x62, 0x3a, 0x70, 0x15, 0xef, 0xeb, 0xce, 0x19, 0xa0, 0x69, 0x17, 0xc6, 0xee,
0x79, 0x44, 0xbf, 0xb1, 0xf1, 0x91, 0x08, 0x08, 0x3e, 0x97, 0x76, 0x41, 0x46, 0x4c, 0x0d, 0xbb,
0x90, 0x09, 0xb1, 0x1a, 0x76, 0x21, 0x17, 0x62, 0x35, 0x96, 0x5a, 0x46, 0x6c, 0x89, 0x0f, 0x0b,
0xb9, 0xa8, 0xac, 0x32, 0x09, 0xd3, 0x62, 0xb9, 0xdd, 0x6b, 0xd3, 0x09, 0xcc, 0xde, 0xd6, 0xcd,
0xde, 0x0e, 0x61, 0x6e, 0x9b, 0xf2, 0xc5, 0xe2, 0xc9, 0x3b, 0x99, 0xc4, 0x71, 0x3d, 0x35, 0x28,
0xab, 0xc0, 0xb1, 0xce, 0x34, 0xfc, 0x98, 0x39, 0x43, 0xbe, 0x0a, 0x8d, 0xfb, 0x34, 0x91, 0xd9,
0x3a, 0xca, 0xf5, 0xcc, 0xa4, 0xef, 0x74, 0x0b, 0x92, 0x7d, 0x4c, 0x9e, 0xc1, 0xd6, 0x36, 0xe8,
0x60, 0x48, 0xb9, 0x72, 0xea, 0x79, 0x83, 0xe7, 0xe4, 0x27, 0xb0, 0x71, 0x95, 0x54, 0xb8, 0xa2,
0xa5, 0x6a, 0xe8, 0x8d, 0xcf, 0x67, 0xf0, 0xa2, 0x96, 0x83, 0x70, 0x40, 0x35, 0x17, 0x28, 0x80,
0x86, 0x96, 0x0b, 0xab, 0x04, 0x28, 0x9f, 0xba, 0xac, 0x04, 0xa8, 0x20, 0x75, 0xd6, 0x5e, 0xc3,
0x7e, 0x6c, 0x72, 0x2d, 0xed, 0x87, 0xa7, 0xcb, 0xa6, 0x3d, 0x6d, 0x7c, 0xe4, 0x8e, 0x92, 0xe7,
0xe4, 0x09, 0xbe, 0x5e, 0xd7, 0x33, 0x92, 0x52, 0x5f, 0x3a, 0x9b, 0xbc, 0xa4, 0x16, 0x4b, 0xab,
0x32, 0xfd, 0x6b, 0xde, 0x15, 0x7a, 0x4a, 0x9f, 0x01, 0x38, 0x4c, 0xc2, 0xf1, 0xb6, 0x4b, 0x47,
0x61, 0x90, 0xea, 0xda, 0x34, 0x77, 0x26, 0xd5, 0x5f, 0x5a, 0x02, 0x0d, 0x79, 0xa2, 0x1d, 0x3e,
0x8c, 0x84, 0x2e, 0xc9, 0x5c, 0x53, 0xd3, 0x6b, 0xd4, 0x82, 0x14, 0xa4, 0xd8, 0xdc, 0xb2, 0xc8,
0x26, 0x40, 0x1a, 0x96, 0x57, 0x47, 0x89, 0x5c, 0xc4, 0x5f, 0xa9, 0xbd, 0x82, 0x18, 0xfe, 0x01,
0xd4, 0xd3, 0x38, 0xef, 0x6a, 0x9a, 0xae, 0x6d, 0x44, 0x85, 0x95, 0x05, 0xcf, 0x45, 0x5f, 0xed,
0x36, 0x2e, 0x15, 0x90, 0x1a, 0x5b, 0x2a, 0x0c, 0xa9, 0x7a, 0xb0, 0xc8, 0x07, 0xa8, 0xdc, 0x11,
0xcc, 0x06, 0x91, 0x33, 0x29, 0x88, 0x80, 0x2a, 0x69, 0x2e, 0x0c, 0x20, 0x1a, 0x11, 0x11, 0xc6,
0xad, 0x3c, 0x13, 0x85, 0xa9, 0xe6, 0x11, 0x2c, 0xe4, 0x22, 0x5c, 0x4a, 0xa4, 0xa7, 0x05, 0x1d,
0x95, 0x48, 0x4f, 0x0d, 0x8e, 0xd9, 0xcb, 0xd8, 0xe5, 0xbc, 0x0d, 0x78, 0x02, 0x3a, 0xf3, 0x92,
0xfe, 0xc9, 0xbb, 0xd6, 0xfa, 0xdd, 0x1b, 0x1f, 0xfc, 0xaf, 0xa1, 0x97, 0x9c, 0x4c, 0x8e, 0x6e,
0xf6, 0xc3, 0xd1, 0x86, 0x2f, 0xc3, 0x16, 0x22, 0x1b, 0x6c, 0xc3, 0x0f, 0x06, 0x1b, 0xd8, 0xf2,
0xd1, 0x0c, 0xfe, 0x66, 0xf2, 0xa7, 0xfe, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x79, 0x09, 0x65, 0x5e,
0x65, 0x59, 0x00, 0x00,
var fileDescriptor_rpc_189cdc2900530099 = []byte{
// 7650 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7c, 0x5d, 0x6c, 0x24, 0xd9,
0x59, 0xa8, 0xab, 0x7f, 0xec, 0xee, 0xaf, 0xdb, 0xed, 0xf6, 0xf1, 0x5f, 0x4f, 0xef, 0xec, 0xac,
0xb7, 0x32, 0x77, 0xc7, 0xf1, 0xee, 0x1d, 0xcf, 0x4e, 0x92, 0xcd, 0x64, 0xe7, 0xe6, 0xde, 0xeb,
0xbf, 0x19, 0x4f, 0xe2, 0xf5, 0x38, 0xe5, 0x99, 0x9d, 0x64, 0x93, 0xab, 0x4e, 0xb9, 0xfb, 0xb8,
0x5d, 0x3b, 0xdd, 0x55, 0x9d, 0xaa, 0x6a, 0x7b, 0x9c, 0xbd, 0x23, 0x5d, 0x5d, 0x21, 0x40, 0x08,
0x84, 0x02, 0x42, 0x22, 0x08, 0x84, 0x94, 0x20, 0x41, 0xc4, 0x13, 0x0f, 0x41, 0x48, 0x10, 0x5e,
0x91, 0x22, 0x21, 0x84, 0xf2, 0x08, 0x42, 0x42, 0xf0, 0x02, 0x3c, 0x20, 0x90, 0x78, 0x44, 0x42,
0xe7, 0x3b, 0x3f, 0x75, 0x4e, 0x55, 0xf5, 0x78, 0x36, 0x09, 0x3c, 0x75, 0x9f, 0xef, 0x7c, 0x75,
0x7e, 0xbf, 0xff, 0xf3, 0x9d, 0x03, 0xd5, 0x70, 0xd4, 0xbd, 0x39, 0x0a, 0x83, 0x38, 0x20, 0xe5,
0x81, 0x1f, 0x8e, 0xba, 0xed, 0xab, 0xfd, 0x20, 0xe8, 0x0f, 0xe8, 0x86, 0x3b, 0xf2, 0x36, 0x5c,
0xdf, 0x0f, 0x62, 0x37, 0xf6, 0x02, 0x3f, 0xe2, 0x48, 0xf6, 0xd7, 0xa1, 0x71, 0x9f, 0xfa, 0x47,
0x94, 0xf6, 0x1c, 0xfa, 0x8d, 0x31, 0x8d, 0x62, 0xf2, 0x26, 0xcc, 0xbb, 0xf4, 0x9b, 0x94, 0xf6,
0x3a, 0x23, 0x37, 0x8a, 0x46, 0xa7, 0xa1, 0x1b, 0xd1, 0x96, 0xb5, 0x6a, 0xad, 0xd5, 0x9d, 0x26,
0xaf, 0x38, 0x54, 0x70, 0xf2, 0x3a, 0xd4, 0x23, 0x86, 0x4a, 0xfd, 0x38, 0x0c, 0x46, 0x17, 0xad,
0x02, 0xe2, 0xd5, 0x18, 0x6c, 0x97, 0x83, 0xec, 0x01, 0xcc, 0xa9, 0x1e, 0xa2, 0x51, 0xe0, 0x47,
0x94, 0xdc, 0x82, 0xc5, 0xae, 0x37, 0x3a, 0xa5, 0x61, 0x07, 0x3f, 0x1e, 0xfa, 0x74, 0x18, 0xf8,
0x5e, 0xb7, 0x65, 0xad, 0x16, 0xd7, 0xaa, 0x0e, 0xe1, 0x75, 0xec, 0x8b, 0xf7, 0x44, 0x0d, 0xb9,
0x01, 0x73, 0xd4, 0xe7, 0x70, 0xda, 0xc3, 0xaf, 0x44, 0x57, 0x8d, 0x04, 0xcc, 0x3e, 0xb0, 0x7f,
0xbe, 0x00, 0xf3, 0x0f, 0x7c, 0x2f, 0x7e, 0xe2, 0x0e, 0x06, 0x34, 0x96, 0x73, 0xba, 0x01, 0x73,
0xe7, 0x08, 0xc0, 0x39, 0x9d, 0x07, 0x61, 0x4f, 0xcc, 0xa8, 0xc1, 0xc1, 0x87, 0x02, 0x3a, 0x71,
0x64, 0x85, 0x89, 0x23, 0xcb, 0x5d, 0xae, 0xe2, 0x84, 0xe5, 0xba, 0x01, 0x73, 0x21, 0xed, 0x06,
0x67, 0x34, 0xbc, 0xe8, 0x9c, 0x7b, 0x7e, 0x2f, 0x38, 0x6f, 0x95, 0x56, 0xad, 0xb5, 0xb2, 0xd3,
0x90, 0xe0, 0x27, 0x08, 0x25, 0x5b, 0x30, 0xd7, 0x3d, 0x75, 0x7d, 0x9f, 0x0e, 0x3a, 0xc7, 0x6e,
0xf7, 0xe9, 0x78, 0x14, 0xb5, 0xca, 0xab, 0xd6, 0x5a, 0xed, 0xf6, 0x95, 0x9b, 0xb8, 0xab, 0x37,
0xb7, 0x4f, 0x5d, 0x7f, 0x0b, 0x6b, 0x8e, 0x7c, 0x77, 0x14, 0x9d, 0x06, 0xb1, 0xd3, 0x10, 0x5f,
0x70, 0x70, 0x64, 0x2f, 0x02, 0xd1, 0x57, 0x82, 0xaf, 0xbd, 0xfd, 0xfb, 0x16, 0x2c, 0x3c, 0xf6,
0x07, 0x41, 0xf7, 0xe9, 0x8f, 0xb9, 0x44, 0x39, 0x73, 0x28, 0xbc, 0xec, 0x1c, 0x8a, 0x1f, 0x77,
0x0e, 0xcb, 0xb0, 0x68, 0x0e, 0x56, 0xcc, 0x82, 0xc2, 0x12, 0xfb, 0xba, 0x4f, 0xe5, 0xb0, 0xe4,
0x34, 0x3e, 0x09, 0xcd, 0xee, 0x38, 0x0c, 0xa9, 0x9f, 0x99, 0xc7, 0x9c, 0x80, 0xab, 0x89, 0xbc,
0x0e, 0x75, 0x9f, 0x9e, 0x27, 0x68, 0x82, 0x76, 0x7d, 0x7a, 0x2e, 0x51, 0xec, 0x16, 0x2c, 0xa7,
0xbb, 0x11, 0x03, 0xf8, 0x5b, 0x0b, 0x4a, 0x8f, 0xe3, 0x67, 0x01, 0xb9, 0x09, 0xa5, 0xf8, 0x62,
0xc4, 0x39, 0xa4, 0x71, 0x9b, 0x88, 0xa9, 0x6d, 0xf6, 0x7a, 0x21, 0x8d, 0xa2, 0x47, 0x17, 0x23,
0xea, 0xd4, 0x5d, 0x5e, 0xe8, 0x30, 0x3c, 0xd2, 0x82, 0x19, 0x51, 0xc6, 0x0e, 0xab, 0x8e, 0x2c,
0x92, 0x6b, 0x00, 0xee, 0x30, 0x18, 0xfb, 0x71, 0x27, 0x72, 0x63, 0x5c, 0xaa, 0xa2, 0xa3, 0x41,
0xc8, 0x55, 0xa8, 0x8e, 0x9e, 0x76, 0xa2, 0x6e, 0xe8, 0x8d, 0x62, 0x24, 0x9b, 0xaa, 0x93, 0x00,
0xc8, 0x9b, 0x50, 0x09, 0xc6, 0xf1, 0x28, 0xf0, 0xfc, 0x58, 0x90, 0xca, 0x9c, 0x18, 0xcb, 0xc3,
0x71, 0x7c, 0xc8, 0xc0, 0x8e, 0x42, 0x20, 0xd7, 0x61, 0xb6, 0x1b, 0xf8, 0x27, 0x5e, 0x38, 0xe4,
0xc2, 0xa0, 0x35, 0x8d, 0xbd, 0x99, 0x40, 0xfb, 0xdb, 0x05, 0xa8, 0x3d, 0x0a, 0x5d, 0x3f, 0x72,
0xbb, 0x0c, 0xc0, 0x86, 0x1e, 0x3f, 0xeb, 0x9c, 0xba, 0xd1, 0x29, 0xce, 0xb6, 0xea, 0xc8, 0x22,
0x59, 0x86, 0x69, 0x3e, 0x50, 0x9c, 0x53, 0xd1, 0x11, 0x25, 0xf2, 0x16, 0xcc, 0xfb, 0xe3, 0x61,
0xc7, 0xec, 0xab, 0x88, 0xd4, 0x92, 0xad, 0x60, 0x0b, 0x70, 0xcc, 0xf6, 0x9a, 0x77, 0xc1, 0x67,
0xa8, 0x41, 0x88, 0x0d, 0x75, 0x51, 0xa2, 0x5e, 0xff, 0x94, 0x4f, 0xb3, 0xec, 0x18, 0x30, 0xd6,
0x46, 0xec, 0x0d, 0x69, 0x27, 0x8a, 0xdd, 0xe1, 0x48, 0x4c, 0x4b, 0x83, 0x60, 0x7d, 0x10, 0xbb,
0x83, 0xce, 0x09, 0xa5, 0x51, 0x6b, 0x46, 0xd4, 0x2b, 0x08, 0x79, 0x03, 0x1a, 0x3d, 0x1a, 0xc5,
0x1d, 0xb1, 0x29, 0x34, 0x6a, 0x55, 0x90, 0xf5, 0x53, 0x50, 0x46, 0x19, 0xf7, 0x69, 0xac, 0xad,
0x4e, 0x24, 0x28, 0xd0, 0xde, 0x07, 0xa2, 0x81, 0x77, 0x68, 0xec, 0x7a, 0x83, 0x88, 0xbc, 0x03,
0xf5, 0x58, 0x43, 0x46, 0x51, 0x57, 0x53, 0xe4, 0xa2, 0x7d, 0xe0, 0x18, 0x78, 0xf6, 0x7d, 0xa8,
0xdc, 0xa3, 0x74, 0xdf, 0x1b, 0x7a, 0x31, 0x59, 0x86, 0xf2, 0x89, 0xf7, 0x8c, 0x72, 0x82, 0x2e,
0xee, 0x4d, 0x39, 0xbc, 0x48, 0xda, 0x30, 0x33, 0xa2, 0x61, 0x97, 0xca, 0xe5, 0xdf, 0x9b, 0x72,
0x24, 0x60, 0x6b, 0x06, 0xca, 0x03, 0xf6, 0xb1, 0xfd, 0x2f, 0x05, 0xa8, 0x1d, 0x51, 0x5f, 0x31,
0x0a, 0x81, 0x12, 0x9b, 0x92, 0x60, 0x0e, 0xfc, 0x4f, 0x5e, 0x83, 0x1a, 0x4e, 0x33, 0x8a, 0x43,
0xcf, 0xef, 0x0b, 0xfa, 0x04, 0x06, 0x3a, 0x42, 0x08, 0x69, 0x42, 0xd1, 0x1d, 0x4a, 0xda, 0x64,
0x7f, 0x19, 0x13, 0x8d, 0xdc, 0x8b, 0x21, 0xe3, 0x37, 0xb5, 0x6b, 0x75, 0xa7, 0x26, 0x60, 0x7b,
0x6c, 0xdb, 0x6e, 0xc2, 0x82, 0x8e, 0x22, 0x5b, 0x2f, 0x63, 0xeb, 0xf3, 0x1a, 0xa6, 0xe8, 0xe4,
0x06, 0xcc, 0x49, 0xfc, 0x90, 0x0f, 0x16, 0xf7, 0xb1, 0xea, 0x34, 0x04, 0x58, 0x4e, 0x61, 0x0d,
0x9a, 0x27, 0x9e, 0xef, 0x0e, 0x3a, 0xdd, 0x41, 0x7c, 0xd6, 0xe9, 0xd1, 0x41, 0xec, 0xe2, 0x8e,
0x96, 0x9d, 0x06, 0xc2, 0xb7, 0x07, 0xf1, 0xd9, 0x0e, 0x83, 0x92, 0xb7, 0xa0, 0x7a, 0x42, 0x69,
0x07, 0x57, 0xa2, 0x55, 0x31, 0xb8, 0x43, 0xae, 0xae, 0x53, 0x39, 0x91, 0xeb, 0xbc, 0x06, 0xcd,
0x60, 0x1c, 0xf7, 0x03, 0xcf, 0xef, 0x77, 0x98, 0x3c, 0xea, 0x78, 0xbd, 0x56, 0x75, 0xd5, 0x5a,
0x2b, 0x39, 0x0d, 0x09, 0x67, 0x52, 0xe1, 0x41, 0x8f, 0xbc, 0x0a, 0x80, 0x7d, 0xf3, 0x86, 0x61,
0xd5, 0x5a, 0x9b, 0x75, 0xaa, 0x0c, 0x82, 0x0d, 0xd9, 0x7f, 0x64, 0x41, 0x9d, 0xaf, 0xb9, 0x50,
0x7c, 0xd7, 0x61, 0x56, 0x4e, 0x8d, 0x86, 0x61, 0x10, 0x0a, 0x3e, 0x32, 0x81, 0x64, 0x1d, 0x9a,
0x12, 0x30, 0x0a, 0xa9, 0x37, 0x74, 0xfb, 0x54, 0x08, 0xa7, 0x0c, 0x9c, 0xdc, 0x4e, 0x5a, 0x0c,
0x83, 0x71, 0x4c, 0x85, 0x88, 0xad, 0x8b, 0xd9, 0x39, 0x0c, 0xe6, 0x98, 0x28, 0x8c, 0x8f, 0x72,
0xf6, 0xcc, 0x80, 0xd9, 0xdf, 0xb7, 0x80, 0xb0, 0xa1, 0x3f, 0x0a, 0x78, 0x13, 0x62, 0xc9, 0xd3,
0xdb, 0x6d, 0xbd, 0xf4, 0x76, 0x17, 0x26, 0x6d, 0xf7, 0x1a, 0x4c, 0xe3, 0xb0, 0x98, 0x60, 0x28,
0xa6, 0x87, 0xbe, 0x55, 0x68, 0x59, 0x8e, 0xa8, 0x27, 0x36, 0x94, 0xf9, 0x1c, 0x4b, 0x39, 0x73,
0xe4, 0x55, 0xf6, 0x77, 0x2c, 0xa8, 0x6f, 0x73, 0x1d, 0x82, 0x42, 0x8f, 0xdc, 0x02, 0x72, 0x32,
0xf6, 0x7b, 0x6c, 0x2f, 0xe3, 0x67, 0x5e, 0xaf, 0x73, 0x7c, 0xc1, 0xba, 0xc2, 0x71, 0xef, 0x4d,
0x39, 0x39, 0x75, 0xe4, 0x2d, 0x68, 0x1a, 0xd0, 0x28, 0x0e, 0xf9, 0xe8, 0xf7, 0xa6, 0x9c, 0x4c,
0x0d, 0x5b, 0x4c, 0x26, 0x56, 0xc7, 0x71, 0xc7, 0xf3, 0x7b, 0xf4, 0x19, 0xae, 0xff, 0xac, 0x63,
0xc0, 0xb6, 0x1a, 0x50, 0xd7, 0xbf, 0xb3, 0x3f, 0x84, 0x8a, 0x14, 0xca, 0x28, 0x90, 0x52, 0xe3,
0x72, 0x34, 0x08, 0x69, 0x43, 0xc5, 0x1c, 0x85, 0x53, 0xf9, 0x38, 0x7d, 0xdb, 0xff, 0x13, 0x9a,
0xfb, 0x4c, 0x32, 0xfa, 0x9e, 0xdf, 0x17, 0x5a, 0x89, 0x89, 0xeb, 0xd1, 0xf8, 0xf8, 0x29, 0xbd,
0x10, 0xf4, 0x27, 0x4a, 0x4c, 0x26, 0x9c, 0x06, 0x51, 0x2c, 0xfa, 0xc1, 0xff, 0xf6, 0x9f, 0x59,
0x40, 0x76, 0xa3, 0xd8, 0x1b, 0xba, 0x31, 0xbd, 0x47, 0x15, 0x21, 0x3c, 0x84, 0x3a, 0x6b, 0xed,
0x51, 0xb0, 0xc9, 0xe5, 0x3e, 0x97, 0x67, 0x6f, 0x8a, 0x2d, 0xc9, 0x7e, 0x70, 0x53, 0xc7, 0x66,
0xa6, 0xe1, 0x85, 0x63, 0x34, 0xc0, 0x64, 0x4f, 0xec, 0x86, 0x7d, 0x1a, 0xa3, 0x52, 0x10, 0x26,
0x05, 0x70, 0xd0, 0x76, 0xe0, 0x9f, 0xb4, 0xff, 0x17, 0xcc, 0x67, 0xda, 0x60, 0x02, 0x29, 0x99,
0x06, 0xfb, 0x4b, 0x16, 0xa1, 0x7c, 0xe6, 0x0e, 0xc6, 0x54, 0x68, 0x22, 0x5e, 0x78, 0xb7, 0x70,
0xc7, 0xb2, 0xbb, 0xb0, 0x60, 0x8c, 0x4b, 0xf0, 0x64, 0x0b, 0x66, 0x98, 0x6c, 0x60, 0x3a, 0x17,
0xe5, 0xaa, 0x23, 0x8b, 0xe4, 0x36, 0x2c, 0x9e, 0x50, 0x1a, 0xba, 0x31, 0x16, 0x3b, 0x23, 0x1a,
0xe2, 0x9e, 0x88, 0x96, 0x73, 0xeb, 0xec, 0xbf, 0xb3, 0x60, 0x8e, 0xf1, 0xcd, 0x7b, 0xae, 0x7f,
0x21, 0xd7, 0x6a, 0x3f, 0x77, 0xad, 0xd6, 0xc4, 0x5a, 0xa5, 0xb0, 0x3f, 0xee, 0x42, 0x15, 0xd3,
0x0b, 0x45, 0x56, 0xa1, 0x6e, 0x0c, 0xb7, 0xcc, 0x95, 0x5c, 0xe4, 0xc6, 0x87, 0x34, 0xdc, 0xba,
0x88, 0xe9, 0x4f, 0xbe, 0x94, 0x6f, 0x40, 0x33, 0x19, 0xb6, 0x58, 0x47, 0x02, 0x25, 0x46, 0x98,
0xa2, 0x01, 0xfc, 0x6f, 0xff, 0xa6, 0xc5, 0x11, 0xb7, 0x03, 0x4f, 0x29, 0x48, 0x86, 0xc8, 0xf4,
0xa8, 0x44, 0x64, 0xff, 0x27, 0x1a, 0x10, 0x3f, 0xf9, 0x64, 0xc9, 0x15, 0xa8, 0x44, 0xd4, 0xef,
0x75, 0xdc, 0xc1, 0x00, 0xf5, 0x48, 0xc5, 0x99, 0x61, 0xe5, 0xcd, 0xc1, 0xc0, 0xbe, 0x01, 0xf3,
0xda, 0xe8, 0x5e, 0x30, 0x8f, 0x03, 0x20, 0xfb, 0x5e, 0x14, 0x3f, 0xf6, 0xa3, 0x91, 0xa6, 0x7f,
0x5e, 0x81, 0xea, 0xd0, 0xf3, 0x71, 0x64, 0x9c, 0x73, 0xcb, 0x4e, 0x65, 0xe8, 0xf9, 0x6c, 0x5c,
0x11, 0x56, 0xba, 0xcf, 0x44, 0x65, 0x41, 0x54, 0xba, 0xcf, 0xb0, 0xd2, 0xbe, 0x03, 0x0b, 0x46,
0x7b, 0xa2, 0xeb, 0xd7, 0xa1, 0x3c, 0x8e, 0x9f, 0x05, 0xd2, 0x3a, 0xa8, 0x09, 0x0a, 0x61, 0x76,
0xa6, 0xc3, 0x6b, 0xec, 0xbb, 0x30, 0x7f, 0x40, 0xcf, 0x05, 0x23, 0xcb, 0x81, 0xbc, 0x71, 0xa9,
0x0d, 0x8a, 0xf5, 0xf6, 0x4d, 0x20, 0xfa, 0xc7, 0x09, 0x03, 0x48, 0x8b, 0xd4, 0x32, 0x2c, 0x52,
0xfb, 0x0d, 0x20, 0x47, 0x5e, 0xdf, 0x7f, 0x8f, 0x46, 0x91, 0xdb, 0x57, 0xac, 0xdf, 0x84, 0xe2,
0x30, 0xea, 0x0b, 0x51, 0xc5, 0xfe, 0xda, 0x9f, 0x82, 0x05, 0x03, 0x4f, 0x34, 0x7c, 0x15, 0xaa,
0x91, 0xd7, 0xf7, 0xdd, 0x78, 0x1c, 0x52, 0xd1, 0x74, 0x02, 0xb0, 0xef, 0xc1, 0xe2, 0xfb, 0x34,
0xf4, 0x4e, 0x2e, 0x2e, 0x6b, 0xde, 0x6c, 0xa7, 0x90, 0x6e, 0x67, 0x17, 0x96, 0x52, 0xed, 0x88,
0xee, 0x39, 0xf9, 0x8a, 0x9d, 0xac, 0x38, 0xbc, 0xa0, 0xc9, 0xbe, 0x82, 0x2e, 0xfb, 0xec, 0xc7,
0x40, 0xb6, 0x03, 0xdf, 0xa7, 0xdd, 0xf8, 0x90, 0xd2, 0x30, 0x71, 0x86, 0x13, 0x5a, 0xad, 0xdd,
0x5e, 0x11, 0x2b, 0x9b, 0x16, 0xa8, 0x82, 0x88, 0x09, 0x94, 0x46, 0x34, 0x1c, 0x62, 0xc3, 0x15,
0x07, 0xff, 0xdb, 0x4b, 0xb0, 0x60, 0x34, 0x2b, 0xdc, 0x87, 0xb7, 0x61, 0x69, 0xc7, 0x8b, 0xba,
0xd9, 0x0e, 0x5b, 0x30, 0x33, 0x1a, 0x1f, 0x77, 0x12, 0x4e, 0x94, 0x45, 0x66, 0x71, 0xa6, 0x3f,
0x11, 0x8d, 0xfd, 0xac, 0x05, 0xa5, 0xbd, 0x47, 0xfb, 0xdb, 0x4c, 0x57, 0x78, 0x7e, 0x37, 0x18,
0x32, 0x7d, 0xcb, 0x27, 0xad, 0xca, 0x13, 0x39, 0xec, 0x2a, 0x54, 0x51, 0x4d, 0x33, 0x23, 0x5a,
0xf8, 0xad, 0x09, 0x80, 0x19, 0xf0, 0xf4, 0xd9, 0xc8, 0x0b, 0xd1, 0x42, 0x97, 0x76, 0x77, 0x09,
0xd5, 0x4c, 0xb6, 0xc2, 0xfe, 0x61, 0x19, 0x66, 0x84, 0xf2, 0xc5, 0xfe, 0xba, 0xb1, 0x77, 0x46,
0xc5, 0x48, 0x44, 0x89, 0x99, 0x40, 0x21, 0x1d, 0x06, 0x31, 0xed, 0x18, 0xdb, 0x60, 0x02, 0xd1,
0x41, 0x11, 0xbe, 0x23, 0x77, 0x69, 0x8a, 0x1c, 0xcb, 0x00, 0xb2, 0xc5, 0x92, 0xf6, 0x59, 0x09,
0xed, 0x33, 0x59, 0x64, 0x2b, 0xd1, 0x75, 0x47, 0x6e, 0xd7, 0x8b, 0x2f, 0x84, 0x48, 0x50, 0x65,
0xd6, 0xf6, 0x20, 0xe8, 0xba, 0xcc, 0x2b, 0x1d, 0xb8, 0x7e, 0x97, 0x4a, 0xe7, 0xc7, 0x00, 0x32,
0x47, 0x40, 0x0c, 0x49, 0xa2, 0x71, 0x67, 0x21, 0x05, 0x65, 0xfa, 0xbb, 0x1b, 0x0c, 0x87, 0x5e,
0xcc, 0xfc, 0x07, 0xb4, 0x2d, 0x8b, 0x8e, 0x06, 0xe1, 0xae, 0x16, 0x96, 0xce, 0xf9, 0xea, 0x55,
0xa5, 0xab, 0xa5, 0x01, 0x59, 0x2b, 0x4c, 0xeb, 0x30, 0x31, 0xf6, 0xf4, 0x1c, 0x0d, 0xc9, 0xa2,
0xa3, 0x41, 0xd8, 0x3e, 0x8c, 0xfd, 0x88, 0xc6, 0xf1, 0x80, 0xf6, 0xd4, 0x80, 0x6a, 0x88, 0x96,
0xad, 0x20, 0xb7, 0x60, 0x81, 0xbb, 0x34, 0x91, 0x1b, 0x07, 0xd1, 0xa9, 0x17, 0x75, 0x22, 0xe6,
0x1c, 0xd4, 0x11, 0x3f, 0xaf, 0x8a, 0xdc, 0x81, 0x95, 0x14, 0x38, 0xa4, 0x5d, 0xea, 0x9d, 0xd1,
0x5e, 0x6b, 0x16, 0xbf, 0x9a, 0x54, 0x4d, 0x56, 0xa1, 0xc6, 0x3c, 0xb9, 0xf1, 0xa8, 0xe7, 0x32,
0x03, 0xa6, 0x81, 0xfb, 0xa0, 0x83, 0xc8, 0xdb, 0x30, 0x3b, 0xa2, 0xdc, 0xfa, 0x39, 0x8d, 0x07,
0xdd, 0xa8, 0x35, 0x67, 0x48, 0x37, 0x46, 0xb9, 0x8e, 0x89, 0xc1, 0x88, 0xb2, 0x1b, 0xa1, 0x49,
0xef, 0x5e, 0xb4, 0x9a, 0xc2, 0xac, 0x96, 0x00, 0xe4, 0x91, 0xd0, 0x3b, 0x73, 0x63, 0xda, 0x9a,
0xe7, 0x02, 0x5d, 0x14, 0xd9, 0x77, 0x9e, 0xef, 0xc5, 0x9e, 0x1b, 0x07, 0x61, 0x8b, 0x60, 0x5d,
0x02, 0x60, 0x8b, 0x88, 0xf4, 0x11, 0xc5, 0x6e, 0x3c, 0x8e, 0x3a, 0x27, 0x03, 0xb7, 0x1f, 0xb5,
0x16, 0xb8, 0x5d, 0x9a, 0xa9, 0xb0, 0x7f, 0xdb, 0xe2, 0x42, 0x5a, 0x10, 0xb4, 0x12, 0xb6, 0xaf,
0x41, 0x8d, 0x93, 0x72, 0x27, 0xf0, 0x07, 0x17, 0x82, 0xba, 0x81, 0x83, 0x1e, 0xfa, 0x83, 0x0b,
0xf2, 0x09, 0x98, 0xf5, 0x7c, 0x1d, 0x85, 0xcb, 0x83, 0xba, 0x04, 0x22, 0xd2, 0x6b, 0x50, 0x1b,
0x8d, 0x8f, 0x07, 0x5e, 0x97, 0xa3, 0x14, 0x79, 0x2b, 0x1c, 0x84, 0x08, 0xcc, 0xd2, 0xe6, 0xb3,
0xe2, 0x18, 0x25, 0xc4, 0xa8, 0x09, 0x18, 0x43, 0xb1, 0xb7, 0x60, 0xd1, 0x1c, 0xa0, 0x10, 0x7c,
0xeb, 0x50, 0x11, 0x7c, 0x12, 0xb5, 0x6a, 0xb8, 0xd6, 0x0d, 0x2d, 0xe2, 0xe2, 0xd3, 0x81, 0xa3,
0xea, 0xed, 0x3f, 0x2c, 0xc1, 0x82, 0x80, 0x6e, 0x0f, 0x82, 0x88, 0x1e, 0x8d, 0x87, 0x43, 0x37,
0xcc, 0x61, 0x40, 0xeb, 0x12, 0x06, 0x2c, 0x98, 0x0c, 0xc8, 0xd8, 0xe2, 0xd4, 0xf5, 0x7c, 0xee,
0x26, 0x70, 0xee, 0xd5, 0x20, 0x64, 0x0d, 0xe6, 0xba, 0x83, 0x20, 0xe2, 0x26, 0xb1, 0xee, 0xf0,
0xa7, 0xc1, 0x59, 0x81, 0x51, 0xce, 0x13, 0x18, 0x3a, 0xc3, 0x4f, 0xa7, 0x18, 0xde, 0x86, 0x3a,
0x6b, 0x94, 0x4a, 0xf9, 0x35, 0xc3, 0xcd, 0x64, 0x1d, 0xc6, 0xc6, 0x93, 0x66, 0x2f, 0xce, 0xcb,
0x73, 0x79, 0xcc, 0xe5, 0x0d, 0x29, 0xca, 0x47, 0x0d, 0xbb, 0x2a, 0x98, 0x2b, 0x5b, 0x45, 0xee,
0x31, 0x2f, 0x91, 0xf5, 0x85, 0x4a, 0x1a, 0x50, 0x49, 0xbf, 0x61, 0xee, 0x88, 0xbe, 0xf6, 0x37,
0x59, 0x61, 0x1c, 0x52, 0x54, 0xdc, 0xda, 0x97, 0xf6, 0x2f, 0x58, 0x50, 0xd3, 0xea, 0xc8, 0x12,
0xcc, 0x6f, 0x3f, 0x7c, 0x78, 0xb8, 0xeb, 0x6c, 0x3e, 0x7a, 0xf0, 0xfe, 0x6e, 0x67, 0x7b, 0xff,
0xe1, 0xd1, 0x6e, 0x73, 0x8a, 0x81, 0xf7, 0x1f, 0x6e, 0x6f, 0xee, 0x77, 0xee, 0x3d, 0x74, 0xb6,
0x25, 0xd8, 0x22, 0xcb, 0x40, 0x9c, 0xdd, 0xf7, 0x1e, 0x3e, 0xda, 0x35, 0xe0, 0x05, 0xd2, 0x84,
0xfa, 0x96, 0xb3, 0xbb, 0xb9, 0xbd, 0x27, 0x20, 0x45, 0xb2, 0x08, 0xcd, 0x7b, 0x8f, 0x0f, 0x76,
0x1e, 0x1c, 0xdc, 0xef, 0x6c, 0x6f, 0x1e, 0x6c, 0xef, 0xee, 0xef, 0xee, 0x34, 0x4b, 0x64, 0x16,
0xaa, 0x9b, 0x5b, 0x9b, 0x07, 0x3b, 0x0f, 0x0f, 0x76, 0x77, 0x9a, 0x65, 0xfb, 0x6f, 0x2c, 0x58,
0xc2, 0x51, 0xf7, 0xd2, 0x0c, 0xb2, 0x0a, 0xb5, 0x6e, 0x10, 0x8c, 0x98, 0x71, 0x9c, 0x88, 0x7f,
0x1d, 0xc4, 0x88, 0x9f, 0x0b, 0xdb, 0x93, 0x20, 0xec, 0x52, 0xc1, 0x1f, 0x80, 0xa0, 0x7b, 0x0c,
0xc2, 0x88, 0x5f, 0x6c, 0x2f, 0xc7, 0xe0, 0xec, 0x51, 0xe3, 0x30, 0x8e, 0xb2, 0x0c, 0xd3, 0xc7,
0x21, 0x75, 0xbb, 0xa7, 0x82, 0x33, 0x44, 0x89, 0x7c, 0x32, 0xf1, 0xde, 0xba, 0x6c, 0xf5, 0x07,
0xb4, 0x87, 0x14, 0x53, 0x71, 0xe6, 0x04, 0x7c, 0x5b, 0x80, 0x99, 0xb4, 0x70, 0x8f, 0x5d, 0xbf,
0x17, 0xf8, 0xb4, 0x27, 0x4c, 0xc3, 0x04, 0x60, 0x1f, 0xc2, 0x72, 0x7a, 0x7e, 0x82, 0xbf, 0xde,
0xd1, 0xf8, 0x8b, 0x5b, 0x6a, 0xed, 0xc9, 0xbb, 0xa9, 0xf1, 0xda, 0x3f, 0x5a, 0x50, 0x62, 0x8a,
0x7b, 0xb2, 0x92, 0xd7, 0x6d, 0xb1, 0x62, 0x26, 0x3a, 0x88, 0x0e, 0x21, 0x17, 0xe5, 0x5c, 0xdd,
0x69, 0x90, 0xa4, 0x3e, 0xa4, 0xdd, 0x33, 0x9c, 0xb1, 0xaa, 0x67, 0x10, 0xc6, 0x20, 0xcc, 0x50,
0xc6, 0xaf, 0x05, 0x83, 0xc8, 0xb2, 0xac, 0xc3, 0x2f, 0x67, 0x92, 0x3a, 0xfc, 0xae, 0x05, 0x33,
0x9e, 0x7f, 0x1c, 0x8c, 0xfd, 0x1e, 0x32, 0x44, 0xc5, 0x91, 0x45, 0x8c, 0x47, 0x22, 0xa3, 0x7a,
0x43, 0x49, 0xfe, 0x09, 0xc0, 0x26, 0xcc, 0xef, 0x8c, 0xd0, 0x50, 0x51, 0xa1, 0xb1, 0x77, 0x60,
0x5e, 0x83, 0x25, 0x46, 0xef, 0x88, 0x01, 0x52, 0x46, 0x2f, 0x5a, 0x38, 0xbc, 0xc6, 0x6e, 0x42,
0xe3, 0x3e, 0x8d, 0x1f, 0xf8, 0x27, 0x81, 0x6c, 0xe9, 0x77, 0x4b, 0x30, 0xa7, 0x40, 0xa2, 0xa1,
0x35, 0x98, 0xf3, 0x7a, 0xd4, 0x8f, 0xbd, 0xf8, 0xa2, 0x63, 0xb8, 0xb7, 0x69, 0x30, 0xb3, 0x0c,
0xdd, 0x81, 0xe7, 0xca, 0x08, 0x2c, 0x2f, 0x30, 0x77, 0x8f, 0xa9, 0x2d, 0xa9, 0x89, 0xd4, 0x16,
0x73, 0xaf, 0x3a, 0xb7, 0x8e, 0x09, 0x03, 0x06, 0x17, 0xd2, 0x5e, 0x7d, 0xc2, 0x2d, 0xa4, 0xbc,
0x2a, 0xb6, 0x6a, 0xbc, 0x25, 0x36, 0xe5, 0x32, 0x57, 0x6d, 0x0a, 0x90, 0x09, 0x71, 0x4e, 0x73,
0x51, 0x95, 0x0e, 0x71, 0x6a, 0x61, 0xd2, 0x4a, 0x26, 0x4c, 0xca, 0x44, 0xd9, 0x85, 0xdf, 0xa5,
0xbd, 0x4e, 0x1c, 0x74, 0x50, 0xe4, 0xe2, 0xee, 0x54, 0x9c, 0x34, 0x98, 0x5c, 0x85, 0x99, 0x98,
0x46, 0xb1, 0x4f, 0x79, 0xec, 0xaa, 0x82, 0xd1, 0x16, 0x09, 0x62, 0xe6, 0xec, 0x38, 0xf4, 0xa2,
0x56, 0x1d, 0x03, 0xa0, 0xf8, 0x9f, 0x7c, 0x1a, 0x96, 0x8e, 0x69, 0x14, 0x77, 0x4e, 0xa9, 0xdb,
0xa3, 0x21, 0xee, 0x34, 0x8f, 0xb4, 0x72, 0x2b, 0x21, 0xbf, 0x92, 0xd1, 0xd0, 0x19, 0x0d, 0x23,
0x2f, 0xf0, 0xd1, 0x3e, 0xa8, 0x3a, 0xb2, 0xc8, 0xda, 0x63, 0x93, 0x57, 0xfa, 0x52, 0xad, 0xe0,
0x1c, 0x4e, 0x3c, 0xbf, 0x92, 0x5c, 0x87, 0x69, 0x9c, 0x40, 0xd4, 0x6a, 0x1a, 0x21, 0xa3, 0x6d,
0x06, 0x74, 0x44, 0xdd, 0x17, 0x4a, 0x95, 0x5a, 0xb3, 0x6e, 0x7f, 0x16, 0xca, 0x08, 0x66, 0x9b,
0xce, 0x17, 0x83, 0x13, 0x05, 0x2f, 0xb0, 0xa1, 0xf9, 0x34, 0x3e, 0x0f, 0xc2, 0xa7, 0x32, 0x1c,
0x2f, 0x8a, 0xf6, 0x37, 0xd1, 0x21, 0x50, 0xe1, 0xe9, 0xc7, 0x68, 0xcd, 0x30, 0xb7, 0x8e, 0x2f,
0x75, 0x74, 0xea, 0x0a, 0x1f, 0xa5, 0x82, 0x80, 0xa3, 0x53, 0x97, 0x89, 0x2d, 0x63, 0xf7, 0xb8,
0xdb, 0x57, 0x43, 0xd8, 0x1e, 0xdf, 0xbc, 0xeb, 0xd0, 0x90, 0x81, 0xef, 0xa8, 0x33, 0xa0, 0x27,
0xb1, 0x0c, 0xda, 0xf8, 0xe3, 0x21, 0xfa, 0x86, 0xfb, 0xf4, 0x24, 0xb6, 0x0f, 0x60, 0x5e, 0x88,
0x92, 0x87, 0x23, 0x2a, 0xbb, 0xfe, 0x5c, 0x9e, 0x4a, 0xae, 0xdd, 0x5e, 0x30, 0x65, 0x0f, 0x0f,
0xf5, 0x9b, 0x98, 0xb6, 0x03, 0x44, 0x17, 0x4d, 0xa2, 0x41, 0xa1, 0x17, 0x65, 0x58, 0x4a, 0x4c,
0xc7, 0x80, 0xb1, 0xf5, 0x89, 0xc6, 0xdd, 0xae, 0x3c, 0xae, 0x60, 0xce, 0x33, 0x2f, 0xda, 0xbf,
0x67, 0xc1, 0x02, 0xb6, 0x26, 0x8d, 0x0a, 0x21, 0xfe, 0xef, 0x7c, 0x8c, 0x61, 0xd6, 0xbb, 0x7a,
0xa8, 0x6e, 0x11, 0xca, 0xba, 0x42, 0xe0, 0x85, 0x8f, 0x1f, 0x02, 0x28, 0xa5, 0x43, 0x00, 0xf6,
0xaf, 0x5b, 0x30, 0xcf, 0x65, 0x32, 0x1a, 0x78, 0x62, 0xfa, 0xff, 0x03, 0x66, 0xb9, 0x72, 0x15,
0x5c, 0x2d, 0x06, 0xba, 0xa8, 0x04, 0x10, 0x42, 0x39, 0xf2, 0xde, 0x94, 0x63, 0x22, 0x93, 0xbb,
0x68, 0xe0, 0xf8, 0x1d, 0x84, 0xe6, 0x1c, 0x6c, 0x99, 0x6b, 0xbd, 0x37, 0xe5, 0x68, 0xe8, 0x5b,
0x15, 0x98, 0xe6, 0xd6, 0xb1, 0x7d, 0x1f, 0x66, 0x8d, 0x8e, 0x8c, 0xf0, 0x43, 0x9d, 0x87, 0x1f,
0x32, 0x71, 0xbe, 0x42, 0x4e, 0x9c, 0xef, 0x0f, 0x8a, 0x40, 0x18, 0xb1, 0xa4, 0x76, 0x83, 0x99,
0xe7, 0x41, 0xcf, 0x70, 0xb6, 0xea, 0x8e, 0x0e, 0x22, 0x37, 0x81, 0x68, 0x45, 0x19, 0xae, 0xe5,
0xda, 0x27, 0xa7, 0x86, 0x89, 0x49, 0xa1, 0xbc, 0x85, 0x9a, 0x15, 0x6e, 0x25, 0x5f, 0xf6, 0xdc,
0x3a, 0xa6, 0x60, 0x46, 0xe3, 0xe8, 0x14, 0x83, 0x6c, 0xc2, 0x1d, 0x93, 0xe5, 0xf4, 0xfe, 0x4e,
0x5f, 0xba, 0xbf, 0x33, 0x99, 0x10, 0x8f, 0xe6, 0x10, 0x54, 0x4c, 0x87, 0xe0, 0x3a, 0xcc, 0x0e,
0x99, 0xc9, 0x19, 0x0f, 0xba, 0x9d, 0x21, 0xeb, 0x5d, 0x78, 0x5f, 0x06, 0x90, 0xac, 0x43, 0x53,
0x98, 0x1b, 0x89, 0xd7, 0xc1, 0x83, 0xf9, 0x19, 0x38, 0x93, 0xdf, 0x49, 0xd0, 0xa7, 0x86, 0x83,
0x4d, 0x00, 0xcc, 0xc5, 0x88, 0x18, 0x85, 0x74, 0xc6, 0xbe, 0x38, 0xdb, 0xa2, 0x3d, 0xf4, 0xbb,
0x2a, 0x4e, 0xb6, 0xc2, 0xfe, 0x15, 0x0b, 0x9a, 0x6c, 0xcf, 0x0c, 0xb2, 0x7c, 0x17, 0x90, 0x2b,
0x5e, 0x92, 0x2a, 0x0d, 0x5c, 0x72, 0x07, 0xaa, 0x58, 0x0e, 0x46, 0xd4, 0x17, 0x34, 0xd9, 0x32,
0x69, 0x32, 0x91, 0x27, 0x7b, 0x53, 0x4e, 0x82, 0xac, 0x51, 0xe4, 0x5f, 0x58, 0x50, 0x13, 0xbd,
0xfc, 0xd8, 0x41, 0x85, 0xb6, 0x76, 0x18, 0xc9, 0x29, 0x29, 0x39, 0x7b, 0x5c, 0x83, 0xb9, 0xa1,
0x1b, 0x8f, 0x43, 0xa6, 0x8f, 0x8d, 0x80, 0x42, 0x1a, 0xcc, 0x94, 0x2b, 0x8a, 0xce, 0xa8, 0x13,
0x7b, 0x83, 0x8e, 0xac, 0x15, 0xc7, 0x7e, 0x79, 0x55, 0x4c, 0x82, 0x44, 0xb1, 0xdb, 0xa7, 0x42,
0x6f, 0xf2, 0x82, 0xdd, 0x82, 0x65, 0x31, 0xa1, 0x94, 0xa9, 0x6a, 0xff, 0xa0, 0x0e, 0x2b, 0x99,
0x2a, 0x95, 0xa4, 0x20, 0x3c, 0xe5, 0x81, 0x37, 0x3c, 0x0e, 0x94, 0x9d, 0x6f, 0xe9, 0x4e, 0xb4,
0x51, 0x45, 0xfa, 0xb0, 0x24, 0x0d, 0x04, 0xb6, 0xa6, 0x89, 0x32, 0x2b, 0xa0, 0x96, 0x7a, 0xdb,
0xdc, 0xc2, 0x74, 0x87, 0x12, 0xae, 0x33, 0x71, 0x7e, 0x7b, 0xe4, 0x14, 0x5a, 0xca, 0x12, 0x11,
0xc2, 0x5a, 0xb3, 0x56, 0x58, 0x5f, 0x6f, 0x5d, 0xd2, 0x97, 0x61, 0xd9, 0x3a, 0x13, 0x5b, 0x23,
0x17, 0x70, 0x4d, 0xd6, 0xa1, 0x34, 0xce, 0xf6, 0x57, 0x7a, 0xa9, 0xb9, 0xa1, 0xcd, 0x6e, 0x76,
0x7a, 0x49, 0xc3, 0xe4, 0x43, 0x58, 0x3e, 0x77, 0xbd, 0x58, 0x0e, 0x4b, 0xb3, 0x0d, 0xca, 0xd8,
0xe5, 0xed, 0x4b, 0xba, 0x7c, 0xc2, 0x3f, 0x36, 0x54, 0xd4, 0x84, 0x16, 0xdb, 0x3f, 0xb4, 0xa0,
0x61, 0xb6, 0xc3, 0xc8, 0x54, 0xf0, 0xbe, 0x94, 0x81, 0xd2, 0x9a, 0x4c, 0x81, 0xb3, 0xae, 0x72,
0x21, 0xcf, 0x55, 0xd6, 0x1d, 0xd4, 0xe2, 0x65, 0x11, 0xa9, 0xd2, 0xcb, 0x45, 0xa4, 0xca, 0x79,
0x11, 0xa9, 0xf6, 0xbf, 0x59, 0x40, 0xb2, 0xb4, 0x44, 0xee, 0x73, 0x5f, 0xdd, 0xa7, 0x03, 0x21,
0x52, 0xfe, 0xfb, 0xcb, 0xd1, 0xa3, 0x5c, 0x3b, 0xf9, 0x35, 0x63, 0x0c, 0xfd, 0xdc, 0x5e, 0x37,
0x76, 0x66, 0x9d, 0xbc, 0xaa, 0x54, 0x8c, 0xac, 0x74, 0x79, 0x8c, 0xac, 0x7c, 0x79, 0x8c, 0x6c,
0x3a, 0x1d, 0x23, 0x6b, 0xff, 0x8c, 0x05, 0x0b, 0x39, 0x9b, 0xfe, 0xd3, 0x9b, 0x38, 0xdb, 0x26,
0x43, 0x16, 0x14, 0xc4, 0x36, 0xe9, 0xc0, 0xf6, 0xff, 0x85, 0x59, 0x83, 0xd0, 0x7f, 0x7a, 0xfd,
0xa7, 0xed, 0x35, 0x4e, 0x67, 0x06, 0xac, 0xfd, 0x4f, 0x05, 0x20, 0x59, 0x66, 0xfb, 0x2f, 0x1d,
0x43, 0x76, 0x9d, 0x8a, 0x39, 0xeb, 0xf4, 0x9f, 0xaa, 0x07, 0xde, 0x82, 0x79, 0x91, 0x8c, 0xa4,
0x45, 0x68, 0x38, 0xc5, 0x64, 0x2b, 0x98, 0xc5, 0x6a, 0x06, 0x28, 0x2b, 0x46, 0x72, 0x86, 0xa6,
0x0c, 0x53, 0x71, 0x4a, 0xbb, 0x0d, 0x2d, 0xb1, 0x42, 0xbb, 0x67, 0xd4, 0x8f, 0x8f, 0xc6, 0xc7,
0x3c, 0x1b, 0xc7, 0x0b, 0x7c, 0xfb, 0xfb, 0x45, 0x65, 0x74, 0x63, 0xa5, 0x50, 0xef, 0x9f, 0x86,
0xba, 0x2e, 0xcc, 0xc5, 0x76, 0xa4, 0x02, 0x74, 0x4c, 0xb1, 0xeb, 0x58, 0x64, 0x07, 0x1a, 0x28,
0xb2, 0x7a, 0xea, 0xbb, 0x02, 0x7e, 0xf7, 0x82, 0xc0, 0xc3, 0xde, 0x94, 0x93, 0xfa, 0x86, 0x7c,
0x1e, 0x1a, 0xa6, 0x2b, 0x25, 0x6c, 0x84, 0x3c, 0xdb, 0x9c, 0x7d, 0x6e, 0x22, 0x93, 0x4d, 0x68,
0xa6, 0x7d, 0x31, 0x71, 0x14, 0x3f, 0xa1, 0x81, 0x0c, 0x3a, 0xb9, 0x23, 0x4e, 0xaa, 0xca, 0x18,
0x04, 0xbb, 0x6e, 0x7e, 0xa6, 0x2d, 0xd3, 0x4d, 0xfe, 0xa3, 0x9d, 0x5d, 0x7d, 0x0d, 0x20, 0x81,
0x91, 0x26, 0xd4, 0x1f, 0x1e, 0xee, 0x1e, 0x74, 0xb6, 0xf7, 0x36, 0x0f, 0x0e, 0x76, 0xf7, 0x9b,
0x53, 0x84, 0x40, 0x03, 0xe3, 0x57, 0x3b, 0x0a, 0x66, 0x31, 0xd8, 0xe6, 0x36, 0x8f, 0x8d, 0x09,
0x58, 0x81, 0x2c, 0x42, 0xf3, 0xc1, 0x41, 0x0a, 0x5a, 0xdc, 0xaa, 0x2a, 0xfe, 0xb0, 0x97, 0x61,
0x91, 0x27, 0x9b, 0x6d, 0x71, 0xf2, 0x90, 0xb6, 0xc2, 0x6f, 0x59, 0xb0, 0x94, 0xaa, 0x48, 0xb2,
0x3a, 0xb8, 0x39, 0x60, 0xda, 0x08, 0x26, 0x10, 0xa3, 0xcf, 0xd2, 0xf2, 0x4b, 0x49, 0x90, 0x6c,
0x05, 0xa3, 0x79, 0xcd, 0x52, 0x4c, 0x71, 0x52, 0x5e, 0x95, 0xbd, 0xc2, 0x53, 0xe2, 0x30, 0x79,
0xce, 0x18, 0xf8, 0x09, 0x4f, 0x62, 0xd3, 0x2b, 0x92, 0x93, 0x3f, 0x73, 0xc8, 0xb2, 0xc8, 0x8c,
0x7c, 0xc3, 0xf4, 0x30, 0xc7, 0x9b, 0x5b, 0x67, 0xff, 0x69, 0x01, 0xc8, 0x97, 0xc6, 0x34, 0xbc,
0xc0, 0x84, 0x0c, 0x15, 0x0e, 0x5c, 0x49, 0x07, 0xbb, 0xa6, 0x47, 0xe3, 0xe3, 0x2f, 0xd2, 0x0b,
0x99, 0x4c, 0x54, 0xd0, 0x93, 0x89, 0x80, 0x39, 0xc7, 0x2a, 0x1d, 0xc4, 0x5a, 0x2b, 0x63, 0x48,
0xa2, 0xea, 0x8f, 0x87, 0xbc, 0xd1, 0xdc, 0x9c, 0x9f, 0xd2, 0xe5, 0x39, 0x3f, 0xe5, 0xcb, 0x72,
0x7e, 0x3e, 0x01, 0xb3, 0x5e, 0xdf, 0x0f, 0x98, 0x58, 0x60, 0x8a, 0x3d, 0x6a, 0x4d, 0xaf, 0x16,
0x99, 0x33, 0x2c, 0x80, 0x07, 0x0c, 0x46, 0x3e, 0x9b, 0x20, 0xd1, 0x5e, 0x1f, 0xf3, 0xc7, 0x74,
0x41, 0xb1, 0xdb, 0xeb, 0xd3, 0xfd, 0xa0, 0xeb, 0xc6, 0x41, 0xa8, 0x3e, 0x64, 0xb0, 0x88, 0x79,
0xfd, 0x51, 0x30, 0x66, 0x66, 0x8e, 0x5c, 0x0a, 0x1e, 0xb6, 0xa9, 0x73, 0xe8, 0x21, 0x2e, 0x88,
0xfd, 0x15, 0xa8, 0x69, 0x4d, 0x60, 0x72, 0x91, 0x30, 0x21, 0x84, 0x3f, 0x58, 0xe2, 0x16, 0xbb,
0x4f, 0x07, 0x0f, 0x7a, 0xe4, 0x4d, 0x98, 0xef, 0x79, 0x21, 0xc5, 0x3c, 0xb1, 0x4e, 0x48, 0xcf,
0x68, 0x18, 0x49, 0xcf, 0xb9, 0xa9, 0x2a, 0x1c, 0x0e, 0xb7, 0xef, 0xc2, 0x82, 0xb1, 0x35, 0x8a,
0x72, 0x65, 0xee, 0x8d, 0x95, 0xcd, 0xbd, 0x91, 0x79, 0x37, 0xf6, 0xcf, 0x15, 0xa0, 0xb8, 0x17,
0x8c, 0xf4, 0x68, 0xbf, 0x65, 0x46, 0xfb, 0x85, 0x09, 0xd4, 0x51, 0x16, 0x8e, 0xd0, 0x8c, 0x06,
0x90, 0xac, 0x43, 0xc3, 0x1d, 0xc6, 0x9d, 0x38, 0x60, 0x26, 0xdf, 0xb9, 0x1b, 0xf6, 0x38, 0x39,
0xe3, 0x16, 0xa7, 0x6a, 0xc8, 0x22, 0x14, 0x95, 0xad, 0x80, 0x08, 0xac, 0xc8, 0xfc, 0x0d, 0x3c,
0x75, 0xbc, 0x10, 0x91, 0x33, 0x51, 0x62, 0xdc, 0x62, 0x7e, 0xcf, 0x9d, 0x3d, 0x2e, 0xf1, 0xf3,
0xaa, 0x98, 0x39, 0xc6, 0xa8, 0x03, 0xd1, 0x44, 0xc8, 0x53, 0x96, 0xf5, 0xf0, 0x6c, 0xc5, 0x3c,
0x83, 0xfd, 0x07, 0x0b, 0xca, 0xb8, 0x36, 0x4c, 0x7b, 0x71, 0xf6, 0x56, 0x01, 0x7f, 0x5c, 0x93,
0x59, 0x27, 0x0d, 0x26, 0xb6, 0x91, 0x71, 0x58, 0x50, 0x13, 0xd2, 0xb3, 0x0e, 0x57, 0xa1, 0xca,
0x4b, 0x2a, 0xbb, 0x8e, 0xd3, 0xbd, 0x02, 0x92, 0x6b, 0x50, 0x3a, 0x0d, 0x46, 0xd2, 0xdc, 0x06,
0x79, 0x76, 0x16, 0x8c, 0x1c, 0x84, 0x27, 0xe3, 0x61, 0xed, 0xf1, 0x69, 0x71, 0x23, 0x2a, 0x0d,
0x66, 0x66, 0xa4, 0x6a, 0x56, 0x5f, 0xa6, 0x14, 0xd4, 0x5e, 0x87, 0x39, 0x46, 0xf5, 0x5a, 0xd4,
0x75, 0x22, 0x2b, 0xdb, 0xff, 0xcf, 0x82, 0x8a, 0x44, 0x26, 0x6b, 0x50, 0x62, 0x2c, 0x94, 0x72,
0x5c, 0xd5, 0x99, 0x39, 0xc3, 0x73, 0x10, 0x83, 0x19, 0x13, 0x18, 0x0c, 0x4b, 0xfc, 0x24, 0x19,
0x0a, 0x4b, 0xdc, 0x00, 0x35, 0xdc, 0x94, 0xf5, 0x9c, 0x82, 0xda, 0xdf, 0xb3, 0x60, 0xd6, 0xe8,
0x83, 0xac, 0x42, 0x6d, 0xe0, 0x46, 0xb1, 0x38, 0x87, 0x14, 0xdb, 0xa3, 0x83, 0xf4, 0x8d, 0x2e,
0x98, 0x71, 0x78, 0x15, 0x21, 0x2e, 0xea, 0x11, 0xe2, 0x5b, 0x50, 0x4d, 0xf2, 0x42, 0x4b, 0x06,
0xef, 0xb3, 0x1e, 0x65, 0x36, 0x40, 0x82, 0x84, 0x41, 0xc7, 0x60, 0x10, 0x84, 0xe2, 0xd0, 0x8a,
0x17, 0xec, 0xbb, 0x50, 0xd3, 0xf0, 0xf5, 0x18, 0xa4, 0x65, 0xc4, 0x20, 0x55, 0xaa, 0x4c, 0x21,
0x49, 0x95, 0xb1, 0xff, 0xd9, 0x82, 0x59, 0x46, 0x83, 0x9e, 0xdf, 0x3f, 0x0c, 0x06, 0x5e, 0xf7,
0x02, 0xf7, 0x5e, 0x92, 0x9b, 0x10, 0x89, 0x92, 0x16, 0x4d, 0x30, 0xa3, 0x7a, 0x19, 0xf9, 0x10,
0x2c, 0xaa, 0xca, 0x8c, 0x87, 0x19, 0x07, 0x1c, 0xbb, 0x91, 0x60, 0x0b, 0x61, 0xb5, 0x19, 0x40,
0xc6, 0x69, 0x0c, 0x80, 0x89, 0x4f, 0x43, 0x6f, 0x30, 0xf0, 0x38, 0x2e, 0xb7, 0xe9, 0xf3, 0xaa,
0x58, 0x9f, 0x3d, 0x2f, 0x72, 0x8f, 0x93, 0x83, 0x18, 0x55, 0xc6, 0xf0, 0x8c, 0xfb, 0x4c, 0x0b,
0xcf, 0x4c, 0xa3, 0x5c, 0x31, 0x81, 0xf6, 0x1f, 0x17, 0xa0, 0x26, 0x4d, 0x84, 0x5e, 0x9f, 0x8a,
0xb3, 0x45, 0x53, 0x30, 0x6a, 0x10, 0x59, 0x6f, 0x78, 0x63, 0x1a, 0x24, 0x4d, 0x18, 0xc5, 0x2c,
0x61, 0x5c, 0x85, 0x2a, 0x23, 0xd0, 0xb7, 0xd1, 0xed, 0x13, 0xa9, 0xd6, 0x0a, 0x20, 0x6b, 0x6f,
0x63, 0x6d, 0x39, 0xa9, 0x45, 0xc0, 0x0b, 0x4f, 0x22, 0xef, 0x40, 0x5d, 0x34, 0x83, 0x3b, 0x87,
0x92, 0x27, 0x61, 0x11, 0x63, 0x57, 0x1d, 0x03, 0x53, 0x7e, 0x79, 0x5b, 0x7e, 0x59, 0xb9, 0xec,
0x4b, 0x89, 0x69, 0xdf, 0x57, 0x07, 0xbc, 0xf7, 0x43, 0x77, 0x74, 0x2a, 0x79, 0xf9, 0x16, 0x2c,
0x78, 0x7e, 0x77, 0x30, 0xee, 0xd1, 0xce, 0xd8, 0x77, 0x7d, 0x3f, 0x18, 0xfb, 0x5d, 0x2a, 0x73,
0x65, 0xf2, 0xaa, 0xec, 0x9e, 0xca, 0xac, 0xc4, 0x86, 0xc8, 0x3a, 0x94, 0xb9, 0xaa, 0xe4, 0xba,
0x23, 0x9f, 0xd1, 0x39, 0x0a, 0x59, 0x83, 0x32, 0xd7, 0x98, 0x05, 0x83, 0x6b, 0xb4, 0x5d, 0x75,
0x38, 0x02, 0x13, 0x3b, 0x98, 0x5c, 0x6b, 0x8a, 0x1d, 0x53, 0xef, 0x4c, 0x77, 0x31, 0xfd, 0xd6,
0x5e, 0x04, 0x72, 0xc0, 0x39, 0x45, 0x3f, 0x1b, 0xfa, 0x41, 0x11, 0x6a, 0x1a, 0x98, 0x49, 0x90,
0x3e, 0x1b, 0x70, 0xa7, 0xe7, 0xb9, 0x43, 0x1a, 0xd3, 0x50, 0x70, 0x47, 0x0a, 0xca, 0xf0, 0xdc,
0xb3, 0x7e, 0x27, 0x18, 0xc7, 0x9d, 0x1e, 0xed, 0x87, 0x94, 0x6b, 0x53, 0xa6, 0x9a, 0x0c, 0x28,
0xc3, 0x63, 0xf4, 0xa9, 0xe1, 0x71, 0x0a, 0x4a, 0x41, 0xe5, 0x49, 0x0f, 0x5f, 0xa3, 0x52, 0x72,
0xd2, 0xc3, 0x57, 0x24, 0x2d, 0xfb, 0xca, 0x39, 0xb2, 0xef, 0x1d, 0x58, 0xe6, 0x52, 0x4e, 0xc8,
0x83, 0x4e, 0x8a, 0xb0, 0x26, 0xd4, 0x92, 0x75, 0x68, 0xb2, 0x31, 0x4b, 0x96, 0x88, 0xbc, 0x6f,
0xf2, 0xa8, 0xa9, 0xe5, 0x64, 0xe0, 0x0c, 0x17, 0xc3, 0x97, 0x3a, 0x2e, 0x3f, 0xf9, 0xce, 0xc0,
0x11, 0xd7, 0x7d, 0x66, 0xe2, 0x56, 0x05, 0x6e, 0x0a, 0x4e, 0xee, 0xc0, 0xca, 0x90, 0xf6, 0x3c,
0xd7, 0x6c, 0x02, 0x23, 0xc0, 0x3c, 0xbd, 0x65, 0x52, 0xb5, 0x3d, 0x0b, 0xb5, 0xa3, 0x38, 0x18,
0xc9, 0xed, 0x6c, 0x40, 0x9d, 0x17, 0x45, 0xb6, 0xd3, 0x2b, 0x70, 0x05, 0xe9, 0xef, 0x51, 0x30,
0x0a, 0x06, 0x41, 0xff, 0xc2, 0x70, 0xba, 0xfe, 0xdc, 0x82, 0x05, 0xa3, 0x36, 0xf1, 0xba, 0x30,
0x5e, 0x23, 0xd3, 0x54, 0x38, 0xc9, 0xce, 0x6b, 0xc2, 0x9b, 0x23, 0xf2, 0xd0, 0xf8, 0x63, 0x91,
0xb9, 0xb2, 0x99, 0xdc, 0x60, 0x91, 0x1f, 0x72, 0xfa, 0x6d, 0x65, 0xe9, 0x57, 0x7c, 0x2f, 0x2f,
0xb0, 0xc8, 0x26, 0x3e, 0x2f, 0x72, 0x0f, 0xb8, 0x13, 0x26, 0xc3, 0x73, 0xca, 0x6d, 0xd3, 0x9d,
0x74, 0x39, 0x82, 0xae, 0x02, 0x46, 0xf6, 0x2f, 0x5a, 0x00, 0xc9, 0xe8, 0xf0, 0xc4, 0x5a, 0x29,
0x20, 0x7e, 0x5b, 0x4a, 0x53, 0x36, 0xaf, 0x43, 0x5d, 0x9d, 0x74, 0x26, 0x3a, 0xad, 0x26, 0x61,
0xcc, 0xe6, 0xbe, 0x01, 0x73, 0xfd, 0x41, 0x70, 0x8c, 0x06, 0x01, 0xa6, 0xcf, 0x45, 0x22, 0xe7,
0xab, 0xc1, 0xc1, 0xf7, 0x04, 0x34, 0x51, 0x80, 0x25, 0x4d, 0x01, 0xda, 0xbf, 0x54, 0x50, 0x07,
0x53, 0xc9, 0x9c, 0x27, 0xf2, 0x27, 0xb9, 0x9d, 0x11, 0xc4, 0x13, 0xce, 0x81, 0xd0, 0xac, 0x3d,
0xbc, 0x34, 0x4e, 0x76, 0x17, 0x1a, 0x21, 0x97, 0x74, 0x52, 0x0c, 0x96, 0x5e, 0x20, 0x06, 0x67,
0x43, 0x43, 0x4b, 0x7e, 0x12, 0x9a, 0x6e, 0xef, 0x8c, 0x86, 0xb1, 0x87, 0x91, 0x0a, 0x34, 0x51,
0xb8, 0xf0, 0x9e, 0xd3, 0xe0, 0x68, 0x39, 0xdc, 0x80, 0x39, 0x91, 0x67, 0xa7, 0x30, 0xc5, 0x0d,
0x84, 0x04, 0xcc, 0x10, 0xed, 0xef, 0xca, 0x33, 0x30, 0x73, 0x0f, 0x27, 0xaf, 0x88, 0x3e, 0xbb,
0x42, 0x6a, 0x76, 0x9f, 0x10, 0xe7, 0x51, 0x3d, 0x19, 0x0e, 0x29, 0x6a, 0x79, 0x2a, 0x3d, 0x71,
0x7e, 0x68, 0x2e, 0x69, 0xe9, 0x65, 0x96, 0xd4, 0xfe, 0x91, 0x05, 0x33, 0x7b, 0xc1, 0x68, 0x4f,
0x64, 0xec, 0x20, 0x23, 0xa8, 0x04, 0x57, 0x59, 0x7c, 0x41, 0x2e, 0x4f, 0xae, 0x65, 0x30, 0x9b,
0xb6, 0x0c, 0xfe, 0x37, 0xbc, 0x82, 0xc1, 0xb8, 0x30, 0x18, 0x05, 0x21, 0x63, 0x46, 0x77, 0xc0,
0xcd, 0x80, 0xc0, 0x8f, 0x4f, 0xa5, 0x00, 0x7c, 0x11, 0x0a, 0x7a, 0xc8, 0xcc, 0xab, 0xe3, 0x46,
0xbd, 0xb0, 0x64, 0xb8, 0x5c, 0xcc, 0x56, 0xd8, 0x9f, 0x83, 0x2a, 0x9a, 0xe2, 0x38, 0xad, 0xb7,
0xa0, 0x7a, 0x1a, 0x8c, 0x3a, 0xa7, 0x9e, 0x1f, 0x4b, 0xe6, 0x6e, 0x24, 0x36, 0xf2, 0x1e, 0x2e,
0x88, 0x42, 0xb0, 0x7f, 0x6d, 0x1a, 0x66, 0x1e, 0xf8, 0x67, 0x81, 0xd7, 0xc5, 0xf3, 0xb6, 0x21,
0x1d, 0x06, 0x32, 0xdd, 0x97, 0xfd, 0x27, 0x57, 0x61, 0x06, 0xf3, 0xdb, 0x46, 0x9c, 0x68, 0xeb,
0xfc, 0x5c, 0x5c, 0x80, 0x98, 0x79, 0x11, 0x26, 0x17, 0x33, 0x38, 0xfb, 0x68, 0x10, 0xe6, 0xa4,
0x84, 0xfa, 0xc5, 0x0a, 0x51, 0x4a, 0xd2, 0xa9, 0xcb, 0x5a, 0x3a, 0x35, 0xeb, 0x4b, 0x64, 0x18,
0xf1, 0x14, 0x14, 0xde, 0x97, 0x00, 0xa1, 0x63, 0x15, 0x52, 0x1e, 0x4c, 0x45, 0x63, 0x65, 0x46,
0x38, 0x56, 0x3a, 0x90, 0x19, 0x34, 0xfc, 0x03, 0x8e, 0xc3, 0xc5, 0xb7, 0x0e, 0x62, 0x26, 0x62,
0xfa, 0x4e, 0x4d, 0x95, 0xd3, 0x7e, 0x0a, 0xcc, 0x64, 0x7c, 0x8f, 0x2a, 0x81, 0xca, 0xe7, 0x01,
0xfc, 0xf2, 0x49, 0x1a, 0xae, 0xb9, 0x63, 0x3c, 0x15, 0x51, 0xba, 0x63, 0x8c, 0x60, 0xdc, 0xc1,
0xe0, 0xd8, 0xed, 0x3e, 0xc5, 0x2b, 0x53, 0x78, 0x02, 0x56, 0x75, 0x4c, 0x20, 0xe6, 0x09, 0x25,
0xbb, 0x8a, 0x19, 0x04, 0x25, 0x47, 0x07, 0x91, 0xdb, 0x50, 0x43, 0x17, 0x54, 0xec, 0x6b, 0x03,
0xf7, 0xb5, 0xa9, 0xfb, 0xa8, 0xb8, 0xb3, 0x3a, 0x92, 0x7e, 0x16, 0x38, 0x97, 0x49, 0x0e, 0x74,
0x7b, 0x3d, 0x71, 0x84, 0xda, 0xe4, 0xee, 0xb4, 0x02, 0x30, 0x7d, 0x2c, 0x16, 0x8c, 0x23, 0xcc,
0x23, 0x82, 0x01, 0x23, 0xd7, 0xa0, 0xc2, 0xdc, 0xa3, 0x91, 0xeb, 0xf5, 0x30, 0xbb, 0x90, 0x7b,
0x69, 0x0a, 0xc6, 0xda, 0x90, 0xff, 0x51, 0xd1, 0x2d, 0xe0, 0xaa, 0x18, 0x30, 0xb6, 0x36, 0xaa,
0x8c, 0xcc, 0xb4, 0xc8, 0x77, 0xd4, 0x00, 0x92, 0xb7, 0xf1, 0x20, 0x2b, 0xa6, 0xad, 0x25, 0x0c,
0x94, 0xbd, 0x22, 0xe6, 0x2c, 0x88, 0x56, 0xfe, 0x1e, 0x31, 0x14, 0x87, 0x63, 0xda, 0x9b, 0x50,
0xd7, 0xc1, 0xa4, 0x02, 0xa5, 0x87, 0x87, 0xbb, 0x07, 0xcd, 0x29, 0x52, 0x83, 0x99, 0xa3, 0xdd,
0x47, 0x8f, 0xf6, 0x77, 0x77, 0x9a, 0x16, 0xa9, 0x43, 0x45, 0x25, 0x75, 0x15, 0x58, 0x69, 0x73,
0x7b, 0x7b, 0xf7, 0xf0, 0xd1, 0xee, 0x4e, 0xb3, 0x68, 0xc7, 0x40, 0x36, 0x7b, 0x3d, 0xd1, 0x8a,
0x0a, 0x12, 0x24, 0xf4, 0x6c, 0x19, 0xf4, 0x9c, 0x43, 0x53, 0x85, 0x7c, 0x9a, 0x7a, 0xe1, 0xca,
0xdb, 0xbb, 0x50, 0x3b, 0xd4, 0xee, 0x0f, 0x21, 0x7b, 0xc9, 0x9b, 0x43, 0x82, 0x2d, 0x35, 0x88,
0x36, 0x9c, 0x82, 0x3e, 0x1c, 0xfb, 0x77, 0x2c, 0x9e, 0xa4, 0xaf, 0x86, 0xcf, 0xfb, 0xb6, 0xa1,
0xae, 0xa2, 0x55, 0x49, 0xbe, 0xa6, 0x01, 0x63, 0x38, 0x38, 0x94, 0x4e, 0x70, 0x72, 0x12, 0x51,
0x99, 0x5d, 0x65, 0xc0, 0x18, 0x5f, 0x30, 0xdb, 0x8c, 0xd9, 0x39, 0x1e, 0xef, 0x21, 0x12, 0x59,
0x56, 0x19, 0x38, 0x93, 0xf2, 0x22, 0x20, 0x23, 0xf3, 0xca, 0x54, 0x59, 0xa5, 0x95, 0xa6, 0x57,
0x79, 0x1d, 0x2a, 0xaa, 0x5d, 0x53, 0x80, 0x49, 0x4c, 0x55, 0xcf, 0x04, 0x25, 0x7a, 0x2b, 0xc6,
0xa0, 0xb9, 0xd0, 0xce, 0x56, 0x90, 0x9b, 0x40, 0x4e, 0xbc, 0x30, 0x8d, 0x5e, 0x44, 0xf4, 0x9c,
0x1a, 0xfb, 0x09, 0x2c, 0x48, 0x42, 0xd2, 0x4c, 0x2b, 0x73, 0x13, 0xad, 0xcb, 0xd8, 0xa7, 0x90,
0x65, 0x1f, 0xfb, 0xdf, 0x2d, 0x98, 0x11, 0x3b, 0x9d, 0xb9, 0x83, 0xc6, 0xf7, 0xd9, 0x80, 0x91,
0x96, 0x71, 0xff, 0x04, 0x79, 0x4d, 0x08, 0xcd, 0x8c, 0x58, 0x2c, 0xe6, 0x89, 0x45, 0x02, 0xa5,
0x91, 0x1b, 0x9f, 0xa2, 0xa7, 0x5e, 0x75, 0xf0, 0x3f, 0x69, 0xf2, 0xb8, 0x12, 0x17, 0xc1, 0x18,
0x53, 0xca, 0xbb, 0x6d, 0xc7, 0xb5, 0x7d, 0xf6, 0xb6, 0xdd, 0x55, 0xa8, 0xe2, 0x00, 0x3a, 0x49,
0xd8, 0x28, 0x01, 0x30, 0xca, 0xe5, 0x05, 0xe4, 0x6b, 0x91, 0x0a, 0x9e, 0x40, 0xec, 0x25, 0xbe,
0xf3, 0x62, 0x09, 0xd4, 0x21, 0xb4, 0x48, 0xe3, 0x4d, 0xc0, 0x09, 0x45, 0x88, 0x01, 0xa4, 0x29,
0x42, 0xa0, 0x3a, 0xaa, 0xde, 0x6e, 0x43, 0x6b, 0x87, 0x0e, 0x68, 0x4c, 0x37, 0x07, 0x83, 0x74,
0xfb, 0xaf, 0xc0, 0x95, 0x9c, 0x3a, 0x61, 0x4d, 0x7f, 0x09, 0x96, 0x36, 0x79, 0xca, 0xe3, 0x4f,
0x2b, 0x8d, 0xc7, 0x6e, 0xc1, 0x72, 0xba, 0x49, 0xd1, 0xd9, 0x3d, 0x98, 0xdf, 0xa1, 0xc7, 0xe3,
0xfe, 0x3e, 0x3d, 0x4b, 0x3a, 0x22, 0x50, 0x8a, 0x4e, 0x83, 0x73, 0xc1, 0x98, 0xf8, 0x9f, 0xbc,
0x0a, 0x30, 0x60, 0x38, 0x9d, 0x68, 0x44, 0xbb, 0xf2, 0xca, 0x07, 0x42, 0x8e, 0x46, 0xb4, 0x6b,
0xbf, 0x03, 0x44, 0x6f, 0x47, 0xac, 0x17, 0xd3, 0x82, 0xe3, 0xe3, 0x4e, 0x74, 0x11, 0xc5, 0x74,
0x28, 0xef, 0xb2, 0xe8, 0x20, 0xfb, 0x06, 0xd4, 0x0f, 0xdd, 0x0b, 0x87, 0x7e, 0x43, 0x5c, 0x3d,
0x5c, 0x81, 0x99, 0x91, 0x7b, 0xc1, 0xc4, 0x94, 0x8a, 0x67, 0x61, 0xb5, 0xfd, 0xaf, 0x05, 0x98,
0xe6, 0x98, 0xac, 0xd5, 0x1e, 0x8d, 0x62, 0xcf, 0x47, 0xc2, 0x92, 0xad, 0x6a, 0xa0, 0x0c, 0x29,
0x17, 0x72, 0x48, 0x59, 0x78, 0x7b, 0x32, 0x7d, 0x5e, 0xd0, 0xab, 0x01, 0x63, 0xc4, 0x95, 0xe4,
0xd3, 0xf1, 0x80, 0x4a, 0x02, 0x48, 0x85, 0x3e, 0x13, 0x5d, 0xcb, 0xc7, 0x27, 0xb9, 0x54, 0x50,
0xae, 0x0e, 0xca, 0xd5, 0xe8, 0x33, 0x9c, 0xc0, 0x33, 0x1a, 0x3d, 0xa3, 0xb9, 0x2b, 0x2f, 0xa1,
0xb9, 0xb9, 0x0b, 0xf8, 0x22, 0xcd, 0x0d, 0x2f, 0xa1, 0xb9, 0x6d, 0x02, 0x4d, 0xbc, 0x97, 0xc7,
0x6c, 0x43, 0x49, 0xbb, 0xdf, 0xb6, 0xa0, 0x29, 0xa8, 0x48, 0xd5, 0x91, 0xd7, 0x0d, 0x1b, 0x38,
0x37, 0x31, 0xfd, 0x3a, 0xcc, 0xa2, 0x65, 0xaa, 0x62, 0xbc, 0x22, 0x20, 0x6d, 0x00, 0xd9, 0x3c,
0xe4, 0xf9, 0xf1, 0xd0, 0x1b, 0x88, 0x4d, 0xd1, 0x41, 0x32, 0x4c, 0x1c, 0xba, 0x22, 0xaf, 0xcc,
0x72, 0x54, 0xd9, 0xfe, 0x13, 0x0b, 0xe6, 0xb5, 0x01, 0x0b, 0x2a, 0xbc, 0x0b, 0x92, 0x1b, 0x78,
0xc0, 0x97, 0x73, 0xee, 0x8a, 0xc9, 0x36, 0xc9, 0x67, 0x06, 0x32, 0x6e, 0xa6, 0x7b, 0x81, 0x03,
0x8c, 0xc6, 0x43, 0x21, 0x44, 0x75, 0x10, 0x23, 0xa4, 0x73, 0x4a, 0x9f, 0x2a, 0x14, 0x2e, 0xc6,
0x0d, 0x18, 0x46, 0xd5, 0x98, 0x45, 0xad, 0x90, 0x4a, 0x22, 0xaa, 0xa6, 0x03, 0xed, 0xbf, 0xb2,
0x60, 0x81, 0xbb, 0x46, 0xc2, 0xf1, 0x54, 0x37, 0x90, 0xa6, 0xb9, 0x2f, 0xc8, 0x39, 0x72, 0x6f,
0xca, 0x11, 0x65, 0xf2, 0x99, 0x97, 0x74, 0xe7, 0x54, 0xb2, 0xdb, 0x84, 0xbd, 0x28, 0xe6, 0xed,
0xc5, 0x0b, 0x56, 0x3a, 0x2f, 0xc0, 0x59, 0xce, 0x0d, 0x70, 0x6e, 0xcd, 0x40, 0x39, 0xea, 0x06,
0x23, 0x6a, 0x2f, 0xc3, 0xa2, 0x39, 0x39, 0x21, 0x82, 0xbe, 0x63, 0x41, 0xeb, 0x1e, 0x3f, 0x08,
0xf0, 0xfc, 0xfe, 0x9e, 0x17, 0xc5, 0x41, 0xa8, 0x2e, 0x6a, 0x5e, 0x03, 0x88, 0x62, 0x37, 0x8c,
0x79, 0x4a, 0xb3, 0x08, 0x2c, 0x26, 0x10, 0x36, 0x46, 0xea, 0xf7, 0x78, 0x2d, 0xdf, 0x1b, 0x55,
0xce, 0xd8, 0x10, 0xc2, 0x79, 0x33, 0x34, 0xf1, 0x1b, 0x3c, 0xf9, 0x93, 0xd9, 0x0a, 0xf4, 0x0c,
0xe5, 0x3a, 0xf7, 0x8a, 0x52, 0x50, 0xfb, 0x2f, 0x2d, 0x98, 0x4b, 0x06, 0x89, 0xc7, 0xa2, 0xa6,
0x74, 0x10, 0xea, 0x37, 0x91, 0x0e, 0x32, 0xe4, 0xe9, 0x31, 0x7d, 0x2c, 0xc6, 0xa6, 0x41, 0x90,
0x63, 0x45, 0x29, 0x18, 0x4b, 0x03, 0x47, 0x07, 0xf1, 0x54, 0x2e, 0x66, 0x09, 0x08, 0xab, 0x46,
0x94, 0x30, 0x23, 0x7d, 0x18, 0xe3, 0x57, 0x3c, 0x38, 0x2b, 0x8b, 0x52, 0x95, 0xce, 0x20, 0x14,
0x55, 0xa9, 0x7e, 0xa8, 0x52, 0xe1, 0xeb, 0x23, 0xcb, 0xf6, 0x2f, 0x5b, 0x70, 0x25, 0x67, 0xe1,
0x05, 0xd7, 0xec, 0xc0, 0xfc, 0x89, 0xaa, 0x94, 0x8b, 0xc3, 0x59, 0x67, 0x59, 0x1e, 0xda, 0x99,
0x0b, 0xe2, 0x64, 0x3f, 0x50, 0x76, 0x11, 0x5f, 0x6e, 0x23, 0x59, 0x32, 0x5b, 0x61, 0x1f, 0x42,
0x7b, 0xf7, 0x19, 0x63, 0xc2, 0x6d, 0xfd, 0xcd, 0x11, 0x49, 0x0b, 0xb7, 0x33, 0x42, 0xe6, 0x72,
0x47, 0xfb, 0x04, 0x66, 0x8d, 0xb6, 0xc8, 0xa7, 0x5e, 0xb6, 0x91, 0x54, 0x78, 0x1a, 0x4b, 0xfc,
0xd1, 0x14, 0x99, 0xb2, 0xa9, 0x81, 0xec, 0x33, 0x98, 0x7b, 0x6f, 0x3c, 0x88, 0xbd, 0xe4, 0x01,
0x15, 0xf2, 0x19, 0xf1, 0x11, 0x36, 0x21, 0x97, 0x2e, 0xb7, 0x2b, 0x1d, 0x8f, 0xad, 0xd8, 0x90,
0xb5, 0xd4, 0xc9, 0xf6, 0x98, 0xad, 0xb0, 0xaf, 0xc0, 0x4a, 0xd2, 0x25, 0x5f, 0x3b, 0x29, 0xa8,
0xbf, 0x6b, 0xf1, 0x6c, 0x07, 0xf3, 0x3d, 0x17, 0x72, 0x1f, 0x16, 0x22, 0xcf, 0xef, 0x0f, 0xa8,
0xde, 0x4e, 0x24, 0x56, 0x62, 0xc9, 0x1c, 0x9e, 0x78, 0xf3, 0xc5, 0xc9, 0xfb, 0x82, 0x11, 0x48,
0xfe, 0x40, 0x13, 0x02, 0x49, 0x2d, 0x49, 0xde, 0x04, 0xbe, 0x00, 0x0d, 0xb3, 0x33, 0x72, 0x47,
0x64, 0x5b, 0x26, 0x23, 0xd3, 0x63, 0xd9, 0x26, 0x65, 0x18, 0x98, 0xf6, 0xb7, 0x2c, 0x68, 0x39,
0x94, 0x91, 0x31, 0xd5, 0x3a, 0x15, 0xd4, 0x73, 0x37, 0xd3, 0xec, 0xe4, 0x09, 0xab, 0x2c, 0x4e,
0x39, 0xd7, 0x9b, 0x13, 0x37, 0x65, 0x6f, 0x2a, 0x67, 0x56, 0x5b, 0x15, 0x98, 0x16, 0xf3, 0x5b,
0x81, 0x25, 0x31, 0x24, 0x39, 0x9c, 0x24, 0x68, 0x6a, 0x74, 0x6a, 0x04, 0x4d, 0x8f, 0xa1, 0xc5,
0x6f, 0xd0, 0xea, 0xf3, 0x48, 0x72, 0x1b, 0xf8, 0x76, 0x44, 0x1d, 0xfd, 0x32, 0xad, 0x09, 0x64,
0x24, 0xcb, 0x87, 0xc5, 0x71, 0xf8, 0x29, 0xb4, 0x0e, 0x5a, 0x7f, 0x0e, 0x35, 0xed, 0x3e, 0x32,
0x59, 0x81, 0x85, 0x27, 0x0f, 0x1e, 0x1d, 0xec, 0x1e, 0x1d, 0x75, 0x0e, 0x1f, 0x6f, 0x7d, 0x71,
0xf7, 0x2b, 0x9d, 0xbd, 0xcd, 0xa3, 0xbd, 0xe6, 0x14, 0x59, 0x06, 0x72, 0xb0, 0x7b, 0xf4, 0x68,
0x77, 0xc7, 0x80, 0x5b, 0xe4, 0x1a, 0xb4, 0x1f, 0x1f, 0x3c, 0x3e, 0xda, 0xdd, 0xe9, 0xe4, 0x7d,
0x57, 0x20, 0xaf, 0xc2, 0x15, 0x51, 0x9f, 0xf3, 0x79, 0xf1, 0xf6, 0xb7, 0x8a, 0xd0, 0xe0, 0xc9,
0x1b, 0xfc, 0x39, 0x21, 0x1a, 0x92, 0xf7, 0x60, 0x46, 0xbc, 0x4b, 0x45, 0xe4, 0xbe, 0x98, 0x2f,
0x61, 0xb5, 0x97, 0xd3, 0x60, 0xb1, 0x98, 0x0b, 0xff, 0xff, 0x47, 0x7f, 0xff, 0xab, 0x85, 0x59,
0x52, 0xdb, 0x38, 0x7b, 0x7b, 0xa3, 0x4f, 0xfd, 0x88, 0xb5, 0xf1, 0x35, 0x80, 0xe4, 0xb5, 0x25,
0xd2, 0x52, 0xbe, 0x5b, 0xea, 0x29, 0xaa, 0xf6, 0x95, 0x9c, 0x1a, 0xd1, 0xee, 0x15, 0x6c, 0x77,
0xc1, 0x6e, 0xb0, 0x76, 0x3d, 0xdf, 0x8b, 0xf9, 0xcb, 0x4b, 0xef, 0x5a, 0xeb, 0xa4, 0x07, 0x75,
0xfd, 0x1d, 0x24, 0x22, 0x03, 0xc8, 0x39, 0x2f, 0x39, 0xb5, 0x5f, 0xc9, 0xad, 0x93, 0x84, 0x80,
0x7d, 0x2c, 0xd9, 0x4d, 0xd6, 0xc7, 0x18, 0x31, 0x92, 0x5e, 0x06, 0x9c, 0x3d, 0x92, 0xe7, 0x8e,
0xc8, 0x55, 0x8d, 0x62, 0x33, 0x8f, 0x2d, 0xb5, 0x5f, 0x9d, 0x50, 0x2b, 0xfa, 0x7a, 0x15, 0xfb,
0x5a, 0xb1, 0x09, 0xeb, 0xab, 0x8b, 0x38, 0xf2, 0xb1, 0xa5, 0x77, 0xad, 0xf5, 0xdb, 0x7f, 0x7d,
0x1d, 0xaa, 0xea, 0xb0, 0x88, 0x7c, 0x08, 0xb3, 0x46, 0x76, 0x0d, 0x91, 0xd3, 0xc8, 0x4b, 0xc6,
0x69, 0x5f, 0xcd, 0xaf, 0x14, 0x1d, 0x5f, 0xc3, 0x8e, 0x5b, 0x64, 0x99, 0x75, 0x2c, 0xd2, 0x53,
0x36, 0x30, 0x4f, 0x8c, 0x5f, 0xfa, 0x78, 0xaa, 0x89, 0x01, 0xde, 0xd9, 0xd5, 0x34, 0x67, 0x1a,
0xbd, 0xbd, 0x3a, 0xa1, 0x56, 0x74, 0x77, 0x15, 0xbb, 0x5b, 0x26, 0x8b, 0x7a, 0x77, 0xea, 0x10,
0x87, 0xe2, 0x4d, 0x25, 0xfd, 0xa5, 0x20, 0xf2, 0xaa, 0x22, 0xac, 0xbc, 0x17, 0x84, 0x14, 0x89,
0x64, 0x9f, 0x11, 0xb2, 0x5b, 0xd8, 0x15, 0x21, 0xb8, 0x7d, 0xfa, 0x43, 0x41, 0xe4, 0x18, 0x6a,
0xda, 0xeb, 0x16, 0xe4, 0xca, 0xc4, 0x97, 0x38, 0xda, 0xed, 0xbc, 0xaa, 0xbc, 0xa9, 0xe8, 0xed,
0x6f, 0x30, 0xfd, 0xfe, 0x55, 0xa8, 0xaa, 0xf7, 0x12, 0xc8, 0x8a, 0xf6, 0x7e, 0x85, 0xfe, 0xbe,
0x43, 0xbb, 0x95, 0xad, 0xc8, 0x23, 0x3e, 0xbd, 0x75, 0x46, 0x7c, 0x4f, 0xa0, 0xa6, 0xbd, 0x89,
0xa0, 0x26, 0x90, 0x7d, 0x77, 0x41, 0x4d, 0x20, 0xe7, 0x09, 0x05, 0x7b, 0x1e, 0xbb, 0xa8, 0x91,
0x2a, 0xd2, 0x77, 0xfc, 0x2c, 0x88, 0xc8, 0x3e, 0x2c, 0x09, 0x71, 0x77, 0x4c, 0x3f, 0xce, 0x36,
0xe4, 0x3c, 0xce, 0x74, 0xcb, 0x22, 0x77, 0xa1, 0x22, 0x9f, 0xbe, 0x20, 0xcb, 0xf9, 0x4f, 0x78,
0xb4, 0x57, 0x32, 0x70, 0x21, 0x4d, 0xbf, 0x02, 0x90, 0x3c, 0xc0, 0xa0, 0x84, 0x44, 0xe6, 0x41,
0x07, 0x45, 0x01, 0xd9, 0xd7, 0x1a, 0xec, 0x65, 0x9c, 0x60, 0x93, 0xa0, 0x90, 0xf0, 0xe9, 0xb9,
0xbc, 0x1f, 0xf8, 0x75, 0xa8, 0x69, 0x6f, 0x30, 0xa8, 0xe5, 0xcb, 0xbe, 0xdf, 0xa0, 0x96, 0x2f,
0xe7, 0xc9, 0x06, 0xbb, 0x8d, 0xad, 0x2f, 0xda, 0x73, 0xac, 0xf5, 0xc8, 0xeb, 0xfb, 0x43, 0x8e,
0xc0, 0x36, 0xe8, 0x14, 0x66, 0x8d, 0x87, 0x16, 0x14, 0x87, 0xe6, 0x3d, 0xe3, 0xa0, 0x38, 0x34,
0xf7, 0x6d, 0x06, 0x49, 0x67, 0xf6, 0x3c, 0xeb, 0xe7, 0x0c, 0x51, 0xb4, 0x9e, 0x3e, 0x80, 0x9a,
0xf6, 0x68, 0x82, 0x9a, 0x4b, 0xf6, 0x7d, 0x06, 0x35, 0x97, 0xbc, 0x37, 0x16, 0x16, 0xb1, 0x8f,
0x86, 0x8d, 0xa4, 0x80, 0xd7, 0xeb, 0x58, 0xdb, 0x1f, 0x42, 0xc3, 0x7c, 0x46, 0x41, 0xf1, 0x7e,
0xee, 0x83, 0x0c, 0x8a, 0xf7, 0x27, 0xbc, 0xbd, 0x20, 0x48, 0x7a, 0x7d, 0x41, 0x75, 0xb2, 0xf1,
0x91, 0x48, 0x22, 0x79, 0x4e, 0xbe, 0xc4, 0x04, 0x9c, 0xb8, 0xef, 0x48, 0x56, 0x34, 0xaa, 0xd5,
0x6f, 0x45, 0x2a, 0x7e, 0xc9, 0x5c, 0x8d, 0x34, 0x89, 0x99, 0x5f, 0x10, 0x44, 0xad, 0x85, 0xf7,
0x1e, 0x35, 0xad, 0xa5, 0x5f, 0x8d, 0xd4, 0xb4, 0x96, 0x71, 0x3d, 0x32, 0xad, 0xb5, 0x62, 0x8f,
0xb5, 0xe1, 0xc3, 0x5c, 0x2a, 0x03, 0x58, 0x71, 0x45, 0xfe, 0x95, 0x89, 0xf6, 0xb5, 0x17, 0x27,
0x0e, 0x9b, 0x12, 0x44, 0x0a, 0xc1, 0x0d, 0x79, 0x41, 0xe5, 0xff, 0x40, 0x5d, 0xbf, 0xb2, 0x4e,
0x74, 0x56, 0x4e, 0xf7, 0xf4, 0x4a, 0x6e, 0x9d, 0xb9, 0xb9, 0xa4, 0xae, 0x77, 0x43, 0xde, 0x87,
0x65, 0xc5, 0xea, 0x7a, 0x52, 0x69, 0x44, 0x5e, 0xcb, 0x49, 0x35, 0xd5, 0x8d, 0xa0, 0xf6, 0x95,
0x89, 0xb9, 0xa8, 0xb7, 0x2c, 0x46, 0x34, 0xe6, 0x5d, 0xe0, 0x44, 0x61, 0xe4, 0x5d, 0x81, 0x4e,
0x14, 0x46, 0xee, 0x05, 0x62, 0x49, 0x34, 0x64, 0xc1, 0x58, 0x23, 0x7e, 0xce, 0x47, 0x3e, 0x80,
0x39, 0x2d, 0x6d, 0xff, 0xe8, 0xc2, 0xef, 0x2a, 0x06, 0xc8, 0xde, 0xef, 0x6a, 0xe7, 0x99, 0xf8,
0xf6, 0x0a, 0xb6, 0x3f, 0x6f, 0x1b, 0x8b, 0xc3, 0x88, 0x7f, 0x1b, 0x6a, 0xfa, 0x95, 0x80, 0x17,
0xb4, 0xbb, 0xa2, 0x55, 0xe9, 0xd7, 0x93, 0x6e, 0x59, 0xe4, 0x37, 0x2c, 0xa8, 0x1b, 0x09, 0xf6,
0xc6, 0x69, 0x76, 0xaa, 0x9d, 0x96, 0x5e, 0xa7, 0x37, 0x64, 0x3b, 0x38, 0xc8, 0xfd, 0xf5, 0x2f,
0x18, 0x8b, 0xf0, 0x91, 0x11, 0xc7, 0xb9, 0x99, 0x7e, 0x6e, 0xeb, 0x79, 0x1a, 0x41, 0xbf, 0x03,
0xf7, 0xfc, 0x96, 0x45, 0xbe, 0x67, 0x41, 0xc3, 0x8c, 0x3e, 0xaa, 0xad, 0xca, 0x8d, 0x73, 0xaa,
0xad, 0x9a, 0x10, 0xb2, 0xfc, 0x00, 0x47, 0xf9, 0x68, 0xdd, 0x31, 0x46, 0x29, 0x6e, 0x89, 0xff,
0x64, 0xa3, 0x25, 0xef, 0xf2, 0x17, 0xf9, 0x64, 0x48, 0x9c, 0x68, 0x5a, 0x23, 0xbd, 0xbd, 0xfa,
0x2b, 0x72, 0x6b, 0xd6, 0x2d, 0x8b, 0x7c, 0x9d, 0x3f, 0x33, 0x25, 0xbe, 0x45, 0x2a, 0x79, 0xd9,
0xef, 0xed, 0xeb, 0x38, 0xa7, 0x6b, 0xf6, 0x15, 0x63, 0x4e, 0x69, 0x7d, 0xbc, 0xc9, 0x47, 0x27,
0x1e, 0x80, 0x4b, 0x14, 0x4a, 0xe6, 0x51, 0xb8, 0xc9, 0x83, 0x1c, 0xf2, 0x41, 0x0a, 0x74, 0x83,
0x94, 0x5f, 0xb2, 0x19, 0x7b, 0x1d, 0xc7, 0x7a, 0xdd, 0x7e, 0x6d, 0xe2, 0x58, 0x37, 0x30, 0x86,
0xc8, 0x46, 0x7c, 0x08, 0x90, 0x1c, 0x5f, 0x91, 0xd4, 0xf1, 0x89, 0x62, 0xf0, 0xec, 0x09, 0x97,
0xc9, 0x2f, 0xf2, 0x94, 0x85, 0xb5, 0xf8, 0x55, 0x2e, 0xae, 0x1e, 0xc8, 0x83, 0x17, 0xdd, 0x28,
0x31, 0xcf, 0x99, 0x0c, 0xa3, 0x24, 0xdd, 0xbe, 0x21, 0xac, 0xd4, 0x29, 0xce, 0x63, 0x98, 0xdd,
0x0f, 0x82, 0xa7, 0xe3, 0x91, 0x3a, 0x8a, 0x36, 0xc3, 0xfb, 0x7b, 0x6e, 0x74, 0xda, 0x4e, 0xcd,
0xc2, 0x5e, 0xc5, 0xa6, 0xda, 0xa4, 0xa5, 0x35, 0xb5, 0xf1, 0x51, 0x72, 0x3c, 0xf6, 0x9c, 0xb8,
0x30, 0xaf, 0x64, 0xa0, 0x1a, 0x78, 0xdb, 0x6c, 0xc6, 0x90, 0x7c, 0xe9, 0x2e, 0x0c, 0xeb, 0x59,
0x8e, 0x76, 0x23, 0x92, 0x6d, 0xde, 0xb2, 0xc8, 0x21, 0xd4, 0x77, 0x68, 0x37, 0xe8, 0x51, 0x11,
0x23, 0x5f, 0x48, 0x06, 0xae, 0x82, 0xeb, 0xed, 0x59, 0x03, 0x68, 0xea, 0x85, 0x91, 0x7b, 0x11,
0xd2, 0x6f, 0x6c, 0x7c, 0x24, 0xa2, 0xef, 0xcf, 0xa5, 0x5e, 0x90, 0xc7, 0x13, 0x86, 0x5e, 0x48,
0x9d, 0x67, 0x18, 0x7a, 0x21, 0x73, 0x9e, 0x61, 0x2c, 0xb5, 0x3c, 0x1e, 0x21, 0x03, 0x98, 0xcf,
0x1c, 0x81, 0x28, 0x95, 0x30, 0xe9, 0xe0, 0xa4, 0xbd, 0x3a, 0x19, 0xc1, 0xec, 0x6d, 0xdd, 0xec,
0xed, 0x08, 0x66, 0x77, 0x28, 0x5f, 0x2c, 0x9e, 0x29, 0x97, 0xba, 0xa5, 0xa1, 0xe7, 0xe1, 0xa5,
0x05, 0x38, 0xd6, 0x99, 0x8a, 0x1f, 0xd3, 0xd4, 0xc8, 0x57, 0xa1, 0x76, 0x9f, 0xc6, 0x32, 0x35,
0x4e, 0x99, 0x9e, 0xa9, 0x5c, 0xb9, 0x76, 0x4e, 0x66, 0x9d, 0x49, 0x33, 0xd8, 0xda, 0x06, 0xed,
0xf5, 0x29, 0x17, 0x4e, 0x1d, 0xaf, 0xf7, 0x9c, 0x7c, 0x19, 0x1b, 0x57, 0x19, 0xbc, 0xcb, 0x5a,
0x5e, 0x94, 0xde, 0xf8, 0x5c, 0x0a, 0x9e, 0xd7, 0xb2, 0x1f, 0xf4, 0xa8, 0x66, 0x02, 0xf9, 0x50,
0xd3, 0x12, 0xcf, 0x15, 0x03, 0x65, 0xef, 0x09, 0x28, 0x06, 0xca, 0xc9, 0x53, 0xb7, 0xd7, 0xb0,
0x1f, 0x9b, 0xac, 0x26, 0xfd, 0xf0, 0xdc, 0xf4, 0xa4, 0xa7, 0x8d, 0x8f, 0xdc, 0x61, 0xfc, 0x9c,
0x3c, 0xc1, 0xa7, 0x22, 0xf4, 0xf4, 0xbf, 0xc4, 0x96, 0x4e, 0x67, 0x0a, 0xaa, 0xc5, 0xd2, 0xaa,
0x4c, 0xfb, 0x9a, 0x77, 0x85, 0x96, 0xd2, 0x67, 0x00, 0x8e, 0xe2, 0x60, 0xb4, 0xe3, 0xd2, 0x61,
0xe0, 0x27, 0xb2, 0x36, 0x49, 0x54, 0x4b, 0xe4, 0x97, 0x96, 0xad, 0x46, 0x9e, 0x68, 0xce, 0x87,
0x91, 0x3d, 0x29, 0x89, 0x6b, 0x62, 0x2e, 0x9b, 0x5a, 0x90, 0x9c, 0x7c, 0xb6, 0x5b, 0x16, 0xd9,
0x04, 0x48, 0xce, 0xc0, 0x94, 0x2b, 0x91, 0x39, 0x5e, 0x53, 0x62, 0x2f, 0xe7, 0xc0, 0xec, 0x10,
0xaa, 0xc9, 0xa1, 0xca, 0x4a, 0x72, 0x37, 0xc2, 0x38, 0x82, 0x51, 0x1a, 0x3c, 0x73, 0xd4, 0x61,
0x37, 0x71, 0xa9, 0x80, 0x54, 0xd8, 0x52, 0xe1, 0xf9, 0x85, 0x07, 0x0b, 0x7c, 0x80, 0xca, 0x1c,
0xc1, 0xd4, 0x2b, 0x39, 0x93, 0x9c, 0xe3, 0x06, 0xc5, 0xcd, 0xb9, 0xd1, 0x7a, 0x23, 0x22, 0xc2,
0xa8, 0x95, 0xa7, 0x7d, 0x31, 0xd1, 0x3c, 0x84, 0xf9, 0x4c, 0x38, 0x59, 0xb1, 0xf4, 0xa4, 0x08,
0xbf, 0x62, 0xe9, 0x89, 0x91, 0x68, 0x7b, 0x09, 0xbb, 0x9c, 0xb3, 0x01, 0x3d, 0xa0, 0x73, 0x2f,
0xee, 0x9e, 0x72, 0xdd, 0xb2, 0x90, 0x13, 0x2c, 0x26, 0xaf, 0x4b, 0x5f, 0x7a, 0x62, 0x20, 0xb9,
0x9d, 0x1b, 0x4b, 0xb4, 0xa7, 0xc8, 0xfb, 0xb0, 0xc2, 0xbf, 0xda, 0x1c, 0x0c, 0x52, 0x41, 0xc9,
0x6b, 0x99, 0x07, 0xb2, 0x8d, 0x60, 0x6b, 0x7b, 0xf2, 0x03, 0xda, 0xf6, 0x14, 0x39, 0x84, 0x66,
0x3a, 0x9a, 0x47, 0x26, 0x7f, 0xd0, 0x7e, 0xcd, 0xf0, 0xd3, 0xb2, 0x11, 0x40, 0x7b, 0x8a, 0x7c,
0x59, 0x45, 0x15, 0x53, 0xe3, 0x94, 0xdf, 0x4e, 0x0a, 0x83, 0x2a, 0x27, 0x30, 0x3f, 0x28, 0x39,
0x45, 0x3e, 0x80, 0x95, 0x34, 0x77, 0xc8, 0xb6, 0x57, 0xf3, 0x96, 0x6d, 0xa2, 0xc5, 0x9e, 0x5e,
0x85, 0x5b, 0xd6, 0xd6, 0x8d, 0x0f, 0xfe, 0x5b, 0xdf, 0x8b, 0x4f, 0xc7, 0xc7, 0x37, 0xbb, 0xc1,
0x70, 0x63, 0x20, 0x03, 0x4d, 0x22, 0x59, 0x76, 0x63, 0xe0, 0xf7, 0x36, 0xf0, 0xfb, 0xe3, 0x69,
0x7c, 0x09, 0xff, 0x53, 0xff, 0x11, 0x00, 0x00, 0xff, 0xff, 0x26, 0x18, 0x47, 0xb5, 0x3b, 0x5f,
0x00, 0x00,
}

@ -145,11 +145,21 @@ message InitWalletRequest {
/**
recovery_window is an optional argument specifying the address lookahead
when restoring a wallet seed. The recovery window applies to each
invdividual branch of the BIP44 derivation paths. Supplying a recovery
individual branch of the BIP44 derivation paths. Supplying a recovery
window of zero indicates that no addresses should be recovered, such after
the first initialization of the wallet.
*/
int32 recovery_window = 4;
/**
channel_backups is an optional argument that allows clients to recover the
settled funds within a set of channels. This should be populated if the
user was unable to close out all channels and sweep funds before partial or
total data loss occurred. If specified, then after on-chain recovery of
funds, lnd begin to carry out the data loss recovery protocol in order to
recover the funds in each channel from a remote force closed transaction.
*/
ChanBackupSnapshot channel_backups = 5;
}
message InitWalletResponse {
}
@ -170,6 +180,16 @@ message UnlockWalletRequest {
the first initialization of the wallet.
*/
int32 recovery_window = 2;
/**
channel_backups is an optional argument that allows clients to recover the
settled funds within a set of channels. This should be populated if the
user was unable to close out all channels and sweep funds before partial or
total data loss occurred. If specified, then after on-chain recovery of
funds, lnd begin to carry out the data loss recovery protocol in order to
recover the funds in each channel from a remote force closed transaction.
*/
ChanBackupSnapshot channel_backups = 3;
}
message UnlockWalletResponse {}
@ -668,7 +688,7 @@ service Lightning {
/** lncli: `fwdinghistory`
ForwardingHistory allows the caller to query the htlcswitch for a record of
all HTLC's forwarded within the target time range, and integer offset
all HTLCs forwarded within the target time range, and integer offset
within that time range. If no time-range is specified, then the first chunk
of the past 24 hrs of forwarding history are returned.
@ -684,6 +704,57 @@ service Lightning {
body: "*"
};
};
/** lncli: `exportchanbackup`
ExportChannelBackup attempts to return an encrypted static channel backup
for the target channel identified by it channel point. The backup is
encrypted with a key generated from the aezeed seed of the user. The
returned backup can either be restored using the RestoreChannelBackup
method once lnd is running, or via the InitWallet and UnlockWallet methods
from the WalletUnlocker service.
*/
rpc ExportChannelBackup(ExportChannelBackupRequest) returns (ChannelBackup) {
};
/**
ExportAllChannelBackups returns static channel backups for all existing
channels known to lnd. A set of regular singular static channel backups for
each channel are returned. Additionally, a multi-channel backup is returned
as well, which contains a single encrypted blob containing the backups of
each channel.
*/
rpc ExportAllChannelBackups(ChanBackupExportRequest) returns (ChanBackupSnapshot) {
};
/**
VerifyChanBackup allows a caller to verify the integrity of a channel
backup snapshot. This method will accept both a packed Single, and also a
Packed multi. Two bools are returned which indicate if the passed Single
(if present) is valid and also if the passed Multi (if present) is valid.
*/
rpc VerifyChanBackup(ChanBackupSnapshot) returns (VerifyChanBackupResponse) {
};
/** lncli: `restorechanbackup`
RestoreChannelBackups accepts a set of singular channel backups, or a
single encrypted multi-chan backup and attempts to recover any funds
remaining within the channel. If we are able to unpack the backup, then the
new channel will be shown under listchannels, as well as pending channels.
*/
rpc RestoreChannelBackups(RestoreChanBackupRequest) returns (RestoreBackupResponse) {
};
/**
SubscribeChannelBackups allows a client to sub-subscribe to the most up to
date information concerning the state of all channel backups. Each time a
new channel is added, we return the new set of channels, along with a
multi-chan backup containing the backup info for all channels. Each time a
channel is closed, we send a new update, which contains new new chan back
ups, but the updated set of encrypted multi-chan backups with the closed
channel(s) removed.
*/
rpc SubscribeChannelBackups(ChannelBackupSubscription) returns (stream ChanBackupSnapshot) {
};
}
message Utxo {
@ -1067,9 +1138,8 @@ message Channel {
repeated HTLC pending_htlcs = 15 [json_name = "pending_htlcs"];
/**
The CSV delay expressed in relative blocks. If the channel is force
closed, we'll need to wait for this many blocks before we can regain our
funds.
The CSV delay expressed in relative blocks. If the channel is force closed,
we will need to wait for this many blocks before we can regain our funds.
*/
uint32 csv_delay = 16 [json_name = "csv_delay"];
@ -1078,6 +1148,9 @@ message Channel {
/// True if we were the ones that created the channel.
bool initiator = 18 [json_name = "initiator"];
/// A set of flags showing the current state of the cahnnel.
string chan_status_flags = 19 [json_name = "chan_status_flags"];
}
@ -2190,3 +2263,74 @@ message ForwardingHistoryResponse {
/// The index of the last time in the set of returned forwarding events. Can be used to seek further, pagination style.
uint32 last_offset_index = 2 [json_name = "last_offset_index"];
}
message ExportChannelBackupRequest {
/// The target chanenl point to obtain a back up for.
ChannelPoint chan_point = 1;
}
message ChannelBackup {
/**
Identifies the channel that this backup belongs to.
*/
ChannelPoint chan_point = 1 [ json_name = "chan_point" ];
/**
Is an encrypted single-chan backup. this can be passed to
RestoreChannelBackups, or the WalletUnlocker Innit and Unlock methods in
order to trigger the recovery protocol.
*/
bytes chan_backup = 2 [ json_name = "chan_backup" ];
}
message MultiChanBackup {
/**
Is the set of all channels that are included in this multi-channel backup.
*/
repeated ChannelPoint chan_points = 1 [ json_name = "chan_points" ];
/**
A single encrypted blob containing all the static channel backups of the
channel listed above. This can be stored as a single file or blob, and
safely be replaced with any prior/future versions.
*/
bytes multi_chan_backup = 2 [ json_name = "multi_chan_backup" ];
}
message ChanBackupExportRequest {}
message ChanBackupSnapshot {
/**
The set of new channels that have been added since the last channel backup
snapshot was requested.
*/
ChannelBackups single_chan_backups = 1 [ json_name = "single_chan_backups" ];
/**
A multi-channel backup that covers all open channels currently known to
lnd.
*/
MultiChanBackup multi_chan_backup = 2 [ json_name = "multi_chan_backup" ];
}
message ChannelBackups {
/**
A set of single-chan static channel backups.
*/
repeated ChannelBackup chan_backups = 1 [ json_name = "chan_backups" ];
}
message RestoreChanBackupRequest {
oneof backup {
ChannelBackups chan_backups = 1 [ json_name = "chan_backups" ];
bytes multi_chan_backup = 2 [ json_name = "multi_chan_backup" ];
}
}
message RestoreBackupResponse {}
message ChannelBackupSubscription {}
message VerifyChanBackupResponse {
bool singles_valid = 1 [ json_name = "singles_valid"];
bool multi_valid = 2 [ json_name = "multi_valid"];
}

@ -976,7 +976,7 @@
},
"/v1/switch": {
"post": {
"summary": "* lncli: `fwdinghistory`\nForwardingHistory allows the caller to query the htlcswitch for a record of\nall HTLC's forwarded within the target time range, and integer offset\nwithin that time range. If no time-range is specified, then the first chunk\nof the past 24 hrs of forwarding history are returned.",
"summary": "* lncli: `fwdinghistory`\nForwardingHistory allows the caller to query the htlcswitch for a record of\nall HTLCs forwarded within the target time range, and integer offset\nwithin that time range. If no time-range is specified, then the first chunk\nof the past 24 hrs of forwarding history are returned.",
"description": "A list of forwarding events are returned. The size of each forwarding event\nis 40 bytes, and the max message size able to be returned in gRPC is 4 MiB.\nAs a result each message can only contain 50k entries. Each response has\nthe index offset of the last entry. The index offset can be provided to the\nrequest to allow the caller to skip a series of records.",
"operationId": "ForwardingHistory",
"responses": {
@ -1358,6 +1358,19 @@
}
}
},
"lnrpcChanBackupSnapshot": {
"type": "object",
"properties": {
"single_chan_backups": {
"$ref": "#/definitions/lnrpcChannelBackups",
"description": "*\nThe set of new channels that have been added since the last channel backup\nsnapshot was requested."
},
"multi_chan_backup": {
"$ref": "#/definitions/lnrpcMultiChanBackup",
"description": "*\nA multi-channel backup that covers all open channels currently known to\nlnd."
}
}
},
"lnrpcChangePasswordRequest": {
"type": "object",
"properties": {
@ -1457,7 +1470,7 @@
"csv_delay": {
"type": "integer",
"format": "int64",
"description": "*\nThe CSV delay expressed in relative blocks. If the channel is force\nclosed, we'll need to wait for this many blocks before we can regain our\nfunds."
"description": "*\nThe CSV delay expressed in relative blocks. If the channel is force closed,\nwe will need to wait for this many blocks before we can regain our funds."
},
"private": {
"type": "boolean",
@ -1468,6 +1481,36 @@
"type": "boolean",
"format": "boolean",
"description": "/ True if we were the ones that created the channel."
},
"chan_status_flags": {
"type": "string",
"description": "/ A set of flags showing the current state of the cahnnel."
}
}
},
"lnrpcChannelBackup": {
"type": "object",
"properties": {
"chan_point": {
"$ref": "#/definitions/lnrpcChannelPoint",
"description": "*\nIdentifies the channel that this backup belongs to."
},
"chan_backup": {
"type": "string",
"format": "byte",
"description": "*\nIs an encrypted single-chan backup. this can be passed to\nRestoreChannelBackups, or the WalletUnlocker Innit and Unlock methods in\norder to trigger the recovery protocol."
}
}
},
"lnrpcChannelBackups": {
"type": "object",
"properties": {
"chan_backups": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcChannelBackup"
},
"description": "*\nA set of single-chan static channel backups."
}
}
},
@ -2139,7 +2182,11 @@
"recovery_window": {
"type": "integer",
"format": "int32",
"description": "*\nrecovery_window is an optional argument specifying the address lookahead\nwhen restoring a wallet seed. The recovery window applies to each\ninvdividual branch of the BIP44 derivation paths. Supplying a recovery\nwindow of zero indicates that no addresses should be recovered, such after\nthe first initialization of the wallet."
"description": "*\nrecovery_window is an optional argument specifying the address lookahead\nwhen restoring a wallet seed. The recovery window applies to each\nindividual branch of the BIP44 derivation paths. Supplying a recovery\nwindow of zero indicates that no addresses should be recovered, such after\nthe first initialization of the wallet."
},
"channel_backups": {
"$ref": "#/definitions/lnrpcChanBackupSnapshot",
"description": "*\nchannel_backups is an optional argument that allows clients to recover the\nsettled funds within a set of channels. This should be populated if the\nuser was unable to close out all channels and sweep funds before partial or\ntotal data loss occurred. If specified, then after on-chain recovery of\nfunds, lnd begin to carry out the data loss recovery protocol in order to\nrecover the funds in each channel from a remote force closed transaction."
}
}
},
@ -2362,6 +2409,23 @@
}
}
},
"lnrpcMultiChanBackup": {
"type": "object",
"properties": {
"chan_points": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcChannelPoint"
},
"description": "*\nIs the set of all channels that are included in this multi-channel backup."
},
"multi_chan_backup": {
"type": "string",
"format": "byte",
"description": "*\nA single encrypted blob containing all the static channel backups of the\nchannel listed above. This can be stored as a single file or blob, and\nsafely be replaced with any prior/future versions."
}
}
},
"lnrpcNetworkInfo": {
"type": "object",
"properties": {
@ -2812,6 +2876,9 @@
}
}
},
"lnrpcRestoreBackupResponse": {
"type": "object"
},
"lnrpcRoute": {
"type": "object",
"properties": {
@ -3124,6 +3191,10 @@
"type": "integer",
"format": "int32",
"description": "*\nrecovery_window is an optional argument specifying the address lookahead\nwhen restoring a wallet seed. The recovery window applies to each\ninvdividual branch of the BIP44 derivation paths. Supplying a recovery\nwindow of zero indicates that no addresses should be recovered, such after\nthe first initialization of the wallet."
},
"channel_backups": {
"$ref": "#/definitions/lnrpcChanBackupSnapshot",
"description": "*\nchannel_backups is an optional argument that allows clients to recover the\nsettled funds within a set of channels. This should be populated if the\nuser was unable to close out all channels and sweep funds before partial or\ntotal data loss occurred. If specified, then after on-chain recovery of\nfunds, lnd begin to carry out the data loss recovery protocol in order to\nrecover the funds in each channel from a remote force closed transaction."
}
}
},
@ -3161,6 +3232,19 @@
}
}
},
"lnrpcVerifyChanBackupResponse": {
"type": "object",
"properties": {
"singles_valid": {
"type": "boolean",
"format": "boolean"
},
"multi_valid": {
"type": "boolean",
"format": "boolean"
}
}
},
"lnrpcVerifyMessageRequest": {
"type": "object",
"properties": {

@ -248,7 +248,7 @@ func (n *NetworkHarness) TearDownAll() error {
// current instance of the network harness. The created node is running, but
// not yet connected to other nodes within the network.
func (n *NetworkHarness) NewNode(name string, extraArgs []string) (*HarnessNode, error) {
return n.newNode(name, extraArgs, false)
return n.newNode(name, extraArgs, false, nil)
}
// NewNodeWithSeed fully initializes a new HarnessNode after creating a fresh
@ -258,7 +258,7 @@ func (n *NetworkHarness) NewNode(name string, extraArgs []string) (*HarnessNode,
func (n *NetworkHarness) NewNodeWithSeed(name string, extraArgs []string,
password []byte) (*HarnessNode, []string, error) {
node, err := n.newNode(name, extraArgs, true)
node, err := n.newNode(name, extraArgs, true, password)
if err != nil {
return nil, nil, err
}
@ -301,14 +301,15 @@ func (n *NetworkHarness) NewNodeWithSeed(name string, extraArgs []string,
}
// RestoreNodeWithSeed fully initializes a HarnessNode using a chosen mnemonic,
// password, and recovery window. After providing the initialization request to
// unlock the node, this method will finish initializing the LightningClient
// such that the HarnessNode can be used for regular rpc operations.
// password, recovery window, and optionally a set of static channel backups.
// After providing the initialization request to unlock the node, this method
// will finish initializing the LightningClient such that the HarnessNode can
// be used for regular rpc operations.
func (n *NetworkHarness) RestoreNodeWithSeed(name string, extraArgs []string,
password []byte, mnemonic []string,
recoveryWindow int32) (*HarnessNode, error) {
password []byte, mnemonic []string, recoveryWindow int32,
chanBackups *lnrpc.ChanBackupSnapshot) (*HarnessNode, error) {
node, err := n.newNode(name, extraArgs, true)
node, err := n.newNode(name, extraArgs, true, password)
if err != nil {
return nil, err
}
@ -318,6 +319,7 @@ func (n *NetworkHarness) RestoreNodeWithSeed(name string, extraArgs []string,
CipherSeedMnemonic: mnemonic,
AezeedPassphrase: password,
RecoveryWindow: recoveryWindow,
ChannelBackups: chanBackups,
}
err = node.Init(context.Background(), initReq)
@ -337,10 +339,12 @@ func (n *NetworkHarness) RestoreNodeWithSeed(name string, extraArgs []string,
// can be used immediately. Otherwise, the node will require an additional
// initialization phase where the wallet is either created or restored.
func (n *NetworkHarness) newNode(name string, extraArgs []string,
hasSeed bool) (*HarnessNode, error) {
hasSeed bool, password []byte) (*HarnessNode, error) {
node, err := newNode(nodeConfig{
Name: name,
HasSeed: hasSeed,
Password: password,
BackendCfg: n.BackendCfg,
NetParams: n.netParams,
ExtraArgs: extraArgs,
@ -573,8 +577,12 @@ func (n *NetworkHarness) DisconnectNodes(ctx context.Context, a, b *HarnessNode)
//
// This method can be useful when testing edge cases such as a node broadcast
// and invalidated prior state, or persistent state recovery, simulating node
// crashes, etc.
func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error) error {
// crashes, etc. Additionally, each time the node is restarted, the caller can
// pass a set of SCBs to pass in via the Unlock method allowing them to restore
// channels during restart.
func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error,
chanBackups ...*lnrpc.ChanBackupSnapshot) error {
if err := node.stop(); err != nil {
return err
}
@ -585,7 +593,27 @@ func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error) e
}
}
return node.start(n.lndErrorChan)
if err := node.start(n.lndErrorChan); err != nil {
return err
}
// If the node doesn't have a password set, then we can exit here as we
// don't need to unlock it.
if len(node.cfg.Password) == 0 {
return nil
}
// Otherwise, we'll unlock the wallet, then complete the final steps
// for the node initialization process.
unlockReq := &lnrpc.UnlockWalletRequest{
WalletPassword: node.cfg.Password,
}
if len(chanBackups) != 0 {
unlockReq.ChannelBackups = chanBackups[0]
unlockReq.RecoveryWindow = 1000
}
return node.Unlock(context.Background(), unlockReq)
}
// SuspendNode stops the given node and returns a callback that can be used to

@ -25,6 +25,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
)
@ -117,7 +118,8 @@ type nodeConfig struct {
ReadMacPath string
InvoiceMacPath string
HasSeed bool
HasSeed bool
Password []byte
P2PPort int
RPCPort int
@ -141,6 +143,16 @@ func (cfg nodeConfig) DBPath() string {
fmt.Sprintf("%v/channel.db", cfg.NetParams.Name))
}
func (cfg nodeConfig) ChanBackupPath() string {
return filepath.Join(
cfg.DataDir, "chain", "bitcoin",
fmt.Sprintf(
"%v/%v", cfg.NetParams.Name,
chanbackup.DefaultBackupFileName,
),
)
}
// genArgs generates a slice of command line arguments from the lightning node
// config struct.
func (cfg nodeConfig) genArgs() []string {
@ -278,6 +290,12 @@ func (hn *HarnessNode) Name() string {
return hn.cfg.Name
}
// ChanBackupPath returns the fielpath to the on-disk channels.backup file for
// this node.
func (hn *HarnessNode) ChanBackupPath() string {
return hn.cfg.ChanBackupPath()
}
// Start launches a new process running lnd. Additionally, the PID of the
// launched process is saved in order to possibly kill the process forcibly
// later.
@ -404,6 +422,24 @@ func (hn *HarnessNode) start(lndError chan<- error) error {
return hn.initLightningClient(conn)
}
// initClientWhenReady waits until the main gRPC server is detected as active,
// then complete the normal HarnessNode gRPC connection creation. This can be
// used it a node has just been unlocked, or has its wallet state initialized.
func (hn *HarnessNode) initClientWhenReady() error {
var (
conn *grpc.ClientConn
connErr error
)
if err := WaitNoError(func() error {
conn, connErr = hn.ConnectRPC(true)
return connErr
}, 5*time.Second); err != nil {
return err
}
return hn.initLightningClient(conn)
}
// Init initializes a harness node by passing the init request via rpc. After
// the request is submitted, this method will block until an
// macaroon-authenticated rpc connection can be established to the harness node.
@ -412,8 +448,7 @@ func (hn *HarnessNode) start(lndError chan<- error) error {
func (hn *HarnessNode) Init(ctx context.Context,
initReq *lnrpc.InitWalletRequest) error {
timeout := time.Duration(time.Second * 15)
ctxt, _ := context.WithTimeout(ctx, timeout)
ctxt, _ := context.WithTimeout(ctx, DefaultTimeout)
_, err := hn.InitWallet(ctxt, initReq)
if err != nil {
return err
@ -421,15 +456,27 @@ func (hn *HarnessNode) Init(ctx context.Context,
// Wait for the wallet to finish unlocking, such that we can connect to
// it via a macaroon-authenticated rpc connection.
var conn *grpc.ClientConn
if err = WaitPredicate(func() bool {
conn, err = hn.ConnectRPC(true)
return err == nil
}, 5*time.Second); err != nil {
return hn.initClientWhenReady()
}
// Unlock attempts to unlock the wallet of the target HarnessNode. This method
// should be called after the restart of a HarnessNode that was created with a
// seed+password. Once this method returns, the HarnessNode will be ready to
// accept normal gRPC requests and harness command.
func (hn *HarnessNode) Unlock(ctx context.Context,
unlockReq *lnrpc.UnlockWalletRequest) error {
ctxt, _ := context.WithTimeout(ctx, DefaultTimeout)
// Otherwise, we'll need to unlock the node before it's able to start
// up properly.
if _, err := hn.UnlockWallet(ctxt, unlockReq); err != nil {
return err
}
return hn.initLightningClient(conn)
// Now that the wallet has been unlocked, we'll wait for the RPC client
// to be ready, then establish the normal gRPC connection.
return hn.initClientWhenReady()
}
// initLightningClient constructs the grpc LightningClient from the given client

@ -856,12 +856,16 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool,
// haven't yet received a responding commitment from the remote party.
var localCommitKeys, remoteCommitKeys *CommitmentKeyRing
if localCommitPoint != nil {
localCommitKeys = deriveCommitmentKeys(localCommitPoint, true,
lc.localChanCfg, lc.remoteChanCfg)
localCommitKeys = deriveCommitmentKeys(
localCommitPoint, true, lc.localChanCfg,
lc.remoteChanCfg,
)
}
if remoteCommitPoint != nil {
remoteCommitKeys = deriveCommitmentKeys(remoteCommitPoint, false,
lc.localChanCfg, lc.remoteChanCfg)
remoteCommitKeys = deriveCommitmentKeys(
remoteCommitPoint, false, lc.localChanCfg,
lc.remoteChanCfg,
)
}
// With the key rings re-created, we'll now convert all the on-disk
@ -1408,8 +1412,7 @@ func NewLightningChannel(signer input.Signer, pCache PreimageCache,
// Create the sign descriptor which we'll be using very frequently to
// request a signature for the 2-of-2 multi-sig from the signer in
// order to complete channel state transitions.
err = lc.createSignDesc()
if err != nil {
if err := lc.createSignDesc(); err != nil {
return nil, err
}
@ -1701,7 +1704,8 @@ func (lc *LightningChannel) restoreCommitState(
// Finally, with the commitment states restored, we'll now restore the
// state logs based on the current local+remote commit, and any pending
// remote commit that exists.
err = lc.restoreStateLogs(localCommit, remoteCommit, pendingRemoteCommit,
err = lc.restoreStateLogs(
localCommit, remoteCommit, pendingRemoteCommit,
pendingRemoteCommitDiff, pendingRemoteKeyChain,
)
if err != nil {
@ -3248,6 +3252,13 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
}
}
// If we detect that this is is a restored channel, then we can skip a
// portion of the verification, as we already know that we're unable to
// proceed with any updates.
isRestoredChan := lc.channelState.HasChanStatus(
channeldb.ChanStatusRestored,
)
// Take note of our current commit chain heights before we begin adding
// more to them.
var (
@ -3265,12 +3276,17 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
// If their reported height for our local chain tail is ahead of our
// view, then we're behind!
case msg.RemoteCommitTailHeight > localTailHeight:
case msg.RemoteCommitTailHeight > localTailHeight || isRestoredChan:
walletLog.Errorf("ChannelPoint(%v), sync failed with local "+
"data loss: remote believes our tail height is %v, "+
"while we have %v!", lc.channelState.FundingOutpoint,
msg.RemoteCommitTailHeight, localTailHeight)
if isRestoredChan {
walletLog.Warnf("ChannelPoint(%v): detected restored " +
"triggering DLP")
}
// We must check that we had recovery options to ensure the
// commitment secret matched up, and the remote is just not
// lying about its height.
@ -3480,12 +3496,21 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
// is valid.
var commitPoint *btcec.PublicKey
switch {
case msg.NextLocalCommitHeight == remoteTailHeight+2:
commitPoint = lc.channelState.RemoteNextRevocation
// If their height is one beyond what we know their current height to
// be, then we need to compare their current unrevoked commitment point
// as that's what they should send.
case msg.NextLocalCommitHeight == remoteTailHeight+1:
commitPoint = lc.channelState.RemoteCurrentRevocation
// Alternatively, if their height is two beyond what we know their best
// height to be, then they're holding onto two commitments, and the
// highest unrevoked point is their next revocation.
//
// TODO(roasbeef): verify this in the spec...
case msg.NextLocalCommitHeight == remoteTailHeight+2:
commitPoint = lc.channelState.RemoteNextRevocation
}
if commitPoint != nil &&
!commitPoint.IsEqual(msg.LocalUnrevokedCommitPoint) {
@ -3497,6 +3522,7 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
if err := lc.channelState.MarkBorked(); err != nil {
return nil, nil, nil, err
}
// TODO(halseth): force close?
return nil, nil, nil, ErrInvalidLocalUnrevokedCommitPoint
}
@ -3515,7 +3541,14 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
// it.
// 3. We didn't get the last RevokeAndAck message they sent, so they'll
// re-send it.
func ChanSyncMsg(c *channeldb.OpenChannel) (*lnwire.ChannelReestablish, error) {
//
// The isRestoredChan bool indicates if we need to craft a chan sync message
// for a channel that's been restored. If this is a restored channel, then
// we'll modify our typical chan sync message to ensure they force close even
// if we're on the very first state.
func ChanSyncMsg(c *channeldb.OpenChannel,
isRestoredChan bool) (*lnwire.ChannelReestablish, error) {
c.Lock()
defer c.Unlock()
@ -3556,6 +3589,13 @@ func ChanSyncMsg(c *channeldb.OpenChannel) (*lnwire.ChannelReestablish, error) {
return nil, err
}
// If we've restored this channel, then we'll purposefully give them an
// invalid LocalUnrevokedCommitPoint so they'll force close the channel
// allowing us to sweep our funds.
if isRestoredChan {
currentCommitSecret[0] ^= 1
}
return &lnwire.ChannelReestablish{
ChanID: lnwire.NewChanIDFromOutPoint(
&c.FundingOutpoint,
@ -3616,7 +3656,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
// will remove their corresponding added HTLCs. The resulting filtered
// view will only have Add entries left, making it easy to compare the
// channel constraints to the final commitment state. If any fee
// updates are found in the logs, the comitment fee rate should be
// updates are found in the logs, the commitment fee rate should be
// changed, so we'll also set the feePerKw to this new value.
filteredHTLCView := lc.evaluateHTLCView(view, &ourBalance,
&theirBalance, nextHeight, remoteChain, updateState)
@ -5070,12 +5110,14 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
// Next, we'll obtain HTLC resolutions for all the outgoing HTLC's we
// had on their commitment transaction.
htlcResolutions, err := extractHtlcResolutions(
SatPerKWeight(remoteCommit.FeePerKw), false, signer, remoteCommit.Htlcs,
keyRing, &chanState.LocalChanCfg, &chanState.RemoteChanCfg,
*commitSpend.SpenderTxHash, pCache,
SatPerKWeight(remoteCommit.FeePerKw), false, signer,
remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, *commitSpend.SpenderTxHash,
pCache,
)
if err != nil {
return nil, fmt.Errorf("unable to create htlc resolutions: %v", err)
return nil, fmt.Errorf("unable to create htlc "+
"resolutions: %v", err)
}
commitTxBroadcast := commitSpend.SpendingTx
@ -5085,7 +5127,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
// transaction.
selfP2WKH, err := input.CommitScriptUnencumbered(keyRing.NoDelayKey)
if err != nil {
return nil, fmt.Errorf("unable to create self commit script: %v", err)
return nil, fmt.Errorf("unable to create self commit "+
"script: %v", err)
}
var (
@ -5143,7 +5186,10 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
}
// Attempt to add a channel sync message to the close summary.
chanSync, err := ChanSyncMsg(chanState)
chanSync, err := ChanSyncMsg(
chanState,
chanState.HasChanStatus(channeldb.ChanStatusRestored),
)
if err != nil {
walletLog.Errorf("ChannelPoint(%v): unable to create channel sync "+
"message: %v", chanState.FundingOutpoint, err)

@ -2532,7 +2532,7 @@ func assertNoChanSyncNeeded(t *testing.T, aliceChannel *LightningChannel,
_, _, line, _ := runtime.Caller(1)
aliceChanSyncMsg, err := ChanSyncMsg(aliceChannel.channelState)
aliceChanSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false)
if err != nil {
t.Fatalf("line #%v: unable to produce chan sync msg: %v",
line, err)
@ -2547,7 +2547,7 @@ func assertNoChanSyncNeeded(t *testing.T, aliceChannel *LightningChannel,
"instead wants to send: %v", line, spew.Sdump(bobMsgsToSend))
}
bobChanSyncMsg, err := ChanSyncMsg(bobChannel.channelState)
bobChanSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("line #%v: unable to produce chan sync msg: %v",
line, err)
@ -2780,11 +2780,11 @@ func TestChanSyncOweCommitment(t *testing.T) {
// Bob doesn't get this message so upon reconnection, they need to
// synchronize. Alice should conclude that she owes Bob a commitment,
// while Bob should think he's properly synchronized.
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState)
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3093,11 +3093,11 @@ func TestChanSyncOweRevocation(t *testing.T) {
// If we fetch the channel sync messages at this state, then Alice
// should report that she owes Bob a revocation message, while Bob
// thinks they're fully in sync.
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState)
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3261,11 +3261,11 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) {
// If we now attempt to resync, then Alice should conclude that she
// doesn't need any further updates, while Bob concludes that he needs
// to re-send both his revocation and commit sig message.
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState)
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3470,11 +3470,11 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) {
// Now if we attempt to synchronize states at this point, Alice should
// detect that she owes nothing, while Bob should re-send both his
// RevokeAndAck as well as his commitment message.
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState)
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3674,11 +3674,11 @@ func TestChanSyncFailure(t *testing.T) {
assertLocalDataLoss := func(aliceOld *LightningChannel) {
t.Helper()
aliceSyncMsg, err := ChanSyncMsg(aliceOld.channelState)
aliceSyncMsg, err := ChanSyncMsg(aliceOld.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3752,7 +3752,7 @@ func TestChanSyncFailure(t *testing.T) {
// If we remove the recovery options from Bob's message, Alice cannot
// tell if she lost state, since Bob might be lying. She still should
// be able to detect that chains cannot be synced.
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3766,7 +3766,7 @@ func TestChanSyncFailure(t *testing.T) {
// If Bob lies about the NextLocalCommitHeight, making it greater than
// what Alice expect, she cannot tell for sure whether she lost state,
// but should detect the desync.
bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3779,7 +3779,7 @@ func TestChanSyncFailure(t *testing.T) {
// If Bob's NextLocalCommitHeight is lower than what Alice expects, Bob
// probably lost state.
bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3792,7 +3792,7 @@ func TestChanSyncFailure(t *testing.T) {
// If Alice and Bob's states are in sync, but Bob is sending the wrong
// LocalUnrevokedCommitPoint, Alice should detect this.
bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3821,7 +3821,7 @@ func TestChanSyncFailure(t *testing.T) {
// when there's a pending remote commit.
halfAdvance()
bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err = ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -3909,11 +3909,11 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) {
// Bob doesn't get this message so upon reconnection, they need to
// synchronize. Alice should conclude that she owes Bob a commitment,
// while Bob should think he's properly synchronized.
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState)
aliceSyncMsg, err := ChanSyncMsg(aliceChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState)
bobSyncMsg, err := ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to produce chan sync msg: %v", err)
}
@ -4357,11 +4357,11 @@ func TestChanSyncInvalidLastSecret(t *testing.T) {
}
// Next, we'll produce the ChanSync messages for both parties.
aliceChanSync, err := ChanSyncMsg(aliceChannel.channelState)
aliceChanSync, err := ChanSyncMsg(aliceChannel.channelState, false)
if err != nil {
t.Fatalf("unable to generate chan sync msg: %v", err)
}
bobChanSync, err := ChanSyncMsg(bobChannel.channelState)
bobChanSync, err := ChanSyncMsg(bobChannel.channelState, false)
if err != nil {
t.Fatalf("unable to generate chan sync msg: %v", err)
}

@ -416,6 +416,14 @@ func WriteElement(w io.Writer, element interface{}) error {
return err
}
case bool:
var b [1]byte
if e {
b[0] = 1
}
if _, err := w.Write(b[:]); err != nil {
return err
}
default:
return fmt.Errorf("Unknown type in WriteElement: %T", e)
}
@ -440,6 +448,16 @@ func WriteElements(w io.Writer, elements ...interface{}) error {
func ReadElement(r io.Reader, element interface{}) error {
var err error
switch e := element.(type) {
case *bool:
var b [1]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
if b[0] == 1 {
*e = true
}
case *NodeAlias:
var a [32]byte
if _, err := io.ReadFull(r, a[:]); err != nil {

4
log.go

@ -14,6 +14,7 @@ import (
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/contractcourt"
@ -83,6 +84,7 @@ var (
ntfrLog = build.NewSubLogger("NTFR", backendLog.Logger)
irpcLog = build.NewSubLogger("IRPC", backendLog.Logger)
chnfLog = build.NewSubLogger("CHNF", backendLog.Logger)
chbuLog = build.NewSubLogger("CHBU", backendLog.Logger)
)
// Initialize package-global logger variables.
@ -109,6 +111,7 @@ func init() {
chainrpc.UseLogger(ntfrLog)
invoicesrpc.UseLogger(irpcLog)
channelnotifier.UseLogger(chnfLog)
chanbackup.UseLogger(chbuLog)
addSubLogger(routerrpc.Subsystem, routerrpc.UseLogger)
}
@ -151,6 +154,7 @@ var subsystemLoggers = map[string]btclog.Logger{
"NTFR": ntfnLog,
"IRPC": irpcLog,
"CHNF": chnfLog,
"CHBU": chbuLog,
}
// initLogRotator initializes the logging rotator to write logs to logFile and

@ -438,7 +438,12 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
// Skip adding any permanently irreconcilable channels to the
// htlcswitch.
if dbChan.ChanStatus() != channeldb.ChanStatusDefault {
switch {
case dbChan.HasChanStatus(channeldb.ChanStatusBorked):
fallthrough
case dbChan.HasChanStatus(channeldb.ChanStatusCommitBroadcasted):
fallthrough
case dbChan.HasChanStatus(channeldb.ChanStatusLocalDataLoss):
peerLog.Warnf("ChannelPoint(%v) has status %v, won't "+
"start.", chanPoint, dbChan.ChanStatus())
continue

@ -31,6 +31,7 @@ import (
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/htlcswitch"
@ -357,6 +358,26 @@ var (
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/RestoreChannelBackups": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/ExportChannelBackup": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/VerifyChanBackup": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/ExportAllChannelBackups": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/SubscribeChannelBackups": {{
Entity: "offchain",
Action: "read",
}},
}
)
@ -1415,17 +1436,12 @@ out:
switch update := fundingUpdate.Update.(type) {
case *lnrpc.OpenStatusUpdate_ChanOpen:
chanPoint := update.ChanOpen.ChannelPoint
txidHash, err := getChanPointFundingTxid(chanPoint)
if err != nil {
return err
}
h, err := chainhash.NewHash(txidHash)
txid, err := getChanPointFundingTxid(chanPoint)
if err != nil {
return err
}
outpoint = wire.OutPoint{
Hash: *h,
Hash: *txid,
Index: chanPoint.OutputIndex,
}
@ -1573,7 +1589,7 @@ func (r *rpcServer) OpenChannelSync(ctx context.Context,
// getChanPointFundingTxid returns the given channel point's funding txid in
// raw bytes.
func getChanPointFundingTxid(chanPoint *lnrpc.ChannelPoint) ([]byte, error) {
func getChanPointFundingTxid(chanPoint *lnrpc.ChannelPoint) (*chainhash.Hash, error) {
var txid []byte
// A channel point's funding txid can be get/set as a byte slice or a
@ -1591,7 +1607,7 @@ func getChanPointFundingTxid(chanPoint *lnrpc.ChannelPoint) ([]byte, error) {
txid = h[:]
}
return txid, nil
return chainhash.NewHash(txid)
}
// CloseChannel attempts to close an active channel identified by its channel
@ -1608,16 +1624,11 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
force := in.Force
index := in.ChannelPoint.OutputIndex
txidHash, err := getChanPointFundingTxid(in.GetChannelPoint())
txid, err := getChanPointFundingTxid(in.GetChannelPoint())
if err != nil {
rpcsLog.Errorf("[closechannel] unable to get funding txid: %v", err)
return err
}
txid, err := chainhash.NewHash(txidHash)
if err != nil {
rpcsLog.Errorf("[closechannel] invalid txid: %v", err)
return err
}
chanPoint := wire.NewOutPoint(txid, index)
rpcsLog.Tracef("[closechannel] request for ChannelPoint(%v), force=%v",
@ -1821,11 +1832,7 @@ func (r *rpcServer) AbandonChannel(ctx context.Context,
// We'll parse out the arguments to we can obtain the chanPoint of the
// target channel.
txidHash, err := getChanPointFundingTxid(in.GetChannelPoint())
if err != nil {
return nil, err
}
txid, err := chainhash.NewHash(txidHash)
txid, err := getChanPointFundingTxid(in.GetChannelPoint())
if err != nil {
return nil, err
}
@ -2540,6 +2547,7 @@ func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph,
PendingHtlcs: make([]*lnrpc.HTLC, len(localCommit.Htlcs)),
CsvDelay: uint32(dbChannel.LocalChanCfg.CsvDelay),
Initiator: dbChannel.IsInitiator,
ChanStatusFlags: dbChannel.ChanStatus().String(),
}
for i, htlc := range localCommit.Htlcs {
@ -4503,11 +4511,7 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context,
// Otherwise, we're targeting an individual channel by its channel
// point.
case *lnrpc.PolicyUpdateRequest_ChanPoint:
txidHash, err := getChanPointFundingTxid(scope.ChanPoint)
if err != nil {
return nil, err
}
txid, err := chainhash.NewHash(txidHash)
txid, err := getChanPointFundingTxid(scope.ChanPoint)
if err != nil {
return nil, err
}
@ -4676,3 +4680,338 @@ func (r *rpcServer) ForwardingHistory(ctx context.Context,
return resp, nil
}
// ExportChannelBackup attempts to return an encrypted static channel backup
// for the target channel identified by it channel point. The backup is
// encrypted with a key generated from the aezeed seed of the user. The
// returned backup can either be restored using the RestoreChannelBackup method
// once lnd is running, or via the InitWallet and UnlockWallet methods from the
// WalletUnlocker service.
func (r *rpcServer) ExportChannelBackup(ctx context.Context,
in *lnrpc.ExportChannelBackupRequest) (*lnrpc.ChannelBackup, error) {
// First, we'll convert the lnrpc channel point into a wire.OutPoint
// that we can manipulate.
txid, err := getChanPointFundingTxid(in.ChanPoint)
if err != nil {
return nil, err
}
chanPoint := wire.OutPoint{
Hash: *txid,
Index: in.ChanPoint.OutputIndex,
}
// Next, we'll attempt to fetch a channel backup for this channel from
// the database. If this channel has been closed, or the outpoint is
// unknown, then we'll return an error
unpackedBackup, err := chanbackup.FetchBackupForChan(
chanPoint, r.server.chanDB,
)
if err != nil {
return nil, err
}
// At this point, we have an unpacked backup (plaintext) so we'll now
// attempt to serialize and encrypt it in order to create a packed
// backup.
packedBackups, err := chanbackup.PackStaticChanBackups(
[]chanbackup.Single{*unpackedBackup},
r.server.cc.keyRing,
)
if err != nil {
return nil, fmt.Errorf("packing of back ups failed: %v", err)
}
// Before we proceed, we'll ensure that we received a backup for this
// channel, otherwise, we'll bail out.
packedBackup, ok := packedBackups[chanPoint]
if !ok {
return nil, fmt.Errorf("expected single backup for "+
"ChannelPoint(%v), got %v", chanPoint,
len(packedBackup))
}
return &lnrpc.ChannelBackup{
ChanPoint: in.ChanPoint,
ChanBackup: packedBackup,
}, nil
}
// VerifyChanBackup allows a caller to verify the integrity of a channel
// backup snapshot. This method will accept both a packed Single, and also a
// Packed multi. Two bools are returned which indicate if the passed Single
// (if present) is valid and also if the passed Multi (if present) is valid.
func (r *rpcServer) VerifyChanBackup(ctx context.Context,
in *lnrpc.ChanBackupSnapshot) (*lnrpc.VerifyChanBackupResponse, error) {
// If neither a Single or Multi has been specified, then we have
// nothing to verify.
if in.GetSingleChanBackups() == nil && in.GetMultiChanBackup() == nil {
return nil, fmt.Errorf("either a Single or Multi channel " +
"backup must be specified")
}
// If a Single is specified then we'll only accept one of them to allow
// the caller to map the valid/invalid state for each individual
// Single.
if in.GetSingleChanBackups() != nil &&
len(in.GetSingleChanBackups().ChanBackups) != 1 {
return nil, fmt.Errorf("only one Single is accepted at a time")
}
// By default, we'll assume that both backups are valid.
resp := lnrpc.VerifyChanBackupResponse{
SinglesValid: true,
MultiValid: true,
}
if in.GetSingleChanBackups() != nil {
// First, we'll convert the raw byte sliice into a type we can
// work with a bit better.
chanBackupsProtos := in.GetSingleChanBackups().ChanBackups
chanBackup := chanbackup.PackedSingles(
[][]byte{chanBackupsProtos[0].ChanBackup},
)
// With our PackedSingles created, we'll attempt to unpack the
// backup. If this fails, then we know the backup is invalid
// for some reason.
_, err := chanBackup.Unpack(r.server.cc.keyRing)
resp.SinglesValid = err == nil
}
if in.GetMultiChanBackup() != nil {
// Similarly, we'll convert the raw byte slice into a
// PackedMulti that we can easily work with.
packedMultiBackup := in.GetMultiChanBackup().MultiChanBackup
packedMulti := chanbackup.PackedMulti(packedMultiBackup)
// We'll now attempt to unpack the Multi. If this fails, then
// we know it's invalid.
_, err := packedMulti.Unpack(r.server.cc.keyRing)
resp.MultiValid = err == nil
}
return &resp, nil
}
// createBackupSnapshot converts the passed Single backup into a snapshot which
// contains individual packed single backups, as well as a single packed multi
// backup.
func (r *rpcServer) createBackupSnapshot(backups []chanbackup.Single) (
*lnrpc.ChanBackupSnapshot, error) {
// Once we have the set of back ups, we'll attempt to pack them all
// into a series of single channel backups.
singleChanPackedBackups, err := chanbackup.PackStaticChanBackups(
backups, r.server.cc.keyRing,
)
if err != nil {
return nil, fmt.Errorf("unable to pack set of chan "+
"backups: %v", err)
}
// Now that we have our set of single packed backups, we'll morph that
// into a form that the proto response requires.
numBackups := len(singleChanPackedBackups)
singleBackupResp := &lnrpc.ChannelBackups{
ChanBackups: make([]*lnrpc.ChannelBackup, 0, numBackups),
}
for chanPoint, singlePackedBackup := range singleChanPackedBackups {
txid := chanPoint.Hash
rpcChanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: txid[:],
},
OutputIndex: chanPoint.Index,
}
singleBackupResp.ChanBackups = append(
singleBackupResp.ChanBackups,
&lnrpc.ChannelBackup{
ChanPoint: rpcChanPoint,
ChanBackup: singlePackedBackup,
},
)
}
// In addition, to the set of single chan backups, we'll also create a
// single multi-channel backup which can be serialized into a single
// file for safe storage.
var b bytes.Buffer
unpackedMultiBackup := chanbackup.Multi{
StaticBackups: backups,
}
err = unpackedMultiBackup.PackToWriter(&b, r.server.cc.keyRing)
if err != nil {
return nil, fmt.Errorf("unable to multi-pack backups: %v", err)
}
multiBackupResp := &lnrpc.MultiChanBackup{
MultiChanBackup: b.Bytes(),
}
for _, singleBackup := range singleBackupResp.ChanBackups {
multiBackupResp.ChanPoints = append(
multiBackupResp.ChanPoints, singleBackup.ChanPoint,
)
}
return &lnrpc.ChanBackupSnapshot{
SingleChanBackups: singleBackupResp,
MultiChanBackup: multiBackupResp,
}, nil
}
// ExportAllChannelBackups returns static channel backups for all existing
// channels known to lnd. A set of regular singular static channel backups for
// each channel are returned. Additionally, a multi-channel backup is returned
// as well, which contains a single encrypted blob containing the backups of
// each channel.
func (r *rpcServer) ExportAllChannelBackups(ctx context.Context,
in *lnrpc.ChanBackupExportRequest) (*lnrpc.ChanBackupSnapshot, error) {
// First, we'll attempt to read back ups for ALL currently opened
// channels from disk.
allUnpackedBackups, err := chanbackup.FetchStaticChanBackups(
r.server.chanDB,
)
if err != nil {
return nil, fmt.Errorf("unable to fetch all static chan "+
"backups: %v", err)
}
// With the backups assembled, we'll create a full snapshot.
return r.createBackupSnapshot(allUnpackedBackups)
}
// RestoreChannelBackups accepts a set of singular channel backups, or a single
// encrypted multi-chan backup and attempts to recover any funds remaining
// within the channel. If we're able to unpack the backup, then the new channel
// will be shown under listchannels, as well as pending channels.
func (r *rpcServer) RestoreChannelBackups(ctx context.Context,
in *lnrpc.RestoreChanBackupRequest) (*lnrpc.RestoreBackupResponse, error) {
// First, we'll make our implementation of the
// chanbackup.ChannelRestorer interface which we'll use to properly
// restore either a set of chanbackup.Single or chanbackup.Multi
// backups.
chanRestorer := &chanDBRestorer{
db: r.server.chanDB,
secretKeys: r.server.cc.keyRing,
chainArb: r.server.chainArb,
}
// We'll accept either a list of Single backups, or a single Multi
// backup which contains several single backups.
switch {
case in.GetChanBackups() != nil:
chanBackupsProtos := in.GetChanBackups()
// Now that we know what type of backup we're working with,
// we'll parse them all out into a more suitable format.
packedBackups := make([][]byte, 0, len(chanBackupsProtos.ChanBackups))
for _, chanBackup := range chanBackupsProtos.ChanBackups {
packedBackups = append(
packedBackups, chanBackup.ChanBackup,
)
}
// With our backups obtained, we'll now restore them which will
// write the new backups to disk, and then attempt to connect
// out to any peers that we know of which were our prior
// channel peers.
err := chanbackup.UnpackAndRecoverSingles(
chanbackup.PackedSingles(packedBackups),
r.server.cc.keyRing, chanRestorer, r.server,
)
if err != nil {
return nil, fmt.Errorf("unable to unpack single "+
"backups: %v", err)
}
case in.GetMultiChanBackup() != nil:
packedMultiBackup := in.GetMultiChanBackup()
// With our backups obtained, we'll now restore them which will
// write the new backups to disk, and then attempt to connect
// out to any peers that we know of which were our prior
// channel peers.
packedMulti := chanbackup.PackedMulti(packedMultiBackup)
err := chanbackup.UnpackAndRecoverMulti(
packedMulti, r.server.cc.keyRing, chanRestorer,
r.server,
)
if err != nil {
return nil, fmt.Errorf("unable to unpack chan "+
"backup: %v", err)
}
}
return &lnrpc.RestoreBackupResponse{}, nil
}
// SubscribeChannelBackups allows a client to sub-subscribe to the most up to
// date information concerning the state of all channel back ups. Each time a
// new channel is added, we return the new set of channels, along with a
// multi-chan backup containing the backup info for all channels. Each time a
// channel is closed, we send a new update, which contains new new chan back
// ups, but the updated set of encrypted multi-chan backups with the closed
// channel(s) removed.
func (r *rpcServer) SubscribeChannelBackups(req *lnrpc.ChannelBackupSubscription,
updateStream lnrpc.Lightning_SubscribeChannelBackupsServer) error {
// First, we'll subscribe to the primary channel notifier so we can
// obtain events for new opened/closed channels.
chanSubscription, err := r.server.channelNotifier.SubscribeChannelEvents()
if err != nil {
return err
}
defer chanSubscription.Cancel()
for {
select {
// A new event has been sent by the channel notifier, we'll
// assemble, then sling out a new event to the client.
case e := <-chanSubscription.Updates():
// TODO(roasbeef): batch dispatch ntnfs
switch e.(type) {
// We only care about new/closed channels, so we'll
// skip any events for active/inactive channels.
case channelnotifier.ActiveChannelEvent:
continue
case channelnotifier.InactiveChannelEvent:
continue
}
// Now that we know the channel state has changed,
// we'll obtains the current set of single channel
// backups from disk.
chanBackups, err := chanbackup.FetchStaticChanBackups(
r.server.chanDB,
)
if err != nil {
return fmt.Errorf("unable to fetch all "+
"static chan backups: %v", err)
}
// With our backups obtained, we'll pack them into a
// snapshot and send them back to the client.
backupSnapshot, err := r.createBackupSnapshot(
chanBackups,
)
if err != nil {
return err
}
err = updateStream.Send(backupSnapshot)
if err != nil {
return err
}
case <-r.quit:
return nil
}
}
}

389
server.go

@ -26,6 +26,7 @@ import (
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/brontide"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/contractcourt"
@ -45,6 +46,7 @@ import (
"github.com/lightningnetwork/lnd/sweep"
"github.com/lightningnetwork/lnd/ticker"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/lightningnetwork/lnd/zpay32"
)
@ -79,8 +81,11 @@ var (
// Additionally, the server is also used as a central messaging bus to interact
// with any of its companion objects.
type server struct {
started int32 // atomic
shutdown int32 // atomic
active int32 // atomic
stopping int32 // atomic
start sync.Once
stop sync.Once
// identityPriv is the private key used to authenticate any incoming
// connections.
@ -184,6 +189,15 @@ type server struct {
// changed since last start.
currentNodeAnn *lnwire.NodeAnnouncement
// chansToRestore is the set of channels that upon starting, the server
// should attempt to restore/recover.
chansToRestore walletunlocker.ChannelsToRecover
// chanSubSwapper is a sub-system that will ensure our on-disk channel
// backups are consistent at all times. It interacts with the
// channelNotifier to be notified of newly opened and closed channels.
chanSubSwapper *chanbackup.SubSwapper
quit chan struct{}
wg sync.WaitGroup
@ -237,7 +251,8 @@ func noiseDial(idPriv *btcec.PrivateKey) func(net.Addr) (net.Conn, error) {
// newServer creates a new instance of the server which is to listen using the
// passed listener address.
func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
privKey *btcec.PrivateKey) (*server, error) {
privKey *btcec.PrivateKey,
chansToRestore walletunlocker.ChannelsToRecover) (*server, error) {
var err error
@ -293,11 +308,12 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
}
s := &server{
chanDB: chanDB,
cc: cc,
sigPool: lnwallet.NewSigPool(cfg.Workers.Sig, cc.signer),
writePool: writePool,
readPool: readPool,
chanDB: chanDB,
cc: cc,
sigPool: lnwallet.NewSigPool(cfg.Workers.Sig, cc.signer),
writePool: writePool,
readPool: readPool,
chansToRestore: chansToRestore,
invoices: invoices.NewRegistry(chanDB, decodeFinalCltvExpiry),
@ -982,6 +998,24 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
return nil, err
}
// Next, we'll assemble the sub-system that will maintain an on-disk
// static backup of the latest channel state.
chanNotifier := &channelNotifier{
chanNotifier: s.channelNotifier,
addrs: s.chanDB,
}
backupFile := chanbackup.NewMultiFile(cfg.BackupFilePath)
startingChans, err := chanbackup.FetchStaticChanBackups(s.chanDB)
if err != nil {
return nil, err
}
s.chanSubSwapper, err = chanbackup.NewSubSwapper(
startingChans, chanNotifier, s.cc.keyRing, backupFile,
)
if err != nil {
return nil, err
}
// Create the connection manager which will be responsible for
// maintaining persistent outbound connections and also accepting new
// incoming connections
@ -1004,115 +1038,176 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
// Started returns true if the server has been started, and false otherwise.
// NOTE: This function is safe for concurrent access.
func (s *server) Started() bool {
return atomic.LoadInt32(&s.started) != 0
return atomic.LoadInt32(&s.active) != 0
}
// Start starts the main daemon server, all requested listeners, and any helper
// goroutines.
// NOTE: This function is safe for concurrent access.
func (s *server) Start() error {
// Already running?
if !atomic.CompareAndSwapInt32(&s.started, 0, 1) {
return nil
}
if s.torController != nil {
if err := s.initTorController(); err != nil {
return err
}
}
if s.natTraversal != nil {
s.wg.Add(1)
go s.watchExternalIP()
}
// Start the notification server. This is used so channel management
// goroutines can be notified when a funding transaction reaches a
// sufficient number of confirmations, or when the input for the
// funding transaction is spent in an attempt at an uncooperative close
// by the counterparty.
if err := s.sigPool.Start(); err != nil {
return err
}
if err := s.writePool.Start(); err != nil {
return err
}
if err := s.readPool.Start(); err != nil {
return err
}
if err := s.cc.chainNotifier.Start(); err != nil {
return err
}
if err := s.channelNotifier.Start(); err != nil {
return err
}
if err := s.sphinx.Start(); err != nil {
return err
}
if err := s.htlcSwitch.Start(); err != nil {
return err
}
if err := s.sweeper.Start(); err != nil {
return err
}
if err := s.utxoNursery.Start(); err != nil {
return err
}
if err := s.chainArb.Start(); err != nil {
return err
}
if err := s.breachArbiter.Start(); err != nil {
return err
}
if err := s.authGossiper.Start(); err != nil {
return err
}
if err := s.chanRouter.Start(); err != nil {
return err
}
if err := s.fundingMgr.Start(); err != nil {
return err
}
s.connMgr.Start()
if err := s.invoices.Start(); err != nil {
return err
}
if err := s.chanStatusMgr.Start(); err != nil {
return err
}
// With all the relevant sub-systems started, we'll now attempt to
// establish persistent connections to our direct channel collaborators
// within the network. Before doing so however, we'll prune our set of
// link nodes found within the database to ensure we don't reconnect to
// any nodes we no longer have open channels with.
if err := s.chanDB.PruneLinkNodes(); err != nil {
return err
}
if err := s.establishPersistentConnections(); err != nil {
return err
}
// If network bootstrapping hasn't been disabled, then we'll configure
// the set of active bootstrappers, and launch a dedicated goroutine to
// maintain a set of persistent connections.
if !cfg.NoNetBootstrap && !(cfg.Bitcoin.SimNet || cfg.Litecoin.SimNet) &&
!(cfg.Bitcoin.RegTest || cfg.Litecoin.RegTest) {
bootstrappers, err := initNetworkBootstrappers(s)
if err != nil {
return err
var startErr error
s.start.Do(func() {
if s.torController != nil {
if err := s.initTorController(); err != nil {
startErr = err
return
}
}
s.wg.Add(1)
go s.peerBootstrapper(defaultMinPeers, bootstrappers)
} else {
srvrLog.Infof("Auto peer bootstrapping is disabled")
}
if s.natTraversal != nil {
s.wg.Add(1)
go s.watchExternalIP()
}
return nil
// Start the notification server. This is used so channel
// management goroutines can be notified when a funding
// transaction reaches a sufficient number of confirmations, or
// when the input for the funding transaction is spent in an
// attempt at an uncooperative close by the counterparty.
if err := s.sigPool.Start(); err != nil {
startErr = err
return
}
if err := s.writePool.Start(); err != nil {
startErr = err
return
}
if err := s.readPool.Start(); err != nil {
startErr = err
return
}
if err := s.cc.chainNotifier.Start(); err != nil {
startErr = err
return
}
if err := s.channelNotifier.Start(); err != nil {
startErr = err
return
}
if err := s.sphinx.Start(); err != nil {
startErr = err
return
}
if err := s.htlcSwitch.Start(); err != nil {
startErr = err
return
}
if err := s.sweeper.Start(); err != nil {
startErr = err
return
}
if err := s.utxoNursery.Start(); err != nil {
startErr = err
return
}
if err := s.chainArb.Start(); err != nil {
startErr = err
return
}
if err := s.breachArbiter.Start(); err != nil {
startErr = err
return
}
if err := s.authGossiper.Start(); err != nil {
startErr = err
return
}
if err := s.chanRouter.Start(); err != nil {
startErr = err
return
}
if err := s.fundingMgr.Start(); err != nil {
startErr = err
return
}
if err := s.invoices.Start(); err != nil {
startErr = err
return
}
if err := s.chanStatusMgr.Start(); err != nil {
startErr = err
return
}
// Before we start the connMgr, we'll check to see if we have
// any backups to recover. We do this now as we want to ensure
// that have all the information we need to handle channel
// recovery _before_ we even accept connections from any peers.
chanRestorer := &chanDBRestorer{
db: s.chanDB,
secretKeys: s.cc.keyRing,
chainArb: s.chainArb,
}
if len(s.chansToRestore.PackedSingleChanBackups) != 0 {
err := chanbackup.UnpackAndRecoverSingles(
s.chansToRestore.PackedSingleChanBackups,
s.cc.keyRing, chanRestorer, s,
)
if err != nil {
startErr = fmt.Errorf("unable to unpack single "+
"backups: %v", err)
return
}
}
if len(s.chansToRestore.PackedMultiChanBackup) != 0 {
err := chanbackup.UnpackAndRecoverMulti(
s.chansToRestore.PackedMultiChanBackup,
s.cc.keyRing, chanRestorer, s,
)
if err != nil {
startErr = fmt.Errorf("unable to unpack chan "+
"backup: %v", err)
return
}
}
if err := s.chanSubSwapper.Start(); err != nil {
startErr = err
return
}
s.connMgr.Start()
// With all the relevant sub-systems started, we'll now attempt
// to establish persistent connections to our direct channel
// collaborators within the network. Before doing so however,
// we'll prune our set of link nodes found within the database
// to ensure we don't reconnect to any nodes we no longer have
// open channels with.
if err := s.chanDB.PruneLinkNodes(); err != nil {
startErr = err
return
}
if err := s.establishPersistentConnections(); err != nil {
startErr = err
return
}
// If network bootstrapping hasn't been disabled, then we'll
// configure the set of active bootstrappers, and launch a
// dedicated goroutine to maintain a set of persistent
// connections.
if !cfg.NoNetBootstrap && !(cfg.Bitcoin.SimNet || cfg.Litecoin.SimNet) &&
!(cfg.Bitcoin.RegTest || cfg.Litecoin.RegTest) {
bootstrappers, err := initNetworkBootstrappers(s)
if err != nil {
startErr = err
return
}
s.wg.Add(1)
go s.peerBootstrapper(defaultMinPeers, bootstrappers)
} else {
srvrLog.Infof("Auto peer bootstrapping is disabled")
}
// Set the active flag now that we've completed the full
// startup.
atomic.StoreInt32(&s.active, 1)
})
return startErr
}
// Stop gracefully shutsdown the main daemon server. This function will signal
@ -1120,48 +1215,48 @@ func (s *server) Start() error {
// all successfully exited. Additionally, any/all listeners are closed.
// NOTE: This function is safe for concurrent access.
func (s *server) Stop() error {
// Bail if we're already shutting down.
if !atomic.CompareAndSwapInt32(&s.shutdown, 0, 1) {
return nil
}
s.stop.Do(func() {
atomic.LoadInt32(&s.stopping)
close(s.quit)
close(s.quit)
if s.torController != nil {
s.torController.Stop()
}
if s.torController != nil {
s.torController.Stop()
}
// Shutdown the wallet, funding manager, and the rpc server.
s.chanStatusMgr.Stop()
s.cc.chainNotifier.Stop()
s.chanRouter.Stop()
s.htlcSwitch.Stop()
s.sphinx.Stop()
s.utxoNursery.Stop()
s.breachArbiter.Stop()
s.authGossiper.Stop()
s.chainArb.Stop()
s.sweeper.Stop()
s.channelNotifier.Stop()
s.cc.wallet.Shutdown()
s.cc.chainView.Stop()
s.connMgr.Stop()
s.cc.feeEstimator.Stop()
s.invoices.Stop()
s.fundingMgr.Stop()
// Shutdown the wallet, funding manager, and the rpc server.
s.chanStatusMgr.Stop()
s.cc.chainNotifier.Stop()
s.chanRouter.Stop()
s.htlcSwitch.Stop()
s.sphinx.Stop()
s.utxoNursery.Stop()
s.breachArbiter.Stop()
s.authGossiper.Stop()
s.chainArb.Stop()
s.sweeper.Stop()
s.channelNotifier.Stop()
s.cc.wallet.Shutdown()
s.cc.chainView.Stop()
s.connMgr.Stop()
s.cc.feeEstimator.Stop()
s.invoices.Stop()
s.fundingMgr.Stop()
s.chanSubSwapper.Start()
// Disconnect from each active peers to ensure that
// peerTerminationWatchers signal completion to each peer.
for _, peer := range s.Peers() {
s.DisconnectPeer(peer.addr.IdentityKey)
}
// Disconnect from each active peers to ensure that
// peerTerminationWatchers signal completion to each peer.
for _, peer := range s.Peers() {
s.DisconnectPeer(peer.addr.IdentityKey)
}
// Wait for all lingering goroutines to quit.
s.wg.Wait()
// Wait for all lingering goroutines to quit.
s.wg.Wait()
s.sigPool.Stop()
s.writePool.Stop()
s.readPool.Stop()
s.sigPool.Stop()
s.writePool.Stop()
s.readPool.Stop()
})
return nil
}
@ -1169,7 +1264,7 @@ func (s *server) Stop() error {
// Stopped returns true if the server has been instructed to shutdown.
// NOTE: This function is safe for concurrent access.
func (s *server) Stopped() bool {
return atomic.LoadInt32(&s.shutdown) != 0
return atomic.LoadInt32(&s.stopping) != 0
}
// configurePortForwarding attempts to set up port forwarding for the different
@ -2719,7 +2814,6 @@ type openChanReq struct {
//
// NOTE: This function is safe for concurrent access.
func (s *server) ConnectToPeer(addr *lnwire.NetAddress, perm bool) error {
targetPub := string(addr.IdentityKey.SerializeCompressed())
// Acquire mutex, but use explicit unlocking instead of defer for
@ -2761,7 +2855,8 @@ func (s *server) ConnectToPeer(addr *lnwire.NetAddress, perm bool) error {
s.persistentPeersBackoff[targetPub] = cfg.MinBackoff
}
s.persistentConnReqs[targetPub] = append(
s.persistentConnReqs[targetPub], connReq)
s.persistentConnReqs[targetPub], connReq,
)
s.mu.Unlock()
go s.connMgr.Connect(connReq)

@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
@ -17,6 +18,19 @@ import (
"golang.org/x/net/context"
)
// ChannelsToRecover wraps any set of packed (serialized+encrypted) channel
// back ups together. These can be passed in when unlocking the wallet, or
// creating a new wallet for the first time with an existing seed.
type ChannelsToRecover struct {
// PackedMultiChanBackup is an encrypted and serialized multi-channel
// backup.
PackedMultiChanBackup chanbackup.PackedMulti
// PackedSingleChanBackups is a series of encrypted and serialized
// single-channel backup for one or more channels.
PackedSingleChanBackups chanbackup.PackedSingles
}
// WalletInitMsg is a message sent by the UnlockerService when a user wishes to
// set up the internal wallet for the first time. The user MUST provide a
// passphrase, but is also able to provide their own source of entropy. If
@ -36,6 +50,10 @@ type WalletInitMsg struct {
// recovery should be attempted, such as after the wallet's initial
// creation.
RecoveryWindow uint32
// ChanBackups a set of static channel backups that should be received
// after the wallet has been initialized.
ChanBackups ChannelsToRecover
}
// WalletUnlockMsg is a message sent by the UnlockerService when a user wishes
@ -53,12 +71,16 @@ type WalletUnlockMsg struct {
// creation, but before any addresses have been created.
RecoveryWindow uint32
// Wallet is the loaded and unlocked Wallet. This is returned
// through the channel to avoid it being unlocked twice (once to check
// if the password is correct, here in the WalletUnlocker and again
// later when lnd actually uses it). Because unlocking involves scrypt
// which is resource intensive, we want to avoid doing it twice.
// Wallet is the loaded and unlocked Wallet. This is returned through
// the channel to avoid it being unlocked twice (once to check if the
// password is correct, here in the WalletUnlocker and again later when
// lnd actually uses it). Because unlocking involves scrypt which is
// resource intensive, we want to avoid doing it twice.
Wallet *wallet.Wallet
// ChanBackups a set of static channel backups that should be received
// after the wallet has been unlocked.
ChanBackups ChannelsToRecover
}
// UnlockerService implements the WalletUnlocker service used to provide lnd
@ -167,6 +189,43 @@ func (u *UnlockerService) GenSeed(ctx context.Context,
}, nil
}
// extractChanBackups is a helper function that extracts the set of channel
// backups from the proto into a format that we'll pass to higher level
// sub-systems.
func extractChanBackups(chanBackups *lnrpc.ChanBackupSnapshot) *ChannelsToRecover {
// If there aren't any populated channel backups, then we can exit
// early as there's nothing to extract.
if chanBackups == nil || (chanBackups.SingleChanBackups == nil &&
chanBackups.MultiChanBackup == nil) {
return nil
}
// Now that we know there's at least a single back up populated, we'll
// extract the multi-chan backup (if it's there).
var backups ChannelsToRecover
if chanBackups.MultiChanBackup != nil {
multiBackup := chanBackups.MultiChanBackup
backups.PackedMultiChanBackup = chanbackup.PackedMulti(
multiBackup.MultiChanBackup,
)
}
if chanBackups.SingleChanBackups == nil {
return &backups
}
// Finally, we can extract all the single chan backups as well.
for _, backup := range chanBackups.SingleChanBackups.ChanBackups {
singleChanBackup := backup.ChanBackup
backups.PackedSingleChanBackups = append(
backups.PackedSingleChanBackups, singleChanBackup,
)
}
return &backups
}
// InitWallet is used when lnd is starting up for the first time to fully
// initialize the daemon and its internal wallet. At the very least a wallet
// password must be provided. This will be used to encrypt sensitive material
@ -233,6 +292,13 @@ func (u *UnlockerService) InitWallet(ctx context.Context,
RecoveryWindow: uint32(recoveryWindow),
}
// Before we return the unlock payload, we'll check if we can extract
// any channel backups to pass up to the higher level sub-system.
chansToRestore := extractChanBackups(in.ChannelBackups)
if chansToRestore != nil {
initMsg.ChanBackups = *chansToRestore
}
u.InitMsgs <- initMsg
return &lnrpc.InitWalletResponse{}, nil
@ -277,6 +343,13 @@ func (u *UnlockerService) UnlockWallet(ctx context.Context,
Wallet: unlockedWallet,
}
// Before we return the unlock payload, we'll check if we can extract
// any channel backups to pass up to the higher level sub-system.
chansToRestore := extractChanBackups(in.ChannelBackups)
if chansToRestore != nil {
walletUnlockMsg.ChanBackups = *chansToRestore
}
// At this point we was able to open the existing wallet with the
// provided password. We send the password over the UnlockMsgs
// channel, such that it can be used by lnd to open the wallet.