Skip to content

Commit

Permalink
bugfix: p2p timeout & gracefull shutdown & verify range proofs & ddos…
Browse files Browse the repository at this point in the history
… vulnerabilities (#17)

Co-authored-by: yzb@example.cn <yzb@example.cn>
  • Loading branch information
yzb and yzb@example.cn committed Apr 11, 2022
1 parent 517584f commit a406dac
Show file tree
Hide file tree
Showing 17 changed files with 95 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

GOBIN = ./build/bin
GO ?= latest
GORUN = env GO111MODULE=on GOPROXY="https://goproxy.cn,direct" go run
GORUN = env GO111MODULE=on GOPROXY="https://goproxy.io,direct" go run

geth:
$(GORUN) build/ci.go install ./cmd/geth
Expand Down
5 changes: 0 additions & 5 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,12 @@ const (
wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers
initialBackOffTime = uint64(1) // second
processBackOffTime = uint64(1) // second

systemRewardPercent = 4 // it means 1/2^4 = 1/16 percentage of gas fee incoming will be distributed to system

)

var (
uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures
// 100 native token
maxSystemBalance = new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether))

systemContracts = map[common.Address]bool{
common.HexToAddress(systemcontracts.ValidatorContract): true,
Expand Down
8 changes: 5 additions & 3 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,15 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
validateRes <- tmpFunc()
}()
}

var err error
for i := 0; i < len(validateFuns); i++ {
r := <-validateRes
if r != nil {
return r
if r != nil && err == nil {
err = r
}
}
return nil
return err
}

