github.com/decred/dcrlnd@v0.7.6/htlcswitch/decayedlog_test.go (about) 1 package htlcswitch 2 3 import ( 4 "crypto/rand" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "testing" 9 "time" 10 11 "github.com/decred/dcrlnd/chainntnfs" 12 "github.com/decred/dcrlnd/kvdb" 13 "github.com/decred/dcrlnd/lntest/mock" 14 sphinx "github.com/decred/lightning-onion/v4" 15 ) 16 17 const ( 18 cltv uint32 = 100000 19 ) 20 21 // tempDecayedLogPath creates a new temporary database path to back a single 22 // decayed log instance. 23 func tempDecayedLogPath(t *testing.T) string { 24 dir, err := ioutil.TempDir("", "decayedlog") 25 if err != nil { 26 t.Fatalf("unable to create temporary decayed log dir: %v", err) 27 } 28 29 return dir 30 } 31 32 // startup sets up the DecayedLog and possibly the garbage collector. 33 func startup(dbPath string, notifier bool) (sphinx.ReplayLog, 34 *mock.ChainNotifier, *sphinx.HashPrefix, func(), error) { 35 36 cfg := &kvdb.BoltConfig{ 37 DBTimeout: time.Second, 38 } 39 backend, err := NewBoltBackendCreator(dbPath, "sphinxreplay.db")(cfg) 40 if err != nil { 41 return nil, nil, nil, nil, fmt.Errorf("unable to create temporary "+ 42 "decayed log db: %v", err) 43 } 44 45 var log sphinx.ReplayLog 46 var chainNotifier *mock.ChainNotifier 47 if notifier { 48 49 // Create the MockNotifier which triggers the garbage collector 50 chainNotifier = &mock.ChainNotifier{ 51 SpendChan: make(chan *chainntnfs.SpendDetail), 52 EpochChan: make(chan *chainntnfs.BlockEpoch, 1), 53 ConfChan: make(chan *chainntnfs.TxConfirmation), 54 } 55 56 // Initialize the DecayedLog object 57 log = NewDecayedLog(backend, chainNotifier) 58 } else { 59 // Initialize the DecayedLog object 60 log = NewDecayedLog(backend, nil) 61 } 62 63 // Open the channeldb (start the garbage collector) 64 err = log.Start() 65 if err != nil { 66 return nil, nil, nil, nil, err 67 } 68 69 // Create a HashPrefix identifier for a packet. Instead of actually 70 // generating an ECDH secret and hashing it, simulate with random bytes. 71 // This is used as a key to retrieve the cltv value. 72 var hashedSecret sphinx.HashPrefix 73 _, err = rand.Read(hashedSecret[:]) 74 if err != nil { 75 return nil, nil, nil, nil, err 76 } 77 78 stop := func() { 79 _ = log.Stop() 80 backend.Close() 81 } 82 83 return log, chainNotifier, &hashedSecret, stop, nil 84 } 85 86 // shutdown deletes the temporary directory that the test database uses 87 // and handles closing the database. 88 func shutdown(dir string, d sphinx.ReplayLog) { 89 d.Stop() 90 os.RemoveAll(dir) 91 } 92 93 // TestDecayedLogGarbageCollector tests the ability of the garbage collector 94 // to delete expired cltv values every time a block is received. Expired cltv 95 // values are cltv values that are < current block height. 96 func TestDecayedLogGarbageCollector(t *testing.T) { 97 t.Parallel() 98 99 dbPath := tempDecayedLogPath(t) 100 101 d, notifier, hashedSecret, _, err := startup(dbPath, true) 102 if err != nil { 103 t.Fatalf("Unable to start up DecayedLog: %v", err) 104 } 105 defer shutdown(dbPath, d) 106 107 // Store <hashedSecret, cltv> in the sharedHashBucket. 108 err = d.Put(hashedSecret, cltv) 109 if err != nil { 110 t.Fatalf("Unable to store in channeldb: %v", err) 111 } 112 113 // Wait for database write (GC is in a goroutine) 114 time.Sleep(500 * time.Millisecond) 115 116 // Send block notifications to garbage collector. The garbage collector 117 // should remove the entry by block 100001. 118 119 // Send block 100000 120 notifier.EpochChan <- &chainntnfs.BlockEpoch{ 121 Height: 100000, 122 } 123 124 // Assert that hashedSecret is still in the sharedHashBucket 125 val, err := d.Get(hashedSecret) 126 if err != nil { 127 t.Fatalf("Get failed - received an error upon Get: %v", err) 128 } 129 130 if val != cltv { 131 t.Fatalf("GC incorrectly deleted CLTV") 132 } 133 134 // Send block 100001 (expiry block) 135 notifier.EpochChan <- &chainntnfs.BlockEpoch{ 136 Height: 100001, 137 } 138 139 // Wait for database write (GC is in a goroutine) 140 time.Sleep(500 * time.Millisecond) 141 142 // Assert that hashedSecret is not in the sharedHashBucket 143 _, err = d.Get(hashedSecret) 144 if err == nil { 145 t.Fatalf("CLTV was not deleted") 146 } 147 if err != sphinx.ErrLogEntryNotFound { 148 t.Fatalf("Get failed - received unexpected error upon Get: %v", err) 149 } 150 } 151 152 // TestDecayedLogPersistentGarbageCollector tests the persistence property of 153 // the garbage collector. The garbage collector will be restarted immediately and 154 // a block that expires the stored CLTV value will be sent to the ChainNotifier. 155 // We test that this causes the <hashedSecret, CLTV> pair to be deleted even 156 // on GC restarts. 157 func TestDecayedLogPersistentGarbageCollector(t *testing.T) { 158 t.Parallel() 159 160 dbPath := tempDecayedLogPath(t) 161 162 d, _, hashedSecret, stop, err := startup(dbPath, true) 163 if err != nil { 164 t.Fatalf("Unable to start up DecayedLog: %v", err) 165 } 166 defer shutdown(dbPath, d) 167 168 // Store <hashedSecret, cltv> in the sharedHashBucket 169 if err = d.Put(hashedSecret, cltv); err != nil { 170 t.Fatalf("Unable to store in channeldb: %v", err) 171 } 172 173 // The hash prefix should be retrievable from the decayed log. 174 _, err = d.Get(hashedSecret) 175 if err != nil { 176 t.Fatalf("Get failed - received unexpected error upon Get: %v", err) 177 } 178 179 // Shut down DecayedLog and the garbage collector along with it. 180 stop() 181 182 d2, notifier2, _, _, err := startup(dbPath, true) 183 if err != nil { 184 t.Fatalf("Unable to restart DecayedLog: %v", err) 185 } 186 defer shutdown(dbPath, d2) 187 188 // Check that the hash prefix still exists in the new db instance. 189 _, err = d2.Get(hashedSecret) 190 if err != nil { 191 t.Fatalf("Get failed - received unexpected error upon Get: %v", err) 192 } 193 194 // Send a block notification to the garbage collector that expires 195 // the stored CLTV. 196 notifier2.EpochChan <- &chainntnfs.BlockEpoch{ 197 Height: int32(100001), 198 } 199 200 // Wait for database write (GC is in a goroutine) 201 time.Sleep(500 * time.Millisecond) 202 203 // Assert that hashedSecret is not in the sharedHashBucket 204 _, err = d2.Get(hashedSecret) 205 if err != sphinx.ErrLogEntryNotFound { 206 t.Fatalf("Get failed - received unexpected error upon Get: %v", err) 207 } 208 } 209 210 // TestDecayedLogInsertionAndRetrieval inserts a cltv value into the 211 // sharedHashBucket and then deletes it and finally asserts that we can no 212 // longer retrieve it. 213 func TestDecayedLogInsertionAndDeletion(t *testing.T) { 214 t.Parallel() 215 216 dbPath := tempDecayedLogPath(t) 217 218 d, _, hashedSecret, _, err := startup(dbPath, false) 219 if err != nil { 220 t.Fatalf("Unable to start up DecayedLog: %v", err) 221 } 222 defer shutdown(dbPath, d) 223 224 // Store <hashedSecret, cltv> in the sharedHashBucket. 225 err = d.Put(hashedSecret, cltv) 226 if err != nil { 227 t.Fatalf("Unable to store in channeldb: %v", err) 228 } 229 230 // Delete hashedSecret from the sharedHashBucket. 231 err = d.Delete(hashedSecret) 232 if err != nil { 233 t.Fatalf("Unable to delete from channeldb: %v", err) 234 } 235 236 // Assert that hashedSecret is not in the sharedHashBucket 237 _, err = d.Get(hashedSecret) 238 if err == nil { 239 t.Fatalf("CLTV was not deleted") 240 } 241 if err != sphinx.ErrLogEntryNotFound { 242 t.Fatalf("Get failed - received unexpected error upon Get: %v", err) 243 } 244 } 245 246 // TestDecayedLogStartAndStop tests for persistence. The DecayedLog is started, 247 // a cltv value is stored in the sharedHashBucket, and then it the DecayedLog 248 // is stopped. The DecayedLog is then started up again and we test that the 249 // cltv value is indeed still stored in the sharedHashBucket. We then delete 250 // the cltv value and check that it persists upon startup. 251 func TestDecayedLogStartAndStop(t *testing.T) { 252 t.Parallel() 253 254 dbPath := tempDecayedLogPath(t) 255 256 d, _, hashedSecret, stop, err := startup(dbPath, false) 257 if err != nil { 258 t.Fatalf("Unable to start up DecayedLog: %v", err) 259 } 260 defer shutdown(dbPath, d) 261 262 // Store <hashedSecret, cltv> in the sharedHashBucket. 263 err = d.Put(hashedSecret, cltv) 264 if err != nil { 265 t.Fatalf("Unable to store in channeldb: %v", err) 266 } 267 268 // Shutdown the DecayedLog's channeldb 269 stop() 270 271 d2, _, hashedSecret2, stop, err := startup(dbPath, false) 272 if err != nil { 273 t.Fatalf("Unable to restart DecayedLog: %v", err) 274 } 275 defer shutdown(dbPath, d2) 276 277 // Retrieve the stored cltv value given the hashedSecret key. 278 value, err := d2.Get(hashedSecret) 279 if err != nil { 280 t.Fatalf("Unable to retrieve from channeldb: %v", err) 281 } 282 283 // Check that the original cltv value matches the retrieved cltv 284 // value. 285 if cltv != value { 286 t.Fatalf("Value retrieved doesn't match value stored") 287 } 288 289 // Delete hashedSecret from sharedHashBucket 290 err = d2.Delete(hashedSecret2) 291 if err != nil { 292 t.Fatalf("Unable to delete from channeldb: %v", err) 293 } 294 295 // Shutdown the DecayedLog's channeldb 296 stop() 297 298 d3, _, hashedSecret3, _, err := startup(dbPath, false) 299 if err != nil { 300 t.Fatalf("Unable to restart DecayedLog: %v", err) 301 } 302 defer shutdown(dbPath, d3) 303 304 // Assert that hashedSecret is not in the sharedHashBucket 305 _, err = d3.Get(hashedSecret3) 306 if err == nil { 307 t.Fatalf("CLTV was not deleted") 308 } 309 if err != sphinx.ErrLogEntryNotFound { 310 t.Fatalf("Get failed - received unexpected error upon Get: %v", err) 311 } 312 } 313 314 // TestDecayedLogStorageAndRetrieval stores a cltv value and then retrieves it 315 // via the nested sharedHashBucket and finally asserts that the original stored 316 // and retrieved cltv values are equal. 317 func TestDecayedLogStorageAndRetrieval(t *testing.T) { 318 t.Parallel() 319 320 dbPath := tempDecayedLogPath(t) 321 322 d, _, hashedSecret, _, err := startup(dbPath, false) 323 if err != nil { 324 t.Fatalf("Unable to start up DecayedLog: %v", err) 325 } 326 defer shutdown(dbPath, d) 327 328 // Store <hashedSecret, cltv> in the sharedHashBucket 329 err = d.Put(hashedSecret, cltv) 330 if err != nil { 331 t.Fatalf("Unable to store in channeldb: %v", err) 332 } 333 334 // Retrieve the stored cltv value given the hashedSecret key. 335 value, err := d.Get(hashedSecret) 336 if err != nil { 337 t.Fatalf("Unable to retrieve from channeldb: %v", err) 338 } 339 340 // If the original cltv value does not match the value retrieved, 341 // then the test failed. 342 if cltv != value { 343 t.Fatalf("Value retrieved doesn't match value stored") 344 } 345 }