github.com/decred/dcrd/blockchain@v1.2.1/sequencelock_test.go (about) 1 // Copyright (c) 2017-2018 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package blockchain 6 7 import ( 8 "fmt" 9 "testing" 10 "time" 11 12 "github.com/decred/dcrd/chaincfg" 13 "github.com/decred/dcrd/dcrutil" 14 "github.com/decred/dcrd/wire" 15 ) 16 17 // mustLockTimeToSeq converts the passed relative lock time to a sequence number 18 // by using LockTimeToSequence. It only differs in that it will panic if there 19 // is an error so errors in the source code can be detected. It will only (and 20 // must only) be called with hard-coded, and therefore known good, values. 21 func mustLockTimeToSeq(isSeconds bool, lockTime uint32) uint32 { 22 sequence, err := LockTimeToSequence(isSeconds, lockTime) 23 if err != nil { 24 panic(fmt.Sprintf("invalid lock time in source file: "+ 25 "isSeconds: %v, lockTime: %d", isSeconds, lockTime)) 26 } 27 return sequence 28 } 29 30 // TestCalcSequenceLock exercises several combinations of inputs to the 31 // CalcSequenceLock function in order to ensure the returned sequence locks are 32 // as expected. 33 func TestCalcSequenceLock(t *testing.T) { 34 // Generate a synthetic simnet chain with enough nodes to properly test 35 // the sequence lock functionality. 36 numBlocks := uint32(20) 37 params := &chaincfg.RegNetParams 38 bc := newFakeChain(params) 39 node := bc.bestChain.Tip() 40 blockTime := time.Unix(node.timestamp, 0) 41 for i := uint32(0); i < numBlocks; i++ { 42 blockTime = blockTime.Add(time.Second) 43 node = newFakeNode(node, 1, 1, 0, blockTime) 44 bc.index.AddNode(node) 45 bc.bestChain.SetTip(node) 46 } 47 48 // Create a utxo view with a fake utxo for the inputs used in the 49 // transactions created below. This utxo is added such that it has an 50 // age of 4 blocks. 51 targetTx := dcrutil.NewTx(&wire.MsgTx{ 52 TxOut: []*wire.TxOut{{ 53 Value: 10, 54 Version: 0, 55 PkScript: nil, 56 }}, 57 }) 58 view := NewUtxoViewpoint() 59 view.AddTxOuts(targetTx, int64(numBlocks)-4, 0) 60 view.SetBestHash(&node.hash) 61 62 // Create a utxo that spends the fake utxo created above for use in the 63 // transactions created in the tests. It has an age of 4 blocks. Note 64 // that the sequence lock heights are always calculated from the same 65 // point of view that they were originally calculated from for a given 66 // utxo. That is to say, the height prior to it. 67 utxo := wire.OutPoint{ 68 Hash: *targetTx.Hash(), 69 Index: 0, 70 Tree: wire.TxTreeRegular, 71 } 72 prevUtxoHeight := int64(numBlocks) - 4 73 74 // Obtain the median time past from the PoV of the input created above. 75 // The median time for the input is the median time from the PoV of the 76 // block *prior* to the one that included it. 77 medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix() 78 79 // The median time calculated from the PoV of the best block in the 80 // test chain. For unconfirmed inputs, this value will be used since 81 // the median time will be calculated from the PoV of the 82 // yet-to-be-mined block. 83 nextMedianTime := node.CalcPastMedianTime().Unix() 84 nextBlockHeight := int64(numBlocks) + 1 85 86 // Add an additional transaction which will serve as our unconfirmed 87 // output. 88 unConfTx := &wire.MsgTx{ 89 TxOut: []*wire.TxOut{{ 90 Value: 5, 91 Version: 0, 92 PkScript: nil, 93 }}, 94 } 95 unConfUtxo := wire.OutPoint{ 96 Hash: unConfTx.TxHash(), 97 Index: 0, 98 Tree: wire.TxTreeRegular, 99 } 100 101 // Adding a utxo with a height of 0x7fffffff indicates that the output 102 // is currently unmined. 103 view.AddTxOuts(dcrutil.NewTx(unConfTx), 0x7fffffff, wire.NullBlockIndex) 104 105 tests := []struct { 106 name string 107 txVersion uint16 108 inputs []*wire.TxIn 109 isActive bool 110 want SequenceLock 111 }{ 112 { 113 // A transaction of version one should disable sequence 114 // locks as the new sequence number semantics only apply 115 // to transactions version 2 or higher. 116 name: "v1 transaction", 117 txVersion: 1, 118 inputs: []*wire.TxIn{{ 119 PreviousOutPoint: utxo, 120 Sequence: mustLockTimeToSeq(false, 3), 121 }}, 122 isActive: true, 123 want: SequenceLock{ 124 MinHeight: -1, 125 MinTime: -1, 126 }, 127 }, 128 { 129 // A transaction with a single input with max sequence 130 // number. This sequence number has the high bit set, 131 // so sequence locks should be disabled. 132 name: "max sequence number", 133 txVersion: 2, 134 inputs: []*wire.TxIn{{ 135 PreviousOutPoint: utxo, 136 Sequence: wire.MaxTxInSequenceNum, 137 }}, 138 isActive: true, 139 want: SequenceLock{ 140 MinHeight: -1, 141 MinTime: -1, 142 }, 143 }, 144 { 145 // A transaction that would result in a specific 146 // sequence lock except set the agenda is not being 147 // active yet, so sequence locks should be disabled. 148 name: "agenda not yet active", 149 txVersion: 2, 150 inputs: []*wire.TxIn{{ 151 PreviousOutPoint: utxo, 152 Sequence: mustLockTimeToSeq(true, 2), 153 }}, 154 isActive: false, 155 want: SequenceLock{ 156 MinHeight: -1, 157 MinTime: -1, 158 }, 159 }, 160 { 161 // A transaction with a single input whose locktime is 162 // expressed in seconds. However, the specified lock 163 // time is below the required floor for time based lock 164 // times since they have time granularity of 512 165 // seconds. As a result, the seconds locktime should be 166 // just before the median time of the targeted block. 167 name: "seconds below granularity", 168 txVersion: 2, 169 inputs: []*wire.TxIn{{ 170 PreviousOutPoint: utxo, 171 Sequence: mustLockTimeToSeq(true, 2), 172 }}, 173 isActive: true, 174 want: SequenceLock{ 175 MinHeight: -1, 176 MinTime: medianTime - 1, 177 }, 178 }, 179 { 180 // A transaction with a single input whose locktime is 181 // expressed in seconds. The number of seconds should 182 // be 1023 seconds after the median past time of the 183 // input. 184 name: "1024 seconds", 185 txVersion: 2, 186 inputs: []*wire.TxIn{{ 187 PreviousOutPoint: utxo, 188 Sequence: mustLockTimeToSeq(true, 1024), 189 }}, 190 isActive: true, 191 want: SequenceLock{ 192 MinHeight: -1, 193 MinTime: medianTime + 1023, 194 }, 195 }, 196 { 197 // A transaction with multiple inputs. The first input 198 // has a locktime expressed in seconds. The second 199 // input has a sequence lock in blocks with a value of 200 // 4. The last input has a sequence number with a value 201 // of 5, but has the disable bit set. So the first lock 202 // should be selected as it's the latest lock that isn't 203 // disabled. 204 name: "multiple inputs, 1 disabled", 205 txVersion: 2, 206 inputs: []*wire.TxIn{{ 207 PreviousOutPoint: utxo, 208 Sequence: mustLockTimeToSeq(true, 2560), 209 }, { 210 PreviousOutPoint: utxo, 211 Sequence: mustLockTimeToSeq(false, 4), 212 }, { 213 PreviousOutPoint: utxo, 214 Sequence: mustLockTimeToSeq(false, 5) | 215 wire.SequenceLockTimeDisabled, 216 }}, 217 isActive: true, 218 want: SequenceLock{ 219 MinHeight: prevUtxoHeight + 3, 220 MinTime: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1, 221 }, 222 }, 223 { 224 // A transaction with a single input. The input's 225 // sequence number encodes a relative locktime in blocks 226 // (3 blocks). The sequence lock should have a value 227 // of -1 for seconds, but a height of 2 meaning it can 228 // be included at height 3. 229 name: "3 blocks", 230 txVersion: 2, 231 inputs: []*wire.TxIn{{ 232 PreviousOutPoint: utxo, 233 Sequence: mustLockTimeToSeq(false, 3), 234 }}, 235 isActive: true, 236 want: SequenceLock{ 237 MinHeight: prevUtxoHeight + 2, 238 MinTime: -1, 239 }, 240 }, 241 { 242 // A transaction with two inputs with locktimes 243 // expressed in seconds. The selected sequence lock 244 // value for seconds should be the time further in the 245 // future. 246 name: "2 inputs both in seconds", 247 txVersion: 2, 248 inputs: []*wire.TxIn{{ 249 PreviousOutPoint: utxo, 250 Sequence: mustLockTimeToSeq(true, 5120), 251 }, { 252 PreviousOutPoint: utxo, 253 Sequence: mustLockTimeToSeq(true, 2560), 254 }}, 255 isActive: true, 256 want: SequenceLock{ 257 MinHeight: -1, 258 MinTime: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1, 259 }, 260 }, 261 { 262 // A transaction with two inputs with locktimes 263 // expressed in blocks. The selected sequence lock 264 // value for blocks should be the height further in the 265 // future, so a height of 10 indicating it can be 266 // included at height 11. 267 name: "2 inputs both in blocks", 268 txVersion: 2, 269 inputs: []*wire.TxIn{{ 270 PreviousOutPoint: utxo, 271 Sequence: mustLockTimeToSeq(false, 1), 272 }, { 273 PreviousOutPoint: utxo, 274 Sequence: mustLockTimeToSeq(false, 11), 275 }}, 276 isActive: true, 277 want: SequenceLock{ 278 MinHeight: prevUtxoHeight + 10, 279 MinTime: -1, 280 }, 281 }, 282 { 283 // A transaction with multiple inputs. Two inputs are 284 // seconds and the other two are blocks. The lock 285 // further into the future for both inputs should be 286 // chosen. 287 name: "4 inputs, 2 in seconds, 2 in blocks", 288 txVersion: 2, 289 inputs: []*wire.TxIn{{ 290 PreviousOutPoint: utxo, 291 Sequence: mustLockTimeToSeq(true, 2560), 292 }, { 293 PreviousOutPoint: utxo, 294 Sequence: mustLockTimeToSeq(true, 6656), 295 }, { 296 PreviousOutPoint: utxo, 297 Sequence: mustLockTimeToSeq(false, 3), 298 }, { 299 PreviousOutPoint: utxo, 300 Sequence: mustLockTimeToSeq(false, 9), 301 }}, 302 isActive: true, 303 want: SequenceLock{ 304 MinHeight: prevUtxoHeight + 8, 305 MinTime: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1, 306 }, 307 }, 308 { 309 // A transaction with a single unconfirmed input. Since 310 // the input is unconfirmed, the height of the input 311 // should be interpreted as the height of the *next* 312 // block. So, a 2 block relative lock means the 313 // sequence lock should be for 1 block after the *next* 314 // block height, indicating it can be included 2 blocks 315 // after that. 316 name: "unconfirmed input in blocks", 317 txVersion: 2, 318 inputs: []*wire.TxIn{{ 319 PreviousOutPoint: unConfUtxo, 320 Sequence: mustLockTimeToSeq(false, 2), 321 }}, 322 isActive: true, 323 want: SequenceLock{ 324 MinHeight: nextBlockHeight + 1, 325 MinTime: -1, 326 }, 327 }, 328 { 329 // A transaction with a single unconfirmed input. The 330 // input has locktime in seconds, so the locktime should 331 // be based off the median time of the *next* block. 332 name: "unconfirmed input in seconds", 333 txVersion: 2, 334 inputs: []*wire.TxIn{{ 335 PreviousOutPoint: unConfUtxo, 336 Sequence: mustLockTimeToSeq(true, 1024), 337 }}, 338 isActive: true, 339 want: SequenceLock{ 340 MinHeight: -1, 341 MinTime: nextMedianTime + 1023, 342 }, 343 }, 344 } 345 346 for i, test := range tests { 347 // Create fake spending transaction per the test input data. 348 tx := wire.MsgTx{ 349 SerType: wire.TxSerializeFull, 350 Version: test.txVersion, 351 LockTime: 0, 352 Expiry: 0, 353 TxOut: nil, 354 } 355 for _, txIn := range test.inputs { 356 tx.AddTxIn(txIn) 357 } 358 utilTx := dcrutil.NewTx(&tx) 359 360 // Calculate the sequence lock for the test input data. Since 361 // the exported function always has the agenda active, use the 362 // unexported function when simulating the agenda not being 363 // active, and alternate between them to ensure both are 364 // exercised. 365 var seqLock *SequenceLock 366 var err error 367 if test.isActive && i%2 == 0 { 368 seqLock, err = bc.CalcSequenceLock(utilTx, view) 369 } else { 370 bc.chainLock.Lock() 371 seqLock, err = bc.calcSequenceLock(node, utilTx, view, 372 test.isActive) 373 bc.chainLock.Unlock() 374 } 375 if err != nil { 376 t.Errorf("%s: unable to calc sequence lock: %v", 377 test.name, err) 378 continue 379 } 380 381 // Ensure both the returned sequence lock seconds and block 382 // height match the expected values. 383 if seqLock.MinTime != test.want.MinTime { 384 t.Errorf("%s: mistmached seconds - got %v, want %v", 385 test.name, seqLock.MinTime, test.want.MinTime) 386 continue 387 } 388 if seqLock.MinHeight != test.want.MinHeight { 389 t.Errorf("%s: mismatched height - got %v, want %v", 390 test.name, seqLock.MinHeight, 391 test.want.MinHeight) 392 } 393 } 394 } 395 396 // TestLockTimeToSequence ensure the convenience function to convert relative 397 // lock times to a sequence number works as expected. 398 func TestLockTimeToSequence(t *testing.T) { 399 const ( 400 // The following constants are used over the package-level 401 // definitions to ensure tests correctly detect any changes to 402 // them. 403 secondsGranularityBits = 9 404 secondsBit = 1 << 22 405 maxValue = 1<<16 - 1 406 maxBlockHeight = maxValue 407 maxSeconds = maxValue << secondsGranularityBits 408 ) 409 410 tests := []struct { 411 name string 412 locktime uint32 413 isSeconds bool 414 expected uint32 415 invalid bool 416 }{ 417 { 418 name: "relative block height 0", 419 locktime: 0, 420 isSeconds: false, 421 expected: 0, 422 }, 423 { 424 name: "max relative block height", 425 locktime: maxBlockHeight, 426 isSeconds: false, 427 expected: maxBlockHeight, 428 }, 429 { 430 name: "max relative block height +1", 431 locktime: maxBlockHeight + 1, 432 isSeconds: false, 433 expected: 0, 434 invalid: true, 435 }, 436 { 437 name: "relative seconds 0", 438 locktime: 0, 439 isSeconds: true, 440 expected: secondsBit, 441 }, 442 { 443 name: "relative seconds granularity - 1", 444 locktime: (1 << secondsGranularityBits) - 1, 445 isSeconds: true, 446 expected: secondsBit, 447 }, 448 { 449 name: "relative seconds exact granularity", 450 locktime: 1 << secondsGranularityBits, 451 isSeconds: true, 452 expected: secondsBit + 1, 453 }, 454 { 455 name: "relative seconds granularity + 1", 456 locktime: (1 << secondsGranularityBits) + 1, 457 isSeconds: true, 458 expected: secondsBit + 1, 459 }, 460 { 461 name: "relative seconds max - 1", 462 locktime: maxSeconds - 1, 463 isSeconds: true, 464 expected: secondsBit + maxValue - 1, 465 }, 466 { 467 name: "relative seconds max", 468 locktime: maxSeconds, 469 isSeconds: true, 470 expected: secondsBit + maxValue, 471 }, 472 { 473 name: "relative seconds max +1", 474 locktime: maxSeconds + 1, 475 isSeconds: true, 476 expected: 0, 477 invalid: true, 478 }, 479 } 480 481 for _, test := range tests { 482 gotSequence, err := LockTimeToSequence(test.isSeconds, 483 test.locktime) 484 if err != nil && !test.invalid { 485 t.Errorf("%s: unexpected error: %v", test.name, err) 486 continue 487 488 } 489 if err == nil && test.invalid { 490 t.Errorf("%s: did not receive expected error", test.name) 491 continue 492 } 493 494 if gotSequence != test.expected { 495 t.Errorf("%s: mismatched sequence - got %d, want %d", 496 test.name, gotSequence, test.expected) 497 continue 498 } 499 500 } 501 }