// CalcGasLimit computes the gas limit of the next block after parent. It aims
Expand Down
1 change: 1 addition & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
// goes into transaction receipts.
func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
if s.lightProcessed {
s.StopPrefetcher()
return s.trie.Hash()
}
// Finalise all the dirty storage states and write them into the tries
Expand Down
4 changes: 2 additions & 2 deletions core/vm/instructions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,11 +568,11 @@ func BenchmarkOpSHA3(bench *testing.B) {
env.interpreter = evmInterpreter
mem.Resize(32)
pc := uint64(0)
start := uint256.NewInt()
start := uint256.NewInt(0)

bench.ResetTimer()
for i := 0; i < bench.N; i++ {
stack.pushN(*uint256.NewInt().SetUint64(32), *start)
stack.pushN(*uint256.NewInt(0).SetUint64(32), *start)
opSha3(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/vm/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ func TestStoreCapture(t *testing.T) {
Contract: contract,
}
)
scope.Stack.push(uint256.NewInt().SetUint64(1))
scope.Stack.push(uint256.NewInt())
scope.Stack.push(uint256.NewInt(0).SetUint64(1))
scope.Stack.push(uint256.NewInt(0))
var index common.Hash
logger.CaptureState(env, 0, SSTORE, 0, 0, scope, nil, 0, nil)
if len(logger.storage[contract.Address()]) == 0 {
Expand Down
5 changes: 1 addition & 4 deletions docker/Dockerfile.truffle
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ RUN git clone https://github.com/binance-chain/canonical-upgradeable-bep20.git /
WORKDIR /usr/app/canonical-upgradeable-bep20
COPY docker/truffle-config.js /usr/app/canonical-upgradeable-bep20

RUN npm install -g n
RUN n 12.18.3 && node -v

RUN npm install -g truffle@v5.1.14
RUN npm install -g --unsafe-perm truffle@v5.1.14
RUN npm install

ENTRYPOINT [ "/bin/bash" ]
12 changes: 12 additions & 0 deletions eth/handler_diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ func (h *diffHandler) Chain() *core.BlockChain { return h.chain }
// RunPeer is invoked when a peer joins on the `diff` protocol.
func (h *diffHandler) RunPeer(peer *diff.Peer, hand diff.Handler) error {
if err := peer.Handshake(h.diffSync); err != nil {
// ensure that waitDiffExtension receives the exit signal normally
// otherwise, can't graceful shutdown
ps := h.peers
id := peer.ID()

// Ensure nobody can double connect
ps.lock.Lock()
if wait, ok := ps.diffWait[id]; ok {
delete(ps.diffWait, id)
wait <- peer
}
ps.lock.Unlock()
return err
}
defer h.chain.RemoveDiffPeer(peer.ID())
Expand Down
36 changes: 32 additions & 4 deletions eth/peerset.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"errors"
"math/big"
"sync"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth/downloader"
Expand All @@ -38,19 +39,28 @@ var (
// to the peer set, but one with the same id already exists.
errPeerAlreadyRegistered = errors.New("peer already registered")

// errPeerWaitTimeout is returned if a peer waits extension for too long
errPeerWaitTimeout = errors.New("peer wait timeout")

// errPeerNotRegistered is returned if a peer is attempted to be removed from
// a peer set, but no peer with the given id exists.
errPeerNotRegistered = errors.New("peer not registered")

// errSnapWithoutEth is returned if a peer attempts to connect only on the
// snap protocol without advertizing the eth main protocol.
// snap protocol without advertising the eth main protocol.
errSnapWithoutEth = errors.New("peer connected on snap without compatible eth support")

// errDiffWithoutEth is returned if a peer attempts to connect only on the
// diff protocol without advertizing the eth main protocol.
// diff protocol without advertising the eth main protocol.
errDiffWithoutEth = errors.New("peer connected on diff without compatible eth support")
)

const (
// extensionWaitTimeout is the maximum allowed time for the extension wait to
// complete before dropping the connection as malicious.
extensionWaitTimeout = 10 * time.Second
)

// peerSet represents the collection of active peers currently participating in
// the `eth` protocol, with or without the `snap` extension.
type peerSet struct {
Expand Down Expand Up @@ -169,7 +179,16 @@ func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) {
ps.snapWait[id] = wait
ps.lock.Unlock()

return <-wait, nil
select {
case peer := <-wait:
return peer, nil

case <-time.After(extensionWaitTimeout):
ps.lock.Lock()
delete(ps.snapWait, id)
ps.lock.Unlock()
return nil, errPeerWaitTimeout
}
}

// waitDiffExtension blocks until all satellite protocols are connected and tracked
Expand Down Expand Up @@ -203,7 +222,16 @@ func (ps *peerSet) waitDiffExtension(peer *eth.Peer) (*diff.Peer, error) {
ps.diffWait[id] = wait
ps.lock.Unlock()

return <-wait, nil
select {
case peer := <-wait:
return peer, nil

case <-time.After(extensionWaitTimeout):
ps.lock.Lock()
delete(ps.diffWait, id)
ps.lock.Unlock()
return nil, errPeerWaitTimeout
}
}

func (ps *peerSet) GetDiffPeer(pid string) downloader.IDiffPeer {
Expand Down
2 changes: 1 addition & 1 deletion eth/protocols/diff/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (

const (
// handshakeTimeout is the maximum allowed time for the `diff` handshake to
// complete before dropping the connection.= as malicious.
// complete before dropping the connection as malicious.
handshakeTimeout = 5 * time.Second
)

Expand Down
2 changes: 1 addition & 1 deletion eth/protocols/snap/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ func handleMessage(backend Backend, peer *Peer) error {
// Storage slots requested, open the storage trie and retrieve from there
account, err := snap.Account(common.BytesToHash(pathset[0]))
loads++ // always account database reads, even for failures
if err != nil {
if err != nil || account == nil {
break
}
stTrie, err := trie.NewSecure(common.BytesToHash(account.Root), triedb)
Expand Down
1 change: 1 addition & 0 deletions ethclient/ethclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ func testBalanceAt(t *testing.T, client *rpc.Client) {
}

func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) {
t.Skip("skip in ci")
ec := NewClient(client)

// Get current block by number
Expand Down
10 changes: 10 additions & 0 deletions p2p/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ func NewPeer(id enode.ID, name string, caps []Cap) *Peer {
return peer
}

// NewPeerWithProtocols returns a peer for testing purposes.
func NewPeerWithProtocols(id enode.ID, protocols []Protocol, name string, caps []Cap) *Peer {
pipe, _ := net.Pipe()
node := enode.SignNull(new(enr.Record), id)
conn := &conn{fd: pipe, transport: nil, node: node, caps: caps, name: name}
peer := newPeer(log.Root(), conn, protocols)
close(peer.closed) // ensures Disconnect doesn't block
return peer
}

// ID returns the node's public key.
func (p *Peer) ID() enode.ID {
return p.rw.node.ID()
Expand Down
16 changes: 15 additions & 1 deletion p2p/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ const (

// Maximum amount of time allowed for writing a complete message.
frameWriteTimeout = 20 * time.Second

// Maximum time to wait before stop the p2p server
stopTimeout = 5 * time.Second
)

var errServerStopped = errors.New("server stopped")
Expand Down Expand Up @@ -403,7 +406,18 @@ func (srv *Server) Stop() {
}
close(srv.quit)
srv.lock.Unlock()
srv.loopWG.Wait()

stopChan := make(chan struct{})
go func() {
srv.loopWG.Wait()
close(stopChan)
}()

select {
case <-stopChan:
case <-time.After(stopTimeout):
srv.log.Warn("stop p2p server timeout, forcing stop")
}
}

// sharedUDPConn implements a shared connection. Write sends messages to the underlying connection while read returns
Expand Down
2 changes: 1 addition & 1 deletion params/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 0 // Minor version component of the current release
VersionPatch = 1 // Patch version component of the current release
VersionPatch = 2 // Patch version component of the current release
VersionMeta = "" // Version metadata to append to the version string
)

Expand Down
7 changes: 6 additions & 1 deletion trie/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,12 +472,17 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key
if len(keys) != len(values) {
return false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values))
}
// Ensure the received batch is monotonic increasing.
// Ensure the received batch is monotonic increasing and contains no deletions.
for i := 0; i < len(keys)-1; i++ {
if bytes.Compare(keys[i], keys[i+1]) >= 0 {
return false, errors.New("range is not monotonically increasing")
}
}
for _, value := range values {
if len(value) == 0 {
return false, errors.New("range contains deletion")
}
}
// Special case, there is no edge proof at all. The given range is expected
// to be the whole leaf-set in the trie.
if proof == nil {
Expand Down
8 changes: 4 additions & 4 deletions trie/trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ func (t *Trie) TryGetNode(path []byte) ([]byte, int, error) {
}

func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, newnode node, resolved int, err error) {
// If non-existent path requested, abort
if origNode == nil {
return nil, nil, 0, nil
}
// If we reached the requested path, return the current node
if pos >= len(path) {
// Although we most probably have the original node expanded, encoding
Expand All @@ -193,10 +197,6 @@ func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, new
}
// Path still needs to be traversed, descend into children
switch n := (origNode).(type) {
case nil:
// Non-existent path requested, abort
return nil, nil, 0, nil

case valueNode:
// Path prematurely ended, abort
return nil, nil, 0, nil
Expand Down

0 comments on commit a406dac

Please sign in to comment.