github.com/decred/dcrlnd@v0.7.6/chanbackup/pubsub.go (about)

     1  package chanbackup
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net"
     7  	"os"
     8  	"sync"
     9  
    10  	"github.com/decred/dcrd/wire"
    11  	"github.com/decred/dcrlnd/channeldb"
    12  	"github.com/decred/dcrlnd/keychain"
    13  )
    14  
    15  // Swapper is an interface that allows the chanbackup.SubSwapper to update the
    16  // main multi backup location once it learns of new channels or that prior
    17  // channels have been closed.
    18  type Swapper interface {
    19  	// UpdateAndSwap attempts to atomically update the main multi back up
    20  	// file location with the new fully packed multi-channel backup.
    21  	UpdateAndSwap(newBackup PackedMulti) error
    22  
    23  	// ExtractMulti attempts to obtain and decode the current SCB instance
    24  	// stored by the Swapper instance.
    25  	ExtractMulti(keychain keychain.KeyRing) (*Multi, error)
    26  }
    27  
    28  // ChannelWithAddrs bundles an open channel along with all the addresses for
    29  // the channel peer.
    30  type ChannelWithAddrs struct {
    31  	*channeldb.OpenChannel
    32  
    33  	// Addrs is the set of addresses that we can use to reach the target
    34  	// peer.
    35  	Addrs []net.Addr
    36  }
    37  
    38  // ChannelEvent packages a new update of new channels since subscription, and
    39  // channels that have been opened since prior channel event.
    40  type ChannelEvent struct {
    41  	// ClosedChans are the set of channels that have been closed since the
    42  	// last event.
    43  	ClosedChans []wire.OutPoint
    44  
    45  	// NewChans is the set of channels that have been opened since the last
    46  	// event.
    47  	NewChans []ChannelWithAddrs
    48  }
    49  
    50  // ChannelSubscription represents an intent to be notified of any updates to
    51  // the primary channel state.
    52  type ChannelSubscription struct {
    53  	// ChanUpdates is a channel that will be sent upon once the primary
    54  	// channel state is updated.
    55  	ChanUpdates chan ChannelEvent
    56  
    57  	// Cancel is a closure that allows the caller to cancel their
    58  	// subscription and free up any resources allocated.
    59  	Cancel func()
    60  }
    61  
    62  // ChannelNotifier represents a system that allows the chanbackup.SubSwapper to
    63  // be notified of any changes to the primary channel state.
    64  type ChannelNotifier interface {
    65  	// SubscribeChans requests a new channel subscription relative to the
    66  	// initial set of known channels. We use the knownChans as a
    67  	// synchronization point to ensure that the chanbackup.SubSwapper does
    68  	// not miss any channel open or close events in the period between when
    69  	// it's created, and when it requests the channel subscription.
    70  	SubscribeChans(map[wire.OutPoint]struct{}) (*ChannelSubscription, error)
    71  }
    72  
    73  // SubSwapper subscribes to new updates to the open channel state, and then
    74  // swaps out the on-disk channel backup state in response.  This sub-system
    75  // that will ensure that the multi chan backup file on disk will always be
    76  // updated with the latest channel back up state. We'll receive new
    77  // opened/closed channels from the ChannelNotifier, then use the Swapper to
    78  // update the file state on disk with the new set of open channels.  This can
    79  // be used to implement a system that always keeps the multi-chan backup file
    80  // on disk in a consistent state for safety purposes.
    81  type SubSwapper struct {
    82  	started sync.Once
    83  	stopped sync.Once
    84  
    85  	// backupState are the set of SCBs for all open channels we know of.
    86  	backupState map[wire.OutPoint]Single
    87  
    88  	// chanEvents is an active subscription to receive new channel state
    89  	// over.
    90  	chanEvents *ChannelSubscription
    91  
    92  	// keyRing is the main key ring that will allow us to pack the new
    93  	// multi backup.
    94  	keyRing keychain.KeyRing
    95  
    96  	Swapper
    97  
    98  	quit chan struct{}
    99  	wg   sync.WaitGroup
   100  }
   101  
   102  // NewSubSwapper creates a new instance of the SubSwapper given the starting
   103  // set of channels, and the required interfaces to be notified of new channel
   104  // updates, pack a multi backup, and swap the current best backup from its
   105  // storage location.
   106  func NewSubSwapper(startingChans []Single, chanNotifier ChannelNotifier,
   107  	keyRing keychain.KeyRing, backupSwapper Swapper) (*SubSwapper, error) {
   108  
   109  	// First, we'll subscribe to the latest set of channel updates given
   110  	// the set of channels we already know of.
   111  	knownChans := make(map[wire.OutPoint]struct{})
   112  	for _, chanBackup := range startingChans {
   113  		knownChans[chanBackup.FundingOutpoint] = struct{}{}
   114  	}
   115  	chanEvents, err := chanNotifier.SubscribeChans(knownChans)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	// Next, we'll construct our own backup state so we can add/remove
   121  	// channels that have been opened and closed.
   122  	backupState := make(map[wire.OutPoint]Single)
   123  	for _, chanBackup := range startingChans {
   124  		backupState[chanBackup.FundingOutpoint] = chanBackup
   125  	}
   126  
   127  	return &SubSwapper{
   128  		backupState: backupState,
   129  		chanEvents:  chanEvents,
   130  		keyRing:     keyRing,
   131  		Swapper:     backupSwapper,
   132  		quit:        make(chan struct{}),
   133  	}, nil
   134  }
   135  
   136  // Start starts the chanbackup.SubSwapper.
   137  func (s *SubSwapper) Start() error {
   138  	var startErr error
   139  	s.started.Do(func() {
   140  		log.Infof("Starting chanbackup.SubSwapper")
   141  
   142  		// Before we enter our main loop, we'll update the on-disk
   143  		// state with the latest Single state, as nodes may have new
   144  		// advertised addresses.
   145  		if err := s.updateBackupFile(); err != nil {
   146  			startErr = fmt.Errorf("unable to refresh backup "+
   147  				"file: %v", err)
   148  			return
   149  		}
   150  
   151  		s.wg.Add(1)
   152  		go s.backupUpdater()
   153  	})
   154  
   155  	return startErr
   156  }
   157  
   158  // Stop signals the SubSwapper to being a graceful shutdown.
   159  func (s *SubSwapper) Stop() error {
   160  	s.stopped.Do(func() {
   161  		log.Infof("Stopping chanbackup.SubSwapper")
   162  
   163  		close(s.quit)
   164  		s.wg.Wait()
   165  	})
   166  	return nil
   167  }
   168  
   169  // updateBackupFile updates the backup file in place given the current state of
   170  // the SubSwapper. We accept the set of channels that were closed between this
   171  // update and the last to make sure we leave them out of our backup set union.
   172  func (s *SubSwapper) updateBackupFile(closedChans ...wire.OutPoint) error {
   173  	// Before we pack the new set of SCBs, we'll first decode what we
   174  	// already have on-disk, to make sure we can decode it (proper seed)
   175  	// and that we're able to combine it with our new data.
   176  	diskMulti, err := s.Swapper.ExtractMulti(s.keyRing)
   177  
   178  	// If the file doesn't exist on disk, then that's OK as it was never
   179  	// created. In this case we'll continue onwards as it isn't a critical
   180  	// error.
   181  	if err != nil && !os.IsNotExist(err) {
   182  		return fmt.Errorf("unable to extract on disk encrypted "+
   183  			"SCB: %v", err)
   184  	}
   185  
   186  	// Now that we have channels stored on-disk, we'll create a new set of
   187  	// the combined old and new channels to make sure we retain what's
   188  	// already on-disk.
   189  	//
   190  	// NOTE: The ordering of this operations means that our in-memory
   191  	// structure will replace what we read from disk.
   192  	combinedBackup := make(map[wire.OutPoint]Single)
   193  	if diskMulti != nil {
   194  		for _, diskChannel := range diskMulti.StaticBackups {
   195  			chanPoint := diskChannel.FundingOutpoint
   196  			combinedBackup[chanPoint] = diskChannel
   197  		}
   198  	}
   199  	for _, memChannel := range s.backupState {
   200  		chanPoint := memChannel.FundingOutpoint
   201  		if _, ok := combinedBackup[chanPoint]; ok {
   202  			log.Warnf("Replacing disk backup for ChannelPoint(%v) "+
   203  				"w/ newer version", chanPoint)
   204  		}
   205  
   206  		combinedBackup[chanPoint] = memChannel
   207  	}
   208  
   209  	// Remove the set of closed channels from the final set of backups.
   210  	for _, closedChan := range closedChans {
   211  		delete(combinedBackup, closedChan)
   212  	}
   213  
   214  	// With our updated channel state obtained, we'll create a new multi
   215  	// from our series of singles.
   216  	var newMulti Multi
   217  	for _, backup := range combinedBackup {
   218  		newMulti.StaticBackups = append(
   219  			newMulti.StaticBackups, backup,
   220  		)
   221  	}
   222  
   223  	// Now that our multi has been assembled, we'll attempt to pack
   224  	// (encrypt+encode) the new channel state to our target reader.
   225  	var b bytes.Buffer
   226  	err = newMulti.PackToWriter(&b, s.keyRing)
   227  	if err != nil {
   228  		return fmt.Errorf("unable to pack multi backup: %v", err)
   229  	}
   230  
   231  	// Finally, we'll swap out the old backup for this new one in a single
   232  	// atomic step, combining the file already on-disk with this set of new
   233  	// channels.
   234  	err = s.Swapper.UpdateAndSwap(PackedMulti(b.Bytes()))
   235  	if err != nil {
   236  		return fmt.Errorf("unable to update multi backup: %v", err)
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  // backupFileUpdater is the primary goroutine of the SubSwapper which is
   243  // responsible for listening for changes to the channel, and updating the
   244  // persistent multi backup state with a new packed multi of the latest channel
   245  // state.
   246  func (s *SubSwapper) backupUpdater() {
   247  	// Ensure that once we exit, we'll cancel our active channel
   248  	// subscription.
   249  	defer s.chanEvents.Cancel()
   250  	defer s.wg.Done()
   251  
   252  	log.Debugf("SubSwapper's backupUpdater is active!")
   253  
   254  	for {
   255  		select {
   256  		// The channel state has been modified! We'll evaluate all
   257  		// changes, and swap out the old packed multi with a new one
   258  		// with the latest channel state.
   259  		case chanUpdate := <-s.chanEvents.ChanUpdates:
   260  			oldStateSize := len(s.backupState)
   261  
   262  			// For all new open channels, we'll create a new SCB
   263  			// given the required information.
   264  			for _, newChan := range chanUpdate.NewChans {
   265  				log.Debugf("Adding channel %v to backup state",
   266  					newChan.FundingOutpoint)
   267  
   268  				s.backupState[newChan.FundingOutpoint] = NewSingle(
   269  					newChan.OpenChannel, newChan.Addrs,
   270  				)
   271  			}
   272  
   273  			// For all closed channels, we'll remove the prior
   274  			// backup state.
   275  			closedChans := make(
   276  				[]wire.OutPoint, 0, len(chanUpdate.ClosedChans),
   277  			)
   278  			for i, closedChan := range chanUpdate.ClosedChans {
   279  				log.Debugf("Removing channel %v from backup "+
   280  					"state", newLogClosure(func() string {
   281  					return chanUpdate.ClosedChans[i].String()
   282  				}))
   283  
   284  				delete(s.backupState, closedChan)
   285  
   286  				closedChans = append(closedChans, closedChan)
   287  			}
   288  
   289  			newStateSize := len(s.backupState)
   290  
   291  			log.Infof("Updating on-disk multi SCB backup: "+
   292  				"num_old_chans=%v, num_new_chans=%v",
   293  				oldStateSize, newStateSize)
   294  
   295  			// With out new state constructed, we'll, atomically
   296  			// update the on-disk backup state.
   297  			if err := s.updateBackupFile(closedChans...); err != nil {
   298  				log.Errorf("unable to update backup file: %v",
   299  					err)
   300  			}
   301  
   302  		// TODO(roasbeef): refresh periodically on a time basis due to
   303  		// possible addr changes from node
   304  
   305  		// Exit at once if a quit signal is detected.
   306  		case <-s.quit:
   307  			return
   308  		}
   309  	}
   310  }