github.com/decred/dcrlnd@v0.7.6/chainscan/multi_test.go (about) 1 package chainscan 2 3 import ( 4 "bytes" 5 "context" 6 "testing" 7 "time" 8 9 "github.com/decred/dcrd/wire" 10 ) 11 12 // scannerTestCase is a test case that must be fulfilled by both the historical 13 // and the tip watcher individually. 14 type scannerTestCase struct { 15 name string 16 target func(*testBlock) Target 17 manglers []blockMangler 18 wantFound bool 19 wantMF MatchField 20 } 21 22 var scannerTestCases = []scannerTestCase{ 23 24 // Basic tests where a block should match the target. 25 26 { 27 name: "ConfirmedScript", 28 target: func(b *testBlock) Target { 29 return ConfirmedScript(0, testPkScript) 30 }, 31 manglers: []blockMangler{ 32 confirmScript(testPkScript), 33 cfilterData(testPkScript), 34 }, 35 wantFound: true, 36 wantMF: MatchTxOut, 37 }, 38 39 { 40 name: "ConfirmedOutPoint", 41 target: func(b *testBlock) Target { 42 outp := wire.OutPoint{ 43 Hash: b.block.Transactions[0].TxHash(), 44 Index: 1, 45 } 46 return ConfirmedOutPoint(outp, 0, testPkScript) 47 }, 48 manglers: []blockMangler{ 49 confirmScript(testPkScript), 50 cfilterData(testPkScript), 51 }, 52 wantFound: true, 53 wantMF: MatchTxOut, 54 }, 55 56 { 57 name: "SpentScript", 58 target: func(b *testBlock) Target { 59 return SpentScript(0, testPkScript) 60 }, 61 manglers: []blockMangler{ 62 spendScript(testSigScript), 63 cfilterData(testPkScript), 64 }, 65 wantFound: true, 66 wantMF: MatchTxIn, 67 }, 68 69 { 70 name: "SpentOutPoint", 71 target: func(b *testBlock) Target { 72 outp := b.block.Transactions[0].TxIn[1].PreviousOutPoint 73 return SpentOutPoint(outp, 0, testPkScript) 74 }, 75 manglers: []blockMangler{ 76 spendOutPoint(testOutPoint), 77 cfilterData(testPkScript), 78 }, 79 wantFound: true, 80 wantMF: MatchTxIn, 81 }, 82 83 // This test forces something that would ordinarily match in the block 84 // to not be included in the cfilter. This is not a realistic scenario 85 // in practice (due to consensus rules enforcing the correct behavior) 86 // but shows that if the cfilter doesn't match the block itself isn't 87 // tested. 88 { 89 name: "ConfirmedScript with cfilter miss", 90 target: func(b *testBlock) Target { 91 return ConfirmedScript(0, testPkScript) 92 }, 93 manglers: []blockMangler{ 94 confirmScript(testPkScript), 95 }, 96 }, 97 98 // The rest of the tests all force trigger a block check since a 99 // cfilter miss is trivial. 100 101 { 102 name: "ConfirmedScript without match", 103 target: func(b *testBlock) Target { 104 return ConfirmedScript(0, testPkScript) 105 }, 106 manglers: []blockMangler{ 107 // Note testPkScript is _not_ confirmed 108 cfilterData(testPkScript), 109 }, 110 }, 111 112 { 113 name: "ConfirmedOutPoint without match", 114 target: func(b *testBlock) Target { 115 outp := wire.OutPoint{ 116 Hash: b.block.Transactions[0].TxHash(), 117 Index: 1, 118 } 119 return ConfirmedOutPoint(outp, 0, testPkScript) 120 }, 121 manglers: []blockMangler{ 122 cfilterData(testPkScript), 123 }, 124 }, 125 126 // This tests that trying to watch for a specific outpoint fails when 127 // the script is confirmed in a _different_ outpoint. 128 { 129 name: "ConfirmedOutPoint with different outpoint", 130 target: func(b *testBlock) Target { 131 outp := wire.OutPoint{ 132 Hash: b.block.Transactions[0].TxHash(), 133 Index: 0, 134 } 135 return ConfirmedOutPoint(outp, 0, testPkScript) 136 }, 137 manglers: []blockMangler{ 138 confirmScript(testPkScript), 139 cfilterData(testPkScript), 140 }, 141 }, 142 143 { 144 name: "SpentScript without match", 145 target: func(b *testBlock) Target { 146 return SpentScript(0, testPkScript) 147 }, 148 manglers: []blockMangler{ 149 cfilterData(testPkScript), 150 }, 151 }, 152 153 { 154 name: "SpentOutPoint without match", 155 target: func(b *testBlock) Target { 156 outp := wire.OutPoint{ 157 Hash: b.block.Transactions[0].TxHash(), 158 Index: 1, 159 } 160 return SpentOutPoint(outp, 0, testPkScript) 161 }, 162 manglers: []blockMangler{ 163 cfilterData(testPkScript), 164 }, 165 }, 166 167 // This tests that a match is triggered even when the signatureScript 168 // of a watched outpoint does not correspond to the requested pkscript 169 // to watch for. 170 // 171 // Note that this is technically a client error given that watching for 172 // an outpoint when its correspondong pkscript is not the one specified 173 // in SpentOutpoint might cause the cfilter to never trigger a block 174 // download. 175 // 176 // Nevertheless we provide this test to fixate the TipWatcher's 177 // behavior in this situation. 178 { 179 name: "SpentOutPoint with different script", 180 target: func(b *testBlock) Target { 181 outp := b.block.Transactions[0].TxIn[1].PreviousOutPoint 182 return SpentOutPoint(outp, 0, testPkScript) 183 }, 184 manglers: []blockMangler{ 185 spendScript([]byte{0x00}), 186 cfilterData(testPkScript), 187 }, 188 wantFound: true, 189 wantMF: MatchTxIn, 190 }, 191 192 // This asserts that a match is not triggered for a confirmation when 193 // the script is spent in a random input. 194 { 195 name: "ConfirmedScript with spent script", 196 target: func(b *testBlock) Target { 197 return ConfirmedScript(0, testPkScript) 198 }, 199 manglers: []blockMangler{ 200 spendScript(testSigScript), 201 cfilterData(testPkScript), 202 }, 203 }, 204 205 // The next tests all deal with transactions in the stake tree. 206 207 // This test asserts that spending an output which is in the stake tree 208 // gets detected. 209 { 210 name: "Spent Stake OutPoint", 211 target: func(b *testBlock) Target { 212 outp := b.block.Transactions[0].TxIn[1].PreviousOutPoint 213 return SpentOutPoint(outp, 0, testPkScript) 214 }, 215 manglers: []blockMangler{ 216 spendOutPoint(wire.OutPoint{ 217 Hash: testOutPoint.Hash, 218 Tree: wire.TxTreeStake, 219 }), 220 cfilterData(testPkScript), 221 }, 222 wantFound: true, 223 wantMF: MatchTxIn, 224 }, 225 226 { 227 name: "ConfirmedOutPoint in stake tx", 228 target: func(b *testBlock) Target { 229 outp := wire.OutPoint{ 230 Hash: b.block.STransactions[0].TxHash(), 231 Index: 1, 232 Tree: wire.TxTreeStake, 233 } 234 return ConfirmedOutPoint(outp, 0, testPkScript) 235 }, 236 manglers: []blockMangler{ 237 confirmScript(testPkScript), 238 cfilterData(testPkScript), 239 moveRegularToStakeTree(), 240 }, 241 wantFound: true, 242 wantMF: MatchTxOut, 243 }, 244 245 { 246 name: "ConfirmedScript in stake tx", 247 target: func(b *testBlock) Target { 248 return ConfirmedScript(0, testPkScript) 249 }, 250 manglers: []blockMangler{ 251 confirmScript(testPkScript), 252 cfilterData(testPkScript), 253 moveRegularToStakeTree(), 254 }, 255 wantFound: true, 256 wantMF: MatchTxOut, 257 }, 258 259 // This test ensures that trying to watch for a script which should be 260 // confirmed in a specific output in the regular transaction tree fails 261 // to trigger a found event when that same script is actually confirmed 262 // in the stake tree. 263 { 264 name: "ConfirmedOutPoint in wrong tx tree", 265 target: func(b *testBlock) Target { 266 outp := wire.OutPoint{ 267 Hash: b.block.STransactions[0].TxHash(), 268 Index: 1, 269 Tree: wire.TxTreeRegular, 270 } 271 return ConfirmedOutPoint(outp, 0, testPkScript) 272 }, 273 manglers: []blockMangler{ 274 confirmScript(testPkScript), 275 cfilterData(testPkScript), 276 moveRegularToStakeTree(), 277 }, 278 }, 279 280 { 281 name: "ConfirmedScript with large script", 282 target: func(b *testBlock) Target { 283 return ConfirmedScript(0, bytes.Repeat([]byte{0x55}, 128)) 284 }, 285 manglers: []blockMangler{ 286 confirmScript(bytes.Repeat([]byte{0x55}, 128)), 287 cfilterData(bytes.Repeat([]byte{0x55}, 128)), 288 }, 289 wantFound: true, 290 wantMF: MatchTxOut, 291 }, 292 293 { 294 name: "ConfirmedScript with large script without match", 295 target: func(b *testBlock) Target { 296 return ConfirmedScript(0, bytes.Repeat([]byte{0x55}, 128)) 297 }, 298 manglers: []blockMangler{ 299 confirmScript(bytes.Repeat([]byte{0x55}, 127)), 300 cfilterData(bytes.Repeat([]byte{0x55}, 128)), 301 }, 302 }, 303 } 304 305 // TestSimultaneousScanners tests that when running both a TipWatcher and a 306 // Historical rescan against the same chain, events are consistent with the 307 // expected behavior of both scanners. 308 func TestSimultaneousScanners(t *testing.T) { 309 ctx, cancel := context.WithCancel(context.Background()) 310 defer cancel() 311 chain := newMockChain() 312 chain.extend(chain.newFromTip()) // Genesis block 313 314 // The manglers that generate a block with a match. 315 confirmManglers := []blockMangler{ 316 confirmScript(testPkScript), 317 cfilterData(testPkScript), 318 } 319 320 // The generated test chain is: 321 // - 5 blocks that miss the cfilter match 322 // - 5 blocks with a cfilter match 323 // - block which confirms the test pkscript 324 // - 5 blocks with a cfilter match 325 chain.genBlocks(5) 326 chain.genBlocks(5, cfilterData(testPkScript)) 327 b := chain.newFromTip(confirmManglers...) 328 chain.extend(b) 329 chain.genBlocks(5, cfilterData(testPkScript)) 330 331 // Create and run the scanners. 332 tw := NewTipWatcher(chain) 333 hist := NewHistorical(chain) 334 go func() { 335 tw.Run(ctx) 336 }() 337 go func() { 338 hist.Run(ctx) 339 }() 340 341 // Give it enough time for the TipWatcher to process the chain. 342 time.Sleep(10 * time.Millisecond) 343 344 // Attempt a search in both scanners in a consistent way. We first 345 // watch for the desired target in the tip watcher and use the returned 346 // starting watch height as the end of the historical search. 347 histFoundChan := make(chan Event) 348 tipFoundChan := make(chan Event) 349 swhChan := make(chan int32) 350 351 tw.Find( 352 ConfirmedScript(0, testPkScript), 353 WithFoundChan(tipFoundChan), 354 WithStartWatchHeightChan(swhChan), 355 ) 356 357 endHeight := assertStartWatchHeightSignalled(t, swhChan) 358 359 // Generate a new block with the target script to simulate the tip 360 // changing between the call to TipWatcher.Find() and 361 // Historical.Find(). This could lead to multiple matches if the usage 362 // of the two scanners is inconsistent. 363 tip := chain.newFromTip(confirmManglers...) 364 chain.extend(tip) 365 chain.signalNewTip() 366 367 // The TipWatcher should trigger a match. 368 assertFoundChanRcvHeight(t, tipFoundChan, int32(tip.block.Header.Height)) 369 370 // Run the historical scanner. 371 hist.Find( 372 ConfirmedScript(0, testPkScript), 373 WithFoundChan(histFoundChan), 374 WithEndHeight(endHeight), 375 ) 376 377 // Generate a new tip confirming the test script. 378 tip = chain.newFromTip(confirmManglers...) 379 chain.extend(tip) 380 chain.signalNewTip() 381 382 // We expect to find one (and only one) signal in both chans, each 383 // pointing to their respective triggered event. 384 assertFoundChanRcvHeight(t, histFoundChan, int32(b.block.Header.Height)) 385 assertFoundChanRcvHeight(t, tipFoundChan, int32(tip.block.Header.Height)) 386 assertFoundChanEmpty(t, histFoundChan) 387 assertFoundChanEmpty(t, tipFoundChan) 388 }