github.com/decred/dcrd/blockchain@v1.2.1/agendas_test.go (about) 1 // Copyright (c) 2017-2019 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 "testing" 9 "time" 10 11 "github.com/decred/dcrd/blockchain/chaingen" 12 "github.com/decred/dcrd/blockchain/stake" 13 "github.com/decred/dcrd/chaincfg" 14 "github.com/decred/dcrd/dcrutil" 15 "github.com/decred/dcrd/txscript" 16 "github.com/decred/dcrd/wire" 17 ) 18 19 // testLNFeaturesDeployment ensures the deployment of the LN features agenda 20 // activates the expected changes for the provided network parameters. 21 func testLNFeaturesDeployment(t *testing.T, params *chaincfg.Params) { 22 // baseConsensusScriptVerifyFlags are the expected script flags when the 23 // agenda is not active. 24 const baseConsensusScriptVerifyFlags = txscript.ScriptVerifyCleanStack | 25 txscript.ScriptVerifyCheckLockTimeVerify 26 27 // Clone the parameters so they can be mutated, find the correct deployment 28 // for the LN features agenda as well as the yes vote choice within it, and, 29 // finally, ensure it is always available to vote by removing the time 30 // constraints to prevent test failures when the real expiration time 31 // passes. 32 params = cloneParams(params) 33 deploymentVer, deployment, err := findDeployment(params, 34 chaincfg.VoteIDLNFeatures) 35 if err != nil { 36 t.Fatal(err) 37 } 38 yesChoice, err := findDeploymentChoice(deployment, "yes") 39 if err != nil { 40 t.Fatal(err) 41 } 42 removeDeploymentTimeConstraints(deployment) 43 44 // Shorter versions of params for convenience. 45 stakeValidationHeight := uint32(params.StakeValidationHeight) 46 ruleChangeActivationInterval := params.RuleChangeActivationInterval 47 48 tests := []struct { 49 name string 50 numNodes uint32 // num fake nodes to create 51 curActive bool // whether agenda active for current block 52 nextActive bool // whether agenda active for NEXT block 53 expectedFlags txscript.ScriptFlags 54 }{ 55 { 56 name: "stake validation height", 57 numNodes: stakeValidationHeight, 58 curActive: false, 59 nextActive: false, 60 expectedFlags: baseConsensusScriptVerifyFlags, 61 }, 62 { 63 name: "started", 64 numNodes: ruleChangeActivationInterval, 65 curActive: false, 66 nextActive: false, 67 expectedFlags: baseConsensusScriptVerifyFlags, 68 }, 69 { 70 name: "lockedin", 71 numNodes: ruleChangeActivationInterval, 72 curActive: false, 73 nextActive: false, 74 expectedFlags: baseConsensusScriptVerifyFlags, 75 }, 76 { 77 name: "one before active", 78 numNodes: ruleChangeActivationInterval - 1, 79 curActive: false, 80 nextActive: true, 81 expectedFlags: baseConsensusScriptVerifyFlags, 82 }, 83 { 84 name: "exactly active", 85 numNodes: 1, 86 curActive: true, 87 nextActive: true, 88 expectedFlags: baseConsensusScriptVerifyFlags | 89 txscript.ScriptVerifyCheckSequenceVerify | 90 txscript.ScriptVerifySHA256, 91 }, 92 { 93 name: "one after active", 94 numNodes: 1, 95 curActive: true, 96 nextActive: true, 97 expectedFlags: baseConsensusScriptVerifyFlags | 98 txscript.ScriptVerifyCheckSequenceVerify | 99 txscript.ScriptVerifySHA256, 100 }, 101 } 102 103 curTimestamp := time.Now() 104 bc := newFakeChain(params) 105 node := bc.bestChain.Tip() 106 for _, test := range tests { 107 for i := uint32(0); i < test.numNodes; i++ { 108 node = newFakeNode(node, int32(deploymentVer), 109 deploymentVer, 0, curTimestamp) 110 111 // Create fake votes that vote yes on the agenda to 112 // ensure it is activated. 113 for j := uint16(0); j < params.TicketsPerBlock; j++ { 114 node.votes = append(node.votes, stake.VoteVersionTuple{ 115 Version: deploymentVer, 116 Bits: yesChoice.Bits | 0x01, 117 }) 118 } 119 bc.bestChain.SetTip(node) 120 curTimestamp = curTimestamp.Add(time.Second) 121 } 122 123 // Ensure the agenda reports the expected activation status for 124 // the current block. 125 gotActive, err := bc.isLNFeaturesAgendaActive(node.parent) 126 if err != nil { 127 t.Errorf("%s: unexpected err: %v", test.name, err) 128 continue 129 } 130 if gotActive != test.curActive { 131 t.Errorf("%s: mismatched current active status - got: "+ 132 "%v, want: %v", test.name, gotActive, 133 test.curActive) 134 continue 135 } 136 137 // Ensure the agenda reports the expected activation status for 138 // the NEXT block 139 gotActive, err = bc.IsLNFeaturesAgendaActive() 140 if err != nil { 141 t.Errorf("%s: unexpected err: %v", test.name, err) 142 continue 143 } 144 if gotActive != test.nextActive { 145 t.Errorf("%s: mismatched next active status - got: %v, "+ 146 "want: %v", test.name, gotActive, 147 test.nextActive) 148 continue 149 } 150 151 // Ensure the consensus script verify flags are as expected. 152 gotFlags, err := bc.consensusScriptVerifyFlags(node) 153 if err != nil { 154 t.Errorf("%s: unexpected err: %v", test.name, err) 155 continue 156 } 157 if gotFlags != test.expectedFlags { 158 t.Errorf("%s: mismatched flags - got %v, want %v", 159 test.name, gotFlags, test.expectedFlags) 160 continue 161 } 162 } 163 } 164 165 // TestLNFeaturesDeployment ensures the deployment of the LN features agenda 166 // activate the expected changes. 167 func TestLNFeaturesDeployment(t *testing.T) { 168 testLNFeaturesDeployment(t, &chaincfg.MainNetParams) 169 testLNFeaturesDeployment(t, &chaincfg.RegNetParams) 170 } 171 172 // testFixSeqLocksDeployment ensures the deployment of the fix sequence locks 173 // agenda activates for the provided network parameters. 174 func testFixSeqLocksDeployment(t *testing.T, params *chaincfg.Params) { 175 // Clone the parameters so they can be mutated, find the correct deployment 176 // for the fix sequence locks agenda as well as the yes vote choice within 177 // it, and, finally, ensure it is always available to vote by removing the 178 // time constraints to prevent test failures when the real expiration time 179 // passes. 180 params = cloneParams(params) 181 deploymentVer, deployment, err := findDeployment(params, 182 chaincfg.VoteIDFixLNSeqLocks) 183 if err != nil { 184 t.Fatal(err) 185 } 186 yesChoice, err := findDeploymentChoice(deployment, "yes") 187 if err != nil { 188 t.Fatal(err) 189 } 190 removeDeploymentTimeConstraints(deployment) 191 192 // Shorter versions of params for convenience. 193 stakeValidationHeight := uint32(params.StakeValidationHeight) 194 ruleChangeActivationInterval := params.RuleChangeActivationInterval 195 196 tests := []struct { 197 name string 198 numNodes uint32 // num fake nodes to create 199 curActive bool // whether agenda active for current block 200 nextActive bool // whether agenda active for NEXT block 201 }{ 202 { 203 name: "stake validation height", 204 numNodes: stakeValidationHeight, 205 curActive: false, 206 nextActive: false, 207 }, 208 { 209 name: "started", 210 numNodes: ruleChangeActivationInterval, 211 curActive: false, 212 nextActive: false, 213 }, 214 { 215 name: "lockedin", 216 numNodes: ruleChangeActivationInterval, 217 curActive: false, 218 nextActive: false, 219 }, 220 { 221 name: "one before active", 222 numNodes: ruleChangeActivationInterval - 1, 223 curActive: false, 224 nextActive: true, 225 }, 226 { 227 name: "exactly active", 228 numNodes: 1, 229 curActive: true, 230 nextActive: true, 231 }, 232 { 233 name: "one after active", 234 numNodes: 1, 235 curActive: true, 236 nextActive: true, 237 }, 238 } 239 240 curTimestamp := time.Now() 241 bc := newFakeChain(params) 242 node := bc.bestChain.Tip() 243 for _, test := range tests { 244 for i := uint32(0); i < test.numNodes; i++ { 245 node = newFakeNode(node, int32(deploymentVer), deploymentVer, 0, 246 curTimestamp) 247 248 // Create fake votes that vote yes on the agenda to ensure it is 249 // activated. 250 for j := uint16(0); j < params.TicketsPerBlock; j++ { 251 node.votes = append(node.votes, stake.VoteVersionTuple{ 252 Version: deploymentVer, 253 Bits: yesChoice.Bits | 0x01, 254 }) 255 } 256 bc.bestChain.SetTip(node) 257 curTimestamp = curTimestamp.Add(time.Second) 258 } 259 260 // Ensure the agenda reports the expected activation status for the 261 // current block. 262 gotActive, err := bc.isFixSeqLocksAgendaActive(node.parent) 263 if err != nil { 264 t.Errorf("%s: unexpected err: %v", test.name, err) 265 continue 266 } 267 if gotActive != test.curActive { 268 t.Errorf("%s: mismatched current active status - got: %v, want: %v", 269 test.name, gotActive, test.curActive) 270 continue 271 } 272 273 // Ensure the agenda reports the expected activation status for the NEXT 274 // block 275 gotActive, err = bc.IsFixSeqLocksAgendaActive() 276 if err != nil { 277 t.Errorf("%s: unexpected err: %v", test.name, err) 278 continue 279 } 280 if gotActive != test.nextActive { 281 t.Errorf("%s: mismatched next active status - got: %v, want: %v", 282 test.name, gotActive, test.nextActive) 283 continue 284 } 285 } 286 } 287 288 // TestFixSeqLocksDeployment ensures the deployment of the fix sequence locks 289 // agenda activates as expected. 290 func TestFixSeqLocksDeployment(t *testing.T) { 291 testFixSeqLocksDeployment(t, &chaincfg.MainNetParams) 292 testFixSeqLocksDeployment(t, &chaincfg.RegNetParams) 293 } 294 295 // TestFixedSequenceLocks ensures that sequence locks within blocks behave as 296 // expected once the fix sequence locks agenda is active. 297 func TestFixedSequenceLocks(t *testing.T) { 298 // Use a set of test chain parameters which allow for quicker vote 299 // activation as compared to various existing network params. 300 params := quickVoteActivationParams() 301 302 // Clone the parameters so they can be mutated, find the correct deployment 303 // for the fix sequence locks agenda, and, finally, ensure it is always 304 // available to vote by removing the time constraints to prevent test 305 // failures when the real expiration time passes. 306 const fslVoteID = chaincfg.VoteIDFixLNSeqLocks 307 params = cloneParams(params) 308 fslVersion, deployment, err := findDeployment(params, fslVoteID) 309 if err != nil { 310 t.Fatal(err) 311 } 312 removeDeploymentTimeConstraints(deployment) 313 314 // Create a test harness initialized with the genesis block as the tip. 315 g, teardownFunc := newChaingenHarness(t, params, "fixseqlockstest") 316 defer teardownFunc() 317 318 // replaceFixSeqLocksVersions is a munge function which modifies the 319 // provided block by replacing the block, stake, and vote versions with the 320 // fix sequence locks deployment version. 321 replaceFixSeqLocksVersions := func(b *wire.MsgBlock) { 322 chaingen.ReplaceBlockVersion(int32(fslVersion))(b) 323 chaingen.ReplaceStakeVersion(fslVersion)(b) 324 chaingen.ReplaceVoteVersions(fslVersion)(b) 325 } 326 327 // --------------------------------------------------------------------- 328 // Generate and accept enough blocks with the appropriate vote bits set 329 // to reach one block prior to the fix sequence locks agenda becoming 330 // active. 331 // --------------------------------------------------------------------- 332 333 g.AdvanceToStakeValidationHeight() 334 g.AdvanceFromSVHToActiveAgenda(fslVoteID) 335 336 // --------------------------------------------------------------------- 337 // Perform a series of sequence lock tests now that fix sequence locks 338 // enforcement is active. 339 // --------------------------------------------------------------------- 340 341 // enableSeqLocks modifies the passed transaction to enable sequence locks 342 // for the provided input. 343 enableSeqLocks := func(tx *wire.MsgTx, txInIdx int) { 344 tx.Version = 2 345 tx.TxIn[txInIdx].Sequence = 0 346 } 347 348 // --------------------------------------------------------------------- 349 // Create block that has a transaction with an input shared with a 350 // transaction in the stake tree and has several outputs used in 351 // subsequent blocks. Also, enable sequence locks for the first of 352 // those outputs. 353 // 354 // ... -> b0 355 // --------------------------------------------------------------------- 356 357 outs := g.OldestCoinbaseOuts() 358 b0 := g.NextBlock("b0", &outs[0], outs[1:], replaceFixSeqLocksVersions, 359 func(b *wire.MsgBlock) { 360 // Save the current outputs of the spend tx and clear them. 361 tx := b.Transactions[1] 362 origOut := tx.TxOut[0] 363 origOpReturnOut := tx.TxOut[1] 364 tx.TxOut = tx.TxOut[:0] 365 366 // Evenly split the original output amount over multiple outputs. 367 const numOutputs = 6 368 amount := origOut.Value / numOutputs 369 for i := 0; i < numOutputs; i++ { 370 if i == numOutputs-1 { 371 amount = origOut.Value - amount*(numOutputs-1) 372 } 373 tx.AddTxOut(wire.NewTxOut(amount, origOut.PkScript)) 374 } 375 376 // Add the original op return back to the outputs and enable 377 // sequence locks for the first output. 378 tx.AddTxOut(origOpReturnOut) 379 enableSeqLocks(tx, 0) 380 }) 381 g.SaveTipCoinbaseOuts() 382 g.AcceptTipBlock() 383 384 // --------------------------------------------------------------------- 385 // Create block that spends from an output created in the previous 386 // block. 387 // 388 // ... -> b0 -> b1a 389 // --------------------------------------------------------------------- 390 391 outs = g.OldestCoinbaseOuts() 392 g.NextBlock("b1a", nil, outs[1:], replaceFixSeqLocksVersions, 393 func(b *wire.MsgBlock) { 394 spend := chaingen.MakeSpendableOut(b0, 1, 0) 395 tx := g.CreateSpendTx(&spend, dcrutil.Amount(1)) 396 enableSeqLocks(tx, 0) 397 b.AddTransaction(tx) 398 }) 399 g.AcceptTipBlock() 400 401 // --------------------------------------------------------------------- 402 // Create block that involves reorganize to a sequence lock spending 403 // from an output created in a block prior to the parent also spent on 404 // on the side chain. 405 // 406 // ... -> b0 -> b1 -> b2 407 // \-> b1a 408 // --------------------------------------------------------------------- 409 g.SetTip("b0") 410 g.NextBlock("b1", nil, outs[1:], replaceFixSeqLocksVersions) 411 g.SaveTipCoinbaseOuts() 412 g.AcceptedToSideChainWithExpectedTip("b1a") 413 414 outs = g.OldestCoinbaseOuts() 415 g.NextBlock("b2", nil, outs[1:], replaceFixSeqLocksVersions, 416 func(b *wire.MsgBlock) { 417 spend := chaingen.MakeSpendableOut(b0, 1, 0) 418 tx := g.CreateSpendTx(&spend, dcrutil.Amount(1)) 419 enableSeqLocks(tx, 0) 420 b.AddTransaction(tx) 421 }) 422 g.SaveTipCoinbaseOuts() 423 g.AcceptTipBlock() 424 g.ExpectTip("b2") 425 426 // --------------------------------------------------------------------- 427 // Create block that involves a sequence lock on a vote. 428 // 429 // ... -> b2 -> b3 430 // --------------------------------------------------------------------- 431 432 outs = g.OldestCoinbaseOuts() 433 g.NextBlock("b3", nil, outs[1:], replaceFixSeqLocksVersions, 434 func(b *wire.MsgBlock) { 435 enableSeqLocks(b.STransactions[0], 0) 436 }) 437 g.SaveTipCoinbaseOuts() 438 g.AcceptTipBlock() 439 440 // --------------------------------------------------------------------- 441 // Create block that involves a sequence lock on a ticket. 442 // 443 // ... -> b3 -> b4 444 // --------------------------------------------------------------------- 445 446 outs = g.OldestCoinbaseOuts() 447 g.NextBlock("b4", nil, outs[1:], replaceFixSeqLocksVersions, 448 func(b *wire.MsgBlock) { 449 enableSeqLocks(b.STransactions[5], 0) 450 }) 451 g.SaveTipCoinbaseOuts() 452 g.AcceptTipBlock() 453 454 // --------------------------------------------------------------------- 455 // Create two blocks such that the tip block involves a sequence lock 456 // spending from a different output of a transaction the parent block 457 // also spends from. 458 // 459 // ... -> b4 -> b5 -> b6 460 // --------------------------------------------------------------------- 461 462 outs = g.OldestCoinbaseOuts() 463 g.NextBlock("b5", nil, outs[1:], replaceFixSeqLocksVersions, 464 func(b *wire.MsgBlock) { 465 spend := chaingen.MakeSpendableOut(b0, 1, 1) 466 tx := g.CreateSpendTx(&spend, dcrutil.Amount(1)) 467 b.AddTransaction(tx) 468 }) 469 g.SaveTipCoinbaseOuts() 470 g.AcceptTipBlock() 471 472 outs = g.OldestCoinbaseOuts() 473 g.NextBlock("b6", nil, outs[1:], replaceFixSeqLocksVersions, 474 func(b *wire.MsgBlock) { 475 spend := chaingen.MakeSpendableOut(b0, 1, 2) 476 tx := g.CreateSpendTx(&spend, dcrutil.Amount(1)) 477 enableSeqLocks(tx, 0) 478 b.AddTransaction(tx) 479 }) 480 g.SaveTipCoinbaseOuts() 481 g.AcceptTipBlock() 482 483 // --------------------------------------------------------------------- 484 // Create block that involves a sequence lock spending from a regular 485 // tree transaction earlier in the block. This used to be rejected 486 // due to a consensus bug, however the fix sequence locks agenda allows 487 // it to be accepted as desired. 488 // 489 // ... -> b6 -> b7 490 // --------------------------------------------------------------------- 491 492 outs = g.OldestCoinbaseOuts() 493 g.NextBlock("b7", &outs[0], outs[1:], replaceFixSeqLocksVersions, 494 func(b *wire.MsgBlock) { 495 spend := chaingen.MakeSpendableOut(b, 1, 0) 496 tx := g.CreateSpendTx(&spend, dcrutil.Amount(1)) 497 enableSeqLocks(tx, 0) 498 b.AddTransaction(tx) 499 }) 500 g.SaveTipCoinbaseOuts() 501 g.AcceptTipBlock() 502 503 // --------------------------------------------------------------------- 504 // Create block that involves a sequence lock spending from a block 505 // prior to the parent. This used to be rejected due to a consensus 506 // bug, however the fix sequence locks agenda allows it to be accepted 507 // as desired. 508 // 509 // ... -> b6 -> b8 -> b9 510 // --------------------------------------------------------------------- 511 512 outs = g.OldestCoinbaseOuts() 513 g.NextBlock("b8", nil, outs[1:], replaceFixSeqLocksVersions) 514 g.SaveTipCoinbaseOuts() 515 g.AcceptTipBlock() 516 517 outs = g.OldestCoinbaseOuts() 518 g.NextBlock("b9", nil, outs[1:], replaceFixSeqLocksVersions, 519 func(b *wire.MsgBlock) { 520 spend := chaingen.MakeSpendableOut(b0, 1, 3) 521 tx := g.CreateSpendTx(&spend, dcrutil.Amount(1)) 522 enableSeqLocks(tx, 0) 523 b.AddTransaction(tx) 524 }) 525 g.SaveTipCoinbaseOuts() 526 g.AcceptTipBlock() 527 528 // --------------------------------------------------------------------- 529 // Create two blocks such that the tip block involves a sequence lock 530 // spending from a different output of a transaction the parent block 531 // also spends from when the parent block has been disapproved. This 532 // used to be rejected due to a consensus bug, however the fix sequence 533 // locks agenda allows it to be accepted as desired. 534 // 535 // ... -> b8 -> b10 -> b11 536 // --------------------------------------------------------------------- 537 538 const ( 539 // vbDisapprovePrev and vbApprovePrev represent no and yes votes, 540 // respectively, on whether or not to approve the previous block. 541 vbDisapprovePrev = 0x0000 542 vbApprovePrev = 0x0001 543 ) 544 545 outs = g.OldestCoinbaseOuts() 546 g.NextBlock("b10", nil, outs[1:], replaceFixSeqLocksVersions, 547 func(b *wire.MsgBlock) { 548 spend := chaingen.MakeSpendableOut(b0, 1, 4) 549 tx := g.CreateSpendTx(&spend, dcrutil.Amount(1)) 550 b.AddTransaction(tx) 551 }) 552 g.SaveTipCoinbaseOuts() 553 g.AcceptTipBlock() 554 555 outs = g.OldestCoinbaseOuts() 556 g.NextBlock("b11", nil, outs[1:], replaceFixSeqLocksVersions, 557 chaingen.ReplaceVotes(vbDisapprovePrev, fslVersion), 558 func(b *wire.MsgBlock) { 559 b.Header.VoteBits &^= vbApprovePrev 560 spend := chaingen.MakeSpendableOut(b0, 1, 5) 561 tx := g.CreateSpendTx(&spend, dcrutil.Amount(1)) 562 enableSeqLocks(tx, 0) 563 b.AddTransaction(tx) 564 }) 565 g.SaveTipCoinbaseOuts() 566 g.AcceptTipBlock() 567 }