quorum/raft/minter_test.go

204 lines
6.5 KiB
Go

package raft
import (
"fmt"
"math/big"
"strings"
"testing"
"time"
"github.com/coreos/etcd/raft/raftpb"
mapset "github.com/deckarep/golang-set"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp"
)
const TEST_URL = "enode://3d9ca5956b38557aba991e31cf510d4df641dce9cc26bfeb7de082f0c07abb6ede3a58410c8f249dabeecee4ad3979929ac4c7c496ad20b8cfdd061b7401b4f5@127.0.0.1:21003?discport=0&raftport=50404"
func TestSignHeader(t *testing.T) {
//create only what we need to test the seal
var testRaftId uint16 = 5
config := &node.Config{Name: "unit-test", DataDir: ""}
nodeKey := config.NodeKey()
raftProtocolManager := &ProtocolManager{raftId: testRaftId}
raftService := &RaftService{nodeKey: nodeKey, raftProtocolManager: raftProtocolManager}
minter := minter{eth: raftService}
//create some fake header to sign
fakeParentHash := common.HexToHash("0xc2c1dc1be8054808c69e06137429899d")
header := &types.Header{
ParentHash: fakeParentHash,
Number: big.NewInt(1),
Difficulty: big.NewInt(1),
GasLimit: uint64(0),
GasUsed: uint64(0),
Coinbase: minter.coinbase,
Time: uint64(time.Now().UnixNano()),
}
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
sealRaftId, err := hexutil.DecodeUint64("0x" + string(seal.RaftId)) //add the 0x prefix
if err != nil {
t.Errorf("Unable to get RaftId: %s", err.Error())
}
if sealRaftId != uint64(testRaftId) {
t.Errorf("RaftID does not match. Expected: %d, Actual: %d", testRaftId, sealRaftId)
}
//Identify who signed it
sig := seal.Signature
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!")
}
}
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):
t.Errorf("add learner conf change not received")
}
}
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):
t.Errorf("add learner conf change not received")
}
}
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")
}
if err != nil && !strings.Contains(err.Error(), "learner node can't add peer or learner") {
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")
}
if err != nil && !strings.Contains(err.Error(), "learner node can't add peer or learner") {
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")
}
if err != nil && !strings.Contains(err.Error(), "learner node can't promote to peer") {
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 {
return fmt.Errorf("Node URL %s: %v\n", url, err), nil
}
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
}