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  }