github.com/btcsuite/btcd@v0.24.0/blockchain/thresholdstate_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  package blockchain
     6  
     7  import (
     8  	"testing"
     9  
    10  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    11  )
    12  
    13  // TestThresholdStateStringer tests the stringized output for the
    14  // ThresholdState type.
    15  func TestThresholdStateStringer(t *testing.T) {
    16  	t.Parallel()
    17  
    18  	tests := []struct {
    19  		in   ThresholdState
    20  		want string
    21  	}{
    22  		{ThresholdDefined, "ThresholdDefined"},
    23  		{ThresholdStarted, "ThresholdStarted"},
    24  		{ThresholdLockedIn, "ThresholdLockedIn"},
    25  		{ThresholdActive, "ThresholdActive"},
    26  		{ThresholdFailed, "ThresholdFailed"},
    27  		{0xff, "Unknown ThresholdState (255)"},
    28  	}
    29  
    30  	// Detect additional threshold states that don't have the stringer added.
    31  	if len(tests)-1 != int(numThresholdsStates) {
    32  		t.Errorf("It appears a threshold statewas added without " +
    33  			"adding an associated stringer test")
    34  	}
    35  
    36  	t.Logf("Running %d tests", len(tests))
    37  	for i, test := range tests {
    38  		result := test.in.String()
    39  		if result != test.want {
    40  			t.Errorf("String #%d\n got: %s want: %s", i, result,
    41  				test.want)
    42  			continue
    43  		}
    44  	}
    45  }
    46  
    47  // TestThresholdStateCache ensure the threshold state cache works as intended
    48  // including adding entries, updating existing entries, and flushing.
    49  func TestThresholdStateCache(t *testing.T) {
    50  	t.Parallel()
    51  
    52  	tests := []struct {
    53  		name       string
    54  		numEntries int
    55  		state      ThresholdState
    56  	}{
    57  		{name: "2 entries defined", numEntries: 2, state: ThresholdDefined},
    58  		{name: "7 entries started", numEntries: 7, state: ThresholdStarted},
    59  		{name: "10 entries active", numEntries: 10, state: ThresholdActive},
    60  		{name: "5 entries locked in", numEntries: 5, state: ThresholdLockedIn},
    61  		{name: "3 entries failed", numEntries: 3, state: ThresholdFailed},
    62  	}
    63  
    64  nextTest:
    65  	for _, test := range tests {
    66  		cache := &newThresholdCaches(1)[0]
    67  		for i := 0; i < test.numEntries; i++ {
    68  			var hash chainhash.Hash
    69  			hash[0] = uint8(i + 1)
    70  
    71  			// Ensure the hash isn't available in the cache already.
    72  			_, ok := cache.Lookup(&hash)
    73  			if ok {
    74  				t.Errorf("Lookup (%s): has entry for hash %v",
    75  					test.name, hash)
    76  				continue nextTest
    77  			}
    78  
    79  			// Ensure hash that was added to the cache reports it's
    80  			// available and the state is the expected value.
    81  			cache.Update(&hash, test.state)
    82  			state, ok := cache.Lookup(&hash)
    83  			if !ok {
    84  				t.Errorf("Lookup (%s): missing entry for hash "+
    85  					"%v", test.name, hash)
    86  				continue nextTest
    87  			}
    88  			if state != test.state {
    89  				t.Errorf("Lookup (%s): state mismatch - got "+
    90  					"%v, want %v", test.name, state,
    91  					test.state)
    92  				continue nextTest
    93  			}
    94  
    95  			// Ensure adding an existing hash with the same state
    96  			// doesn't break the existing entry.
    97  			cache.Update(&hash, test.state)
    98  			state, ok = cache.Lookup(&hash)
    99  			if !ok {
   100  				t.Errorf("Lookup (%s): missing entry after "+
   101  					"second add for hash %v", test.name,
   102  					hash)
   103  				continue nextTest
   104  			}
   105  			if state != test.state {
   106  				t.Errorf("Lookup (%s): state mismatch after "+
   107  					"second add - got %v, want %v",
   108  					test.name, state, test.state)
   109  				continue nextTest
   110  			}
   111  
   112  			// Ensure adding an existing hash with a different state
   113  			// updates the existing entry.
   114  			newState := ThresholdFailed
   115  			if newState == test.state {
   116  				newState = ThresholdStarted
   117  			}
   118  			cache.Update(&hash, newState)
   119  			state, ok = cache.Lookup(&hash)
   120  			if !ok {
   121  				t.Errorf("Lookup (%s): missing entry after "+
   122  					"state change for hash %v", test.name,
   123  					hash)
   124  				continue nextTest
   125  			}
   126  			if state != newState {
   127  				t.Errorf("Lookup (%s): state mismatch after "+
   128  					"state change - got %v, want %v",
   129  					test.name, state, newState)
   130  				continue nextTest
   131  			}
   132  		}
   133  	}
   134  }
   135  
   136  type customDeploymentChecker struct {
   137  	started bool
   138  	ended   bool
   139  
   140  	eligible bool
   141  
   142  	isSpeedy bool
   143  
   144  	conditionTrue bool
   145  
   146  	activationThreshold uint32
   147  	minerWindow         uint32
   148  }
   149  
   150  func (c customDeploymentChecker) HasStarted(_ *blockNode) bool {
   151  	return c.started
   152  }
   153  
   154  func (c customDeploymentChecker) HasEnded(_ *blockNode) bool {
   155  	return c.ended
   156  }
   157  
   158  func (c customDeploymentChecker) RuleChangeActivationThreshold() uint32 {
   159  	return c.activationThreshold
   160  }
   161  
   162  func (c customDeploymentChecker) MinerConfirmationWindow() uint32 {
   163  	return c.minerWindow
   164  }
   165  
   166  func (c customDeploymentChecker) EligibleToActivate(_ *blockNode) bool {
   167  	return c.eligible
   168  }
   169  
   170  func (c customDeploymentChecker) IsSpeedy() bool {
   171  	return c.isSpeedy
   172  }
   173  
   174  func (c customDeploymentChecker) Condition(_ *blockNode) (bool, error) {
   175  	return c.conditionTrue, nil
   176  }
   177  
   178  // TestThresholdStateTransition tests that the thresholdStateTransition
   179  // properly implements the BIP 009 state machine, along with the speedy trial
   180  // augments.
   181  func TestThresholdStateTransition(t *testing.T) {
   182  	t.Parallel()
   183  
   184  	// Prev node always points back to itself, effectively creating an
   185  	// infinite chain for the purposes of this test.
   186  	prevNode := &blockNode{}
   187  	prevNode.parent = prevNode
   188  
   189  	window := int32(2016)
   190  
   191  	testCases := []struct {
   192  		currentState ThresholdState
   193  		nextState    ThresholdState
   194  
   195  		checker thresholdConditionChecker
   196  	}{
   197  		// From defined, we stay there if we haven't started the
   198  		// window, and the window hasn't ended.
   199  		{
   200  			currentState: ThresholdDefined,
   201  			nextState:    ThresholdDefined,
   202  
   203  			checker: &customDeploymentChecker{},
   204  		},
   205  
   206  		// From defined, we go to failed if the window has ended, and
   207  		// this isn't a speedy trial.
   208  		{
   209  			currentState: ThresholdDefined,
   210  			nextState:    ThresholdFailed,
   211  
   212  			checker: &customDeploymentChecker{
   213  				ended: true,
   214  			},
   215  		},
   216  
   217  		// From defined, even if the window has ended, we go to started
   218  		// if this isn't a speedy trial.
   219  		{
   220  			currentState: ThresholdDefined,
   221  			nextState:    ThresholdStarted,
   222  
   223  			checker: &customDeploymentChecker{
   224  				started: true,
   225  			},
   226  		},
   227  
   228  		// From started, we go to failed if this isn't speed, and the
   229  		// deployment has ended.
   230  		{
   231  			currentState: ThresholdStarted,
   232  			nextState:    ThresholdFailed,
   233  
   234  			checker: &customDeploymentChecker{
   235  				ended: true,
   236  			},
   237  		},
   238  
   239  		// From started, we go to locked in if the window passed the
   240  		// condition.
   241  		{
   242  			currentState: ThresholdStarted,
   243  			nextState:    ThresholdLockedIn,
   244  
   245  			checker: &customDeploymentChecker{
   246  				started:       true,
   247  				conditionTrue: true,
   248  			},
   249  		},
   250  
   251  		// From started, we go to failed if this is a speedy trial, and
   252  		// the condition wasn't met in the window.
   253  		{
   254  			currentState: ThresholdStarted,
   255  			nextState:    ThresholdFailed,
   256  
   257  			checker: &customDeploymentChecker{
   258  				started:             true,
   259  				ended:               true,
   260  				isSpeedy:            true,
   261  				conditionTrue:       false,
   262  				activationThreshold: 1815,
   263  			},
   264  		},
   265  
   266  		// From locked in, we go straight to active is this isn't a
   267  		// speedy trial.
   268  		{
   269  			currentState: ThresholdLockedIn,
   270  			nextState:    ThresholdActive,
   271  
   272  			checker: &customDeploymentChecker{
   273  				eligible: true,
   274  			},
   275  		},
   276  
   277  		// From locked in, we remain in locked in if we're not yet
   278  		// eligible to activate.
   279  		{
   280  			currentState: ThresholdLockedIn,
   281  			nextState:    ThresholdLockedIn,
   282  
   283  			checker: &customDeploymentChecker{},
   284  		},
   285  
   286  		// From active, we always stay here.
   287  		{
   288  			currentState: ThresholdActive,
   289  			nextState:    ThresholdActive,
   290  
   291  			checker: &customDeploymentChecker{},
   292  		},
   293  
   294  		// From failed, we always stay here.
   295  		{
   296  			currentState: ThresholdFailed,
   297  			nextState:    ThresholdFailed,
   298  
   299  			checker: &customDeploymentChecker{},
   300  		},
   301  	}
   302  	for i, testCase := range testCases {
   303  		nextState, err := thresholdStateTransition(
   304  			testCase.currentState, prevNode, testCase.checker,
   305  			window,
   306  		)
   307  		if err != nil {
   308  			t.Fatalf("#%v: unable to transition to next "+
   309  				"state: %v", i, err)
   310  		}
   311  
   312  		if nextState != testCase.nextState {
   313  			t.Fatalf("#%v: incorrect state transition: "+
   314  				"expected %v got %v", i, testCase.nextState,
   315  				nextState)
   316  		}
   317  	}
   318  }