diff --git a/consensus/istanbul/backend.go b/consensus/istanbul/backend.go new file mode 100644 index 0000000000..46039438e7 --- /dev/null +++ b/consensus/istanbul/backend.go @@ -0,0 +1,73 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package istanbul + +import ( + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" +) + +// Backend provides application specific functions for Istanbul core +type Backend interface { + // Address returns the owner's address + Address() common.Address + + // Validators returns the validator set + Validators(proposal Proposal) ValidatorSet + + // EventMux returns the event mux in backend + EventMux() *event.TypeMux + + // Broadcast sends a message to all validators (include self) + Broadcast(valSet ValidatorSet, payload []byte) error + + // Gossip sends a message to all validators (exclude self) + Gossip(valSet ValidatorSet, payload []byte) error + + // Commit delivers an approved proposal to backend. + // The delivered proposal will be put into blockchain. + Commit(proposal Proposal, seals [][]byte) error + + // Verify verifies the proposal. If a consensus.ErrFutureBlock error is returned, + // the time difference of the proposal and current time is also returned. + Verify(Proposal) (time.Duration, error) + + // Sign signs input data with the backend's private key + Sign([]byte) ([]byte, error) + + // CheckSignature verifies the signature by checking if it's signed by + // the given validator + CheckSignature(data []byte, addr common.Address, sig []byte) error + + // LastProposal retrieves latest committed proposal and the address of proposer + LastProposal() (Proposal, common.Address) + + // HasPropsal checks if the combination of the given hash and height matches any existing blocks + HasPropsal(hash common.Hash, number *big.Int) bool + + // GetProposer returns the proposer of the given block height + GetProposer(number uint64) common.Address + + // ParentValidators returns the validator set of the given proposal's parent block + ParentValidators(proposal Proposal) ValidatorSet + + // HasBadBlock returns whether the block with the hash is a bad block + HasBadProposal(hash common.Hash) bool +} diff --git a/consensus/istanbul/config.go b/consensus/istanbul/config.go index e6cb762604..d2d44bf2e8 100644 --- a/consensus/istanbul/config.go +++ b/consensus/istanbul/config.go @@ -24,17 +24,15 @@ const ( ) type Config struct { - RequestTimeout uint64 `toml:",omitempty"` // The timeout for each Istanbul round in milliseconds. This timeout should be larger than BlockPauseTime. + RequestTimeout uint64 `toml:",omitempty"` // The timeout for each Istanbul round in milliseconds. BlockPeriod uint64 `toml:",omitempty"` // Default minimum difference between two consecutive block's timestamps in second - BlockPauseTime uint64 `toml:",omitempty"` // Pause time when zero tx in previous block, values should be larger than istanbul_block_period - ProposerPolicy ProposerPolicy `toml:",omitempty"` // The policy for proposer, the detail is not determined + ProposerPolicy ProposerPolicy `toml:",omitempty"` // The policy for proposer selection Epoch uint64 `toml:",omitempty"` // The number of blocks after which to checkpoint and reset the pending votes } var DefaultConfig = &Config{ RequestTimeout: 10000, BlockPeriod: 1, - BlockPauseTime: 2, ProposerPolicy: RoundRobin, Epoch: 30000, } diff --git a/consensus/istanbul/errors.go b/consensus/istanbul/errors.go new file mode 100644 index 0000000000..ed5b62342f --- /dev/null +++ b/consensus/istanbul/errors.go @@ -0,0 +1,29 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package istanbul + +import "errors" + +var ( + // ErrUnauthorizedAddress is returned when given address cannot be found in + // current validator set. + ErrUnauthorizedAddress = errors.New("unauthorized address") + // ErrStoppedEngine is returned if the engine is stopped + ErrStoppedEngine = errors.New("stopped engine") + // ErrStartedEngine is returned if the engine is already started + ErrStartedEngine = errors.New("started engine") +) diff --git a/consensus/istanbul/events.go b/consensus/istanbul/events.go new file mode 100644 index 0000000000..fb6e5bd9c2 --- /dev/null +++ b/consensus/istanbul/events.go @@ -0,0 +1,31 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package istanbul + +// RequestEvent is posted to propose a proposal +type RequestEvent struct { + Proposal Proposal +} + +// MessageEvent is posted for Istanbul engine communication +type MessageEvent struct { + Payload []byte +} + +// FinalCommittedEvent is posted when a proposal is committed +type FinalCommittedEvent struct { +} diff --git a/consensus/istanbul/types.go b/consensus/istanbul/types.go new file mode 100644 index 0000000000..86b586a2d0 --- /dev/null +++ b/consensus/istanbul/types.go @@ -0,0 +1,147 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package istanbul + +import ( + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Proposal supports retrieving height and serialized block to be used during Istanbul consensus. +type Proposal interface { + // Number retrieves the sequence number of this proposal. + Number() *big.Int + + // Hash retrieves the hash of this proposal. + Hash() common.Hash + + EncodeRLP(w io.Writer) error + + DecodeRLP(s *rlp.Stream) error + + String() string +} + +type Request struct { + Proposal Proposal +} + +// View includes a round number and a sequence number. +// Sequence is the block number we'd like to commit. +// Each round has a number and is composed by 3 steps: preprepare, prepare and commit. +// +// If the given block is not accepted by validators, a round change will occur +// and the validators start a new round with round+1. +type View struct { + Round *big.Int + Sequence *big.Int +} + +// EncodeRLP serializes b into the Ethereum RLP format. +func (v *View) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{v.Round, v.Sequence}) +} + +// DecodeRLP implements rlp.Decoder, and load the consensus fields from a RLP stream. +func (v *View) DecodeRLP(s *rlp.Stream) error { + var view struct { + Round *big.Int + Sequence *big.Int + } + + if err := s.Decode(&view); err != nil { + return err + } + v.Round, v.Sequence = view.Round, view.Sequence + return nil +} + +func (v *View) String() string { + return fmt.Sprintf("{Round: %d, Sequence: %d}", v.Round.Uint64(), v.Sequence.Uint64()) +} + +// Cmp compares v and y and returns: +// -1 if v < y +// 0 if v == y +// +1 if v > y +func (v *View) Cmp(y *View) int { + if v.Sequence.Cmp(y.Sequence) != 0 { + return v.Sequence.Cmp(y.Sequence) + } + if v.Round.Cmp(y.Round) != 0 { + return v.Round.Cmp(y.Round) + } + return 0 +} + +type Preprepare struct { + View *View + Proposal Proposal +} + +// EncodeRLP serializes b into the Ethereum RLP format. +func (b *Preprepare) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{b.View, b.Proposal}) +} + +// DecodeRLP implements rlp.Decoder, and load the consensus fields from a RLP stream. +func (b *Preprepare) DecodeRLP(s *rlp.Stream) error { + var preprepare struct { + View *View + Proposal *types.Block + } + + if err := s.Decode(&preprepare); err != nil { + return err + } + b.View, b.Proposal = preprepare.View, preprepare.Proposal + + return nil +} + +type Subject struct { + View *View + Digest common.Hash +} + +// EncodeRLP serializes b into the Ethereum RLP format. +func (b *Subject) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{b.View, b.Digest}) +} + +// DecodeRLP implements rlp.Decoder, and load the consensus fields from a RLP stream. +func (b *Subject) DecodeRLP(s *rlp.Stream) error { + var subject struct { + View *View + Digest common.Hash + } + + if err := s.Decode(&subject); err != nil { + return err + } + b.View, b.Digest = subject.View, subject.Digest + return nil +} + +func (b *Subject) String() string { + return fmt.Sprintf("{View: %v, Digest: %v}", b.View, b.Digest.String()) +} diff --git a/consensus/istanbul/types_test.go b/consensus/istanbul/types_test.go new file mode 100644 index 0000000000..cc23d486f1 --- /dev/null +++ b/consensus/istanbul/types_test.go @@ -0,0 +1,71 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package istanbul + +import ( + "math/big" + "testing" +) + +func TestViewCompare(t *testing.T) { + // test equality + srvView := &View{ + Sequence: big.NewInt(2), + Round: big.NewInt(1), + } + tarView := &View{ + Sequence: big.NewInt(2), + Round: big.NewInt(1), + } + if r := srvView.Cmp(tarView); r != 0 { + t.Errorf("source(%v) should be equal to target(%v): have %v, want %v", srvView, tarView, r, 0) + } + + // test larger Sequence + tarView = &View{ + Sequence: big.NewInt(1), + Round: big.NewInt(1), + } + if r := srvView.Cmp(tarView); r != 1 { + t.Errorf("source(%v) should be larger than target(%v): have %v, want %v", srvView, tarView, r, 1) + } + + // test larger Round + tarView = &View{ + Sequence: big.NewInt(2), + Round: big.NewInt(0), + } + if r := srvView.Cmp(tarView); r != 1 { + t.Errorf("source(%v) should be larger than target(%v): have %v, want %v", srvView, tarView, r, 1) + } + + // test smaller Sequence + tarView = &View{ + Sequence: big.NewInt(3), + Round: big.NewInt(1), + } + if r := srvView.Cmp(tarView); r != -1 { + t.Errorf("source(%v) should be smaller than target(%v): have %v, want %v", srvView, tarView, r, -1) + } + tarView = &View{ + Sequence: big.NewInt(2), + Round: big.NewInt(2), + } + if r := srvView.Cmp(tarView); r != -1 { + t.Errorf("source(%v) should be smaller than target(%v): have %v, want %v", srvView, tarView, r, -1) + } +} diff --git a/consensus/istanbul/utils.go b/consensus/istanbul/utils.go new file mode 100644 index 0000000000..22137b4148 --- /dev/null +++ b/consensus/istanbul/utils.go @@ -0,0 +1,60 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package istanbul + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +func RLPHash(v interface{}) (h common.Hash) { + hw := sha3.NewKeccak256() + rlp.Encode(hw, v) + hw.Sum(h[:0]) + return h +} + +// GetSignatureAddress gets the signer address from the signature +func GetSignatureAddress(data []byte, sig []byte) (common.Address, error) { + // 1. Keccak data + hashData := crypto.Keccak256(data) + // 2. Recover public key + pubkey, err := crypto.SigToPub(hashData, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*pubkey), nil +} + +func CheckValidatorSignature(valSet ValidatorSet, data []byte, sig []byte) (common.Address, error) { + // 1. Get signature address + signer, err := GetSignatureAddress(data, sig) + if err != nil { + log.Error("Failed to get signer address", "err", err) + return common.Address{}, err + } + + // 2. Check validator + if _, val := valSet.GetByAddress(signer); val != nil { + return val.Address(), nil + } + + return common.Address{}, ErrUnauthorizedAddress +} diff --git a/consensus/istanbul/validator.go b/consensus/istanbul/validator.go new file mode 100644 index 0000000000..e0d142866e --- /dev/null +++ b/consensus/istanbul/validator.go @@ -0,0 +1,80 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package istanbul + +import ( + "strings" + + "github.com/ethereum/go-ethereum/common" +) + +type Validator interface { + // Address returns address + Address() common.Address + + // String representation of Validator + String() string +} + +// ---------------------------------------------------------------------------- + +type Validators []Validator + +func (slice Validators) Len() int { + return len(slice) +} + +func (slice Validators) Less(i, j int) bool { + return strings.Compare(slice[i].String(), slice[j].String()) < 0 +} + +func (slice Validators) Swap(i, j int) { + slice[i], slice[j] = slice[j], slice[i] +} + +// ---------------------------------------------------------------------------- + +type ValidatorSet interface { + // Calculate the proposer + CalcProposer(lastProposer common.Address, round uint64) + // Return the validator size + Size() int + // Return the validator array + List() []Validator + // Get validator by index + GetByIndex(i uint64) Validator + // Get validator by given address + GetByAddress(addr common.Address) (int, Validator) + // Get current proposer + GetProposer() Validator + // Check whether the validator with given address is a proposer + IsProposer(address common.Address) bool + // Add validator + AddValidator(address common.Address) bool + // Remove validator + RemoveValidator(address common.Address) bool + // Copy validator set + Copy() ValidatorSet + // Get the maximum number of faulty nodes + F() int + // Get proposer policy + Policy() ProposerPolicy +} + +// ---------------------------------------------------------------------------- + +type ProposalSelector func(ValidatorSet, common.Address, uint64) Validator