diff --git a/CHANGELOG.md b/CHANGELOG.md index 5940929c4c..8d67cb0026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ # Changelog +## v1.2.10 +FEATURE +* [\#1780](https://github.com/bnb-chain/bsc/pull/1780) log: reduce logs when receiving too much votes from a peer +* [\#1788](https://github.com/bnb-chain/bsc/pull/1788) metrics: add txpool config into metrics server +* [\#1789](https://github.com/bnb-chain/bsc/pull/1789) rpc: add GetFinalizedHeader/Block to simplify using the fast finality feature +* [\#1791](https://github.com/bnb-chain/bsc/pull/1791) finality: add more check to ensure result of assembleVoteAttestation + +BUGFIX +* [\#1773](https://github.com/bnb-chain/bsc/pull/1773) discov: do not filter out bootnodes +* [\#1778](https://github.com/bnb-chain/bsc/pull/1778) vote: backup validator sync votes from corresponding mining validator +* [\#1784](https://github.com/bnb-chain/bsc/pull/1784) fix: exclude same votes when doing malicious voting check + ## v1.2.9 FEATURE * [\#1775](https://github.com/bnb-chain/bsc/pull/1775) upgrade: several hardfork block height on mainnet: Plato, Hertz(Berlin, London) diff --git a/cmd/extradump/extradump_test.go b/cmd/extradump/extradump_test.go new file mode 100644 index 0000000000..f203d322e6 --- /dev/null +++ b/cmd/extradump/extradump_test.go @@ -0,0 +1,108 @@ +package main + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" +) + +func TestExtraParse(t *testing.T) { + // case 1, |---Extra Vanity---|---Empty---|---Empty---|---Extra Seal---| + { + extraData := "0xd983010209846765746889676f312e31392e3131856c696e75780000a6bf97c1e99f701bb14cb7dfb68b90bd3e6d1ca656964630de71beffc7f33f7f08ec99d336ec51ad9fad0ac84ae77ca2e8ad9512acc56e0d7c93f3c2ce7de1b69149a5a400" + _, err := parseExtra(extraData) + assert.NoError(t, err) + } + + // case 2, |---Extra Vanity---|---Validators Number and Validators Bytes---|---Empty---|---Extra Seal---| + { + extraData := "0xd983010209846765746889676f312e31392e3131856c696e75780000a6bf97c1152465176c461afb316ebc773c61faee85a6515daa8a923564c6ffd37fb2fe9f118ef88092e8762c7addb526ab7eb1e772baef85181f892c731be0c1891a50e6b06262c816295e26495cef6f69dfa69911d9d8e4f3bbadb89b977cf58294f7239d515e15b24cfeb82494056cf691eaf729b165f32c9757c429dba5051155903067e56ebe3698678e912d4c407bbe49438ed859fe965b140dcf1aab71a993c1f7f6929d1fe2a17b4e14614ef9fc5bdc713d6631d675403fbeefac55611bf612700b1b65f4744861b80b0f7d6ab03f349bbafec1551819b8be1efea2fc46ca749aa184248a459464eec1a21e7fc7b71a053d9644e9bb8da4853b8f872cd7c1d6b324bf1922829830646ceadfb658d3de009a61dd481a114a2e761c554b641742c973867899d300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000069c77a677c40c7fbea129d4b171a39b7a8ddabfab2317f59d86abfaf690850223d90e9e7593d91a29331dfc2f84d5adecc75fc39ecab4632c1b4400a3dd1e1298835bcca70f657164e5b75689b64b7fd1fa275f334f28e1896a26afa1295da81418593bd12814463d9f6e45c36a0e47eb4cd3e5b6af29c41e2a3a5636430155a466e216585af3ba772b61c6014342d914470ec7ac2975be345796c2b81db0422a5fd08e40db1fc2368d2245e4b18b1d0b85c921aaaafd2e341760e29fc613edd39f71254614e2055c3287a517ae2f5b9e386cd1b50a4550696d957cb4900f03ab84f83ff2df44193496793b847f64e9d6db1b3953682bb95edd096eb1e69bbd357c200992ca78050d0cbe180cfaa018e8b6c8fd93d6f4cea42bbb345dbc6f0dfdb5bec73a8a257074e82b881cfa06ef3eb4efeca060c2531359abd0eab8af1e3edfa2025fca464ac9c3fd123f6c24a0d78869485a6f79b60359f141df90a0c745125b131caaffd12000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b218c5d6af1f979ac42bc68d98a5a0d796c6ab01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b4dd66d7c2c7e57f628210187192fb89d4b99dd4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000be807dddb074639cd9fa61b47676c064fc50d62cb1f2c71577def3144fabeb75a8a1c8cb5b51d1d1b4a05eec67988b8685008baa17459ec425dbaebc852f496dc92196cdcc8e6d00c17eb431350c6c50d8b8f05176b90b11b3a3d4feb825ae9702711566df5dbf38e82add4dd1b573b95d2466fa6501ccb81e9d26a352b96150ccbf7b697fd0a419d1d6bf74282782b0b3eb1413c901d6ecf02e8e28000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e2d3a739effcd3a99387d015e260eefac72ebea1956c470ddff48cb49300200b5f83497f3a3ccb3aeb83c5edd9818569038e61d197184f4aa6939ea5e9911e3e98ac6d21e9ae3261a475a27bb1028f140bc2a7c843318afd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ea0a6e3c511bbd10f4519ece37dc24887e11b55db2d4c6283c44a1c7bd503aaba7666e9f0c830e0ff016c1c750a5e48757a713d0836b1cabfd5c281b1de3b77d1c192183ee226379db83cffc681495730c11fdde79ba4c0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ef0274e31810c9df02f98fafde0f841f4e66a1cd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e99f701bb14cb7dfb68b90bd3e6d1ca656964630de71beffc7f33f7f08ec99d336ec51ad9fad0ac84ae77ca2e8ad9512acc56e0d7c93f3c2ce7de1b69149a5a400" + 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) + } + } + } + + // case 3, |---Extra Vanity---|---Empty---|---Vote Attestation---|---Extra Seal---| + { + extraData := "0xd883010205846765746888676f312e32302e35856c696e75780000002995c52af8b5830563efb86089cf168dcf4c5d3cb057926628ad1bf0f03ea67eef1458485578a4f8489afa8a853ecc7af45e2d145c21b70641c4b29f0febd2dd2c61fa1ba174be3fd47f1f5fa2ab9b5c318563d8b70ca58d0d51e79ee32b2fb721649e2cb9d36538361fba11f84c8401d14bb7a0fa67ddb3ba654d6006bf788710032247aa4d1be0707273e696b422b3ff72e9798401d14bbaa01225f505f5a0e1aefadcd2913b7aac9009fe4fb3d1bf57399e0b9dce5947f94280fe6d3647276c4127f437af59eb7c7985b2ae1ebe432619860695cb6106b80cc66c735bc1709afd11f233a2c97409d38ebaf7178aa53e895aea2fe0a229f71ec601" + extra, err := parseExtra(extraData) + assert.NoError(t, err) + { + var have = common.Bytes2Hex(extra.Data.TargetHash[:]) + var want = "1225f505f5a0e1aefadcd2913b7aac9009fe4fb3d1bf57399e0b9dce5947f942" + if have != want { + t.Fatalf("extra.Data.TargetHash mismatch, have %s, want %s", have, want) + } + } + { + var have = extra.Data.TargetNumber + var want = uint64(30493626) + if have != want { + t.Fatalf("extra.Data.TargetNumber mismatch, have %d, want %d", have, want) + } + } + } + + // case 4, |---Extra Vanity---|---Validators Number and Validators Bytes---|---Vote Attestation---|---Extra Seal---| + { + extraData := "0xd883010209846765746888676f312e31392e38856c696e7578000000dc55905c071284214b9b9c85549ab3d2b972df0deef66ac2c98e82934ca974fdcd97f3309de967d3c9c43fa711a8d673af5d75465844bf8969c8d1948d903748ac7b8b1720fa64e50c35552c16704d214347f29fa77f77da6d75d7c752b742ad4855bae330426b823e742da31f816cc83bc16d69a9134be0cfb4a1d17ec34f1b5b32d5c20440b8536b1e88f0f247788386d0ed6c748e03a53160b4b30ed3748cc5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000980a75ecd1309ea12fa2ed87a8744fbfc9b863d589037a9ace3b590165ea1c0c5ac72bf600b7c88c1e435f41932c1132aae1bfa0bb68e46b96ccb12c3415e4d82af717d8a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0b973c2d38487e58fd6e145491b110080fb14ac915a0411fc78f19e09a399ddee0d20c63a75d8f930f1694544ad2dc01bb71b214cb885500844365e95cd9942c7276e7fd8a2750ec6dded3dcdc2f351782310b0eadc077db59abca0f0cd26776e2e7acb9f3bce40b1fa5221fd1561226c6263cc5ff474cf03cceff28abc65c9cbae594f725c80e12d96c9b86c3400e529bfe184056e257c07940bb664636f689e8d2027c834681f8f878b73445261034e946bb2d901b4b878f8b27bb8608c11016739b3f8a19e54ab8c7abacd936cfeba200f3645a98b65adb0dd3692b69ce0b3ae10e7176b9a4b0d83f04065b1042b4bcb646a34b75c550f92fc34b8b2b1db0fa0d3172db23ba92727c80bcd306320d0ff411bf858525fde13bc8e0370f84c8401e9c2e6a0820dc11d63176a0eb1b828bc5376867b275579112b7013358da40317e7bab6e98401e9c2e7a00edc71ce80105a3220a87bea2792fa340d66c59002f02b0a09349ed1ed284070808b972fac2b9077a4dcb6fc37093799a652858016c99142b227500c844fa97ec22e3f9d3b1e982f14bcd999a7453e89ce5ef5c55f1c7f8f74ba904186cd67828200" + 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) + } + } + } +} diff --git a/cmd/extradump/main.go b/cmd/extradump/main.go new file mode 100644 index 0000000000..a19b6439ee --- /dev/null +++ b/cmd/extradump/main.go @@ -0,0 +1,162 @@ +// Copyright 2023 The bsc Authors +// This file is part of bsc. +package main + +import ( + "bytes" + "encoding/hex" + "flag" + "fmt" + "os" + "sort" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/willf/bitset" +) + +// follow define in parlia +const ( + AddressLength = 20 + BLSPublicKeyLength = 48 + + // follow order in extra field + // |---Extra Vanity---|---Validators Number and Validators Bytes (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 + extraSealLength = 65 // Fixed number of extra-data suffix bytes reserved for signer seal +) + +type Extra struct { + ExtraVanity string + ValidatorSize uint8 + Validators validatorsAscending + *types.VoteAttestation + ExtraSeal []byte +} + +type ValidatorInfo struct { + common.Address + types.BLSPublicKey + VoteIncluded bool +} + +// validatorsAscending implements the sort interface to allow sorting a list of ValidatorInfo +type validatorsAscending []ValidatorInfo + +func (s validatorsAscending) Len() int { return len(s) } +func (s validatorsAscending) Less(i, j int) bool { + return bytes.Compare(s[i].Address[:], s[j].Address[:]) < 0 +} +func (s validatorsAscending) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func init() { + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[extraHexData]") + flag.PrintDefaults() + fmt.Fprintln(os.Stderr, ` +Dumps extra info from the given hex data, only support extra after luban upgrade.`) + } +} + +func main() { + flag.Parse() + extraHexData := os.Args[1] + if extra, err := parseExtra(extraHexData); err == nil { + fmt.Println("extra parsed successly") + prettyExtra(*extra) + } else { + fmt.Println("extra parsed failed", "err", err) + } +} + +// parseExtra parse hex data into type Extra +func parseExtra(hexData string) (*Extra, error) { + // decode hex into bytes + data, err := hex.DecodeString(strings.TrimPrefix(hexData, "0x")) + if err != nil { + return nil, fmt.Errorf("invalid hex data") + } + + // parse ExtraVanity and ExtraSeal + dataLength := len(data) + var extra Extra + if dataLength < extraVanityLength+extraSealLength { + fmt.Println("length less than min required") + } + extra.ExtraVanity = string(data[:extraVanityLength]) + extra.ExtraSeal = data[dataLength-extraSealLength:] + data = data[extraVanityLength : dataLength-extraSealLength] + dataLength = len(data) + + // parse Validators and Vote Attestation + if dataLength > 0 { + // parse Validators + if data[0] != '\xf8' { // rlp format of attestation begin with 'f8' + validatorNum := int(data[0]) + validatorBytesTotalLength := validatorNumberSize + validatorNum*validatorBytesLength + if dataLength < validatorBytesTotalLength { + return nil, fmt.Errorf("parse validators failed") + } + extra.ValidatorSize = uint8(validatorNum) + data = data[validatorNumberSize:] + for i := 0; i < validatorNum; i++ { + var validatorInfo ValidatorInfo + validatorInfo.Address = common.BytesToAddress(data[i*validatorBytesLength : i*validatorBytesLength+common.AddressLength]) + copy(validatorInfo.BLSPublicKey[:], data[i*validatorBytesLength+common.AddressLength:(i+1)*validatorBytesLength]) + extra.Validators = append(extra.Validators, validatorInfo) + } + sort.Sort(extra.Validators) + data = data[validatorBytesTotalLength-validatorNumberSize:] + dataLength = len(data) + } + + // parse Vote Attestation + if dataLength > 0 { + if err := rlp.Decode(bytes.NewReader(data), &extra.VoteAttestation); err != nil { + return nil, fmt.Errorf("parse voteAttestation failed") + } + if extra.ValidatorSize > 0 { + validatorsBitSet := bitset.From([]uint64{uint64(extra.VoteAddressSet)}) + for i := 0; i < int(extra.ValidatorSize); i++ { + if validatorsBitSet.Test(uint(i)) { + extra.Validators[i].VoteIncluded = true + } + } + } + } + } + + return &extra, nil +} + +// prettyExtra print Extra with a pretty format +func prettyExtra(extra Extra) { + fmt.Printf("ExtraVanity : %s\n", extra.ExtraVanity) + + if extra.ValidatorSize > 0 { + fmt.Printf("ValidatorSize : %d\n", extra.ValidatorSize) + for i := 0; i < int(extra.ValidatorSize); i++ { + fmt.Printf("Validator %d\n", i+1) + fmt.Printf("\tAddress : %s\n", common.Bytes2Hex(extra.Validators[i].Address[:])) + fmt.Printf("\tVoteKey : %s\n", common.Bytes2Hex(extra.Validators[i].BLSPublicKey[:])) + fmt.Printf("\tVoteIncluded : %t\n", extra.Validators[i].VoteIncluded) + } + } + + if extra.VoteAttestation != nil { + fmt.Printf("Attestation :\n") + fmt.Printf("\tVoteAddressSet : %b, %d\n", extra.VoteAddressSet, bitset.From([]uint64{uint64(extra.VoteAddressSet)}).Count()) + fmt.Printf("\tAggSignature : %s\n", common.Bytes2Hex(extra.AggSignature[:])) + fmt.Printf("\tVoteData :\n") + fmt.Printf("\t\tSourceNumber : %d\n", extra.Data.SourceNumber) + fmt.Printf("\t\tSourceHash : %s\n", common.Bytes2Hex(extra.Data.SourceHash[:])) + fmt.Printf("\t\tTargetNumber : %d\n", extra.Data.TargetNumber) + fmt.Printf("\t\tTargetHash : %s\n", common.Bytes2Hex(extra.Data.TargetHash[:])) + } + + fmt.Printf("ExtraSeal : %s\n", common.Bytes2Hex(extra.ExtraSeal)) +} diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 0f01f123e8..05d5e19887 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -178,6 +178,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { utils.SetupMetrics(ctx, utils.EnableBuildInfo(gitCommit, gitDate), utils.EnableMinerInfo(ctx, cfg.Eth.Miner), + utils.EnableNodeInfo(cfg.Eth.TxPool), ) return stack, backend } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9064b21d8a..167b9f7a51 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1972,6 +1972,21 @@ func EnableMinerInfo(ctx *cli.Context, minerConfig miner.Config) SetupMetricsOpt } } +func EnableNodeInfo(poolConfig core.TxPoolConfig) SetupMetricsOption { + return func() { + // register node info into metrics + metrics.NewRegisteredLabel("node-info", nil).Mark(map[string]interface{}{ + "PriceLimit": poolConfig.PriceLimit, + "PriceBump": poolConfig.PriceBump, + "AccountSlots": poolConfig.AccountSlots, + "GlobalSlots": poolConfig.GlobalSlots, + "AccountQueue": poolConfig.AccountQueue, + "GlobalQueue": poolConfig.GlobalQueue, + "Lifetime": poolConfig.Lifetime, + }) + } +} + func SetupMetrics(ctx *cli.Context, options ...SetupMetricsOption) { if metrics.Enabled { log.Info("Enabling metrics collection") diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 7522f555ac..3ea1591c1c 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -882,6 +882,11 @@ func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, head attestation.VoteAddressSet |= 1 << (valInfo.Index - 1) //Index is offset by 1 } } + validatorsBitSet := bitset.From([]uint64{uint64(attestation.VoteAddressSet)}) + if validatorsBitSet.Count() < uint(len(signatures)) { + log.Warn(fmt.Sprintf("assembleVoteAttestation, check VoteAddress Set failed, expected:%d, real:%d", len(signatures), validatorsBitSet.Count())) + return fmt.Errorf("invalid attestation, check VoteAddress Set failed") + } // Append attestation to header extra field. buf := new(bytes.Buffer) @@ -1758,10 +1763,11 @@ func (p *Parlia) GetFinalizedHeader(chain consensus.ChainHeaderReader, header *t return nil } - if snap.Attestation != nil { - return chain.GetHeader(snap.Attestation.SourceHash, snap.Attestation.SourceNumber) + if snap.Attestation == nil { + return chain.GetHeaderByNumber(0) // keep consistent with GetJustifiedNumberAndHash } - return nil + + return chain.GetHeader(snap.Attestation.SourceHash, snap.Attestation.SourceNumber) } // =========================== utility function ========================== diff --git a/core/monitor/malicious_vote_monitor.go b/core/monitor/malicious_vote_monitor.go index be8288a4b2..43dc4d4243 100644 --- a/core/monitor/malicious_vote_monitor.go +++ b/core/monitor/malicious_vote_monitor.go @@ -58,6 +58,7 @@ func (m *MaliciousVoteMonitor) ConflictDetect(newVote *types.VoteEnvelope, pendi if !(blockNumber+maliciousVoteSlashScope > pendingBlockNumber) { blockNumber = pendingBlockNumber - maliciousVoteSlashScope + 1 } + newVoteHash := newVote.Data.Hash() for ; blockNumber <= pendingBlockNumber+upperLimitOfVoteBlockNumber; blockNumber++ { if voteDataBuffer.Contains(blockNumber) { voteEnvelope, ok := voteDataBuffer.Get(blockNumber) @@ -66,7 +67,7 @@ func (m *MaliciousVoteMonitor) ConflictDetect(newVote *types.VoteEnvelope, pendi continue } maliciousVote := false - if blockNumber == targetNumber { + if blockNumber == targetNumber && voteEnvelope.(*types.VoteEnvelope).Data.Hash() != newVoteHash { violateRule1Counter.Inc(1) maliciousVote = true } else if (blockNumber < targetNumber && voteEnvelope.(*types.VoteEnvelope).Data.SourceNumber > sourceNumber) || diff --git a/core/monitor/malicious_vote_monitor_test.go b/core/monitor/malicious_vote_monitor_test.go index 0f60ec4897..696d4d64f4 100644 --- a/core/monitor/malicious_vote_monitor_test.go +++ b/core/monitor/malicious_vote_monitor_test.go @@ -22,9 +22,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: uint64(0), - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - maliciousVoteSlashScope - 1, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(1)))), + TargetHash: common.BytesToHash(common.Hex2Bytes(("01"))), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote1, pendingBlockNumber)) @@ -34,9 +34,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: uint64(0), - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - maliciousVoteSlashScope - 1, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(2)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("02")), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote2, pendingBlockNumber)) @@ -54,9 +54,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: uint64(0), - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - maliciousVoteSlashScope - 1, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(1)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("01")), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote1, pendingBlockNumber)) @@ -65,9 +65,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: uint64(0), - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - maliciousVoteSlashScope - 1, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(2)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("02")), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote2, pendingBlockNumber)) @@ -85,9 +85,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: uint64(0), - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - 1, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(1)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("01")), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote1, pendingBlockNumber)) @@ -96,9 +96,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: uint64(0), - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - 1, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(2)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("02")), }, } assert.Equal(t, true, maliciousVoteMonitor.ConflictDetect(vote2, pendingBlockNumber)) @@ -116,9 +116,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: pendingBlockNumber - 4, - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - 1, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(1)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("01")), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote1, pendingBlockNumber)) @@ -127,9 +127,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: pendingBlockNumber - 2, - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - 3, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(2)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("02")), }, } assert.Equal(t, true, maliciousVoteMonitor.ConflictDetect(vote2, pendingBlockNumber)) @@ -147,9 +147,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: pendingBlockNumber - 2, - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - 3, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(1)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("01")), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote1, pendingBlockNumber)) @@ -158,9 +158,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: pendingBlockNumber - 4, - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - 1, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(2)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("02")), }, } assert.Equal(t, true, maliciousVoteMonitor.ConflictDetect(vote2, pendingBlockNumber)) @@ -178,9 +178,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: pendingBlockNumber - 4, - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - 3, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(1)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("01")), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote1, pendingBlockNumber)) @@ -189,9 +189,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: pendingBlockNumber - 3, - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - 2, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(2)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("02")), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote2, pendingBlockNumber)) @@ -200,9 +200,9 @@ func TestMaliciousVoteMonitor(t *testing.T) { Signature: types.BLSSignature{}, Data: &types.VoteData{ SourceNumber: pendingBlockNumber - 2, - SourceHash: common.BytesToHash(common.Hex2Bytes(string(rune(0)))), + SourceHash: common.BytesToHash(common.Hex2Bytes("00")), TargetNumber: pendingBlockNumber - 1, - TargetHash: common.BytesToHash(common.Hex2Bytes(string(rune(2)))), + TargetHash: common.BytesToHash(common.Hex2Bytes("02")), }, } assert.Equal(t, false, maliciousVoteMonitor.ConflictDetect(vote3, pendingBlockNumber)) diff --git a/core/vote/vote_manager.go b/core/vote/vote_manager.go index dcab5c38dd..eb99588f13 100644 --- a/core/vote/vote_manager.go +++ b/core/vote/vote_manager.go @@ -32,6 +32,10 @@ type VoteManager struct { chainHeadCh chan core.ChainHeadEvent chainHeadSub event.Subscription + // used for backup validators to sync votes from corresponding mining validator + syncVoteCh chan core.NewVoteEvent + syncVoteSub event.Subscription + pool *VotePool signer *VoteSigner journal *VoteJournal @@ -46,9 +50,9 @@ func NewVoteManager(eth Backend, chainconfig *params.ChainConfig, chain *core.Bl chain: chain, chainconfig: chainconfig, chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), - - pool: pool, - engine: engine, + syncVoteCh: make(chan core.NewVoteEvent, voteBufferForPut), + pool: pool, + engine: engine, } // Create voteSigner. @@ -69,6 +73,7 @@ func NewVoteManager(eth Backend, chainconfig *params.ChainConfig, chain *core.Bl // Subscribe to chain head event. voteManager.chainHeadSub = voteManager.chain.SubscribeChainHeadEvent(voteManager.chainHeadCh) + voteManager.syncVoteSub = voteManager.pool.SubscribeNewVoteEvent(voteManager.syncVoteCh) go voteManager.loop() @@ -77,6 +82,9 @@ func NewVoteManager(eth Backend, chainconfig *params.ChainConfig, chain *core.Bl func (voteManager *VoteManager) loop() { log.Debug("vote manager routine loop started") + defer voteManager.chainHeadSub.Unsubscribe() + defer voteManager.syncVoteSub.Unsubscribe() + events := voteManager.eth.EventMux().Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{}) defer func() { log.Debug("vote manager loop defer func occur") @@ -164,6 +172,21 @@ func (voteManager *VoteManager) loop() { voteManager.pool.PutVote(voteMessage) votesManagerCounter.Inc(1) } + case event := <-voteManager.syncVoteCh: + voteMessage := event.Vote + if voteManager.eth.IsMining() || !voteManager.signer.UsingKey(&voteMessage.VoteAddress) { + continue + } + if err := voteManager.journal.WriteVote(voteMessage); err != nil { + log.Error("Failed to write vote into journal", "err", err) + voteJournalErrorCounter.Inc(1) + continue + } + log.Debug("vote manager synced vote", "votedBlockNumber", voteMessage.Data.TargetNumber, "votedBlockHash", voteMessage.Data.TargetHash, "voteMessageHash", voteMessage.Hash()) + votesManagerCounter.Inc(1) + case <-voteManager.syncVoteSub.Err(): + log.Debug("voteManager subscribed votes failed") + return case <-voteManager.chainHeadSub.Err(): log.Debug("voteManager subscribed chainHead failed") return diff --git a/core/vote/vote_pool.go b/core/vote/vote_pool.go index 28369ed07c..bd38122e71 100644 --- a/core/vote/vote_pool.go +++ b/core/vote/vote_pool.go @@ -92,6 +92,8 @@ func NewVotePool(chainconfig *params.ChainConfig, chain *core.BlockChain, engine // loop is the vote pool's main even loop, waiting for and reacting to outside blockchain events and votes channel event. func (pool *VotePool) loop() { + defer pool.chainHeadSub.Unsubscribe() + for { select { // Handle ChainHeadEvent. diff --git a/core/vote/vote_signer.go b/core/vote/vote_signer.go index e6c8010b0b..c2cda513f0 100644 --- a/core/vote/vote_signer.go +++ b/core/vote/vote_signer.go @@ -1,6 +1,7 @@ package vote import ( + "bytes" "context" "fmt" "io/ioutil" @@ -104,3 +105,7 @@ func (signer *VoteSigner) SignVote(vote *types.VoteEnvelope) error { copy(vote.Signature[:], signature.Marshal()[:]) return nil } + +func (signer *VoteSigner) UsingKey(bLSPublicKey *types.BLSPublicKey) bool { + return bytes.Equal(signer.pubKey[:], bLSPublicKey[:]) +} diff --git a/eth/handler_bsc.go b/eth/handler_bsc.go index d01a475ed0..4fb1824f27 100644 --- a/eth/handler_bsc.go +++ b/eth/handler_bsc.go @@ -62,7 +62,6 @@ func (h *bscHandler) Handle(peer *bsc.Peer, packet bsc.Packet) error { // votes broadcast for the local node to process. func (h *bscHandler) handleVotesBroadcast(peer *bsc.Peer, votes []*types.VoteEnvelope) error { if peer.IsOverLimitAfterReceiving() { - peer.Log().Warn("peer sending votes too much, votes dropped; it may be a ddos attack, please check!") return nil } // Here we only put the first vote, to avoid ddos attack by sending a large batch of votes. diff --git a/eth/protocols/bsc/peer.go b/eth/protocols/bsc/peer.go index 77ac11599f..242864fdbb 100644 --- a/eth/protocols/bsc/peer.go +++ b/eth/protocols/bsc/peer.go @@ -26,7 +26,7 @@ const ( receiveRateLimitPerSecond = 10 // the time span of one period - secondsPerPeriod = float64(10) + secondsPerPeriod = float64(30) ) // max is a helper function which returns the larger of the two given integers. @@ -133,6 +133,9 @@ func (p *Peer) AsyncSendVotes(votes []*types.VoteEnvelope) { // Otherwise, check whether the number of received votes extra (secondsPerPeriod * receiveRateLimitPerSecond) func (p *Peer) IsOverLimitAfterReceiving() bool { if timeInterval := time.Since(p.periodBegin).Seconds(); timeInterval >= secondsPerPeriod { + if p.periodCounter > uint(secondsPerPeriod*receiveRateLimitPerSecond) { + p.Log().Debug("sending votes too much", "secondsPerPeriod", secondsPerPeriod, "count ", p.periodCounter) + } p.periodBegin = time.Now() p.periodCounter = 0 return false diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index fc903d3602..177ab97c18 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -54,6 +54,14 @@ import ( const UnHealthyTimeout = 5 * time.Second +// max is a helper function which returns the larger of the two given integers. +func max(a, b int64) int64 { + if a > b { + return a + } + return b +} + // PublicEthereumAPI provides an API to access Ethereum related information. // It offers only methods that operate on public data that is freely available to anyone. type PublicEthereumAPI struct { @@ -773,6 +781,52 @@ func (s *PublicBlockChainAPI) Health() bool { return true } +// GetFinalizedHeader returns the requested finalized block header. +// - probabilisticFinalized should be in range [2,21], +// then the block header with number `max(fastFinalized, latest-probabilisticFinalized)` is returned +func (s *PublicBlockChainAPI) GetFinalizedHeader(ctx context.Context, probabilisticFinalized int64) (map[string]interface{}, error) { + if probabilisticFinalized < 2 || probabilisticFinalized > 21 { + return nil, fmt.Errorf("%d out of range [2,21]", probabilisticFinalized) + } + + var err error + fastFinalizedHeader, err := s.b.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) + if err != nil { // impossible + return nil, err + } + latestHeader, err := s.b.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { // impossible + return nil, err + } + finalizedBlockNumber := max(fastFinalizedHeader.Number.Int64(), latestHeader.Number.Int64()-probabilisticFinalized) + + return s.GetHeaderByNumber(ctx, rpc.BlockNumber(finalizedBlockNumber)) +} + +// GetFinalizedBlock returns the requested finalized block. +// - probabilisticFinalized should be in range [2,21], +// then the block with number `max(fastFinalized, latest-probabilisticFinalized)` is returned +// - When fullTx is true all transactions in the block are returned, otherwise +// only the transaction hash is returned. +func (s *PublicBlockChainAPI) GetFinalizedBlock(ctx context.Context, probabilisticFinalized int64, fullTx bool) (map[string]interface{}, error) { + if probabilisticFinalized < 2 || probabilisticFinalized > 21 { + return nil, fmt.Errorf("%d out of range [2,21]", probabilisticFinalized) + } + + var err error + fastFinalizedHeader, err := s.b.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) + if err != nil { // impossible + return nil, err + } + latestHeader, err := s.b.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { // impossible + return nil, err + } + finalizedBlockNumber := max(fastFinalizedHeader.Number.Int64(), latestHeader.Number.Int64()-probabilisticFinalized) + + return s.GetBlockByNumber(ctx, rpc.BlockNumber(finalizedBlockNumber), fullTx) +} + // GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, blockNr) diff --git a/p2p/server.go b/p2p/server.go index b4aac9a13d..ac15f7673b 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -611,7 +611,7 @@ func (srv *Server) setupDiscovery() error { Tail []rlp.RawValue `rlp:"tail"` } if r.Load(enr.WithEntry("eth", ð)) != nil { - return false + return true } return srv.forkFilter(eth.ForkID) == nil } diff --git a/params/version.go b/params/version.go index e3e2605a5c..4fa6fe41f8 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 1 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 9 // Patch version component of the current release + VersionPatch = 10 // Patch version component of the current release VersionMeta = "" // Version metadata to append to the version string )