github.com/lbryio/lbcd@v0.22.119/integration/bip0009_test.go (about)

     1  // Copyright (c) 2016 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file is ignored during the regular tests due to the following build tag.
     6  //go:build rpctest
     7  // +build rpctest
     8  
     9  package integration
    10  
    11  import (
    12  	"fmt"
    13  	"runtime"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/lbryio/lbcd/blockchain"
    18  	"github.com/lbryio/lbcd/chaincfg"
    19  	"github.com/lbryio/lbcd/chaincfg/chainhash"
    20  	"github.com/lbryio/lbcd/integration/rpctest"
    21  )
    22  
    23  const (
    24  	// vbLegacyBlockVersion is the highest legacy block version before the
    25  	// version bits scheme became active.
    26  	vbLegacyBlockVersion = 4
    27  
    28  	// vbTopBits defines the bits to set in the version to signal that the
    29  	// version bits scheme is being used.
    30  	vbTopBits = 0x20000000
    31  )
    32  
    33  // assertVersionBit gets the passed block hash from the given test harness and
    34  // ensures its version either has the provided bit set or unset per the set
    35  // flag.
    36  func assertVersionBit(r *rpctest.Harness, t *testing.T, hash *chainhash.Hash, bit uint8, set bool) {
    37  	block, err := r.Client.GetBlock(hash)
    38  	if err != nil {
    39  		t.Fatalf("failed to retrieve block %v: %v", hash, err)
    40  	}
    41  	switch {
    42  	case set && block.Header.Version&(1<<bit) == 0:
    43  		_, _, line, _ := runtime.Caller(1)
    44  		t.Fatalf("assertion failed at line %d: block %s, version 0x%x "+
    45  			"does not have bit %d set", line, hash,
    46  			block.Header.Version, bit)
    47  	case !set && block.Header.Version&(1<<bit) != 0:
    48  		_, _, line, _ := runtime.Caller(1)
    49  		t.Fatalf("assertion failed at line %d: block %s, version 0x%x "+
    50  			"has bit %d set", line, hash, block.Header.Version, bit)
    51  	}
    52  }
    53  
    54  // assertChainHeight retrieves the current chain height from the given test
    55  // harness and ensures it matches the provided expected height.
    56  func assertChainHeight(r *rpctest.Harness, t *testing.T, expectedHeight uint32) {
    57  	height, err := r.Client.GetBlockCount()
    58  	if err != nil {
    59  		t.Fatalf("failed to retrieve block height: %v", err)
    60  	}
    61  	if uint32(height) != expectedHeight {
    62  		_, _, line, _ := runtime.Caller(1)
    63  		t.Fatalf("assertion failed at line %d: block height of %d "+
    64  			"is not the expected %d", line, height, expectedHeight)
    65  	}
    66  }
    67  
    68  // thresholdStateToStatus converts the passed threshold state to the equivalent
    69  // status string returned in the getblockchaininfo RPC.
    70  func thresholdStateToStatus(state blockchain.ThresholdState) (string, error) {
    71  	switch state {
    72  	case blockchain.ThresholdDefined:
    73  		return "defined", nil
    74  	case blockchain.ThresholdStarted:
    75  		return "started", nil
    76  	case blockchain.ThresholdLockedIn:
    77  		return "lockedin", nil
    78  	case blockchain.ThresholdActive:
    79  		return "active", nil
    80  	case blockchain.ThresholdFailed:
    81  		return "failed", nil
    82  	}
    83  
    84  	return "", fmt.Errorf("unrecognized threshold state: %v", state)
    85  }
    86  
    87  // assertSoftForkStatus retrieves the current blockchain info from the given
    88  // test harness and ensures the provided soft fork key is both available and its
    89  // status is the equivalent of the passed state.
    90  func assertSoftForkStatus(r *rpctest.Harness, t *testing.T, forkKey string, state blockchain.ThresholdState) {
    91  	// Convert the expected threshold state into the equivalent
    92  	// getblockchaininfo RPC status string.
    93  	status, err := thresholdStateToStatus(state)
    94  	if err != nil {
    95  		_, _, line, _ := runtime.Caller(1)
    96  		t.Fatalf("assertion failed at line %d: unable to convert "+
    97  			"threshold state %v to string", line, state)
    98  	}
    99  
   100  	info, err := r.Client.GetBlockChainInfo()
   101  	if err != nil {
   102  		t.Fatalf("failed to retrieve chain info: %v", err)
   103  	}
   104  
   105  	// Ensure the key is available.
   106  	desc, ok := info.SoftForks.Bip9SoftForks[forkKey]
   107  	if !ok {
   108  		_, _, line, _ := runtime.Caller(1)
   109  		t.Fatalf("assertion failed at line %d: softfork status for %q "+
   110  			"is not in getblockchaininfo results", line, forkKey)
   111  	}
   112  
   113  	// Ensure the status it the expected value.
   114  	if desc.Status != status {
   115  		_, _, line, _ := runtime.Caller(1)
   116  		t.Fatalf("assertion failed at line %d: softfork status for %q "+
   117  			"is %v instead of expected %v", line, forkKey,
   118  			desc.Status, status)
   119  	}
   120  }
   121  
   122  // testBIP0009 ensures the BIP0009 soft fork mechanism follows the state
   123  // transition rules set forth by the BIP for the provided soft fork key.  It
   124  // uses the regression test network to signal support and advance through the
   125  // various threshold states including failure to achieve locked in status.
   126  //
   127  // See TestBIP0009 for an overview of what is tested.
   128  //
   129  // NOTE: This only differs from the exported version in that it accepts the
   130  // specific soft fork deployment to test.
   131  func testBIP0009(t *testing.T, forkKey string, deploymentID uint32) {
   132  	// Initialize the primary mining node with only the genesis block.
   133  	r, err := rpctest.New(&chaincfg.SimNetParams, nil, nil, "")
   134  	if err != nil {
   135  		t.Fatalf("unable to create primary harness: %v", err)
   136  	}
   137  	if err := r.SetUp(false, 0); err != nil {
   138  		t.Fatalf("unable to setup test chain: %v", err)
   139  	}
   140  	defer r.TearDown()
   141  
   142  	// *** ThresholdDefined ***
   143  	//
   144  	// Assert the chain height is the expected value and the soft fork
   145  	// status starts out as defined.
   146  	assertChainHeight(r, t, 0)
   147  	assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdDefined)
   148  
   149  	// *** ThresholdDefined part 2 - 1 block prior to ThresholdStarted ***
   150  	//
   151  	// Generate enough blocks to reach the height just before the first
   152  	// state transition without signalling support since the state should
   153  	// move to started once the start time has been reached regardless of
   154  	// support signalling.
   155  	//
   156  	// NOTE: This is two blocks before the confirmation window because the
   157  	// getblockchaininfo RPC reports the status for the block AFTER the
   158  	// current one.  All of the heights below are thus offset by one to
   159  	// compensate.
   160  	//
   161  	// Assert the chain height is the expected value and soft fork status is
   162  	// still defined and did NOT move to started.
   163  	confirmationWindow := r.ActiveNet.MinerConfirmationWindow
   164  	for i := uint32(0); i < confirmationWindow-2; i++ {
   165  		_, err := r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion,
   166  			time.Time{})
   167  		if err != nil {
   168  			t.Fatalf("failed to generated block %d: %v", i, err)
   169  		}
   170  	}
   171  	assertChainHeight(r, t, confirmationWindow-2)
   172  	assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdDefined)
   173  
   174  	// *** ThresholdStarted ***
   175  	//
   176  	// Generate another block to reach the next window.
   177  	//
   178  	// Assert the chain height is the expected value and the soft fork
   179  	// status is started.
   180  	_, err = r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion, time.Time{})
   181  	if err != nil {
   182  		t.Fatalf("failed to generated block: %v", err)
   183  	}
   184  	assertChainHeight(r, t, confirmationWindow-1)
   185  	assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdStarted)
   186  
   187  	// *** ThresholdStarted part 2 - Fail to achieve ThresholdLockedIn ***
   188  	//
   189  	// Generate enough blocks to reach the next window in such a way that
   190  	// the number blocks with the version bit set to signal support is 1
   191  	// less than required to achieve locked in status.
   192  	//
   193  	// Assert the chain height is the expected value and the soft fork
   194  	// status is still started and did NOT move to locked in.
   195  	if deploymentID > uint32(len(r.ActiveNet.Deployments)) {
   196  		t.Fatalf("deployment ID %d does not exist", deploymentID)
   197  	}
   198  	deployment := &r.ActiveNet.Deployments[deploymentID]
   199  	activationThreshold := r.ActiveNet.RuleChangeActivationThreshold
   200  	signalForkVersion := int32(1<<deployment.BitNumber) | vbTopBits
   201  	for i := uint32(0); i < activationThreshold-1; i++ {
   202  		_, err := r.GenerateAndSubmitBlock(nil, signalForkVersion,
   203  			time.Time{})
   204  		if err != nil {
   205  			t.Fatalf("failed to generated block %d: %v", i, err)
   206  		}
   207  	}
   208  	for i := uint32(0); i < confirmationWindow-(activationThreshold-1); i++ {
   209  		_, err := r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion,
   210  			time.Time{})
   211  		if err != nil {
   212  			t.Fatalf("failed to generated block %d: %v", i, err)
   213  		}
   214  	}
   215  	assertChainHeight(r, t, (confirmationWindow*2)-1)
   216  	assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdStarted)
   217  
   218  	// *** ThresholdLockedIn ***
   219  	//
   220  	// Generate enough blocks to reach the next window in such a way that
   221  	// the number blocks with the version bit set to signal support is
   222  	// exactly the number required to achieve locked in status.
   223  	//
   224  	// Assert the chain height is the expected value and the soft fork
   225  	// status moved to locked in.
   226  	for i := uint32(0); i < activationThreshold; i++ {
   227  		_, err := r.GenerateAndSubmitBlock(nil, signalForkVersion,
   228  			time.Time{})
   229  		if err != nil {
   230  			t.Fatalf("failed to generated block %d: %v", i, err)
   231  		}
   232  	}
   233  	for i := uint32(0); i < confirmationWindow-activationThreshold; i++ {
   234  		_, err := r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion,
   235  			time.Time{})
   236  		if err != nil {
   237  			t.Fatalf("failed to generated block %d: %v", i, err)
   238  		}
   239  	}
   240  	assertChainHeight(r, t, (confirmationWindow*3)-1)
   241  	assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdLockedIn)
   242  
   243  	// *** ThresholdLockedIn part 2 -- 1 block prior to ThresholdActive ***
   244  	//
   245  	// Generate enough blocks to reach the height just before the next
   246  	// window without continuing to signal support since it is already
   247  	// locked in.
   248  	//
   249  	// Assert the chain height is the expected value and the soft fork
   250  	// status is still locked in and did NOT move to active.
   251  	for i := uint32(0); i < confirmationWindow-1; i++ {
   252  		_, err := r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion,
   253  			time.Time{})
   254  		if err != nil {
   255  			t.Fatalf("failed to generated block %d: %v", i, err)
   256  		}
   257  	}
   258  	assertChainHeight(r, t, (confirmationWindow*4)-2)
   259  	assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdLockedIn)
   260  
   261  	// *** ThresholdActive ***
   262  	//
   263  	// Generate another block to reach the next window without continuing to
   264  	// signal support since it is already locked in.
   265  	//
   266  	// Assert the chain height is the expected value and the soft fork
   267  	// status moved to active.
   268  	_, err = r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion, time.Time{})
   269  	if err != nil {
   270  		t.Fatalf("failed to generated block: %v", err)
   271  	}
   272  	assertChainHeight(r, t, (confirmationWindow*4)-1)
   273  	assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdActive)
   274  }
   275  
   276  // TestBIP0009 ensures the BIP0009 soft fork mechanism follows the state
   277  // transition rules set forth by the BIP for all soft forks.  It uses the
   278  // regression test network to signal support and advance through the various
   279  // threshold states including failure to achieve locked in status.
   280  //
   281  // Overview:
   282  // - Assert the chain height is 0 and the state is ThresholdDefined
   283  // - Generate 1 fewer blocks than needed to reach the first state transition
   284  //   - Assert chain height is expected and state is still ThresholdDefined
   285  //
   286  // - Generate 1 more block to reach the first state transition
   287  //   - Assert chain height is expected and state moved to ThresholdStarted
   288  //   - Generate enough blocks to reach the next state transition window, but only
   289  //     signal support in 1 fewer than the required number to achieve
   290  //     ThresholdLockedIn
   291  //   - Assert chain height is expected and state is still ThresholdStarted
   292  //   - Generate enough blocks to reach the next state transition window with only
   293  //     the exact number of blocks required to achieve locked in status signalling
   294  //     support.
   295  //   - Assert chain height is expected and state moved to ThresholdLockedIn
   296  //   - Generate 1 fewer blocks than needed to reach the next state transition
   297  //   - Assert chain height is expected and state is still ThresholdLockedIn
   298  //   - Generate 1 more block to reach the next state transition
   299  //   - Assert chain height is expected and state moved to ThresholdActive
   300  func TestBIP0009(t *testing.T) {
   301  	t.Parallel()
   302  
   303  	testBIP0009(t, "dummy", chaincfg.DeploymentTestDummy)
   304  	testBIP0009(t, "segwit", chaincfg.DeploymentSegwit)
   305  }
   306  
   307  // TestBIP0009Mining ensures blocks built via btcd's CPU miner follow the rules
   308  // set forth by BIP0009 by using the test dummy deployment.
   309  //
   310  // Overview:
   311  // - Generate block 1
   312  //   - Assert bit is NOT set (ThresholdDefined)
   313  //
   314  // - Generate enough blocks to reach first state transition
   315  //   - Assert bit is NOT set for block prior to state transition
   316  //   - Assert bit is set for block at state transition (ThresholdStarted)
   317  //
   318  // - Generate enough blocks to reach second state transition
   319  //   - Assert bit is set for block at state transition (ThresholdLockedIn)
   320  //
   321  // - Generate enough blocks to reach third state transition
   322  //   - Assert bit is set for block prior to state transition (ThresholdLockedIn)
   323  //   - Assert bit is NOT set for block at state transition (ThresholdActive)
   324  func TestBIP0009Mining(t *testing.T) {
   325  	t.Parallel()
   326  
   327  	// Initialize the primary mining node with only the genesis block.
   328  	r, err := rpctest.New(&chaincfg.RegressionNetParams, nil, nil, "")
   329  	if err != nil {
   330  		t.Fatalf("unable to create primary harness: %v", err)
   331  	}
   332  	if err := r.SetUp(true, 0); err != nil {
   333  		t.Fatalf("unable to setup test chain: %v", err)
   334  	}
   335  	defer r.TearDown()
   336  
   337  	// Assert the chain only consists of the gensis block.
   338  	assertChainHeight(r, t, 0)
   339  
   340  	// *** ThresholdDefined ***
   341  	//
   342  	// Generate a block that extends the genesis block.  It should not have
   343  	// the test dummy bit set in the version since the first window is
   344  	// in the defined threshold state.
   345  	deployment := &r.ActiveNet.Deployments[chaincfg.DeploymentTestDummy]
   346  	testDummyBitNum := deployment.BitNumber
   347  	hashes, err := r.Client.Generate(1)
   348  	if err != nil {
   349  		t.Fatalf("unable to generate blocks: %v", err)
   350  	}
   351  	assertChainHeight(r, t, 1)
   352  	assertVersionBit(r, t, hashes[0], testDummyBitNum, false)
   353  
   354  	// *** ThresholdStarted ***
   355  	//
   356  	// Generate enough blocks to reach the first state transition.
   357  	//
   358  	// The second to last generated block should not have the test bit set
   359  	// in the version.
   360  	//
   361  	// The last generated block should now have the test bit set in the
   362  	// version since the btcd mining code will have recognized the test
   363  	// dummy deployment as started.
   364  	confirmationWindow := r.ActiveNet.MinerConfirmationWindow
   365  	numNeeded := confirmationWindow - 1
   366  	hashes, err = r.Client.Generate(numNeeded)
   367  	if err != nil {
   368  		t.Fatalf("failed to generated %d blocks: %v", numNeeded, err)
   369  	}
   370  	assertChainHeight(r, t, confirmationWindow)
   371  	assertVersionBit(r, t, hashes[len(hashes)-2], testDummyBitNum, false)
   372  	assertVersionBit(r, t, hashes[len(hashes)-1], testDummyBitNum, true)
   373  
   374  	// *** ThresholdLockedIn ***
   375  	//
   376  	// Generate enough blocks to reach the next state transition.
   377  	//
   378  	// The last generated block should still have the test bit set in the
   379  	// version since the btcd mining code will have recognized the test
   380  	// dummy deployment as locked in.
   381  	hashes, err = r.Client.Generate(confirmationWindow)
   382  	if err != nil {
   383  		t.Fatalf("failed to generated %d blocks: %v", confirmationWindow,
   384  			err)
   385  	}
   386  	assertChainHeight(r, t, confirmationWindow*2)
   387  	assertVersionBit(r, t, hashes[len(hashes)-1], testDummyBitNum, true)
   388  
   389  	// *** ThresholdActivated ***
   390  	//
   391  	// Generate enough blocks to reach the next state transition.
   392  	//
   393  	// The second to last generated block should still have the test bit set
   394  	// in the version since it is still locked in.
   395  	//
   396  	// The last generated block should NOT have the test bit set in the
   397  	// version since the btcd mining code will have recognized the test
   398  	// dummy deployment as activated and thus there is no longer any need
   399  	// to set the bit.
   400  	hashes, err = r.Client.Generate(confirmationWindow)
   401  	if err != nil {
   402  		t.Fatalf("failed to generated %d blocks: %v", confirmationWindow,
   403  			err)
   404  	}
   405  	assertChainHeight(r, t, confirmationWindow*3)
   406  	assertVersionBit(r, t, hashes[len(hashes)-2], testDummyBitNum, true)
   407  	assertVersionBit(r, t, hashes[len(hashes)-1], testDummyBitNum, false)
   408  }