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 }