decred.org/dcrwallet/v3@v3.1.0/wallet/reorg_test.go (about) 1 // Copyright (c) 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 wallet 6 7 import ( 8 "context" 9 "fmt" 10 "testing" 11 12 "github.com/decred/dcrd/blockchain/v5/chaingen" 13 "github.com/decred/dcrd/chaincfg/chainhash" 14 "github.com/decred/dcrd/chaincfg/v3" 15 "github.com/decred/dcrd/gcs/v4/blockcf2" 16 "github.com/decred/dcrd/txscript/v4" 17 "github.com/decred/dcrd/wire" 18 ) 19 20 type tg struct { 21 *testing.T 22 *chaingen.Generator 23 } 24 25 type tw struct { 26 *testing.T 27 *Wallet 28 } 29 30 type gblock struct { 31 *wire.MsgBlock 32 *BlockNode 33 } 34 35 func maketg(t *testing.T, params *chaincfg.Params) *tg { 36 g, err := chaingen.MakeGenerator(params) 37 if err != nil { 38 t.Fatal(err) 39 } 40 return &tg{t, &g} 41 } 42 43 // chaingenPrevScripter is only usable when all spent utxos use the default 44 // chaingen OP_TRUE p2sh pkscript. 45 type chaingenPrevScripter struct{} 46 47 func (cps chaingenPrevScripter) PrevScript(*wire.OutPoint) (uint16, []byte, bool) { 48 // All scripts generated internally by chaingen are the same p2sh 49 // OP_TRUE. 50 script := []byte{ 51 txscript.OP_HASH160, 52 // txscript.hash160([]byte{OP_TRUE}) 53 0xf5, 0xa8, 0x30, 0x2e, 0xe8, 0x69, 0x5b, 0xf8, 0x36, 0x25, 54 0x8b, 0x8f, 0x2b, 0x57, 0xb3, 0x8a, 0x0b, 0xe1, 0x4e, 0x47, 55 txscript.OP_EQUAL, 56 } 57 return 0, script, true 58 } 59 60 func (tg *tg) createBlockOne(name string) *gblock { 61 blockOne := tg.CreateBlockOne(name, 0) 62 f, err := blockcf2.Regular(blockOne, chaingenPrevScripter{}) 63 if err != nil { 64 tg.Fatal(err) 65 } 66 h := blockOne.BlockHash() 67 n := &BlockNode{Header: &blockOne.Header, Hash: &h, FilterV2: f} 68 return &gblock{blockOne, n} 69 } 70 71 func (tg *tg) nextBlock(blockName string, spend *chaingen.SpendableOut, ticketSpends []chaingen.SpendableOut) *gblock { 72 b := tg.NextBlock(blockName, spend, ticketSpends) 73 f, err := blockcf2.Regular(b, chaingenPrevScripter{}) 74 if err != nil { 75 tg.Fatal(err) 76 } 77 h := b.BlockHash() 78 n := &BlockNode{Header: &b.Header, Hash: &h, FilterV2: f} 79 return &gblock{b, n} 80 } 81 82 func (tg *tg) blockHashByName(name string) *chainhash.Hash { 83 b := tg.BlockByName(name) 84 h := b.BlockHash() 85 return &h 86 } 87 88 func mustAddBlockNode(t *testing.T, forest *SidechainForest, n *BlockNode) { 89 if !forest.AddBlockNode(n) { 90 t.Fatalf("Could not add block %v to sidechain forest", n.Hash) 91 } 92 } 93 94 func (tw *tw) evaluateBestChain(ctx context.Context, forest *SidechainForest, 95 expectedBranchLen int, expectedTip *chainhash.Hash) []*BlockNode { 96 97 bestChain, err := tw.EvaluateBestChain(ctx, forest) 98 if err != nil { 99 tw.Fatal(err) 100 } 101 if len(bestChain) != expectedBranchLen { 102 tw.Fatalf("expected best chain len %v, got %v", expectedBranchLen, len(bestChain)) 103 } 104 if len(bestChain) != 0 && *bestChain[len(bestChain)-1].Hash != *expectedTip { 105 tw.Fatalf("expected best chain tip %v, got %v", expectedTip, bestChain[len(bestChain)-1].Hash) 106 } 107 return bestChain 108 } 109 110 func (tw *tw) assertNoBetterChain(ctx context.Context, forest *SidechainForest) { 111 tw.evaluateBestChain(ctx, forest, 0, nil) 112 } 113 114 func (tw *tw) chainSwitch(ctx context.Context, forest *SidechainForest, chain []*BlockNode) { 115 prevChain, err := tw.ChainSwitch(ctx, forest, chain, nil) 116 if err != nil { 117 tw.Fatal(err) 118 } 119 for _, n := range prevChain { 120 forest.AddBlockNode(n) 121 } 122 tip, _ := tw.MainChainTip(ctx) 123 if tip != *chain[len(chain)-1].Hash { 124 tw.Fatalf("expected tip %v, got %v", chain[len(chain)-1].Hash, &tip) 125 } 126 } 127 128 func (tw *tw) expectBlockInMainChain(ctx context.Context, hash *chainhash.Hash, have, invalidated bool) { 129 haveBlock, isInvalidated, err := tw.BlockInMainChain(ctx, hash) 130 if err != nil { 131 tw.Fatal(err) 132 } 133 if haveBlock != have { 134 tw.Fatalf("Expected block %v: %v, actually have block: %v", hash, have, haveBlock) 135 } 136 if isInvalidated != invalidated { 137 tw.Fatalf("Expected block %v invalidated: %v, actually invalidated: %v", hash, invalidated, isInvalidated) 138 } 139 } 140 141 func assertSidechainTree(t *testing.T, tree *sidechainRootedTree, root *chainhash.Hash, tips ...*chainhash.Hash) { 142 if *tree.root.Hash != *root { 143 t.Fatalf("expected root %v, got %v", root, tree.root.Hash) 144 } 145 if len(tips) != len(tree.tips) { 146 t.Fatalf("expected %v tip(s), got %v", len(tips), len(tree.tips)) 147 } 148 for _, tip := range tips { 149 if _, ok := tree.tips[*tip]; !ok { 150 t.Fatalf("missing tip %v", tip) 151 } 152 } 153 } 154 155 func TestReorg(t *testing.T) { 156 t.Parallel() 157 ctx := context.Background() 158 159 cfg := basicWalletConfig 160 w, teardown := testWallet(ctx, t, &cfg) 161 defer teardown() 162 163 tg := maketg(t, cfg.Params) 164 tw := &tw{t, w} 165 forest := new(SidechainForest) 166 167 blockOne := tg.createBlockOne("block-one") 168 mustAddBlockNode(t, forest, blockOne.BlockNode) 169 t.Logf("Generated block one %v", blockOne.Hash) 170 171 bestChain := tw.evaluateBestChain(ctx, forest, 1, blockOne.Hash) 172 tw.chainSwitch(ctx, forest, bestChain) 173 t.Logf("Attached block one %v", blockOne.Hash) 174 if len(forest.trees) != 0 { 175 t.Fatalf("Did not prune block one from forest") 176 } 177 tw.assertNoBetterChain(ctx, forest) 178 179 // Generate blocks 2a and 3a and attach to the wallet's main chain together. 180 for i := 2; i <= 3; i++ { 181 name := fmt.Sprintf("%va", i) 182 b := tg.nextBlock(name, nil, nil) 183 mustAddBlockNode(t, forest, b.BlockNode) 184 t.Logf("Generated block %v name %q", b.Hash, name) 185 } 186 if len(forest.trees) != 1 { 187 t.Fatalf("Expected one tree in forest") 188 } 189 b2aHash := tg.blockHashByName("2a") 190 b3aHash := tg.blockHashByName("3a") 191 assertSidechainTree(t, forest.trees[0], b2aHash, b3aHash) 192 bestChain = tw.evaluateBestChain(ctx, forest, 2, b3aHash) 193 tw.chainSwitch(ctx, forest, bestChain) 194 if len(forest.trees) != 0 { 195 t.Fatalf("Did not prune blocks 2a-3a from forest") 196 } 197 tw.assertNoBetterChain(ctx, forest) 198 199 // Generate sidechain blocks 2b-3b and assert it does not create a better 200 // chain. 201 tg.SetTip("block-one") 202 for i := 2; i <= 3; i++ { 203 name := fmt.Sprintf("%vb", i) 204 b := tg.nextBlock(name, nil, nil) 205 mustAddBlockNode(t, forest, b.BlockNode) 206 t.Logf("Generated block %v name %q", b.Hash, name) 207 } 208 if len(forest.trees) != 1 { 209 t.Fatalf("Expected one tree in forest") 210 } 211 b2bHash := tg.blockHashByName("2b") 212 b3bHash := tg.blockHashByName("3b") 213 assertSidechainTree(t, forest.trees[0], b2bHash, b3bHash) 214 tw.assertNoBetterChain(ctx, forest) 215 216 // Generate sidechain block 4b, and attach the better chain 2b-4b to 217 // wallet's main chain, reorging out 2a and 3a. 218 name := "4b" 219 b := tg.nextBlock(name, nil, nil) 220 mustAddBlockNode(t, forest, b.BlockNode) 221 t.Logf("Generated block %v name %q", b.Hash, name) 222 b4bHash := b.Hash 223 if len(forest.trees) != 1 { 224 t.Fatalf("Expected one tree in forest") 225 } 226 assertSidechainTree(t, forest.trees[0], b2bHash, b4bHash) 227 bestChain = tw.evaluateBestChain(ctx, forest, 3, b4bHash) 228 tw.chainSwitch(ctx, forest, bestChain) 229 if len(forest.trees) != 1 { 230 t.Fatalf("Expected single tree in forest after reorg") 231 } 232 tw.assertNoBetterChain(ctx, forest) 233 assertSidechainTree(t, forest.trees[0], b2aHash, b3aHash) 234 tw.expectBlockInMainChain(ctx, b2aHash, false, false) 235 tw.expectBlockInMainChain(ctx, b3aHash, false, false) 236 tw.expectBlockInMainChain(ctx, b2bHash, true, false) 237 tw.expectBlockInMainChain(ctx, b3bHash, true, false) 238 tw.expectBlockInMainChain(ctx, b4bHash, true, false) 239 }