From 17e0e45a098530e1242a0f0e9f76ac857f8b79c8 Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 23 Jul 2024 13:55:45 +0800 Subject: [PATCH] BEP-341: Validators can produce consecutive blocks (#2482) --- cmd/extradump/extradump_test.go | 90 +++++++++++- cmd/extradump/main.go | 16 ++- cmd/geth/config.go | 3 + cmd/geth/main.go | 1 + cmd/jsutils/get_perf.js | 4 +- cmd/utils/flags.go | 6 + consensus/parlia/abi.go | 13 ++ consensus/parlia/api.go | 13 ++ consensus/parlia/bohrFork.go | 91 ++++++++++++ consensus/parlia/parlia.go | 129 ++++++++++++++---- consensus/parlia/parlia_test.go | 57 +++++--- consensus/parlia/snapshot.go | 129 +++++++++++++----- .../bohr/chapel/ValidatorContract | 0 .../bohr/mainnet/ValidatorContract | 0 core/systemcontracts/bohr/types.go | 15 ++ core/systemcontracts/upgrade.go | 6 + core/vote/vote_manager.go | 39 +++++- eth/api_backend.go | 11 ++ internal/ethapi/api.go | 14 +- internal/ethapi/api_test.go | 5 +- internal/ethapi/backend.go | 2 + internal/ethapi/transaction_args_test.go | 2 + params/protocol_params.go | 5 + 23 files changed, 558 insertions(+), 93 deletions(-) create mode 100644 consensus/parlia/bohrFork.go create mode 100644 core/systemcontracts/bohr/chapel/ValidatorContract create mode 100644 core/systemcontracts/bohr/mainnet/ValidatorContract create mode 100644 core/systemcontracts/bohr/types.go diff --git a/cmd/extradump/extradump_test.go b/cmd/extradump/extradump_test.go index f203d322e..6f9289fc4 100644 --- a/cmd/extradump/extradump_test.go +++ b/cmd/extradump/extradump_test.go @@ -43,7 +43,42 @@ func TestExtraParse(t *testing.T) { } } - // case 3, |---Extra Vanity---|---Empty---|---Vote Attestation---|---Extra Seal---| + // case 3, |---Extra Vanity---|---Validators Number and Validators Bytes---|---Turn Length---|---Empty---|---Extra Seal---| + { + extraData := "0xd983010209846765746889676f312e31392e3131856c696e75780000a6bf97c1152465176c461afb316ebc773c61faee85a6515daa8a923564c6ffd37fb2fe9f118ef88092e8762c7addb526ab7eb1e772baef85181f892c731be0c1891a50e6b06262c816295e26495cef6f69dfa69911d9d8e4f3bbadb89b977cf58294f7239d515e15b24cfeb82494056cf691eaf729b165f32c9757c429dba5051155903067e56ebe3698678e912d4c407bbe49438ed859fe965b140dcf1aab71a993c1f7f6929d1fe2a17b4e14614ef9fc5bdc713d6631d675403fbeefac55611bf612700b1b65f4744861b80b0f7d6ab03f349bbafec1551819b8be1efea2fc46ca749aa184248a459464eec1a21e7fc7b71a053d9644e9bb8da4853b8f872cd7c1d6b324bf1922829830646ceadfb658d3de009a61dd481a114a2e761c554b641742c973867899d300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000069c77a677c40c7fbea129d4b171a39b7a8ddabfab2317f59d86abfaf690850223d90e9e7593d91a29331dfc2f84d5adecc75fc39ecab4632c1b4400a3dd1e1298835bcca70f657164e5b75689b64b7fd1fa275f334f28e1896a26afa1295da81418593bd12814463d9f6e45c36a0e47eb4cd3e5b6af29c41e2a3a5636430155a466e216585af3ba772b61c6014342d914470ec7ac2975be345796c2b81db0422a5fd08e40db1fc2368d2245e4b18b1d0b85c921aaaafd2e341760e29fc613edd39f71254614e2055c3287a517ae2f5b9e386cd1b50a4550696d957cb4900f03ab84f83ff2df44193496793b847f64e9d6db1b3953682bb95edd096eb1e69bbd357c200992ca78050d0cbe180cfaa018e8b6c8fd93d6f4cea42bbb345dbc6f0dfdb5bec73a8a257074e82b881cfa06ef3eb4efeca060c2531359abd0eab8af1e3edfa2025fca464ac9c3fd123f6c24a0d78869485a6f79b60359f141df90a0c745125b131caaffd12000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b218c5d6af1f979ac42bc68d98a5a0d796c6ab01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b4dd66d7c2c7e57f628210187192fb89d4b99dd4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000be807dddb074639cd9fa61b47676c064fc50d62cb1f2c71577def3144fabeb75a8a1c8cb5b51d1d1b4a05eec67988b8685008baa17459ec425dbaebc852f496dc92196cdcc8e6d00c17eb431350c6c50d8b8f05176b90b11b3a3d4feb825ae9702711566df5dbf38e82add4dd1b573b95d2466fa6501ccb81e9d26a352b96150ccbf7b697fd0a419d1d6bf74282782b0b3eb1413c901d6ecf02e8e28000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e2d3a739effcd3a99387d015e260eefac72ebea1956c470ddff48cb49300200b5f83497f3a3ccb3aeb83c5edd9818569038e61d197184f4aa6939ea5e9911e3e98ac6d21e9ae3261a475a27bb1028f140bc2a7c843318afd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ea0a6e3c511bbd10f4519ece37dc24887e11b55db2d4c6283c44a1c7bd503aaba7666e9f0c830e0ff016c1c750a5e48757a713d0836b1cabfd5c281b1de3b77d1c192183ee226379db83cffc681495730c11fdde79ba4c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ef0274e31810c9df02f98fafde0f841f4e66a1cd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e99f701bb14cb7dfb68b90bd3e6d1ca656964630de71beffc7f33f7f08ec99d336ec51ad9fad0ac84ae77ca2e8ad9512acc56e0d7c93f3c2ce7de1b69149a5a400" + extra, err := parseExtra(extraData) + assert.NoError(t, err) + { + var have = extra.ValidatorSize + var want = uint8(21) + if have != want { + t.Fatalf("extra.ValidatorSize mismatch, have %d, want %d", have, want) + } + } + { + var have = common.Bytes2Hex(extra.Validators[14].Address[:]) + var want = "cc8e6d00c17eb431350c6c50d8b8f05176b90b11" + if have != want { + t.Fatalf("extra.Validators[14].Address mismatch, have %s, want %s", have, want) + } + } + { + var have = common.Bytes2Hex(extra.Validators[18].BLSPublicKey[:]) + var want = "b2d4c6283c44a1c7bd503aaba7666e9f0c830e0ff016c1c750a5e48757a713d0836b1cabfd5c281b1de3b77d1c192183" + if have != want { + t.Fatalf("extra.Validators[18].BLSPublicKey mismatch, have %s, want %s", have, want) + } + } + { + var have = extra.TurnLength + var want = uint8(4) + if *have != want { + t.Fatalf("extra.TurnLength mismatch, have %d, want %d", *have, want) + } + } + } + + // case 4, |---Extra Vanity---|---Empty---|---Vote Attestation---|---Extra Seal---| { extraData := "0xd883010205846765746888676f312e32302e35856c696e75780000002995c52af8b5830563efb86089cf168dcf4c5d3cb057926628ad1bf0f03ea67eef1458485578a4f8489afa8a853ecc7af45e2d145c21b70641c4b29f0febd2dd2c61fa1ba174be3fd47f1f5fa2ab9b5c318563d8b70ca58d0d51e79ee32b2fb721649e2cb9d36538361fba11f84c8401d14bb7a0fa67ddb3ba654d6006bf788710032247aa4d1be0707273e696b422b3ff72e9798401d14bbaa01225f505f5a0e1aefadcd2913b7aac9009fe4fb3d1bf57399e0b9dce5947f94280fe6d3647276c4127f437af59eb7c7985b2ae1ebe432619860695cb6106b80cc66c735bc1709afd11f233a2c97409d38ebaf7178aa53e895aea2fe0a229f71ec601" extra, err := parseExtra(extraData) @@ -64,9 +99,9 @@ func TestExtraParse(t *testing.T) { } } - // case 4, |---Extra Vanity---|---Validators Number and Validators Bytes---|---Vote Attestation---|---Extra Seal---| + // case 5, |---Extra Vanity---|---Validators Number and Validators Bytes---|---Vote Attestation---|---Extra Seal---| { - extraData := "0xd883010209846765746888676f312e31392e38856c696e7578000000dc55905c071284214b9b9c85549ab3d2b972df0deef66ac2c98e82934ca974fdcd97f3309de967d3c9c43fa711a8d673af5d75465844bf8969c8d1948d903748ac7b8b1720fa64e50c35552c16704d214347f29fa77f77da6d75d7c752b742ad4855bae330426b823e742da31f816cc83bc16d69a9134be0cfb4a1d17ec34f1b5b32d5c20440b8536b1e88f0f247788386d0ed6c748e03a53160b4b30ed3748cc5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000980a75ecd1309ea12fa2ed87a8744fbfc9b863d589037a9ace3b590165ea1c0c5ac72bf600b7c88c1e435f41932c1132aae1bfa0bb68e46b96ccb12c3415e4d82af717d8a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0b973c2d38487e58fd6e145491b110080fb14ac915a0411fc78f19e09a399ddee0d20c63a75d8f930f1694544ad2dc01bb71b214cb885500844365e95cd9942c7276e7fd8a2750ec6dded3dcdc2f351782310b0eadc077db59abca0f0cd26776e2e7acb9f3bce40b1fa5221fd1561226c6263cc5ff474cf03cceff28abc65c9cbae594f725c80e12d96c9b86c3400e529bfe184056e257c07940bb664636f689e8d2027c834681f8f878b73445261034e946bb2d901b4b878f8b27bb8608c11016739b3f8a19e54ab8c7abacd936cfeba200f3645a98b65adb0dd3692b69ce0b3ae10e7176b9a4b0d83f04065b1042b4bcb646a34b75c550f92fc34b8b2b1db0fa0d3172db23ba92727c80bcd306320d0ff411bf858525fde13bc8e0370f84c8401e9c2e6a0820dc11d63176a0eb1b828bc5376867b275579112b7013358da40317e7bab6e98401e9c2e7a00edc71ce80105a3220a87bea2792fa340d66c59002f02b0a09349ed1ed284070808b972fac2b9077a4dcb6fc37093799a652858016c99142b227500c844fa97ec22e3f9d3b1e982f14bcd999a7453e89ce5ef5c55f1c7f8f74ba904186cd67828200" + extraData := "0xd883010209846765746888676f312e31392e38856c696e7578000000dc55905c071284214b9b9c85549ab3d2b972df0deef66ac2c98e82934ca974fdcd97f3309de967d3c9c43fa711a8d673af5d75465844bf8969c8d1948d903748ac7b8b1720fa64e50c35552c16704d214347f29fa77f77da6d75d7c752b742ad4855bae330426b823e742da31f816cc83bc16d69a9134be0cfb4a1d17ec34f1b5b32d5c20440b8536b1e88f0f247788386d0ed6c748e03a53160b4b30ed3748cc5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000980a75ecd1309ea12fa2ed87a8744fbfc9b863d589037a9ace3b590165ea1c0c5ac72bf600b7c88c1e435f41932c1132aae1bfa0bb68e46b96ccb12c3415e4d82af717d8a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0b973c2d38487e58fd6e145491b110080fb14ac915a0411fc78f19e09a399ddee0d20c63a75d8f930f1694544ad2dc01bb71b214cb885500844365e95cd9942c7276e7fd8a2750ec6dded3dcdc2f351782310b0eadc077db59abca0f0cd26776e2e7acb9f3bce40b1fa5221fd1561226c6263cc5ff474cf03cceff28abc65c9cbae594f725c80e12d96c9b86c3400e529bfe184056e257c07940bb664636f689e8d2027c834681f8f878b73445261034e946bb2d901b4b878f8b27bb8608c11016739b3f8a19e54ab8c7abacd936cfeba200f3645a98b65adb0dd3692b69ce0b3ae10e7176b9a4b0d83f04065b1042b4bcb646a34b75c550f92fc34b8b2b1db0fa0d3172db23ba92727c80bcd306320d0ff411bf858525fde13bc8e0370f84c8401e9c2e6a0820dc11d63176a0eb1b828bc5376867b275579112b7013358da40317e7bab6e98401e9c2e7a00edc71ce80105a3220a87bea2792fa340d66c59002f02b0a09349ed1ed28407080048b972fac2b9077a4dcb6fc37093799a652858016c99142b227500c844fa97ec22e3f9d3b1e982f14bcd999a7453e89ce5ef5c55f1c7f8f74ba904186cd67828200" extra, err := parseExtra(extraData) assert.NoError(t, err) { @@ -105,4 +140,53 @@ func TestExtraParse(t *testing.T) { } } } + + // case 6, |---Extra Vanity---|---Validators Number and Validators Bytes---|---Turn Length---|---Vote Attestation---|---Extra Seal---| + { + extraData := "0xd883010209846765746888676f312e31392e38856c696e7578000000dc55905c071284214b9b9c85549ab3d2b972df0deef66ac2c98e82934ca974fdcd97f3309de967d3c9c43fa711a8d673af5d75465844bf8969c8d1948d903748ac7b8b1720fa64e50c35552c16704d214347f29fa77f77da6d75d7c752b742ad4855bae330426b823e742da31f816cc83bc16d69a9134be0cfb4a1d17ec34f1b5b32d5c20440b8536b1e88f0f247788386d0ed6c748e03a53160b4b30ed3748cc5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000980a75ecd1309ea12fa2ed87a8744fbfc9b863d589037a9ace3b590165ea1c0c5ac72bf600b7c88c1e435f41932c1132aae1bfa0bb68e46b96ccb12c3415e4d82af717d8a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0b973c2d38487e58fd6e145491b110080fb14ac915a0411fc78f19e09a399ddee0d20c63a75d8f930f1694544ad2dc01bb71b214cb885500844365e95cd9942c7276e7fd8a2750ec6dded3dcdc2f351782310b0eadc077db59abca0f0cd26776e2e7acb9f3bce40b1fa5221fd1561226c6263cc5ff474cf03cceff28abc65c9cbae594f725c80e12d96c9b86c3400e529bfe184056e257c07940bb664636f689e8d2027c834681f8f878b73445261034e946bb2d901b4b87804f8b27bb8608c11016739b3f8a19e54ab8c7abacd936cfeba200f3645a98b65adb0dd3692b69ce0b3ae10e7176b9a4b0d83f04065b1042b4bcb646a34b75c550f92fc34b8b2b1db0fa0d3172db23ba92727c80bcd306320d0ff411bf858525fde13bc8e0370f84c8401e9c2e6a0820dc11d63176a0eb1b828bc5376867b275579112b7013358da40317e7bab6e98401e9c2e7a00edc71ce80105a3220a87bea2792fa340d66c59002f02b0a09349ed1ed28407080048b972fac2b9077a4dcb6fc37093799a652858016c99142b227500c844fa97ec22e3f9d3b1e982f14bcd999a7453e89ce5ef5c55f1c7f8f74ba904186cd67828200" + extra, err := parseExtra(extraData) + assert.NoError(t, err) + { + var have = common.Bytes2Hex(extra.Validators[0].Address[:]) + var want = "1284214b9b9c85549ab3d2b972df0deef66ac2c9" + if have != want { + t.Fatalf("extra.Validators[0].Address mismatch, have %s, want %s", have, want) + } + } + { + var have = common.Bytes2Hex(extra.Validators[0].BLSPublicKey[:]) + var want = "8e82934ca974fdcd97f3309de967d3c9c43fa711a8d673af5d75465844bf8969c8d1948d903748ac7b8b1720fa64e50c" + if have != want { + t.Fatalf("extra.Validators[0].BLSPublicKey mismatch, have %s, want %s", have, want) + } + } + { + var have = extra.Validators[0].VoteIncluded + var want = true + if have != want { + t.Fatalf("extra.Validators[0].VoteIncluded mismatch, have %t, want %t", have, want) + } + } + { + var have = common.Bytes2Hex(extra.Data.TargetHash[:]) + var want = "0edc71ce80105a3220a87bea2792fa340d66c59002f02b0a09349ed1ed284070" + if have != want { + t.Fatalf("extra.Data.TargetHash mismatch, have %s, want %s", have, want) + } + } + { + var have = extra.Data.TargetNumber + var want = uint64(32096999) + if have != want { + t.Fatalf("extra.Data.TargetNumber mismatch, have %d, want %d", have, want) + } + } + { + var have = extra.TurnLength + var want = uint8(4) + if *have != want { + t.Fatalf("extra.TurnLength mismatch, have %d, want %d", *have, want) + } + } + } } diff --git a/cmd/extradump/main.go b/cmd/extradump/main.go index bb0673514..49fbc73b8 100644 --- a/cmd/extradump/main.go +++ b/cmd/extradump/main.go @@ -24,7 +24,7 @@ const ( BLSPublicKeyLength = 48 // follow order in extra field - // |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---| + // |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Turn Length (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---| extraVanityLength = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity validatorNumberSize = 1 // Fixed number of extra prefix bytes reserved for validator number after Luban validatorBytesLength = common.AddressLength + types.BLSPublicKeyLength @@ -35,6 +35,7 @@ type Extra struct { ExtraVanity string ValidatorSize uint8 Validators validatorsAscending + TurnLength *uint8 *types.VoteAttestation ExtraSeal []byte } @@ -113,6 +114,15 @@ func parseExtra(hexData string) (*Extra, error) { sort.Sort(extra.Validators) data = data[validatorBytesTotalLength-validatorNumberSize:] dataLength = len(data) + + // parse TurnLength + if dataLength > 0 { + if data[0] != '\xf8' { + extra.TurnLength = &data[0] + data = data[1:] + dataLength = len(data) + } + } } // parse Vote Attestation @@ -148,6 +158,10 @@ func prettyExtra(extra Extra) { } } + if extra.TurnLength != nil { + fmt.Printf("TurnLength : %d\n", *extra.TurnLength) + } + if extra.VoteAttestation != nil { fmt.Printf("Attestation :\n") fmt.Printf("\tVoteAddressSet : %b, %d\n", extra.VoteAddressSet, bitset.From([]uint64{uint64(extra.VoteAddressSet)}).Count()) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 79049ef68..6d4875ccf 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -206,6 +206,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { if ctx.IsSet(utils.OverrideBreatheBlockInterval.Name) { params.BreatheBlockInterval = ctx.Uint64(utils.OverrideBreatheBlockInterval.Name) } + if ctx.IsSet(utils.OverrideFixedTurnLength.Name) { + params.FixedTurnLength = ctx.Uint64(utils.OverrideFixedTurnLength.Name) + } backend, eth := utils.RegisterEthService(stack, &cfg.Eth) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a79ef422f..dd1caaccd 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -78,6 +78,7 @@ var ( utils.OverrideMinBlocksForBlobRequests, utils.OverrideDefaultExtraReserveForBlobRequests, utils.OverrideBreatheBlockInterval, + utils.OverrideFixedTurnLength, utils.EnablePersonal, utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, diff --git a/cmd/jsutils/get_perf.js b/cmd/jsutils/get_perf.js index da0aff0e7..cec035f09 100644 --- a/cmd/jsutils/get_perf.js +++ b/cmd/jsutils/get_perf.js @@ -13,6 +13,8 @@ const main = async () => { let gasUsedTotal = 0; let inturnBlocks = 0; let justifiedBlocks = 0; + let turnLength = await provider.send("parlia_getTurnLength", [ + ethers.toQuantity(program.startNum)]); for (let i = program.startNum; i < program.endNum; i++) { let txCount = await provider.send("eth_getBlockTransactionCountByNumber", [ ethers.toQuantity(i)]); @@ -35,7 +37,7 @@ const main = async () => { } else { console.log("justified unexpected", "BlockNumber =", i,"justifiedNumber",justifiedNumber) } - console.log("BlockNumber =", i, "mod =", i%4, "miner =", header.miner , "difficulty =", difficulty, "txCount =", ethers.toNumber(txCount), "gasUsed", gasUsed, "timestamp", timestamp) + console.log("BlockNumber =", i, "mod =", i%turnLength, "miner =", header.miner , "difficulty =", difficulty, "txCount =", ethers.toNumber(txCount), "gasUsed", gasUsed, "timestamp", timestamp) } let blockCount = program.endNum - program.startNum diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 62b3b2f39..b573b48bb 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -339,6 +339,12 @@ var ( Value: params.BreatheBlockInterval, Category: flags.EthCategory, } + OverrideFixedTurnLength = &cli.Uint64Flag{ + Name: "override.fixedturnlength", + Usage: "It use fixed or random values for turn length instead of reading from the contract, only for testing purpose", + Value: params.FixedTurnLength, + Category: flags.EthCategory, + } SyncModeFlag = &flags.TextMarshalerFlag{ Name: "syncmode", Usage: `Blockchain sync mode ("snap" or "full")`, diff --git a/consensus/parlia/abi.go b/consensus/parlia/abi.go index 0d9ab54cd..804dcdf7e 100644 --- a/consensus/parlia/abi.go +++ b/consensus/parlia/abi.go @@ -2306,6 +2306,19 @@ const validatorSetABI = ` ], "stateMutability": "view" }, + { + "inputs": [], + "name": "getTurnLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "type": "function", "name": "getValidators", diff --git a/consensus/parlia/api.go b/consensus/parlia/api.go index 0527a1c6a..5929e94d1 100644 --- a/consensus/parlia/api.go +++ b/consensus/parlia/api.go @@ -88,6 +88,19 @@ func (api *API) GetJustifiedNumber(number *rpc.BlockNumber) (uint64, error) { return snap.Attestation.TargetNumber, nil } +func (api *API) GetTurnLength(number *rpc.BlockNumber) (uint8, error) { + header := api.getHeader(number) + // Ensure we have an actually valid block and return the validators from its snapshot + if header == nil { + return 0, errUnknownBlock + } + snap, err := api.parlia.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil || snap.TurnLength == 0 { + return 0, err + } + return snap.TurnLength, nil +} + func (api *API) GetFinalizedNumber(number *rpc.BlockNumber) (uint64, error) { header := api.getHeader(number) // Ensure we have an actually valid block and return the validators from its snapshot diff --git a/consensus/parlia/bohrFork.go b/consensus/parlia/bohrFork.go new file mode 100644 index 000000000..7cfe3c2f1 --- /dev/null +++ b/consensus/parlia/bohrFork.go @@ -0,0 +1,91 @@ +package parlia + +import ( + "context" + "errors" + "math/big" + mrand "math/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/systemcontracts" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +func (p *Parlia) getTurnLength(chain consensus.ChainHeaderReader, header *types.Header) (*uint8, error) { + parent := chain.GetHeaderByHash(header.ParentHash) + if parent == nil { + return nil, errors.New("parent not found") + } + + var turnLength uint8 + if p.chainConfig.IsBohr(parent.Number, parent.Time) { + turnLengthFromContract, err := p.getTurnLengthFromContract(parent) + if err != nil { + return nil, err + } + if turnLengthFromContract == nil { + return nil, errors.New("unexpected error when getTurnLengthFromContract") + } + turnLength = uint8(turnLengthFromContract.Int64()) + } else { + turnLength = defaultTurnLength + } + log.Debug("getTurnLength", "turnLength", turnLength) + + return &turnLength, nil +} + +func (p *Parlia) getTurnLengthFromContract(header *types.Header) (turnLength *big.Int, err error) { + // mock to get turnLength from the contract + if params.FixedTurnLength >= 1 && params.FixedTurnLength <= 9 { + if params.FixedTurnLength == 2 { + return p.getRandTurnLength(header) + } + return big.NewInt(int64(params.FixedTurnLength)), nil + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + method := "getTurnLength" + toAddress := common.HexToAddress(systemcontracts.ValidatorContract) + gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) + + data, err := p.validatorSetABI.Pack(method) + if err != nil { + log.Error("Unable to pack tx for getTurnLength", "error", err) + return nil, err + } + msgData := (hexutil.Bytes)(data) + + blockNr := rpc.BlockNumberOrHashWithHash(header.Hash(), false) + result, err := p.ethAPI.Call(ctx, ethapi.TransactionArgs{ + Gas: &gas, + To: &toAddress, + Data: &msgData, + }, &blockNr, nil, nil) + if err != nil { + return nil, err + } + + if err := p.validatorSetABI.UnpackIntoInterface(&turnLength, method, result); err != nil { + return nil, err + } + + return turnLength, nil +} + +// getRandTurnLength returns a random valid value, used to test switching turn length +func (p *Parlia) getRandTurnLength(header *types.Header) (turnLength *big.Int, err error) { + turnLengths := [8]uint8{1, 3, 4, 5, 6, 7, 8, 9} + r := mrand.New(mrand.NewSource(int64(header.Time))) + lengthIndex := int(r.Int31n(int32(len(turnLengths)))) + return big.NewInt(int64(turnLengths[lengthIndex])), nil +} diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index d008678e0..014297785 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -55,10 +55,12 @@ const ( checkpointInterval = 1024 // Number of blocks after which to save the snapshot to the database defaultEpochLength = uint64(100) // Default number of blocks of checkpoint to update validatorSet from contract + defaultTurnLength = uint8(1) // Default consecutive number of blocks a validator receives priority for block production extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal nextForkHashSize = 4 // Fixed number of extra-data suffix bytes reserved for nextForkHash. + turnLengthSize = 1 // Fixed number of extra-data suffix bytes reserved for turnLength validatorBytesLengthBeforeLuban = common.AddressLength validatorBytesLength = common.AddressLength + types.BLSPublicKeyLength @@ -127,6 +129,10 @@ var ( // invalid list of validators (i.e. non divisible by 20 bytes). errInvalidSpanValidators = errors.New("invalid validator list on sprint end block") + // errInvalidTurnLength is returned if a block contains an + // invalid length of turn (i.e. no data left after parsing validators). + errInvalidTurnLength = errors.New("invalid turnLength") + // errInvalidMixDigest is returned if a block's mix digest is non-zero. errInvalidMixDigest = errors.New("non-zero mix digest") @@ -137,6 +143,10 @@ var ( // list of validators different than the one the local node calculated. errMismatchingEpochValidators = errors.New("mismatching validator list on epoch block") + // errMismatchingEpochTurnLength is returned if a sprint block contains a + // turn length different than the one the local node calculated. + errMismatchingEpochTurnLength = errors.New("mismatching turn length on epoch block") + // errInvalidDifficulty is returned if the difficulty of a block is missing. errInvalidDifficulty = errors.New("invalid difficulty") @@ -370,6 +380,7 @@ func (p *Parlia) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*typ // On luban fork, we introduce vote attestation into the header's extra field, so extra format is different from before. // Before luban fork: |---Extra Vanity---|---Validators Bytes (or Empty)---|---Extra Seal---| // After luban fork: |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---| +// After bohr fork: |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Turn Length (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---| func getValidatorBytesFromHeader(header *types.Header, chainConfig *params.ChainConfig, parliaConfig *params.ParliaConfig) []byte { if len(header.Extra) <= extraVanity+extraSeal { return nil @@ -386,11 +397,15 @@ func getValidatorBytesFromHeader(header *types.Header, chainConfig *params.Chain return nil } num := int(header.Extra[extraVanity]) - if num == 0 || len(header.Extra) <= extraVanity+extraSeal+num*validatorBytesLength { - return nil - } start := extraVanity + validatorNumberSize end := start + num*validatorBytesLength + extraMinLen := end + extraSeal + if chainConfig.IsBohr(header.Number, header.Time) { + extraMinLen += turnLengthSize + } + if num == 0 || len(header.Extra) < extraMinLen { + return nil + } return header.Extra[start:end] } @@ -409,11 +424,14 @@ func getVoteAttestationFromHeader(header *types.Header, chainConfig *params.Chai attestationBytes = header.Extra[extraVanity : len(header.Extra)-extraSeal] } else { num := int(header.Extra[extraVanity]) - if len(header.Extra) <= extraVanity+extraSeal+validatorNumberSize+num*validatorBytesLength { + start := extraVanity + validatorNumberSize + num*validatorBytesLength + if chainConfig.IsBohr(header.Number, header.Time) { + start += turnLengthSize + } + end := len(header.Extra) - extraSeal + if end <= start { return nil, nil } - start := extraVanity + validatorNumberSize + num*validatorBytesLength - end := len(header.Extra) - extraSeal attestationBytes = header.Extra[start:end] } @@ -896,6 +914,24 @@ func (p *Parlia) prepareValidators(header *types.Header) error { return nil } +func (p *Parlia) prepareTurnLength(chain consensus.ChainHeaderReader, header *types.Header) error { + if header.Number.Uint64()%p.config.Epoch != 0 || + !p.chainConfig.IsBohr(header.Number, header.Time) { + return nil + } + + turnLength, err := p.getTurnLength(chain, header) + if err != nil { + return err + } + + if turnLength != nil { + header.Extra = append(header.Extra, *turnLength) + } + + return nil +} + func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, header *types.Header) error { if !p.chainConfig.IsLuban(header.Number) || header.Number.Uint64() < 2 { return nil @@ -1027,6 +1063,9 @@ func (p *Parlia) Prepare(chain consensus.ChainHeaderReader, header *types.Header return err } + if err := p.prepareTurnLength(chain, header); err != nil { + return err + } // add extra seal space header.Extra = append(header.Extra, make([]byte, extraSeal)...) @@ -1077,6 +1116,30 @@ func (p *Parlia) verifyValidators(header *types.Header) error { return nil } +func (p *Parlia) verifyTurnLength(chain consensus.ChainHeaderReader, header *types.Header) error { + if header.Number.Uint64()%p.config.Epoch != 0 || + !p.chainConfig.IsBohr(header.Number, header.Time) { + return nil + } + + turnLengthFromHeader, err := parseTurnLength(header, p.chainConfig, p.config) + if err != nil { + return err + } + if turnLengthFromHeader != nil { + turnLength, err := p.getTurnLength(chain, header) + if err != nil { + return err + } + if turnLength != nil && *turnLength == *turnLengthFromHeader { + log.Debug("verifyTurnLength", "turnLength", *turnLength) + return nil + } + } + + return errMismatchingEpochTurnLength +} + func (p *Parlia) distributeFinalityReward(chain consensus.ChainHeaderReader, state *state.StateDB, header *types.Header, cx core.ChainContext, txs *[]*types.Transaction, receipts *[]*types.Receipt, systemTxs *[]*types.Transaction, usedGas *uint64, mining bool) error { @@ -1171,6 +1234,10 @@ func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Heade return err } + if err := p.verifyTurnLength(chain, header); err != nil { + return err + } + cx := chainContext{Chain: chain, parlia: p} parent := chain.GetHeaderByHash(header.ParentHash) @@ -1197,7 +1264,7 @@ func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Heade } } if header.Difficulty.Cmp(diffInTurn) != 0 { - spoiledVal := snap.supposeValidator() + spoiledVal := snap.inturnValidator() signedRecently := false if p.chainConfig.IsPlato(header.Number) { signedRecently = snap.SignRecently(spoiledVal) @@ -1288,7 +1355,7 @@ func (p *Parlia) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * if err != nil { return nil, nil, err } - spoiledVal := snap.supposeValidator() + spoiledVal := snap.inturnValidator() signedRecently := false if p.chainConfig.IsPlato(header.Number) { signedRecently = snap.SignRecently(spoiledVal) @@ -1441,10 +1508,19 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header, leftOv delay = delay - *leftOver } - // The blocking time should be no more than half of period - half := time.Duration(p.config.Period) * time.Second / 2 - if delay > half { - delay = half + // The blocking time should be no more than half of period when snap.TurnLength == 1 + timeForMining := time.Duration(p.config.Period) * time.Second / 2 + if snap.TurnLength > 1 { + if snap.lastBlockInOneTurn(header.Number.Uint64()) { + // To ensure that the next validator produces and broadcasts blocks in a timely manner, + // set the value of timeForMining to a small amount + timeForMining = 500 * time.Millisecond + } else { + timeForMining = time.Duration(p.config.Period) * time.Second * 2 / 3 + } + } + if delay > timeForMining { + delay = timeForMining } return &delay } @@ -1936,31 +2012,24 @@ func (p *Parlia) GetFinalizedHeader(chain consensus.ChainHeaderReader, header *t // =========================== utility function ========================== func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Address) uint64 { if snap.inturn(val) { + log.Debug("backOffTime", "blockNumber", header.Number, "in turn validator", val) return 0 } else { delay := initialBackOffTime validators := snap.validators() if p.chainConfig.IsPlanck(header.Number) { - // reverse the key/value of snap.Recents to get recentsMap - recentsMap := make(map[common.Address]uint64, len(snap.Recents)) - bound := uint64(0) - if n, limit := header.Number.Uint64(), uint64(len(validators)/2+1); n > limit { - bound = n - limit - } - for seen, recent := range snap.Recents { - if seen <= bound { - continue - } - recentsMap[recent] = seen + counts := snap.countRecents() + for addr, seenTimes := range counts { + log.Debug("backOffTime", "blockNumber", header.Number, "validator", addr, "seenTimes", seenTimes) } // The backOffTime does not matter when a validator has signed recently. - if _, ok := recentsMap[val]; ok { + if snap.signRecentlyByCounts(val, counts) { return 0 } - inTurnAddr := validators[(snap.Number+1)%uint64(len(validators))] - if _, ok := recentsMap[inTurnAddr]; ok { + inTurnAddr := snap.inturnValidator() + if snap.signRecentlyByCounts(inTurnAddr, counts) { log.Debug("in turn validator has recently signed, skip initialBackOffTime", "inTurnAddr", inTurnAddr) delay = 0 @@ -1969,7 +2038,7 @@ func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Ad // Exclude the recently signed validators temp := make([]common.Address, 0, len(validators)) for _, addr := range validators { - if _, ok := recentsMap[addr]; ok { + if snap.signRecentlyByCounts(addr, counts) { continue } temp = append(temp, addr) @@ -1989,7 +2058,11 @@ func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Ad return 0 } - s := rand.NewSource(int64(snap.Number)) + randSeed := snap.Number + if p.chainConfig.IsBohr(header.Number, header.Time) { + randSeed = header.Number.Uint64() / uint64(snap.TurnLength) + } + s := rand.NewSource(int64(randSeed)) r := rand.New(s) n := len(validators) backOffSteps := make([]uint64, 0, n) diff --git a/consensus/parlia/parlia_test.go b/consensus/parlia/parlia_test.go index b03f74753..afd30b86e 100644 --- a/consensus/parlia/parlia_test.go +++ b/consensus/parlia/parlia_test.go @@ -22,22 +22,44 @@ func TestImpactOfValidatorOutOfService(t *testing.T) { testCases := []struct { totalValidators int downValidators int + turnLength int }{ - {3, 1}, - {5, 2}, - {10, 1}, - {10, 4}, - {21, 1}, - {21, 3}, - {21, 5}, - {21, 10}, + {3, 1, 1}, + {5, 2, 1}, + {10, 1, 2}, + {10, 4, 2}, + {21, 1, 3}, + {21, 3, 3}, + {21, 5, 4}, + {21, 10, 5}, } for _, tc := range testCases { - simulateValidatorOutOfService(tc.totalValidators, tc.downValidators) + simulateValidatorOutOfService(tc.totalValidators, tc.downValidators, tc.turnLength) } } -func simulateValidatorOutOfService(totalValidators int, downValidators int) { +// refer Snapshot.SignRecently +func signRecently(idx int, recents map[uint64]int, turnLength int) bool { + recentSignTimes := 0 + for _, signIdx := range recents { + if signIdx == idx { + recentSignTimes += 1 + } + } + return recentSignTimes >= turnLength +} + +// refer Snapshot.minerHistoryCheckLen +func minerHistoryCheckLen(totalValidators int, turnLength int) uint64 { + return uint64(totalValidators/2+1)*uint64(turnLength) - 1 +} + +// refer Snapshot.inturnValidator +func inturnValidator(totalValidators int, turnLength int, height int) int { + return height / turnLength % totalValidators +} + +func simulateValidatorOutOfService(totalValidators int, downValidators int, turnLength int) { downBlocks := 10000 recoverBlocks := 10000 recents := make(map[uint64]int) @@ -55,12 +77,7 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) { delete(validators, down[i]) } isRecentSign := func(idx int) bool { - for _, signIdx := range recents { - if signIdx == idx { - return true - } - } - return false + return signRecently(idx, recents, turnLength) } isInService := func(idx int) bool { return validators[idx] @@ -68,10 +85,10 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) { downDelay := uint64(0) for h := 1; h <= downBlocks; h++ { - if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit { + if limit := minerHistoryCheckLen(totalValidators, turnLength) + 1; uint64(h) >= limit { delete(recents, uint64(h)-limit) } - proposer := h % totalValidators + proposer := inturnValidator(totalValidators, turnLength, h) if !isInService(proposer) || isRecentSign(proposer) { candidates := make(map[int]bool, totalValidators/2) for v := range validators { @@ -99,10 +116,10 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) { recoverDelay := uint64(0) lastseen := downBlocks for h := downBlocks + 1; h <= downBlocks+recoverBlocks; h++ { - if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit { + if limit := minerHistoryCheckLen(totalValidators, turnLength) + 1; uint64(h) >= limit { delete(recents, uint64(h)-limit) } - proposer := h % totalValidators + proposer := inturnValidator(totalValidators, turnLength, h) if !isInService(proposer) || isRecentSign(proposer) { lastseen = h candidates := make(map[int]bool, totalValidators/2) diff --git a/consensus/parlia/snapshot.go b/consensus/parlia/snapshot.go index 131543ab8..339736771 100644 --- a/consensus/parlia/snapshot.go +++ b/consensus/parlia/snapshot.go @@ -44,6 +44,7 @@ type Snapshot struct { Number uint64 `json:"number"` // Block number where the snapshot was created Hash common.Hash `json:"hash"` // Block hash where the snapshot was created + TurnLength uint8 `json:"turn_length"` // Length of `turn`, meaning the consecutive number of blocks a validator receives priority for block production Validators map[common.Address]*ValidatorInfo `json:"validators"` // Set of authorized validators at this moment Recents map[uint64]common.Address `json:"recents"` // Set of recent validators for spam protections RecentForkHashes map[uint64]string `json:"recent_fork_hashes"` // Set of recent forkHash @@ -73,6 +74,7 @@ func newSnapshot( sigCache: sigCache, Number: number, Hash: hash, + TurnLength: defaultTurnLength, Recents: make(map[uint64]common.Address), RecentForkHashes: make(map[uint64]string), Validators: make(map[common.Address]*ValidatorInfo), @@ -115,6 +117,10 @@ func loadSnapshot(config *params.ParliaConfig, sigCache *lru.ARCCache, db ethdb. if err := json.Unmarshal(blob, snap); err != nil { return nil, err } + if snap.TurnLength == 0 { // no TurnLength field in old snapshots + snap.TurnLength = defaultTurnLength + } + snap.config = config snap.sigCache = sigCache snap.ethAPI = ethAPI @@ -139,6 +145,7 @@ func (s *Snapshot) copy() *Snapshot { sigCache: s.sigCache, Number: s.Number, Hash: s.Hash, + TurnLength: s.TurnLength, Validators: make(map[common.Address]*ValidatorInfo), Recents: make(map[uint64]common.Address), RecentForkHashes: make(map[uint64]string), @@ -211,17 +218,45 @@ func (s *Snapshot) updateAttestation(header *types.Header, chainConfig *params.C } } -func (s *Snapshot) SignRecently(validator common.Address) bool { - for seen, recent := range s.Recents { - if recent == validator { - if limit := uint64(len(s.Validators)/2 + 1); s.Number+1 < limit || seen > s.Number+1-limit { - return true - } - } +func (s *Snapshot) versionHistoryCheckLen() uint64 { + return uint64(len(s.Validators)) * uint64(s.TurnLength) +} + +func (s *Snapshot) minerHistoryCheckLen() uint64 { + return (uint64(len(s.Validators))/2+1)*uint64(s.TurnLength) - 1 +} + +func (s *Snapshot) countRecents() map[common.Address]uint8 { + leftHistoryBound := uint64(0) // the bound is excluded + checkHistoryLength := s.minerHistoryCheckLen() + if s.Number > checkHistoryLength { + leftHistoryBound = s.Number - checkHistoryLength } + counts := make(map[common.Address]uint8, len(s.Validators)) + for seen, recent := range s.Recents { + if seen <= leftHistoryBound || recent == (common.Address{}) /*when seen == `epochKey`*/ { + continue + } + counts[recent] += 1 + } + return counts +} + +func (s *Snapshot) signRecentlyByCounts(validator common.Address, counts map[common.Address]uint8) bool { + if seenTimes, ok := counts[validator]; ok && seenTimes >= s.TurnLength { + if seenTimes > s.TurnLength { + log.Warn("produce more blocks than expected!", "validator", validator, "seenTimes", seenTimes) + } + return true + } + return false } +func (s *Snapshot) SignRecently(validator common.Address) bool { + return s.signRecentlyByCounts(validator, s.countRecents()) +} + func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderReader, parents []*types.Header, chainConfig *params.ChainConfig) (*Snapshot, error) { // Allow passing in no headers for cleaner code if len(headers) == 0 { @@ -248,10 +283,10 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea for _, header := range headers { number := header.Number.Uint64() // Delete the oldest validator from the recent list to allow it signing again - if limit := uint64(len(snap.Validators)/2 + 1); number >= limit { + if limit := snap.minerHistoryCheckLen() + 1; number >= limit { delete(snap.Recents, number-limit) } - if limit := uint64(len(snap.Validators)); number >= limit { + if limit := snap.versionHistoryCheckLen(); number >= limit { delete(snap.RecentForkHashes, number-limit) } // Resolve the authorization key and check against signers @@ -262,16 +297,22 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea if _, ok := snap.Validators[validator]; !ok { return nil, errUnauthorizedValidator(validator.String()) } - for _, recent := range snap.Recents { - if recent == validator { + if chainConfig.IsBohr(header.Number, header.Time) { + if snap.SignRecently(validator) { return nil, errRecentlySigned } + } else { + for _, recent := range snap.Recents { + if recent == validator { + return nil, errRecentlySigned + } + } } snap.Recents[number] = validator snap.RecentForkHashes[number] = hex.EncodeToString(header.Extra[extraVanity-nextForkHashSize : extraVanity]) snap.updateAttestation(header, chainConfig, s.config) // change validator set - if number > 0 && number%s.config.Epoch == uint64(len(snap.Validators)/2) { + if number > 0 && number%s.config.Epoch == snap.minerHistoryCheckLen() { epochKey := math.MaxUint64 - header.Number.Uint64()/s.config.Epoch // impossible used as a block number if chainConfig.IsBohr(header.Number, header.Time) { // after switching the validator set, snap.Validators may become larger, @@ -281,11 +322,22 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea } } - checkpointHeader := FindAncientHeader(header, uint64(len(snap.Validators)/2), chain, parents) + checkpointHeader := FindAncientHeader(header, snap.minerHistoryCheckLen(), chain, parents) if checkpointHeader == nil { return nil, consensus.ErrUnknownAncestor } + oldVersionsLen := snap.versionHistoryCheckLen() + // get turnLength from headers and use that for new turnLength + turnLength, err := parseTurnLength(checkpointHeader, chainConfig, s.config) + if err != nil { + return nil, err + } + if turnLength != nil { + snap.TurnLength = *turnLength + log.Debug("validator set switch", "turnLength", *turnLength) + } + // get validators from headers and use that for new validator set newValArr, voteAddrs, err := parseValidators(checkpointHeader, chainConfig, s.config) if err != nil { @@ -315,13 +367,6 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea } } } - oldLimit := len(snap.Validators) - newLimit := len(newVals) - if newLimit < oldLimit { - for i := 0; i < oldLimit-newLimit; i++ { - delete(snap.RecentForkHashes, number-uint64(newLimit)-uint64(i)) - } - } snap.Validators = newVals if chainConfig.IsLuban(header.Number) { validators := snap.validators() @@ -329,6 +374,9 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea snap.Validators[val].Index = idx + 1 // offset by 1 } } + for i := snap.versionHistoryCheckLen(); i < oldVersionsLen; i++ { + delete(snap.RecentForkHashes, number-i) + } } } snap.Number += uint64(len(headers)) @@ -346,17 +394,20 @@ func (s *Snapshot) validators() []common.Address { return validators } -// inturn returns if a validator at a given block height is in-turn or not. -func (s *Snapshot) inturn(validator common.Address) bool { - validators := s.validators() - offset := (s.Number + 1) % uint64(len(validators)) - return validators[offset] == validator +// lastBlockInOneTurn returns if the block at height `blockNumber` is the last block in current turn. +func (s *Snapshot) lastBlockInOneTurn(blockNumber uint64) bool { + return (blockNumber+1)%uint64(s.TurnLength) == 0 } -// inturnValidator returns the validator at a given block height. +// inturn returns if a validator at a given block height is in-turn or not. +func (s *Snapshot) inturn(validator common.Address) bool { + return s.inturnValidator() == validator +} + +// inturnValidator returns the validator for the following block height. func (s *Snapshot) inturnValidator() common.Address { validators := s.validators() - offset := (s.Number + 1) % uint64(len(validators)) + offset := (s.Number + 1) / uint64(s.TurnLength) % uint64(len(validators)) return validators[offset] } @@ -394,12 +445,6 @@ func (s *Snapshot) indexOfVal(validator common.Address) int { return -1 } -func (s *Snapshot) supposeValidator() common.Address { - validators := s.validators() - index := (s.Number + 1) % uint64(len(validators)) - return validators[index] -} - func parseValidators(header *types.Header, chainConfig *params.ChainConfig, parliaConfig *params.ParliaConfig) ([]common.Address, []types.BLSPublicKey, error) { validatorsBytes := getValidatorBytesFromHeader(header, chainConfig, parliaConfig) if len(validatorsBytes) == 0 { @@ -425,6 +470,24 @@ func parseValidators(header *types.Header, chainConfig *params.ChainConfig, parl return cnsAddrs, voteAddrs, nil } +func parseTurnLength(header *types.Header, chainConfig *params.ChainConfig, parliaConfig *params.ParliaConfig) (*uint8, error) { + if header.Number.Uint64()%parliaConfig.Epoch != 0 || + !chainConfig.IsBohr(header.Number, header.Time) { + return nil, nil + } + + if len(header.Extra) <= extraVanity+extraSeal { + return nil, errInvalidSpanValidators + } + num := int(header.Extra[extraVanity]) + pos := extraVanity + validatorNumberSize + num*validatorBytesLength + if len(header.Extra) <= pos { + return nil, errInvalidTurnLength + } + turnLength := header.Extra[pos] + return &turnLength, nil +} + func FindAncientHeader(header *types.Header, ite uint64, chain consensus.ChainHeaderReader, candidateParents []*types.Header) *types.Header { ancient := header for i := uint64(1); i <= ite; i++ { diff --git a/core/systemcontracts/bohr/chapel/ValidatorContract b/core/systemcontracts/bohr/chapel/ValidatorContract new file mode 100644 index 000000000..e69de29bb diff --git a/core/systemcontracts/bohr/mainnet/ValidatorContract b/core/systemcontracts/bohr/mainnet/ValidatorContract new file mode 100644 index 000000000..e69de29bb diff --git a/core/systemcontracts/bohr/types.go b/core/systemcontracts/bohr/types.go new file mode 100644 index 000000000..77e3e836d --- /dev/null +++ b/core/systemcontracts/bohr/types.go @@ -0,0 +1,15 @@ +package bohr + +import _ "embed" + +// contract codes for Mainnet upgrade +var ( + //go:embed mainnet/ValidatorContract + MainnetValidatorContract string +) + +// contract codes for Chapel upgrade +var ( + //go:embed chapel/ValidatorContract + ChapelValidatorContract string +) diff --git a/core/systemcontracts/upgrade.go b/core/systemcontracts/upgrade.go index 341fe7e51..c2e73855a 100644 --- a/core/systemcontracts/upgrade.go +++ b/core/systemcontracts/upgrade.go @@ -78,6 +78,8 @@ var ( feynmanFixUpgrade = make(map[string]*Upgrade) haberFixUpgrade = make(map[string]*Upgrade) + + bohrUpgrade = make(map[string]*Upgrade) ) func init() { @@ -816,6 +818,10 @@ func UpgradeBuildInSystemContract(config *params.ChainConfig, blockNumber *big.I applySystemContractUpgrade(haberFixUpgrade[network], blockNumber, statedb, logger) } + if config.IsOnBohr(blockNumber, lastBlockTime, blockTime) { + applySystemContractUpgrade(bohrUpgrade[network], blockNumber, statedb, logger) + } + /* apply other upgrades */ diff --git a/core/vote/vote_manager.go b/core/vote/vote_manager.go index ba422e577..891785482 100644 --- a/core/vote/vote_manager.go +++ b/core/vote/vote_manager.go @@ -3,6 +3,7 @@ package vote import ( "bytes" "fmt" + "math/big" "sync" "time" @@ -19,7 +20,13 @@ import ( const blocksNumberSinceMining = 5 // the number of blocks need to wait before voting, counting from the validator begin to mine +var diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures var votesManagerCounter = metrics.NewRegisteredCounter("votesManager/local", nil) +var notJustified = metrics.NewRegisteredCounter("votesManager/notJustified", nil) +var inTurnJustified = metrics.NewRegisteredCounter("votesManager/inTurnJustified", nil) +var notInTurnJustified = metrics.NewRegisteredCounter("votesManager/notInTurnJustified", nil) +var continuousJustified = metrics.NewRegisteredCounter("votesManager/continuousJustified", nil) +var notContinuousJustified = metrics.NewRegisteredCounter("votesManager/notContinuousJustified", nil) // Backend wraps all methods required for voting. type Backend interface { @@ -155,7 +162,7 @@ func (voteManager *VoteManager) loop() { func(bLSPublicKey *types.BLSPublicKey) bool { return bytes.Equal(voteManager.signer.PubKey[:], bLSPublicKey[:]) }) { - log.Debug("cur validator is not within the validatorSet at curHead") + log.Debug("local validator with voteKey is not within the validatorSet at curHead") continue } @@ -202,6 +209,36 @@ func (voteManager *VoteManager) loop() { voteManager.pool.PutVote(voteMessage) votesManagerCounter.Inc(1) } + + // check the latest justified block, which indicating the stability of the network + curJustifiedNumber, _, err := voteManager.engine.GetJustifiedNumberAndHash(voteManager.chain, []*types.Header{curHead}) + if err == nil && curJustifiedNumber != 0 { + if curJustifiedNumber+1 != curHead.Number.Uint64() { + log.Debug("not justified", "blockNumber", curHead.Number.Uint64()-1) + notJustified.Inc(1) + } else { + parent := voteManager.chain.GetHeaderByHash(curHead.ParentHash) + if parent != nil { + if parent.Difficulty.Cmp(diffInTurn) == 0 { + inTurnJustified.Inc(1) + } else { + log.Debug("not in turn block justified", "blockNumber", parent.Number.Int64(), "blockHash", parent.Hash()) + notInTurnJustified.Inc(1) + } + + lastJustifiedNumber, _, err := voteManager.engine.GetJustifiedNumberAndHash(voteManager.chain, []*types.Header{parent}) + if err == nil { + if lastJustifiedNumber == 0 || lastJustifiedNumber+1 == curJustifiedNumber { + continuousJustified.Inc(1) + } else { + log.Debug("not continuous block justified", "lastJustified", lastJustifiedNumber, "curJustified", curJustifiedNumber) + notContinuousJustified.Inc(1) + } + } + } + } + } + case event := <-voteManager.syncVoteCh: voteMessage := event.Vote if voteManager.eth.IsMining() || !bytes.Equal(voteManager.signer.PubKey[:], voteMessage.VoteAddress[:]) { diff --git a/eth/api_backend.go b/eth/api_backend.go index ae698ade3..d72711929 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/parlia" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" @@ -440,6 +441,16 @@ func (b *EthAPIBackend) Engine() consensus.Engine { return b.eth.engine } +func (b *EthAPIBackend) CurrentTurnLength() (turnLength uint8, err error) { + if p, ok := b.eth.engine.(*parlia.Parlia); ok { + service := p.APIs(b.Chain())[0].Service + currentHead := rpc.LatestBlockNumber + return service.(*parlia.API).GetTurnLength(¤tHead) + } + + return 1, nil +} + func (b *EthAPIBackend) CurrentHeader() *types.Header { return b.eth.blockchain.CurrentHeader() } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e31cee15e..1c81ba544 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -870,7 +870,10 @@ func (s *BlockChainAPI) GetFinalizedHeader(ctx context.Context, probabilisticFin return nil, fmt.Errorf("%d out of range [2,21]", probabilisticFinalized) } - var err error + currentTurnLength, err := s.b.CurrentTurnLength() + if err != nil { // impossible + return nil, err + } fastFinalizedHeader, err := s.b.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) if err != nil { // impossible return nil, err @@ -879,7 +882,7 @@ func (s *BlockChainAPI) GetFinalizedHeader(ctx context.Context, probabilisticFin if err != nil { // impossible return nil, err } - finalizedBlockNumber := max(fastFinalizedHeader.Number.Int64(), latestHeader.Number.Int64()-probabilisticFinalized) + finalizedBlockNumber := max(fastFinalizedHeader.Number.Int64(), latestHeader.Number.Int64()-probabilisticFinalized*int64(currentTurnLength)) return s.GetHeaderByNumber(ctx, rpc.BlockNumber(finalizedBlockNumber)) } @@ -894,7 +897,10 @@ func (s *BlockChainAPI) GetFinalizedBlock(ctx context.Context, probabilisticFina return nil, fmt.Errorf("%d out of range [2,21]", probabilisticFinalized) } - var err error + currentTurnLength, err := s.b.CurrentTurnLength() + if err != nil { // impossible + return nil, err + } fastFinalizedHeader, err := s.b.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) if err != nil { // impossible return nil, err @@ -903,7 +909,7 @@ func (s *BlockChainAPI) GetFinalizedBlock(ctx context.Context, probabilisticFina if err != nil { // impossible return nil, err } - finalizedBlockNumber := max(fastFinalizedHeader.Number.Int64(), latestHeader.Number.Int64()-probabilisticFinalized) + finalizedBlockNumber := max(fastFinalizedHeader.Number.Int64(), latestHeader.Number.Int64()-probabilisticFinalized*int64(currentTurnLength)) return s.GetBlockByNumber(ctx, rpc.BlockNumber(finalizedBlockNumber), fullTx) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 8764f51fb..ac467c352 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -631,8 +631,9 @@ func (b testBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transactio func (b testBackend) SubscribeNewTxsEvent(events chan<- core.NewTxsEvent) event.Subscription { panic("implement me") } -func (b testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() } -func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() } +func (b testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() } +func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() } +func (b testBackend) CurrentTurnLength() (uint8, error) { return 1, nil } func (b testBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { panic("implement me") } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index fd778a3f6..79492cda8 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -89,6 +89,8 @@ type Backend interface { ChainConfig() *params.ChainConfig Engine() consensus.Engine + // CurrentTurnLength return the turnLength at the latest block + CurrentTurnLength() (uint8, error) // This is copied from filters.Backend // eth/filters needs to be initialized from this backend type, so methods needed by diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 07a1c57a4..17bbc49c5 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -416,6 +416,8 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) func (b *backendMock) Engine() consensus.Engine { return nil } +func (b *backendMock) CurrentTurnLength() (uint8, error) { return 1, nil } + func (b *backendMock) MevRunning() bool { return false } func (b *backendMock) HasBuilder(builder common.Address) bool { return false } func (b *backendMock) MevParams() *types.MevParams { diff --git a/params/protocol_params.go b/params/protocol_params.go index 2ecb92bc6..65b2d942c 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -192,6 +192,11 @@ var ( MinBlocksForBlobRequests uint64 = 524288 // it keeps blob data available for ~18.2 days in local, ref: https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP-336.md#51-parameters. DefaultExtraReserveForBlobRequests uint64 = 1 * (24 * 3600) / 3 // it adds more time for expired blobs for some request cases, like expiry blob when remote peer is syncing, default 1 day. BreatheBlockInterval uint64 = 86400 // Controls the interval for updateValidatorSetV2 + // used for testing: + // [1,9] except 2 --> used as turn length directly + // 2 --> use random values to test switching turn length + // 0 and other values --> get turn length from contract + FixedTurnLength uint64 = 0 ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations