github.com/NebulousLabs/Sia@v1.3.7/modules/consensus/accept_reorg_test.go (about)

     1  package consensus
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/NebulousLabs/Sia/build"
     7  	"github.com/NebulousLabs/Sia/modules"
     8  	"github.com/NebulousLabs/Sia/types"
     9  )
    10  
    11  // reorgSets contains multiple consensus sets that share a genesis block, which
    12  // can be manipulated to cause full integration blockchain reorgs.
    13  //
    14  // cstBackup is a holding place for cstMain - the blocks originally in cstMain get moved
    15  // to cstBackup so that cstMain can be reorganized without that history being lost.
    16  // Extending cstBackup will allow cstMain to be reorg'd back to its original blocks.
    17  type reorgSets struct {
    18  	cstMain   *consensusSetTester
    19  	cstAlt    *consensusSetTester
    20  	cstBackup *consensusSetTester
    21  }
    22  
    23  // Close will close all of the testers in the reorgSets. Because we don't yet
    24  // have a good way to check errors on a deferred statement, a panic will be
    25  // thrown if there are any problems closing the reorgSets.
    26  func (rs *reorgSets) Close() error {
    27  	err := rs.cstMain.Close()
    28  	if err != nil {
    29  		panic(err)
    30  	}
    31  	err = rs.cstAlt.Close()
    32  	if err != nil {
    33  		panic(err)
    34  	}
    35  	err = rs.cstBackup.Close()
    36  	if err != nil {
    37  		panic(err)
    38  	}
    39  	return nil
    40  }
    41  
    42  // createReorgSets creates a reorg set that is ready to be manipulated.
    43  func createReorgSets(name string) *reorgSets {
    44  	cstMain, err := createConsensusSetTester(name + " - 1")
    45  	if err != nil {
    46  		panic(err)
    47  	}
    48  	cstAlt, err := createConsensusSetTester(name + " - 2")
    49  	if err != nil {
    50  		panic(err)
    51  	}
    52  	cstBackup, err := createConsensusSetTester(name + " - 3")
    53  	if err != nil {
    54  		panic(err)
    55  	}
    56  
    57  	return &reorgSets{
    58  		cstMain:   cstMain,
    59  		cstAlt:    cstAlt,
    60  		cstBackup: cstBackup,
    61  	}
    62  }
    63  
    64  // save takes all of the blocks in cstMain and moves them to cstBackup.
    65  func (rs *reorgSets) save() {
    66  	mainHeight := rs.cstMain.cs.dbBlockHeight()
    67  	for i := types.BlockHeight(1); i <= mainHeight; i++ {
    68  		id, err := rs.cstMain.cs.dbGetPath(i)
    69  		if err != nil {
    70  			panic(err)
    71  		}
    72  		pb, err := rs.cstMain.cs.dbGetBlockMap(id)
    73  		if err != nil {
    74  			panic(err)
    75  		}
    76  
    77  		// err is not checked - block may already be in cstBackup.
    78  		_ = rs.cstBackup.cs.AcceptBlock(pb.Block)
    79  	}
    80  
    81  	// Check that cstMain and cstBackup are even.
    82  	if rs.cstMain.cs.dbCurrentProcessedBlock().Block.ID() != rs.cstBackup.cs.dbCurrentProcessedBlock().Block.ID() {
    83  		panic("could not save cstMain into cstBackup")
    84  	}
    85  	if rs.cstMain.cs.dbConsensusChecksum() != rs.cstBackup.cs.dbConsensusChecksum() {
    86  		panic("reorg checksums do not match after saving")
    87  	}
    88  }
    89  
    90  // extend adds blocks to cstAlt until cstAlt has more weight than cstMain. Then
    91  // cstMain is caught up, causing cstMain to perform a reorg that extends all
    92  // the way to the genesis block.
    93  func (rs *reorgSets) extend() {
    94  	for rs.cstMain.cs.dbBlockHeight() >= rs.cstAlt.cs.dbBlockHeight() {
    95  		_, err := rs.cstAlt.miner.AddBlock()
    96  		if err != nil {
    97  			panic(err)
    98  		}
    99  	}
   100  	for i := types.BlockHeight(1); i <= rs.cstAlt.cs.dbBlockHeight(); i++ {
   101  		id, err := rs.cstAlt.cs.dbGetPath(i)
   102  		if err != nil {
   103  			panic(err)
   104  		}
   105  		pb, err := rs.cstAlt.cs.dbGetBlockMap(id)
   106  		if err != nil {
   107  			panic(err)
   108  		}
   109  		_ = rs.cstMain.cs.AcceptBlock(pb.Block)
   110  	}
   111  
   112  	// Check that cstMain and cstAlt are even.
   113  	if rs.cstMain.cs.dbCurrentProcessedBlock().Block.ID() != rs.cstAlt.cs.dbCurrentProcessedBlock().Block.ID() {
   114  		panic("could not save cstMain into cstAlt")
   115  	}
   116  	if rs.cstMain.cs.dbConsensusChecksum() != rs.cstAlt.cs.dbConsensusChecksum() {
   117  		panic("reorg checksums do not match after extending")
   118  	}
   119  }
   120  
   121  // restore extends cstBackup until it is ahead of cstMain, and then adds all of
   122  // the blocks from cstBackup to cstMain, causing cstMain to reorg to the state
   123  // of cstBackup.
   124  func (rs *reorgSets) restore() {
   125  	for rs.cstMain.cs.dbBlockHeight() >= rs.cstBackup.cs.dbBlockHeight() {
   126  		_, err := rs.cstBackup.miner.AddBlock()
   127  		if err != nil {
   128  			panic(err)
   129  		}
   130  	}
   131  	for i := types.BlockHeight(1); i <= rs.cstBackup.cs.dbBlockHeight(); i++ {
   132  		id, err := rs.cstBackup.cs.dbGetPath(i)
   133  		if err != nil {
   134  			panic(err)
   135  		}
   136  		pb, err := rs.cstBackup.cs.dbGetBlockMap(id)
   137  		if err != nil {
   138  			panic(err)
   139  		}
   140  		_ = rs.cstMain.cs.AcceptBlock(pb.Block)
   141  	}
   142  
   143  	// Check that cstMain and cstBackup are even.
   144  	if rs.cstMain.cs.dbCurrentProcessedBlock().Block.ID() != rs.cstBackup.cs.dbCurrentProcessedBlock().Block.ID() {
   145  		panic("could not save cstMain into cstBackup")
   146  	}
   147  	if rs.cstMain.cs.dbConsensusChecksum() != rs.cstBackup.cs.dbConsensusChecksum() {
   148  		panic("reorg checksums do not match after restoring")
   149  	}
   150  }
   151  
   152  // fullReorg saves all of the blocks from cstMain into cstBackup, then extends
   153  // cstAlt until cstMain joins cstAlt in structure. Then cstBackup is extended
   154  // and cstMain is reorg'd back to have all of the original blocks.
   155  func (rs *reorgSets) fullReorg() {
   156  	rs.save()
   157  	rs.extend()
   158  	rs.restore()
   159  }
   160  
   161  // TestIntegrationSimpleReorg tries to reorganize a simple block out of, and
   162  // then back into, the consensus set.
   163  func TestIntegrationSimpleReorg(t *testing.T) {
   164  	if testing.Short() {
   165  		t.SkipNow()
   166  	}
   167  	t.Parallel()
   168  	rs := createReorgSets(t.Name())
   169  	defer rs.Close()
   170  
   171  	// Give a simple block to cstMain.
   172  	rs.cstMain.testSimpleBlock()
   173  
   174  	// Try to trigger consensus inconsistencies by doing a full reorg on the
   175  	// simple block.
   176  	rs.fullReorg()
   177  }
   178  
   179  // TestIntegrationSiacoinReorg tries to reorganize a siacoin output block out
   180  // of, and then back into, the consensus set.
   181  func TestIntegrationSiacoinReorg(t *testing.T) {
   182  	if testing.Short() {
   183  		t.SkipNow()
   184  	}
   185  	t.Parallel()
   186  	rs := createReorgSets(t.Name())
   187  	defer rs.Close()
   188  
   189  	// Give a siacoin block to cstMain.
   190  	rs.cstMain.testSpendSiacoinsBlock()
   191  
   192  	// Try to trigger consensus inconsistencies by doing a full reorg on the
   193  	// simple block.
   194  	rs.fullReorg()
   195  }
   196  
   197  // TestIntegrationValidStorageProofReorg tries to reorganize a valid storage
   198  // proof block out of, and then back into, the consensus set.
   199  func TestIntegrationValidStorageProofReorg(t *testing.T) {
   200  	if testing.Short() {
   201  		t.SkipNow()
   202  	}
   203  	t.Parallel()
   204  	rs := createReorgSets(t.Name())
   205  	defer rs.Close()
   206  
   207  	// Give a series of blocks containing a file contract and a valid storage
   208  	// proof to cstMain.
   209  	rs.cstMain.testValidStorageProofBlocks()
   210  
   211  	// Try to trigger consensus inconsistencies by doing a full reorg on the
   212  	// simple block.
   213  	rs.fullReorg()
   214  }
   215  
   216  // TestIntegrationMissedStorageProofReorg tries to reorganize a valid storage
   217  // proof block out of, and then back into, the consensus set.
   218  func TestIntegrationMissedStorageProofReorg(t *testing.T) {
   219  	if testing.Short() {
   220  		t.SkipNow()
   221  	}
   222  	t.Parallel()
   223  	rs := createReorgSets(t.Name())
   224  	defer rs.Close()
   225  
   226  	// Give a series of blocks containing a file contract and a valid storage
   227  	// proof to cstMain.
   228  	rs.cstMain.testMissedStorageProofBlocks()
   229  
   230  	// Try to trigger consensus inconsistencies by doing a full reorg on the
   231  	// simple block.
   232  	rs.fullReorg()
   233  }
   234  
   235  // TestIntegrationFileContractRevisionReorg tries to reorganize a valid storage
   236  // proof block out of, and then back into, the consensus set.
   237  func TestIntegrationFileContractRevisionReorg(t *testing.T) {
   238  	if testing.Short() {
   239  		t.SkipNow()
   240  	}
   241  	t.Parallel()
   242  	rs := createReorgSets(t.Name())
   243  	defer rs.Close()
   244  
   245  	// Give a series of blocks containing a file contract and a valid storage
   246  	// proof to cstMain.
   247  	rs.cstMain.testFileContractRevision()
   248  
   249  	// Try to trigger consensus inconsistencies by doing a full reorg on the
   250  	// simple block.
   251  	rs.fullReorg()
   252  }
   253  
   254  // TestIntegrationComplexReorg stacks up blocks of all types into a single
   255  // blockchain that undergoes a massive reorg as a stress test to the codebase.
   256  func TestIntegrationComplexReorg(t *testing.T) {
   257  	if testing.Short() || !build.VLONG {
   258  		t.SkipNow()
   259  	}
   260  	t.Parallel()
   261  	rs := createReorgSets(t.Name())
   262  	defer rs.Close()
   263  
   264  	// Give a wide variety of block types to cstMain.
   265  	for i := 0; i < 3; i++ {
   266  		rs.cstMain.testBlockSuite()
   267  	}
   268  	// Give fewer blocks to cstAlt, while still using the same variety.
   269  	for i := 0; i < 2; i++ {
   270  		rs.cstAlt.testBlockSuite()
   271  	}
   272  
   273  	// Try to trigger consensus inconsistencies by doing a full reorg on the
   274  	// simple block.
   275  	rs.fullReorg()
   276  }
   277  
   278  /// All functions below this point are deprecated. ///
   279  
   280  // TestBuriedBadFork creates a block with an invalid transaction that's not on
   281  // the longest fork. The consensus set will not validate that block. Then valid
   282  // blocks are added on top of it to make it the longest fork. When it becomes
   283  // the longest fork, all the blocks should be fully validated and thrown out
   284  // because a parent is invalid.
   285  func TestBuriedBadFork(t *testing.T) {
   286  	if testing.Short() {
   287  		t.SkipNow()
   288  	}
   289  	t.Parallel()
   290  	cst, err := createConsensusSetTester(t.Name())
   291  	if err != nil {
   292  		t.Fatal(err)
   293  	}
   294  	defer cst.Close()
   295  	pb := cst.cs.dbCurrentProcessedBlock()
   296  
   297  	// Create a bad block that builds on a parent, so that it is part of not
   298  	// the longest fork.
   299  	badBlock := types.Block{
   300  		ParentID:     pb.Block.ParentID,
   301  		Timestamp:    types.CurrentTimestamp(),
   302  		MinerPayouts: []types.SiacoinOutput{{Value: types.CalculateCoinbase(pb.Height)}},
   303  		Transactions: []types.Transaction{{
   304  			SiacoinInputs: []types.SiacoinInput{{}}, // Will trigger an error on full verification but not partial verification.
   305  		}},
   306  	}
   307  	parent, err := cst.cs.dbGetBlockMap(pb.Block.ParentID)
   308  	if err != nil {
   309  		t.Fatal(err)
   310  	}
   311  	badBlock, _ = cst.miner.SolveBlock(badBlock, parent.ChildTarget)
   312  	err = cst.cs.AcceptBlock(badBlock)
   313  	if err != modules.ErrNonExtendingBlock {
   314  		t.Fatal(err)
   315  	}
   316  
   317  	// Build another bock on top of the bad block that is fully valid, this
   318  	// will cause a fork and full validation of the bad block, both the bad
   319  	// block and this block should be thrown away.
   320  	block := types.Block{
   321  		ParentID:     badBlock.ID(),
   322  		Timestamp:    types.CurrentTimestamp(),
   323  		MinerPayouts: []types.SiacoinOutput{{Value: types.CalculateCoinbase(pb.Height + 1)}},
   324  	}
   325  	block, _ = cst.miner.SolveBlock(block, parent.ChildTarget) // okay because the target will not change
   326  	err = cst.cs.AcceptBlock(block)
   327  	if err == nil {
   328  		t.Fatal("a bad block failed to cause an error")
   329  	}
   330  }