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