github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/accept_reorg_test.go (about)

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