2018-10-03 17:14:48 -07:00
|
|
|
package raft
|
|
|
|
|
|
|
|
import (
|
2019-12-04 06:24:32 -08:00
|
|
|
"fmt"
|
2018-10-03 17:14:48 -07:00
|
|
|
"math/big"
|
2019-12-04 06:24:32 -08:00
|
|
|
"strings"
|
2018-11-29 01:56:02 -08:00
|
|
|
"testing"
|
2018-10-03 17:14:48 -07:00
|
|
|
"time"
|
|
|
|
|
2019-12-04 06:24:32 -08:00
|
|
|
"github.com/coreos/etcd/raft/raftpb"
|
2020-05-11 13:15:27 -07:00
|
|
|
mapset "github.com/deckarep/golang-set"
|
2018-10-03 17:14:48 -07:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2018-11-29 01:56:02 -08:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
2018-10-03 17:14:48 -07:00
|
|
|
"github.com/ethereum/go-ethereum/node"
|
2020-05-11 13:15:27 -07:00
|
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
2018-10-03 17:14:48 -07:00
|
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
|
|
)
|
|
|
|
|
2019-12-04 06:24:32 -08:00
|
|
|
const TEST_URL = "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404"
|
|
|
|
|
2018-11-29 01:56:02 -08:00
|
|
|
func TestSignHeader(t *testing.T) {
|
2018-10-03 17:14:48 -07:00
|
|
|
//create only what we need to test the seal
|
|
|
|
var testRaftId uint16 = 5
|
|
|
|
config := &node.Config{Name: "unit-test", DataDir: ""}
|
|
|
|
|
|
|
|
nodeKey := config.NodeKey()
|
|
|
|
|
2018-11-29 01:56:02 -08:00
|
|
|
raftProtocolManager := &ProtocolManager{raftId: testRaftId}
|
2018-10-03 17:14:48 -07:00
|
|
|
raftService := &RaftService{nodeKey: nodeKey, raftProtocolManager: raftProtocolManager}
|
2018-11-29 01:56:02 -08:00
|
|
|
minter := minter{eth: raftService}
|
2018-10-03 17:14:48 -07:00
|
|
|
|
|
|
|
//create some fake header to sign
|
|
|
|
fakeParentHash := common.HexToHash("0xc2c1dc1be8054808c69e06137429899d")
|
|
|
|
|
|
|
|
header := &types.Header{
|
|
|
|
ParentHash: fakeParentHash,
|
|
|
|
Number: big.NewInt(1),
|
|
|
|
Difficulty: big.NewInt(1),
|
2018-10-09 20:28:19 -07:00
|
|
|
GasLimit: uint64(0),
|
|
|
|
GasUsed: uint64(0),
|
2018-10-03 17:14:48 -07:00
|
|
|
Coinbase: minter.coinbase,
|
2020-04-29 07:50:56 -07:00
|
|
|
Time: uint64(time.Now().UnixNano()),
|
2018-10-03 17:14:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
headerHash := header.Hash()
|
|
|
|
extraDataBytes := minter.buildExtraSeal(headerHash)
|
|
|
|
var seal *extraSeal
|
|
|
|
err := rlp.DecodeBytes(extraDataBytes[:], &seal)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unable to decode seal: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check raftId
|
2018-11-29 01:56:02 -08:00
|
|
|
sealRaftId, err := hexutil.DecodeUint64("0x" + string(seal.RaftId)) //add the 0x prefix
|
2018-10-03 17:14:48 -07:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unable to get RaftId: %s", err.Error())
|
|
|
|
}
|
2018-11-29 01:56:02 -08:00
|
|
|
if sealRaftId != uint64(testRaftId) {
|
2018-10-03 17:14:48 -07:00
|
|
|
t.Errorf("RaftID does not match. Expected: %d, Actual: %d", testRaftId, sealRaftId)
|
|
|
|
}
|
|
|
|
|
|
|
|
//Identify who signed it
|
2018-11-29 01:56:02 -08:00
|
|
|
sig := seal.Signature
|
2018-10-03 17:14:48 -07:00
|
|
|
pubKey, err := crypto.SigToPub(headerHash.Bytes(), sig)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unable to get public key from signature: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
//Compare derived public key to original public key
|
|
|
|
if pubKey.X.Cmp(nodeKey.X) != 0 {
|
|
|
|
t.Errorf("Signature incorrect!")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-12-04 06:24:32 -08:00
|
|
|
|
|
|
|
func TestAddLearner_whenTypical(t *testing.T) {
|
|
|
|
|
|
|
|
raftService := newTestRaftService(t, 1, []uint64{1}, []uint64{})
|
|
|
|
|
|
|
|
propPeer := func() {
|
|
|
|
raftid, err := raftService.raftProtocolManager.ProposeNewPeer(TEST_URL, true)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("propose new peer failed %v\n", err)
|
|
|
|
}
|
|
|
|
if raftid != raftService.raftProtocolManager.raftId+1 {
|
|
|
|
t.Errorf("1. wrong raft id. expected %d got %d\n", raftService.raftProtocolManager.raftId+1, raftid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
go propPeer()
|
|
|
|
select {
|
|
|
|
case confChange := <-raftService.raftProtocolManager.confChangeProposalC:
|
|
|
|
if confChange.Type != raftpb.ConfChangeAddLearnerNode {
|
|
|
|
t.Errorf("expected ConfChangeAddLearnerNode but got %s", confChange.Type.String())
|
|
|
|
}
|
|
|
|
if uint16(confChange.NodeID) != raftService.raftProtocolManager.raftId+1 {
|
|
|
|
t.Errorf("2. wrong raft id. expected %d got %d\n", raftService.raftProtocolManager.raftId+1, uint16(confChange.NodeID))
|
|
|
|
}
|
|
|
|
case <-time.After(time.Millisecond * 200):
|
2020-05-11 13:15:27 -07:00
|
|
|
t.Errorf("add learner conf change not received")
|
2019-12-04 06:24:32 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPromoteLearnerToPeer_whenTypical(t *testing.T) {
|
|
|
|
learnerRaftId := uint16(3)
|
|
|
|
raftService := newTestRaftService(t, 2, []uint64{2}, []uint64{uint64(learnerRaftId)})
|
|
|
|
promoteToPeer := func() {
|
|
|
|
ok, err := raftService.raftProtocolManager.PromoteToPeer(learnerRaftId)
|
|
|
|
if err != nil || !ok {
|
|
|
|
t.Errorf("promote learner to peer failed %v\n", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
go promoteToPeer()
|
|
|
|
select {
|
|
|
|
case confChange := <-raftService.raftProtocolManager.confChangeProposalC:
|
|
|
|
if confChange.Type != raftpb.ConfChangeAddNode {
|
|
|
|
t.Errorf("expected ConfChangeAddNode but got %s", confChange.Type.String())
|
|
|
|
}
|
|
|
|
if uint16(confChange.NodeID) != learnerRaftId {
|
|
|
|
t.Errorf("2. wrong raft id. expected %d got %d\n", learnerRaftId, uint16(confChange.NodeID))
|
|
|
|
}
|
|
|
|
case <-time.After(time.Millisecond * 200):
|
2020-05-11 13:15:27 -07:00
|
|
|
t.Errorf("add learner conf change not received")
|
2019-12-04 06:24:32 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAddLearnerOrPeer_fromLearner(t *testing.T) {
|
|
|
|
|
|
|
|
raftService := newTestRaftService(t, 3, []uint64{2}, []uint64{3})
|
|
|
|
|
|
|
|
_, err := raftService.raftProtocolManager.ProposeNewPeer(TEST_URL, true)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("learner should not be allowed to add learner or peer")
|
|
|
|
}
|
|
|
|
|
2020-05-11 13:15:27 -07:00
|
|
|
if err != nil && !strings.Contains(err.Error(), "learner node can't add peer or learner") {
|
2019-12-04 06:24:32 -08:00
|
|
|
t.Errorf("expect error message: propose new peer failed, got: %v\n", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = raftService.raftProtocolManager.ProposeNewPeer(TEST_URL, false)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("learner should not be allowed to add learner or peer")
|
|
|
|
}
|
|
|
|
|
2020-05-11 13:15:27 -07:00
|
|
|
if err != nil && !strings.Contains(err.Error(), "learner node can't add peer or learner") {
|
2019-12-04 06:24:32 -08:00
|
|
|
t.Errorf("expect error message: propose new peer failed, got: %v\n", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPromoteLearnerToPeer_fromLearner(t *testing.T) {
|
|
|
|
learnerRaftId := uint16(3)
|
|
|
|
raftService := newTestRaftService(t, 2, []uint64{1}, []uint64{2, uint64(learnerRaftId)})
|
|
|
|
|
|
|
|
_, err := raftService.raftProtocolManager.PromoteToPeer(learnerRaftId)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("learner should not be allowed to promote to peer")
|
|
|
|
}
|
|
|
|
|
2020-05-11 13:15:27 -07:00
|
|
|
if err != nil && !strings.Contains(err.Error(), "learner node can't promote to peer") {
|
2019-12-04 06:24:32 -08:00
|
|
|
t.Errorf("expect error message: propose new peer failed, got: %v\n", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func enodeId(id string, ip string, raftPort int) string {
|
|
|
|
return fmt.Sprintf("enode://%s@%s?discport=0&raftport=%d", id, ip, raftPort)
|
|
|
|
}
|
|
|
|
|
|
|
|
func peerList(url string) (error, []*enode.Node) {
|
|
|
|
var nodes []*enode.Node
|
|
|
|
node, err := enode.ParseV4(url)
|
|
|
|
if err != nil {
|
2020-05-11 13:15:27 -07:00
|
|
|
return fmt.Errorf("Node URL %s: %v\n", url, err), nil
|
2019-12-04 06:24:32 -08:00
|
|
|
}
|
|
|
|
nodes = append(nodes, node)
|
|
|
|
return nil, nodes
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTestRaftService(t *testing.T, raftId uint16, nodes []uint64, learners []uint64) *RaftService {
|
|
|
|
//create only what we need to test add learner node
|
|
|
|
config := &node.Config{Name: "unit-test", DataDir: ""}
|
|
|
|
nodeKey := config.NodeKey()
|
|
|
|
enodeIdStr := fmt.Sprintf("%x", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:])
|
|
|
|
url := enodeId(enodeIdStr, "127.0.0.1:21001", 50401)
|
|
|
|
err, peers := peerList(url)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("getting peers failed %v", err)
|
|
|
|
}
|
|
|
|
raftProtocolManager := &ProtocolManager{
|
|
|
|
raftId: raftId,
|
|
|
|
bootstrapNodes: peers,
|
|
|
|
confChangeProposalC: make(chan raftpb.ConfChange),
|
|
|
|
removedPeers: mapset.NewSet(),
|
|
|
|
confState: raftpb.ConfState{Nodes: nodes, Learners: learners},
|
|
|
|
}
|
|
|
|
raftService := &RaftService{nodeKey: nodeKey, raftProtocolManager: raftProtocolManager}
|
|
|
|
return raftService
|
|
|
|
}
|