diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d628cdbcf..848e37c864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,17 @@ # Changelog +## v1.2.12 +FEATURE +* [\#1852](https://github.com/bnb-chain/bsc/pull/1852) discov: add hardcoded bootnodes + +BUGFIX +* [\#1844](https://github.com/bnb-chain/bsc/pull/1844) crypto: Update BLST to v0.3.11 +* [\#1854](https://github.com/bnb-chain/bsc/pull/1854) fetcher: no import blocks before or equal to the finalized height +* [\#1855](https://github.com/bnb-chain/bsc/pull/1855) eth/tracers: trace system tx should add intrinsicGas + +IMPROVEMENT +* [\#1839](https://github.com/bnb-chain/bsc/pull/1839) Update init-network command +* [\#1858](https://github.com/bnb-chain/bsc/pull/1858) vote: check consensus key match vote key before voting + ## v1.2.11 FEATURE * [\#1797](https://github.com/bnb-chain/bsc/pull/1797) client: add FinalizedHeader/Block to use the fast finality diff --git a/README.md b/README.md index f58cb243ff..5910626979 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,35 @@ APIs!** - [BSC-Deploy](https://github.com/bnb-chain/node-deploy/): deploy tool for setting up both BNB Beacon Chain, BNB Smart Chain and the cross chain infrastructure between them. - [BSC-Docker](https://github.com/bnb-chain/bsc-docker): deploy tool for setting up local BSC cluster in container. + +## Running a bootnode + +Bootnodes are super-lightweight nodes that are not behind a NAT and are running just discovery protocol. When you start up a node it should log your enode, which is a public identifier that others can use to connect to your node. + +First the bootnode requires a key, which can be created with the following command, which will save a key to boot.key: + +``` +bootnode -genkey boot.key +``` + +This key can then be used to generate a bootnode as follows: + +``` +bootnode -nodekey boot.key -addr :30311 -network bsc +``` + +The choice of port passed to -addr is arbitrary. +The bootnode command returns the following logs to the terminal, confirming that it is running: + +``` +enode://3063d1c9e1b824cfbb7c7b6abafa34faec6bb4e7e06941d218d760acdd7963b274278c5c3e63914bd6d1b58504c59ec5522c56f883baceb8538674b92da48a96@127.0.0.1:0?discport=30311 +Note: you're using cmd/bootnode, a developer tool. +We recommend using a regular node as bootstrap node for production deployments. +INFO [08-21|11:11:30.687] New local node record seq=1,692,616,290,684 id=2c9af1742f8f85ce ip= udp=0 tcp=0 +INFO [08-21|12:11:30.753] New local node record seq=1,692,616,290,685 id=2c9af1742f8f85ce ip=54.217.128.118 udp=30311 tcp=0 +INFO [09-01|02:46:26.234] New local node record seq=1,692,616,290,686 id=2c9af1742f8f85ce ip=34.250.32.100 udp=30311 tcp=0 +``` + ## Contribution Thank you for considering to help out with the source code! We welcome contributions diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index d2b44a4565..545c10d0c9 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net" "os" "path" @@ -237,31 +238,17 @@ func initGenesis(ctx *cli.Context) error { return nil } -// initNetwork will bootstrap and initialize a new genesis block, and nodekey, config files for network nodes -func initNetwork(ctx *cli.Context) error { - initDir := ctx.String(utils.InitNetworkDir.Name) - if len(initDir) == 0 { - utils.Fatalf("init.dir is required") - } - size := ctx.Int(utils.InitNetworkSize.Name) - port := ctx.Int(utils.InitNetworkPort.Name) - ipStr := ctx.String(utils.InitNetworkIps.Name) - cfgFile := ctx.String(configFileFlag.Name) - - if len(cfgFile) == 0 { - utils.Fatalf("config file is required") - } +func parseIps(ipStr string, size int) ([]string, error) { var ips []string if len(ipStr) != 0 { ips = strings.Split(ipStr, ",") if len(ips) != size { - utils.Fatalf("mismatch of size and length of ips") + return nil, errors.New("mismatch of size and length of ips") } for i := 0; i < size; i++ { _, err := net.ResolveIPAddr("", ips[i]) if err != nil { - utils.Fatalf("invalid format of ip") - return err + return nil, errors.New("invalid format of ip") } } } else { @@ -270,23 +257,103 @@ func initNetwork(ctx *cli.Context) error { ips[i] = "127.0.0.1" } } + return ips, nil +} + +func createPorts(ipStr string, port int, size int) []int { + ports := make([]int, size) + if len(ipStr) == 0 { // localhost , so different ports + for i := 0; i < size; i++ { + ports[i] = port + i + } + } else { // different machines, keep same port + for i := 0; i < size; i++ { + ports[i] = port + } + } + return ports +} + +// Create config for node i in the cluster +func createNodeConfig(baseConfig gethConfig, enodes []*enode.Node, ip string, port int, size int, i int) gethConfig { + baseConfig.Node.HTTPHost = ip + baseConfig.Node.P2P.ListenAddr = fmt.Sprintf(":%d", port+i) + baseConfig.Node.P2P.BootstrapNodes = make([]*enode.Node, size-1) + // Set the P2P connections between this node and the other nodes + for j := 0; j < i; j++ { + baseConfig.Node.P2P.BootstrapNodes[j] = enodes[j] + } + for j := i + 1; j < size; j++ { + baseConfig.Node.P2P.BootstrapNodes[j-1] = enodes[j] + } + return baseConfig +} + +// Create configs for nodes in the cluster +func createNodeConfigs(baseConfig gethConfig, initDir string, ips []string, ports []int, size int) ([]gethConfig, error) { + // Create the nodes + enodes := make([]*enode.Node, size) + for i := 0; i < size; i++ { + stack, err := node.New(&baseConfig.Node) + if err != nil { + return nil, err + } + stack.Config().DataDir = path.Join(initDir, fmt.Sprintf("node%d", i)) + pk := stack.Config().NodeKey() + enodes[i] = enode.NewV4(&pk.PublicKey, net.ParseIP(ips[i]), ports[i], ports[i]) + } + + // Create the configs + configs := make([]gethConfig, size) + for i := 0; i < size; i++ { + configs[i] = createNodeConfig(baseConfig, enodes, ips[i], ports[i], size, i) + } + return configs, nil +} + +// initNetwork will bootstrap and initialize a new genesis block, and nodekey, config files for network nodes +func initNetwork(ctx *cli.Context) error { + initDir := ctx.String(utils.InitNetworkDir.Name) + if len(initDir) == 0 { + utils.Fatalf("init.dir is required") + } + size := ctx.Int(utils.InitNetworkSize.Name) + if size <= 0 { + utils.Fatalf("size should be greater than 0") + } + port := ctx.Int(utils.InitNetworkPort.Name) + if port <= 0 { + utils.Fatalf("port should be greater than 0") + } + ipStr := ctx.String(utils.InitNetworkIps.Name) + cfgFile := ctx.String(configFileFlag.Name) + + if len(cfgFile) == 0 { + utils.Fatalf("config file is required") + } + + ips, err := parseIps(ipStr, size) + if err != nil { + utils.Fatalf("Failed to pase ips string: %v", err) + } + + ports := createPorts(ipStr, port, size) // Make sure we have a valid genesis JSON genesisPath := ctx.Args().First() if len(genesisPath) == 0 { utils.Fatalf("Must supply path to genesis JSON file") } - file, err := os.Open(genesisPath) + inGenesisFile, err := os.Open(genesisPath) if err != nil { utils.Fatalf("Failed to read genesis file: %v", err) } - defer file.Close() + defer inGenesisFile.Close() genesis := new(core.Genesis) - if err := json.NewDecoder(file).Decode(genesis); err != nil { + if err := json.NewDecoder(inGenesisFile).Decode(genesis); err != nil { utils.Fatalf("invalid genesis file: %v", err) } - enodes := make([]*enode.Node, size) // load config var config gethConfig @@ -294,37 +361,38 @@ func initNetwork(ctx *cli.Context) error { if err != nil { return err } - config.Eth.Genesis = genesis + + configs, err := createNodeConfigs(config, initDir, ips, ports, size) + if err != nil { + utils.Fatalf("Failed to create node configs: %v", err) + } for i := 0; i < size; i++ { - stack, err := node.New(&config.Node) + // Write config.toml + configBytes, err := tomlSettings.Marshal(configs[i]) if err != nil { return err } - stack.Config().DataDir = path.Join(initDir, fmt.Sprintf("node%d", i)) - pk := stack.Config().NodeKey() - enodes[i] = enode.NewV4(&pk.PublicKey, net.ParseIP(ips[i]), port, port) - } - - for i := 0; i < size; i++ { - config.Node.HTTPHost = ips[i] - config.Node.P2P.StaticNodes = make([]*enode.Node, size-1) - for j := 0; j < i; j++ { - config.Node.P2P.StaticNodes[j] = enodes[j] + configFile, err := os.OpenFile(path.Join(initDir, fmt.Sprintf("node%d", i), "config.toml"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err } - for j := i + 1; j < size; j++ { - config.Node.P2P.StaticNodes[j-1] = enodes[j] + defer configFile.Close() + configFile.Write(configBytes) + + // Write the input genesis.json to the node's directory + outGenesisFile, err := os.OpenFile(path.Join(initDir, fmt.Sprintf("node%d", i), "genesis.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err } - out, err := tomlSettings.Marshal(config) + _, err = inGenesisFile.Seek(0, io.SeekStart) if err != nil { return err } - dump, err := os.OpenFile(path.Join(initDir, fmt.Sprintf("node%d", i), "config.toml"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + _, err = io.Copy(outGenesisFile, inGenesisFile) if err != nil { return err } - defer dump.Close() - dump.Write(out) } return nil } diff --git a/consensus/consensus.go b/consensus/consensus.go index 30c4559043..854f849862 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -155,5 +155,5 @@ type PoSA interface { GetJustifiedNumberAndHash(chain ChainHeaderReader, header *types.Header) (uint64, common.Hash, error) GetFinalizedHeader(chain ChainHeaderReader, header *types.Header) *types.Header VerifyVote(chain ChainHeaderReader, vote *types.VoteEnvelope) error - IsActiveValidatorAt(chain ChainHeaderReader, header *types.Header) bool + IsActiveValidatorAt(chain ChainHeaderReader, header *types.Header, checkVoteKeyFn func(bLSPublicKey *types.BLSPublicKey) bool) bool } diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 7269b490ee..195d6e2026 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -1205,7 +1205,7 @@ func (p *Parlia) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * return blk, receipts, nil } -func (p *Parlia) IsActiveValidatorAt(chain consensus.ChainHeaderReader, header *types.Header) bool { +func (p *Parlia) IsActiveValidatorAt(chain consensus.ChainHeaderReader, header *types.Header, checkVoteKeyFn func(bLSPublicKey *types.BLSPublicKey) bool) bool { number := header.Number.Uint64() snap, err := p.snapshot(chain, number-1, header.ParentHash, nil) if err != nil { @@ -1213,8 +1213,9 @@ func (p *Parlia) IsActiveValidatorAt(chain consensus.ChainHeaderReader, header * return false } validators := snap.Validators - _, ok := validators[p.val] - return ok + validatorInfo, ok := validators[p.val] + + return ok && (checkVoteKeyFn == nil || (validatorInfo != nil && checkVoteKeyFn(&validatorInfo.VoteAddress))) } // VerifyVote will verify: 1. If the vote comes from valid validators 2. If the vote's sourceNumber and sourceHash are correct diff --git a/core/vm/logger.go b/core/vm/logger.go index 8b5b81d60d..14ce81e434 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -31,6 +31,7 @@ type EVMLogger interface { // Transaction level CaptureTxStart(gasLimit uint64) CaptureTxEnd(restGas uint64) + CaptureSystemTxEnd(intrinsicGas uint64) // Top call frame CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) CaptureEnd(output []byte, gasUsed uint64, err error) diff --git a/core/vote/vote_manager.go b/core/vote/vote_manager.go index eb99588f13..ad8ad38a02 100644 --- a/core/vote/vote_manager.go +++ b/core/vote/vote_manager.go @@ -1,7 +1,9 @@ package vote import ( + "bytes" "fmt" + "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" @@ -97,6 +99,7 @@ func (voteManager *VoteManager) loop() { dlEventCh := events.Chan() startVote := true + var once sync.Once for { select { case ev := <-dlEventCh: @@ -132,11 +135,22 @@ func (voteManager *VoteManager) loop() { curHead := cHead.Block.Header() // Check if cur validator is within the validatorSet at curHead - if !voteManager.engine.IsActiveValidatorAt(voteManager.chain, curHead) { + if !voteManager.engine.IsActiveValidatorAt(voteManager.chain, curHead, + func(bLSPublicKey *types.BLSPublicKey) bool { + return bytes.Equal(voteManager.signer.PubKey[:], bLSPublicKey[:]) + }) { log.Debug("cur validator is not within the validatorSet at curHead") continue } + // Add VoteKey to `miner-info` + once.Do(func() { + minerInfo := metrics.Get("miner-info") + if minerInfo != nil { + minerInfo.(metrics.Label).Value()["VoteKey"] = common.Bytes2Hex(voteManager.signer.PubKey[:]) + } + }) + // Vote for curBlockHeader block. vote := &types.VoteData{ TargetNumber: curHead.Number.Uint64(), @@ -174,7 +188,7 @@ func (voteManager *VoteManager) loop() { } case event := <-voteManager.syncVoteCh: voteMessage := event.Vote - if voteManager.eth.IsMining() || !voteManager.signer.UsingKey(&voteMessage.VoteAddress) { + if voteManager.eth.IsMining() || !bytes.Equal(voteManager.signer.PubKey[:], voteMessage.VoteAddress[:]) { continue } if err := voteManager.journal.WriteVote(voteMessage); err != nil { diff --git a/core/vote/vote_pool_test.go b/core/vote/vote_pool_test.go index 5636bcafea..071a9702f1 100644 --- a/core/vote/vote_pool_test.go +++ b/core/vote/vote_pool_test.go @@ -99,11 +99,11 @@ func (m *mockInvalidPOSA) VerifyVote(chain consensus.ChainHeaderReader, vote *ty return nil } -func (m *mockPOSA) IsActiveValidatorAt(chain consensus.ChainHeaderReader, header *types.Header) bool { +func (m *mockPOSA) IsActiveValidatorAt(chain consensus.ChainHeaderReader, header *types.Header, checkVoteKeyFn func(bLSPublicKey *types.BLSPublicKey) bool) bool { return true } -func (m *mockInvalidPOSA) IsActiveValidatorAt(chain consensus.ChainHeaderReader, header *types.Header) bool { +func (m *mockInvalidPOSA) IsActiveValidatorAt(chain consensus.ChainHeaderReader, header *types.Header, checkVoteKeyFn func(bLSPublicKey *types.BLSPublicKey) bool) bool { return true } diff --git a/core/vote/vote_signer.go b/core/vote/vote_signer.go index c2cda513f0..86f277c37a 100644 --- a/core/vote/vote_signer.go +++ b/core/vote/vote_signer.go @@ -1,7 +1,6 @@ package vote import ( - "bytes" "context" "fmt" "io/ioutil" @@ -28,7 +27,7 @@ var votesSigningErrorCounter = metrics.NewRegisteredCounter("votesSigner/error", type VoteSigner struct { km *keymanager.IKeymanager - pubKey [48]byte + PubKey [48]byte } func NewVoteSigner(blsPasswordPath, blsWalletPath string) (*VoteSigner, error) { @@ -39,7 +38,7 @@ func NewVoteSigner(blsPasswordPath, blsWalletPath string) (*VoteSigner, error) { } if !dirExists { log.Error("BLS wallet did not exists.") - return nil, fmt.Errorf("BLS wallet did not exists.") + return nil, fmt.Errorf("BLS wallet did not exists") } walletPassword, err := ioutil.ReadFile(blsPasswordPath) @@ -76,13 +75,13 @@ func NewVoteSigner(blsPasswordPath, blsWalletPath string) (*VoteSigner, error) { return &VoteSigner{ km: &km, - pubKey: pubKeys[0], + PubKey: pubKeys[0], }, nil } func (signer *VoteSigner) SignVote(vote *types.VoteEnvelope) error { // Sign the vote, fetch the first pubKey as validator's bls public key. - pubKey := signer.pubKey + pubKey := signer.PubKey blsPubKey, err := bls.PublicKeyFromBytes(pubKey[:]) if err != nil { return errors.Wrap(err, "convert public key from bytes to bls failed") @@ -105,7 +104,3 @@ 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/backend.go b/eth/backend.go index 60d95ac188..1c58c0a069 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -54,6 +54,7 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/shutdowncheck" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" @@ -568,8 +569,12 @@ func (s *Ethereum) StartMining(threads int) error { log.Error("Etherbase account unavailable locally", "err", err) return fmt.Errorf("signer missing: %v", err) } - parlia.Authorize(eb, wallet.SignData, wallet.SignTx) + + minerInfo := metrics.Get("miner-info") + if minerInfo != nil { + minerInfo.(metrics.Label).Value()["Etherbase"] = eb.String() + } } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 7953e5e695..f537a775c8 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -94,6 +94,9 @@ type blockBroadcasterFn func(block *types.Block, propagate bool) // chainHeightFn is a callback type to retrieve the current chain height. type chainHeightFn func() uint64 +// chainFinalizedHeightFn is a callback type to retrieve the current chain finalized height. +type chainFinalizedHeightFn func() uint64 + // headersInsertFn is a callback type to insert a batch of headers into the local chain. type headersInsertFn func(headers []*types.Header) (int, error) @@ -189,14 +192,15 @@ type BlockFetcher struct { queued map[common.Hash]*blockOrHeaderInject // Set of already queued blocks (to dedup imports) // Callbacks - getHeader HeaderRetrievalFn // Retrieves a header from the local chain - getBlock blockRetrievalFn // Retrieves a block from the local chain - verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work - broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers - chainHeight chainHeightFn // Retrieves the current chain's height - insertHeaders headersInsertFn // Injects a batch of headers into the chain - insertChain chainInsertFn // Injects a batch of blocks into the chain - dropPeer peerDropFn // Drops a peer for misbehaving + getHeader HeaderRetrievalFn // Retrieves a header from the local chain + getBlock blockRetrievalFn // Retrieves a block from the local chain + verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work + broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers + chainHeight chainHeightFn // Retrieves the current chain's height + chainFinalizedHeight chainFinalizedHeightFn // Retrieves the current chain's finalized height + insertHeaders headersInsertFn // Injects a batch of headers into the chain + insertChain chainInsertFn // Injects a batch of blocks into the chain + dropPeer peerDropFn // Drops a peer for misbehaving // Testing hooks announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the blockAnnounce list @@ -207,32 +211,35 @@ type BlockFetcher struct { } // NewBlockFetcher creates a block fetcher to retrieve blocks based on hash announcements. -func NewBlockFetcher(light bool, getHeader HeaderRetrievalFn, getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertHeaders headersInsertFn, insertChain chainInsertFn, dropPeer peerDropFn) *BlockFetcher { +func NewBlockFetcher(light bool, getHeader HeaderRetrievalFn, getBlock blockRetrievalFn, verifyHeader headerVerifierFn, + broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, chainFinalizedHeight chainFinalizedHeightFn, + insertHeaders headersInsertFn, insertChain chainInsertFn, dropPeer peerDropFn) *BlockFetcher { return &BlockFetcher{ - light: light, - notify: make(chan *blockAnnounce), - inject: make(chan *blockOrHeaderInject), - headerFilter: make(chan chan *headerFilterTask), - bodyFilter: make(chan chan *bodyFilterTask), - done: make(chan common.Hash), - quit: make(chan struct{}), - requeue: make(chan *blockOrHeaderInject), - announces: make(map[string]int), - announced: make(map[common.Hash][]*blockAnnounce), - fetching: make(map[common.Hash]*blockAnnounce), - fetched: make(map[common.Hash][]*blockAnnounce), - completing: make(map[common.Hash]*blockAnnounce), - queue: prque.New(nil), - queues: make(map[string]int), - queued: make(map[common.Hash]*blockOrHeaderInject), - getHeader: getHeader, - getBlock: getBlock, - verifyHeader: verifyHeader, - broadcastBlock: broadcastBlock, - chainHeight: chainHeight, - insertHeaders: insertHeaders, - insertChain: insertChain, - dropPeer: dropPeer, + light: light, + notify: make(chan *blockAnnounce), + inject: make(chan *blockOrHeaderInject), + headerFilter: make(chan chan *headerFilterTask), + bodyFilter: make(chan chan *bodyFilterTask), + done: make(chan common.Hash), + quit: make(chan struct{}), + requeue: make(chan *blockOrHeaderInject), + announces: make(map[string]int), + announced: make(map[common.Hash][]*blockAnnounce), + fetching: make(map[common.Hash]*blockAnnounce), + fetched: make(map[common.Hash][]*blockAnnounce), + completing: make(map[common.Hash]*blockAnnounce), + queue: prque.New(nil), + queues: make(map[string]int), + queued: make(map[common.Hash]*blockOrHeaderInject), + getHeader: getHeader, + getBlock: getBlock, + verifyHeader: verifyHeader, + broadcastBlock: broadcastBlock, + chainHeight: chainHeight, + chainFinalizedHeight: chainFinalizedHeight, + insertHeaders: insertHeaders, + insertChain: insertChain, + dropPeer: dropPeer, } } @@ -361,6 +368,7 @@ func (f *BlockFetcher) loop() { } // Import any queued blocks that could potentially fit height := f.chainHeight() + finalizedHeight := f.chainFinalizedHeight() for !f.queue.Empty() { op := f.queue.PopItem().(*blockOrHeaderInject) hash := op.hash() @@ -377,7 +385,7 @@ func (f *BlockFetcher) loop() { break } // Otherwise if fresh and still unknown, try and import - if (number+maxUncleDist < height) || (f.light && f.getHeader(hash) != nil) || (!f.light && f.getBlock(hash) != nil) { + if (number+maxUncleDist < height) || number <= finalizedHeight || (f.light && f.getHeader(hash) != nil) || (!f.light && f.getBlock(hash) != nil) { f.forgetBlock(hash) continue } @@ -408,7 +416,13 @@ func (f *BlockFetcher) loop() { } // If we have a valid block number, check that it's potentially useful if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist { - log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist) + log.Debug("Peer discarded announcement by distance", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist) + blockAnnounceDropMeter.Mark(1) + break + } + finalized := f.chainFinalizedHeight() + if notification.number <= finalized { + log.Debug("Peer discarded announcement by finality", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "finalized", finalized) blockAnnounceDropMeter.Mark(1) break } @@ -814,6 +828,14 @@ func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.B f.forgetHash(hash) return } + // Discard any block that is below the current finalized height + finalizedHeight := f.chainFinalizedHeight() + if number <= finalizedHeight { + log.Debug("Discarded delivered header or block, below or equal to finalized", "peer", peer, "number", number, "hash", hash, "finalized", finalizedHeight) + blockBroadcastDropMeter.Mark(1) + f.forgetHash(hash) + return + } // Schedule the block for future importing if _, ok := f.queued[hash]; !ok { op := &blockOrHeaderInject{origin: peer} diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index a7e1a2ffb1..79f4e4f40d 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -96,7 +96,9 @@ func newTester(light bool) *fetcherTester { blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis}, drops: make(map[string]bool), } - tester.fetcher = NewBlockFetcher(light, tester.getHeader, tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertHeaders, tester.insertChain, tester.dropPeer) + tester.fetcher = NewBlockFetcher(light, tester.getHeader, tester.getBlock, tester.verifyHeader, + tester.broadcastBlock, tester.chainHeight, tester.chainFinalizedHeight, tester.insertHeaders, + tester.insertChain, tester.dropPeer) tester.fetcher.Start() return tester @@ -138,6 +140,18 @@ func (f *fetcherTester) chainHeight() uint64 { return f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() } +func (f *fetcherTester) chainFinalizedHeight() uint64 { + f.lock.RLock() + defer f.lock.RUnlock() + if len(f.hashes) < 3 { + return 0 + } + if f.fetcher.light { + return f.headers[f.hashes[len(f.hashes)-3]].Number.Uint64() + } + return f.blocks[f.hashes[len(f.hashes)-3]].NumberU64() +} + // insertChain injects a new headers into the simulated chain. func (f *fetcherTester) insertHeaders(headers []*types.Header) (int, error) { f.lock.Lock() @@ -730,6 +744,67 @@ func testDistantAnnouncementDiscarding(t *testing.T, light bool) { } } +// Tests that announcements with numbers much lower or equal to the current finalized block +// head get discarded to prevent wasting resources on useless blocks from faulty peers. +func TestFullFinalizedAnnouncementDiscarding(t *testing.T) { + testFinalizedAnnouncementDiscarding(t, false) +} +func TestLightFinalizedAnnouncementDiscarding(t *testing.T) { + testFinalizedAnnouncementDiscarding(t, true) +} + +func testFinalizedAnnouncementDiscarding(t *testing.T, light bool) { + // Create a long chain to import and define the discard boundaries + hashes, blocks := makeChain(3*maxQueueDist, 0, genesis) + + head := hashes[len(hashes)/2] + justified := hashes[len(hashes)/2+1] + finalized := hashes[len(hashes)/2+2] + beforeFinalized := hashes[len(hashes)/2+3] + + low, equal := len(hashes)/2+3, len(hashes)/2+2 + + // Create a tester and simulate a head block being the middle of the above chain + tester := newTester(light) + + tester.lock.Lock() + tester.hashes = []common.Hash{beforeFinalized, finalized, justified, head} + tester.headers = map[common.Hash]*types.Header{ + beforeFinalized: blocks[beforeFinalized].Header(), + finalized: blocks[finalized].Header(), + justified: blocks[justified].Header(), + head: blocks[head].Header(), + } + tester.blocks = map[common.Hash]*types.Block{ + beforeFinalized: blocks[beforeFinalized], + finalized: blocks[finalized], + justified: blocks[justified], + head: blocks[head], + } + tester.lock.Unlock() + + headerFetcher := tester.makeHeaderFetcher("lower", blocks, -gatherSlack) + bodyFetcher := tester.makeBodyFetcher("lower", blocks, 0) + + fetching := make(chan struct{}, 2) + tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} } + + // Ensure that a block with a lower number than the finalized height is discarded + tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) + select { + case <-time.After(50 * time.Millisecond): + case <-fetching: + t.Fatalf("fetcher requested stale header") + } + // Ensure that a block with a same number of the finalized height is discarded + tester.fetcher.Notify("equal", hashes[equal], blocks[hashes[equal]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) + select { + case <-time.After(50 * time.Millisecond): + case <-fetching: + t.Fatalf("fetcher requested future header") + } +} + // Tests that peers announcing blocks with invalid numbers (i.e. not matching // the headers provided afterwards) get dropped as malicious. func TestFullInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, false) } diff --git a/eth/handler.go b/eth/handler.go index 88afa3afef..316c358a25 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -259,6 +259,13 @@ func newHandler(config *handlerConfig) (*handler, error) { heighter := func() uint64 { return h.chain.CurrentBlock().NumberU64() } + finalizeHeighter := func() uint64 { + fblock := h.chain.CurrentFinalBlock() + if fblock == nil { + return 0 + } + return fblock.Number.Uint64() + } inserter := func(blocks types.Blocks) (int, error) { // All the block fetcher activities should be disabled // after the transition. Print the warning log. @@ -322,7 +329,8 @@ func newHandler(config *handlerConfig) (*handler, error) { } return n, err } - h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer) + h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, + heighter, finalizeHeighter, nil, inserter, h.removePeer) fetchTx := func(peer string, hashes []common.Hash) error { p := h.peers.peer(peer) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index b26b6b5c93..53f057ba84 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -927,6 +927,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex }() defer cancel() + var intrinsicGas uint64 = 0 // Run the transaction with tracing enabled. if posa, ok := api.backend.Engine().(consensus.PoSA); ok && message.From() == vmctx.Coinbase && posa.IsSystemContract(message.To()) && message.GasPrice().Cmp(big.NewInt(0)) == 0 { @@ -935,6 +936,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex statedb.SetBalance(consensus.SystemAddress, big.NewInt(0)) statedb.AddBalance(vmctx.Coinbase, balance) } + intrinsicGas, _ = core.IntrinsicGas(message.Data(), message.AccessList(), false, true, true) } // Call Prepare to clear out the statedb access list @@ -942,6 +944,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())); err != nil { return nil, fmt.Errorf("tracing failed: %w", err) } + tracer.CaptureSystemTxEnd(intrinsicGas) return tracer.GetResult() } diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index 2af81a83c8..911998440a 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -212,6 +212,8 @@ func (t *jsTracer) CaptureTxStart(gasLimit uint64) { // transaction processing. func (t *jsTracer) CaptureTxEnd(restGas uint64) {} +func (t *jsTracer) CaptureSystemTxEnd(intrinsicGas uint64) {} + // CaptureStart implements the Tracer interface to initialize the tracing operation. func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { t.env = env diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 766ee4e4b9..91a2428d79 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -172,6 +172,8 @@ func (*AccessListTracer) CaptureTxStart(gasLimit uint64) {} func (*AccessListTracer) CaptureTxEnd(restGas uint64) {} +func (*AccessListTracer) CaptureSystemTxEnd(intrinsicGas uint64) {} + // AccessList returns the current accesslist maintained by the tracer. func (a *AccessListTracer) AccessList() types.AccessList { return a.list.accessList() diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 5034233a26..6ee34e397d 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -269,6 +269,10 @@ func (l *StructLogger) CaptureTxEnd(restGas uint64) { l.usedGas = l.gasLimit - restGas } +func (l *StructLogger) CaptureSystemTxEnd(intrinsicGas uint64) { + l.usedGas -= intrinsicGas +} + // StructLogs returns the captured log entries. func (l *StructLogger) StructLogs() []StructLog { return l.logs } @@ -398,6 +402,8 @@ func (*mdLogger) CaptureTxStart(gasLimit uint64) {} func (*mdLogger) CaptureTxEnd(restGas uint64) {} +func (*mdLogger) CaptureSystemTxEnd(intrinsicGas uint64) {} + // ExecutionResult groups all structured logs emitted by the EVM // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 37f1371d05..5ec0d2c44b 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -100,3 +100,5 @@ func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {} func (l *JSONLogger) CaptureTxEnd(restGas uint64) {} + +func (l *JSONLogger) CaptureSystemTxEnd(intrinsicGas uint64) {} diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 949504fff8..0eb64ecb0c 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -131,6 +131,8 @@ func (*fourByteTracer) CaptureTxStart(gasLimit uint64) {} func (*fourByteTracer) CaptureTxEnd(restGas uint64) {} +func (*fourByteTracer) CaptureSystemTxEnd(intrinsicGas uint64) {} + // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *fourByteTracer) GetResult() (json.RawMessage, error) { diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index ed26a79c80..883eaae595 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -234,6 +234,10 @@ func (t *callTracer) CaptureTxEnd(restGas uint64) { } } +func (t *callTracer) CaptureSystemTxEnd(intrinsicGas uint64) { + t.callstack[0].GasUsed -= intrinsicGas +} + // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *callTracer) GetResult() (json.RawMessage, error) { diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index 878e2dc9d6..4ccd865941 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -113,6 +113,12 @@ func (t *muxTracer) CaptureTxEnd(restGas uint64) { } } +func (t *muxTracer) CaptureSystemTxEnd(intrinsicGas uint64) { + for _, t := range t.tracers { + t.CaptureSystemTxEnd(intrinsicGas) + } +} + // GetResult returns an empty json object. func (t *muxTracer) GetResult() (json.RawMessage, error) { resObject := make(map[string]json.RawMessage) diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index c1035bd1b7..1fb87a795c 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -67,6 +67,8 @@ func (*noopTracer) CaptureTxStart(gasLimit uint64) {} func (*noopTracer) CaptureTxEnd(restGas uint64) {} +func (*noopTracer) CaptureSystemTxEnd(intrinsicGas uint64) {} + // GetResult returns an empty json object. func (t *noopTracer) GetResult() (json.RawMessage, error) { return json.RawMessage(`{}`), nil diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 6f32af09a9..7a22317a18 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -136,6 +136,8 @@ func (t *prestateTracer) CaptureTxStart(gasLimit uint64) { func (t *prestateTracer) CaptureTxEnd(restGas uint64) {} +func (t *prestateTracer) CaptureSystemTxEnd(intrinsicGas uint64) {} + // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *prestateTracer) GetResult() (json.RawMessage, error) { diff --git a/go.mod b/go.mod index 59643e09f6..54a33e5845 100644 --- a/go.mod +++ b/go.mod @@ -222,7 +222,7 @@ require ( github.com/schollz/progressbar/v3 v3.3.4 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344 // indirect + github.com/supranational/blst v0.3.11 // indirect github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect github.com/tidwall/gjson v1.10.2 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/go.sum b/go.sum index 83ca447367..7ac072ed4a 100644 --- a/go.sum +++ b/go.sum @@ -1409,8 +1409,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/supranational/blst v0.3.5/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344 h1:m+8fKfQwCAy1QjzINvKe/pYtLjo2dl59x2w9YSEJxuY= -github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= diff --git a/params/bootnodes.go b/params/bootnodes.go index 1ed9d74265..25f5ec8750 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -19,17 +19,15 @@ package params import "github.com/ethereum/go-ethereum/common" // MainnetBootnodes are the enode URLs of the P2P bootstrap nodes running on -// the main Ethereum network. +// the main BSC network. var MainnetBootnodes = []string{ - // Ethereum Foundation Go Bootnodes - "enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@18.138.108.67:30303", // bootnode-aws-ap-southeast-1-001 - "enode://22a8232c3abc76a16ae9d6c3b164f98775fe226f0917b0ca871128a74a8e9630b458460865bab457221f1d448dd9791d24c4e5d88786180ac185df813a68d4de@3.209.45.79:30303", // bootnode-aws-us-east-1-001 - "enode://ca6de62fce278f96aea6ec5a2daadb877e51651247cb96ee310a318def462913b653963c155a0ef6c7d50048bba6e6cea881130857413d9f50a621546b590758@34.255.23.113:30303", // bootnode-aws-eu-west-1-001 - "enode://279944d8dcd428dffaa7436f25ca0ca43ae19e7bcf94a8fb7d1641651f92d121e972ac2e8f381414b80cc8e5555811c2ec6e1a99bb009b3f53c4c69923e11bd8@35.158.244.151:30303", // bootnode-aws-eu-central-1-001 - "enode://8499da03c47d637b20eee24eec3c356c9a2e6148d6fe25ca195c7949ab8ec2c03e3556126b0d7ed644675e78c4318b08691b7b57de10e5f0d40d05b09238fa0a@52.187.207.27:30303", // bootnode-azure-australiaeast-001 - "enode://103858bdb88756c71f15e9b5e09b56dc1be52f0a5021d46301dbbfb7e130029cc9d0d6f73f693bc29b665770fff7da4d34f3c6379fe12721b5d7a0bcb5ca1fc1@191.234.162.198:30303", // bootnode-azure-brazilsouth-001 - "enode://715171f50508aba88aecd1250af392a45a330af91d7b90701c436b618c86aaa1589c9184561907bebbb56439b8f8787bc01f49a7c77276c58c1b09822d75e8e8@52.231.165.108:30303", // bootnode-azure-koreasouth-001 - "enode://5d6d7cd20d6da4bb83a1d28cadb5d409b64edf314c0335df658c1a54e32c7c4a7ab7823d57c39b6a757556e68ff1df17c748b698544a55cb488b52479a92b60f@104.42.217.25:30303", // bootnode-azure-westus-001 + // BNB chain Go Bootnodes + "enode://433c8bfdf53a3e2268ccb1b829e47f629793291cbddf0c76ae626da802f90532251fc558e2e0d10d6725e759088439bf1cd4714716b03a259a35d4b2e4acfa7f@52.69.102.73:30311", + "enode://571bee8fb902a625942f10a770ccf727ae2ba1bab2a2b64e121594a99c9437317f6166a395670a00b7d93647eacafe598b6bbcef15b40b6d1a10243865a3e80f@35.73.84.120:30311", + "enode://fac42fb0ba082b7d1eebded216db42161163d42e4f52c9e47716946d64468a62da4ba0b1cac0df5e8bf1e5284861d757339751c33d51dfef318be5168803d0b5@18.203.152.54:30311", + "enode://3063d1c9e1b824cfbb7c7b6abafa34faec6bb4e7e06941d218d760acdd7963b274278c5c3e63914bd6d1b58504c59ec5522c56f883baceb8538674b92da48a96@34.250.32.100:30311", + "enode://ad78c64a4ade83692488aa42e4c94084516e555d3f340d9802c2bf106a3df8868bc46eae083d2de4018f40e8d9a9952c32a0943cd68855a9bc9fd07aac982a6d@34.204.214.24:30311", + "enode://5db798deb67df75d073f8e2953dad283148133acb520625ea804c9c4ad09a35f13592a762d8f89056248f3889f6dcc33490c145774ea4ff2966982294909b37a@107.20.191.97:30311", } var V5Bootnodes = []string{ diff --git a/params/version.go b/params/version.go index 78a85e7323..d9476925ce 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 = 11 // Patch version component of the current release + VersionPatch = 12 // Patch version component of the current release VersionMeta = "" // Version metadata to append to the version string )