github.com/palcoin-project/palcd@v1.0.0/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/palcoin-project/palcd/blockchain" 18 "github.com/palcoin-project/palcd/chaincfg" 19 "github.com/palcoin-project/palcd/chaincfg/chainhash" 20 "github.com/palcoin-project/palcd/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.RegressionNetParams, 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 // - Generate 1 more block to reach the first state transition 286 // - Assert chain height is expected and state moved to ThresholdStarted 287 // - Generate enough blocks to reach the next state transition window, but only 288 // signal support in 1 fewer than the required number to achieve 289 // ThresholdLockedIn 290 // - Assert chain height is expected and state is still ThresholdStarted 291 // - Generate enough blocks to reach the next state transition window with only 292 // the exact number of blocks required to achieve locked in status signalling 293 // support. 294 // - Assert chain height is expected and state moved to ThresholdLockedIn 295 // - Generate 1 fewer blocks than needed to reach the next state transition 296 // - Assert chain height is expected and state is still ThresholdLockedIn 297 // - Generate 1 more block to reach the next state transition 298 // - Assert chain height is expected and state moved to ThresholdActive 299 func TestBIP0009(t *testing.T) { 300 t.Parallel() 301 302 testBIP0009(t, "dummy", chaincfg.DeploymentTestDummy) 303 testBIP0009(t, "segwit", chaincfg.DeploymentSegwit) 304 } 305 306 // TestBIP0009Mining ensures blocks built via btcd's CPU miner follow the rules 307 // set forth by BIP0009 by using the test dummy deployment. 308 // 309 // Overview: 310 // - Generate block 1 311 // - Assert bit is NOT set (ThresholdDefined) 312 // - Generate enough blocks to reach first state transition 313 // - Assert bit is NOT set for block prior to state transition 314 // - Assert bit is set for block at state transition (ThresholdStarted) 315 // - Generate enough blocks to reach second state transition 316 // - Assert bit is set for block at state transition (ThresholdLockedIn) 317 // - Generate enough blocks to reach third state transition 318 // - Assert bit is set for block prior to state transition (ThresholdLockedIn) 319 // - Assert bit is NOT set for block at state transition (ThresholdActive) 320 func TestBIP0009Mining(t *testing.T) { 321 t.Parallel() 322 323 // Initialize the primary mining node with only the genesis block. 324 r, err := rpctest.New(&chaincfg.SimNetParams, nil, nil, "") 325 if err != nil { 326 t.Fatalf("unable to create primary harness: %v", err) 327 } 328 if err := r.SetUp(true, 0); err != nil { 329 t.Fatalf("unable to setup test chain: %v", err) 330 } 331 defer r.TearDown() 332 333 // Assert the chain only consists of the gensis block. 334 assertChainHeight(r, t, 0) 335 336 // *** ThresholdDefined *** 337 // 338 // Generate a block that extends the genesis block. It should not have 339 // the test dummy bit set in the version since the first window is 340 // in the defined threshold state. 341 deployment := &r.ActiveNet.Deployments[chaincfg.DeploymentTestDummy] 342 testDummyBitNum := deployment.BitNumber 343 hashes, err := r.Client.Generate(1) 344 if err != nil { 345 t.Fatalf("unable to generate blocks: %v", err) 346 } 347 assertChainHeight(r, t, 1) 348 assertVersionBit(r, t, hashes[0], testDummyBitNum, false) 349 350 // *** ThresholdStarted *** 351 // 352 // Generate enough blocks to reach the first state transition. 353 // 354 // The second to last generated block should not have the test bit set 355 // in the version. 356 // 357 // The last generated block should now have the test bit set in the 358 // version since the btcd mining code will have recognized the test 359 // dummy deployment as started. 360 confirmationWindow := r.ActiveNet.MinerConfirmationWindow 361 numNeeded := confirmationWindow - 1 362 hashes, err = r.Client.Generate(numNeeded) 363 if err != nil { 364 t.Fatalf("failed to generated %d blocks: %v", numNeeded, err) 365 } 366 assertChainHeight(r, t, confirmationWindow) 367 assertVersionBit(r, t, hashes[len(hashes)-2], testDummyBitNum, false) 368 assertVersionBit(r, t, hashes[len(hashes)-1], testDummyBitNum, true) 369 370 // *** ThresholdLockedIn *** 371 // 372 // Generate enough blocks to reach the next state transition. 373 // 374 // The last generated block should still have the test bit set in the 375 // version since the btcd mining code will have recognized the test 376 // dummy deployment as locked in. 377 hashes, err = r.Client.Generate(confirmationWindow) 378 if err != nil { 379 t.Fatalf("failed to generated %d blocks: %v", confirmationWindow, 380 err) 381 } 382 assertChainHeight(r, t, confirmationWindow*2) 383 assertVersionBit(r, t, hashes[len(hashes)-1], testDummyBitNum, true) 384 385 // *** ThresholdActivated *** 386 // 387 // Generate enough blocks to reach the next state transition. 388 // 389 // The second to last generated block should still have the test bit set 390 // in the version since it is still locked in. 391 // 392 // The last generated block should NOT have the test bit set in the 393 // version since the btcd mining code will have recognized the test 394 // dummy deployment as activated and thus there is no longer any need 395 // to set the bit. 396 hashes, err = r.Client.Generate(confirmationWindow) 397 if err != nil { 398 t.Fatalf("failed to generated %d blocks: %v", confirmationWindow, 399 err) 400 } 401 assertChainHeight(r, t, confirmationWindow*3) 402 assertVersionBit(r, t, hashes[len(hashes)-2], testDummyBitNum, true) 403 assertVersionBit(r, t, hashes[len(hashes)-1], testDummyBitNum, false) 404 }