github.com/decred/dcrlnd@v0.7.6/htlcswitch/circuit_map_test.go (about) 1 package htlcswitch_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "testing" 8 9 "github.com/decred/dcrd/dcrutil/v4" 10 "github.com/decred/dcrd/wire" 11 "github.com/decred/dcrlnd/channeldb" 12 "github.com/decred/dcrlnd/htlcswitch" 13 "github.com/decred/dcrlnd/kvdb" 14 "github.com/decred/dcrlnd/lnwire" 15 "github.com/stretchr/testify/require" 16 ) 17 18 var ( 19 // closedChannelBucket stores summarization information concerning 20 // previously open, but now closed channels. 21 closedChannelBucket = []byte("closed-chan-bucket") 22 ) 23 24 // TestCircuitMapCleanClosedChannels checks that the circuits and keystones are 25 // deleted for closed channels upon restart. 26 func TestCircuitMapCleanClosedChannels(t *testing.T) { 27 t.Parallel() 28 29 var ( 30 // chanID0 is a zero value channel ID indicating a locally 31 // initiated payment. 32 chanID0 = lnwire.NewShortChanIDFromInt(uint64(0)) 33 chanID1 = lnwire.NewShortChanIDFromInt(uint64(1)) 34 chanID2 = lnwire.NewShortChanIDFromInt(uint64(2)) 35 36 inKey00 = htlcswitch.CircuitKey{ChanID: chanID0, HtlcID: 0} 37 inKey10 = htlcswitch.CircuitKey{ChanID: chanID1, HtlcID: 0} 38 inKey11 = htlcswitch.CircuitKey{ChanID: chanID1, HtlcID: 1} 39 inKey20 = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 0} 40 inKey21 = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 1} 41 inKey22 = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 2} 42 outKey00 = htlcswitch.CircuitKey{ChanID: chanID0, HtlcID: 0} 43 outKey10 = htlcswitch.CircuitKey{ChanID: chanID1, HtlcID: 0} 44 outKey11 = htlcswitch.CircuitKey{ChanID: chanID1, HtlcID: 1} 45 outKey20 = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 0} 46 outKey21 = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 1} 47 outKey22 = htlcswitch.CircuitKey{ChanID: chanID2, HtlcID: 2} 48 ) 49 50 type closeChannelParams struct { 51 chanID lnwire.ShortChannelID 52 isPending bool 53 } 54 55 testParams := []struct { 56 name string 57 58 // keystones is used to create and open circuits. A keystone is 59 // a pair of circuit keys, inKey and outKey, with the outKey 60 // optionally being empty. If a keystone with an outKey is used, 61 // a circuit will be created and opened, thus creating a circuit 62 // and a keystone in the DB. Otherwise, only the circuit is 63 // created. 64 keystones []htlcswitch.Keystone 65 66 chanParams []closeChannelParams 67 deleted []htlcswitch.Keystone 68 untouched []htlcswitch.Keystone 69 }{ 70 { 71 name: "no deletion if there are no closed channels", 72 keystones: []htlcswitch.Keystone{ 73 // Creates a circuit and a keystone 74 {InKey: inKey10, OutKey: outKey10}, 75 }, 76 untouched: []htlcswitch.Keystone{ 77 {InKey: inKey10, OutKey: outKey10}, 78 }, 79 }, 80 { 81 name: "no deletion if channel is pending close", 82 chanParams: []closeChannelParams{ 83 // Creates a pending close channel. 84 {chanID: chanID1, isPending: true}, 85 }, 86 keystones: []htlcswitch.Keystone{ 87 // Creates a circuit and a keystone 88 {InKey: inKey10, OutKey: outKey10}, 89 }, 90 untouched: []htlcswitch.Keystone{ 91 {InKey: inKey10, OutKey: outKey10}, 92 }, 93 }, 94 { 95 name: "no deletion if the chanID is zero value", 96 chanParams: []closeChannelParams{ 97 // Creates a close channel with chanID0. 98 {chanID: chanID0, isPending: false}, 99 }, 100 keystones: []htlcswitch.Keystone{ 101 // Creates a circuit and a keystone 102 {InKey: inKey00, OutKey: outKey00}, 103 }, 104 untouched: []htlcswitch.Keystone{ 105 {InKey: inKey00, OutKey: outKey00}, 106 }, 107 }, 108 { 109 name: "delete half circuits on inKey match", 110 chanParams: []closeChannelParams{ 111 // Creates a close channel with chanID1. 112 {chanID: chanID1, isPending: false}, 113 }, 114 keystones: []htlcswitch.Keystone{ 115 // Creates a circuit, no keystone created 116 {InKey: inKey10}, 117 // Creates a circuit, no keystone created 118 {InKey: inKey11}, 119 // Creates a circuit and a keystone 120 {InKey: inKey20, OutKey: outKey20}, 121 }, 122 deleted: []htlcswitch.Keystone{ 123 {InKey: inKey00}, {InKey: inKey11}, 124 }, 125 untouched: []htlcswitch.Keystone{ 126 {InKey: inKey20, OutKey: outKey20}, 127 }, 128 }, 129 { 130 name: "delete half circuits on outKey match", 131 chanParams: []closeChannelParams{ 132 // Creates a close channel with chanID1. 133 {chanID: chanID1, isPending: false}, 134 }, 135 keystones: []htlcswitch.Keystone{ 136 // Creates a circuit and a keystone 137 {InKey: inKey20, OutKey: outKey10}, 138 // Creates a circuit and a keystone 139 {InKey: inKey21, OutKey: outKey11}, 140 // Creates a circuit and a keystone 141 {InKey: inKey22, OutKey: outKey21}, 142 }, 143 deleted: []htlcswitch.Keystone{ 144 {InKey: inKey20, OutKey: outKey10}, 145 {InKey: inKey21, OutKey: outKey11}, 146 }, 147 untouched: []htlcswitch.Keystone{ 148 {InKey: inKey22, OutKey: outKey21}, 149 }, 150 }, 151 { 152 name: "delete full circuits on inKey match", 153 chanParams: []closeChannelParams{ 154 // Creates a close channel with chanID1. 155 {chanID: chanID1, isPending: false}, 156 }, 157 keystones: []htlcswitch.Keystone{ 158 // Creates a circuit and a keystone 159 {InKey: inKey10, OutKey: outKey20}, 160 // Creates a circuit and a keystone 161 {InKey: inKey11, OutKey: outKey21}, 162 // Creates a circuit and a keystone 163 {InKey: inKey20, OutKey: outKey22}, 164 }, 165 deleted: []htlcswitch.Keystone{ 166 {InKey: inKey10, OutKey: outKey20}, 167 {InKey: inKey11, OutKey: outKey21}, 168 }, 169 untouched: []htlcswitch.Keystone{ 170 {InKey: inKey20, OutKey: outKey22}, 171 }, 172 }, 173 { 174 name: "delete full circuits on outKey match", 175 chanParams: []closeChannelParams{ 176 // Creates a close channel with chanID1. 177 {chanID: chanID1, isPending: false}, 178 }, 179 keystones: []htlcswitch.Keystone{ 180 // Creates a circuit and a keystone 181 {InKey: inKey20, OutKey: outKey10}, 182 // Creates a circuit and a keystone 183 {InKey: inKey21, OutKey: outKey11}, 184 // Creates a circuit and a keystone 185 {InKey: inKey22, OutKey: outKey20}, 186 }, 187 deleted: []htlcswitch.Keystone{ 188 {InKey: inKey20, OutKey: outKey10}, 189 {InKey: inKey21, OutKey: outKey11}, 190 }, 191 untouched: []htlcswitch.Keystone{ 192 {InKey: inKey22, OutKey: outKey20}, 193 }, 194 }, 195 { 196 name: "delete all circuits", 197 chanParams: []closeChannelParams{ 198 // Creates a close channel with chanID1. 199 {chanID: chanID1, isPending: false}, 200 // Creates a close channel with chanID2. 201 {chanID: chanID2, isPending: false}, 202 }, 203 keystones: []htlcswitch.Keystone{ 204 // Creates a circuit and a keystone 205 {InKey: inKey20, OutKey: outKey10}, 206 // Creates a circuit and a keystone 207 {InKey: inKey21, OutKey: outKey11}, 208 // Creates a circuit and a keystone 209 {InKey: inKey22, OutKey: outKey20}, 210 }, 211 deleted: []htlcswitch.Keystone{ 212 {InKey: inKey20, OutKey: outKey10}, 213 {InKey: inKey21, OutKey: outKey11}, 214 {InKey: inKey22, OutKey: outKey20}, 215 }, 216 }, 217 } 218 219 for _, tt := range testParams { 220 test := tt 221 222 t.Run(test.name, func(t *testing.T) { 223 cfg, circuitMap := newCircuitMap(t) 224 225 // create test circuits 226 for _, ks := range test.keystones { 227 err := createTestCircuit(ks, circuitMap) 228 require.NoError( 229 t, err, 230 "failed to create test circuit", 231 ) 232 } 233 234 // create close channels 235 err := kvdb.Update(cfg.DB, func(tx kvdb.RwTx) error { 236 for _, channel := range test.chanParams { 237 if err := createTestCloseChannelSummery( 238 tx, channel.isPending, 239 channel.chanID, 240 ); err != nil { 241 return err 242 } 243 } 244 return nil 245 }, func() {}) 246 247 require.NoError( 248 t, err, 249 "failed to create close channel summery", 250 ) 251 252 // Now, restart the circuit map, and check that the 253 // circuits and keystones of closed channels are 254 // deleted in DB. 255 _, circuitMap = restartCircuitMap(t, cfg) 256 257 // Check that items are deleted. LookupCircuit and 258 // LookupOpenCircuit will check the cached circuits, 259 // which are loaded on restart from the DB. 260 for _, ks := range test.deleted { 261 assertKeystoneDeleted(t, circuitMap, ks) 262 } 263 264 // We also check we are not deleting wanted circuits. 265 for _, ks := range test.untouched { 266 assertKeystoneNotDeleted(t, circuitMap, ks) 267 } 268 269 }) 270 } 271 272 } 273 274 // createTestCircuit creates a circuit for testing with its incoming key being 275 // the keystone's InKey. If the keystone has an OutKey, the circuit will be 276 // opened, which causes a Keystone to be created in DB. 277 func createTestCircuit(ks htlcswitch.Keystone, cm htlcswitch.CircuitMap) error { 278 circuit := &htlcswitch.PaymentCircuit{ 279 Incoming: ks.InKey, 280 ErrorEncrypter: testExtracter, 281 } 282 283 // First we will try to add an new circuit to the circuit map, this 284 // should succeed. 285 _, err := cm.CommitCircuits(circuit) 286 if err != nil { 287 return fmt.Errorf("failed to commit circuits: %v", err) 288 } 289 290 // If the keystone has no outgoing key, we won't open it. 291 if ks.OutKey == htlcswitch.EmptyCircuitKey { 292 return nil 293 } 294 295 // Open the circuit, implicitly creates a keystone on disk. 296 err = cm.OpenCircuits(ks) 297 if err != nil { 298 return fmt.Errorf("failed to open circuits: %v", err) 299 } 300 301 return nil 302 } 303 304 // assertKeystoneDeleted checks that a given keystone is deleted from the 305 // circuit map. 306 func assertKeystoneDeleted(t *testing.T, 307 cm htlcswitch.CircuitLookup, ks htlcswitch.Keystone) { 308 309 c := cm.LookupCircuit(ks.InKey) 310 require.Nil(t, c, "no circuit should be found using InKey") 311 312 if ks.OutKey != htlcswitch.EmptyCircuitKey { 313 c = cm.LookupOpenCircuit(ks.OutKey) 314 require.Nil(t, c, "no circuit should be found using OutKey") 315 } 316 } 317 318 // assertKeystoneDeleted checks that a given keystone is not deleted from the 319 // circuit map. 320 func assertKeystoneNotDeleted(t *testing.T, 321 cm htlcswitch.CircuitLookup, ks htlcswitch.Keystone) { 322 323 c := cm.LookupCircuit(ks.InKey) 324 require.NotNil(t, c, "expecting circuit found using InKey") 325 326 if ks.OutKey != htlcswitch.EmptyCircuitKey { 327 c = cm.LookupOpenCircuit(ks.OutKey) 328 require.NotNil(t, c, "expecting circuit found using OutKey") 329 } 330 } 331 332 // createTestCloseChannelSummery creates a CloseChannelSummery for testing. 333 func createTestCloseChannelSummery(tx kvdb.RwTx, isPending bool, 334 chanID lnwire.ShortChannelID) error { 335 336 closedChanBucket, err := tx.CreateTopLevelBucket(closedChannelBucket) 337 if err != nil { 338 return err 339 } 340 outputPoint := wire.OutPoint{Hash: hash1, Index: 1} 341 342 ccs := &channeldb.ChannelCloseSummary{ 343 ChanPoint: outputPoint, 344 ShortChanID: chanID, 345 ChainHash: hash1, 346 ClosingTXID: hash2, 347 CloseHeight: 100, 348 RemotePub: testEphemeralKey, 349 Capacity: dcrutil.Amount(10000), 350 SettledBalance: dcrutil.Amount(50000), 351 CloseType: channeldb.RemoteForceClose, 352 IsPending: isPending, 353 } 354 var b bytes.Buffer 355 if err := serializeChannelCloseSummary(&b, ccs); err != nil { 356 return err 357 } 358 359 var chanPointBuf bytes.Buffer 360 if err := lnwire.WriteOutPoint(&chanPointBuf, outputPoint); err != nil { 361 return err 362 } 363 364 return closedChanBucket.Put(chanPointBuf.Bytes(), b.Bytes()) 365 } 366 367 func serializeChannelCloseSummary( 368 w io.Writer, 369 cs *channeldb.ChannelCloseSummary) error { 370 371 err := channeldb.WriteElements( 372 w, 373 cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID, 374 cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance, 375 cs.TimeLockedBalance, cs.CloseType, cs.IsPending, 376 ) 377 if err != nil { 378 return err 379 } 380 381 // If this is a close channel summary created before the addition of 382 // the new fields, then we can exit here. 383 if cs.RemoteCurrentRevocation == nil { 384 return channeldb.WriteElements(w, false) 385 } 386 387 return nil 388 }