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 }