gecko/snow/consensus/snowman/consensus_test.go

656 lines
13 KiB
Go
Raw Normal View History

2020-03-10 12:20:34 -07:00
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package snowman
import (
"fmt"
"math/rand"
"testing"
"github.com/prometheus/client_golang/prometheus"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/snow/consensus/snowball"
)
2020-04-04 14:47:44 -07:00
var (
2020-04-04 15:45:01 -07:00
GenesisID = ids.Empty.Prefix(0)
Genesis = &TestBlock{
id: GenesisID,
2020-04-04 14:47:44 -07:00
status: choices.Accepted,
}
2020-04-04 15:45:01 -07:00
Tests = []func(*testing.T, Factory){
ParamsTest,
}
2020-04-04 14:47:44 -07:00
)
2020-04-04 15:45:01 -07:00
// Make sure that the passed in params are returned properly from a call to
// Parameters
2020-03-10 12:20:34 -07:00
func ParamsTest(t *testing.T, factory Factory) {
sm := factory.New()
ctx := snow.DefaultContextTest()
params := snowball.Parameters{
2020-04-04 15:45:01 -07:00
Metrics: prometheus.NewRegistry(),
K: 1,
Alpha: 1,
BetaVirtuous: 3,
BetaRogue: 5,
ConcurrentRepolls: 1,
}
sm.Initialize(ctx, params, GenesisID)
if p := sm.Parameters(); p != params {
t.Fatalf("Wrong returned parameters")
2020-03-10 12:20:34 -07:00
}
}
func AddTest(t *testing.T, factory Factory) {
sm := factory.New()
2020-04-04 15:45:01 -07:00
ctx := snow.DefaultContextTest()
2020-03-10 12:20:34 -07:00
params := snowball.Parameters{
2020-04-04 15:45:01 -07:00
Metrics: prometheus.NewRegistry(),
K: 1,
Alpha: 1,
BetaVirtuous: 3,
BetaRogue: 5,
ConcurrentRepolls: 1,
2020-03-10 12:20:34 -07:00
}
2020-04-04 15:45:01 -07:00
sm.Initialize(ctx, params, GenesisID)
2020-03-10 12:20:34 -07:00
2020-04-04 15:45:01 -07:00
if pref := sm.Preference(); !pref.Equals(GenesisID) {
t.Fatalf("Wrong preference. Expected %s, got %s", GenesisID, pref)
2020-03-10 12:20:34 -07:00
}
2020-04-04 15:45:01 -07:00
block0 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.Empty.Prefix(1),
}
2020-04-04 15:45:01 -07:00
// Adding to the previous preference will update the preference
sm.Add(block0)
if pref := sm.Preference(); !pref.Equals(block0.id) {
t.Fatalf("Wrong preference. Expected %s, got %s", block0.id, pref)
2020-03-10 12:20:34 -07:00
}
2020-04-04 15:45:01 -07:00
block1 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.Empty.Prefix(2),
}
2020-04-04 15:45:01 -07:00
// Adding to something other than the previous preference won't update the
// preference
sm.Add(block1)
if pref := sm.Preference(); !pref.Equals(block0.id) {
t.Fatalf("Wrong preference. Expected %s, got %s", block0.id, pref)
2020-03-10 12:20:34 -07:00
}
2020-04-04 15:45:01 -07:00
block2 := &TestBlock{
parent: block0,
2020-03-10 12:20:34 -07:00
id: ids.Empty.Prefix(3),
}
2020-04-04 15:45:01 -07:00
// Adding to the previous preference will update the preference
sm.Add(block2)
if pref := sm.Preference(); !pref.Equals(block2.id) {
t.Fatalf("Wrong preference. Expected %s, got %s", block2.id, pref)
2020-03-10 12:20:34 -07:00
}
2020-04-04 15:45:01 -07:00
block3 := &TestBlock{
2020-04-04 15:07:14 -07:00
parent: &TestBlock{id: ids.Empty.Prefix(4)},
2020-03-10 12:20:34 -07:00
id: ids.Empty.Prefix(5),
}
2020-04-04 15:45:01 -07:00
// Adding a block with an unknown parent means the parent must have already
// been rejected. Therefore the block should be immediately rejected
sm.Add(block3)
if pref := sm.Preference(); !pref.Equals(block2.id) {
t.Fatalf("Wrong preference. Expected %s, got %s", block2.id, pref)
} else if status := block3.Status(); status != choices.Rejected {
t.Fatalf("Should have rejected the block")
2020-03-10 12:20:34 -07:00
}
}
func CollectTest(t *testing.T, factory Factory) {
sm := factory.New()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
}
sm.Initialize(snow.DefaultContextTest(), params, Genesis.ID())
2020-04-04 15:07:14 -07:00
dep1 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.Empty.Prefix(2),
}
sm.Add(dep1)
2020-04-04 15:07:14 -07:00
dep0 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.Empty.Prefix(1),
}
sm.Add(dep0)
2020-04-04 15:07:14 -07:00
dep2 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: dep0,
id: ids.Empty.Prefix(3),
}
sm.Add(dep2)
2020-04-04 15:07:14 -07:00
dep3 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: dep0,
id: ids.Empty.Prefix(4),
}
sm.Add(dep3)
// Current graph structure:
// G
// / \
// 0 1
// / \
// 2 3
// Tail = 1
dep2_2 := ids.Bag{}
dep2_2.AddCount(dep2.id, 2)
sm.RecordPoll(dep2_2)
// Current graph structure:
// G
// / \
// 0 1
// / \
// 2 3
// Tail = 2
if sm.Finalized() {
t.Fatalf("Finalized too early")
} else if !dep2.id.Equals(sm.Preference()) {
t.Fatalf("Wrong preference listed")
}
dep3_2 := ids.Bag{}
dep3_2.AddCount(dep3.id, 2)
sm.RecordPoll(dep3_2)
// Current graph structure:
// 0
// / \
// 2 3
// Tail = 2
if sm.Finalized() {
t.Fatalf("Finalized too early")
} else if !dep2.id.Equals(sm.Preference()) {
t.Fatalf("Wrong preference listed")
}
sm.RecordPoll(dep2_2)
// Current graph structure:
// 0
// / \
// 2 3
// Tail = 2
if sm.Finalized() {
t.Fatalf("Finalized too early")
} else if !dep2.id.Equals(sm.Preference()) {
t.Fatalf("Wrong preference listed")
}
sm.RecordPoll(dep2_2)
// Current graph structure:
// 2
// Tail = 2
if !sm.Finalized() {
t.Fatalf("Finalized too late")
} else if !dep2.id.Equals(sm.Preference()) {
t.Fatalf("Wrong preference listed")
}
if dep0.Status() != choices.Accepted {
t.Fatalf("Should have accepted")
} else if dep1.Status() != choices.Rejected {
t.Fatalf("Should have rejected")
} else if dep2.Status() != choices.Accepted {
t.Fatalf("Should have accepted")
} else if dep3.Status() != choices.Rejected {
t.Fatalf("Should have rejected")
}
}
func CollectNothingTest(t *testing.T, factory Factory) {
sm := factory.New()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
}
sm.Initialize(snow.DefaultContextTest(), params, Genesis.ID())
// Current graph structure:
// G
// Tail = G
genesis1 := ids.Bag{}
genesis1.AddCount(Genesis.ID(), 1)
sm.RecordPoll(genesis1)
// Current graph structure:
// G
// Tail = G
if !sm.Finalized() {
t.Fatalf("Finalized too late")
} else if !Genesis.ID().Equals(sm.Preference()) {
t.Fatalf("Wrong preference listed")
}
}
func CollectTransRejectTest(t *testing.T, factory Factory) {
sm := factory.New()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
}
sm.Initialize(snow.DefaultContextTest(), params, Genesis.ID())
2020-04-04 15:07:14 -07:00
dep1 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.Empty.Prefix(2),
}
sm.Add(dep1)
2020-04-04 15:07:14 -07:00
dep0 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.Empty.Prefix(1),
}
sm.Add(dep0)
2020-04-04 15:07:14 -07:00
dep2 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: dep0,
id: ids.Empty.Prefix(3),
}
sm.Add(dep2)
// Current graph structure:
// G
// / \
// 0 1
// /
// 2
// Tail = 1
dep1_1 := ids.Bag{}
dep1_1.AddCount(dep1.id, 1)
sm.RecordPoll(dep1_1)
sm.RecordPoll(dep1_1)
// Current graph structure:
// 1
// Tail = 1
if !sm.Finalized() {
t.Fatalf("Finalized too late")
} else if !dep1.id.Equals(sm.Preference()) {
t.Fatalf("Wrong preference listed")
}
if dep0.Status() != choices.Rejected {
t.Fatalf("Should have rejected")
} else if dep1.Status() != choices.Accepted {
t.Fatalf("Should have accepted")
} else if dep2.Status() != choices.Rejected {
t.Fatalf("Should have rejected")
}
}
func CollectTransResetTest(t *testing.T, factory Factory) {
sm := factory.New()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
}
sm.Initialize(snow.DefaultContextTest(), params, Genesis.ID())
2020-04-04 15:07:14 -07:00
dep1 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.Empty.Prefix(2),
status: choices.Processing,
}
sm.Add(dep1)
2020-04-04 15:07:14 -07:00
dep0 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.Empty.Prefix(1),
status: choices.Processing,
}
sm.Add(dep0)
2020-04-04 15:07:14 -07:00
dep2 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: dep0,
id: ids.Empty.Prefix(3),
status: choices.Processing,
}
sm.Add(dep2)
// Current graph structure:
// G
// / \
// 0 1
// /
// 2
// Tail = 1
dep1_1 := ids.Bag{}
dep1_1.AddCount(dep1.id, 1)
sm.RecordPoll(dep1_1)
// Current graph structure:
// G
// / \
// 0 1
// /
// 2
// Tail = 1
dep2_1 := ids.Bag{}
dep2_1.AddCount(dep2.id, 1)
sm.RecordPoll(dep2_1)
if sm.Finalized() {
t.Fatalf("Finalized too early")
} else if status := dep0.Status(); status != choices.Processing {
t.Fatalf("Shouldn't have accepted yet %s", status)
}
if !dep1.id.Equals(sm.Preference()) {
t.Fatalf("Wrong preference listed")
}
sm.RecordPoll(dep2_1)
sm.RecordPoll(dep2_1)
if !sm.Finalized() {
t.Fatalf("Finalized too late")
} else if dep0.Status() != choices.Accepted {
t.Fatalf("Should have accepted")
} else if dep1.Status() != choices.Rejected {
t.Fatalf("Should have rejected")
} else if dep2.Status() != choices.Accepted {
t.Fatalf("Should have accepted")
}
}
func CollectTransVoteTest(t *testing.T, factory Factory) {
sm := factory.New()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 3, Alpha: 3, BetaVirtuous: 1, BetaRogue: 1,
}
sm.Initialize(snow.DefaultContextTest(), params, Genesis.ID())
2020-04-04 15:07:14 -07:00
dep0 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.Empty.Prefix(1),
}
sm.Add(dep0)
2020-04-04 15:07:14 -07:00
dep1 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: dep0,
id: ids.Empty.Prefix(2),
}
sm.Add(dep1)
2020-04-04 15:07:14 -07:00
dep2 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: dep1,
id: ids.Empty.Prefix(3),
}
sm.Add(dep2)
2020-04-04 15:07:14 -07:00
dep3 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: dep0,
id: ids.Empty.Prefix(4),
}
sm.Add(dep3)
2020-04-04 15:07:14 -07:00
dep4 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: dep3,
id: ids.Empty.Prefix(5),
}
sm.Add(dep4)
// Current graph structure:
// G
// /
// 0
// / \
// 1 3
// / \
// 2 4
// Tail = 2
dep0_2_4_1 := ids.Bag{}
dep0_2_4_1.AddCount(dep0.id, 1)
dep0_2_4_1.AddCount(dep2.id, 1)
dep0_2_4_1.AddCount(dep4.id, 1)
sm.RecordPoll(dep0_2_4_1)
// Current graph structure:
// 0
// / \
// 1 3
// / \
// 2 4
// Tail = 2
if !dep2.id.Equals(sm.Preference()) {
t.Fatalf("Wrong preference listed")
}
dep2_3 := ids.Bag{}
dep2_3.AddCount(dep2.id, 3)
sm.RecordPoll(dep2_3)
// Current graph structure:
// 2
// Tail = 2
if !dep2.id.Equals(sm.Preference()) {
t.Fatalf("Wrong preference listed")
}
if !sm.Finalized() {
t.Fatalf("Finalized too late")
} else if dep0.Status() != choices.Accepted {
t.Fatalf("Should have accepted")
} else if dep1.Status() != choices.Accepted {
t.Fatalf("Should have accepted")
} else if dep2.Status() != choices.Accepted {
t.Fatalf("Should have accepted")
} else if dep3.Status() != choices.Rejected {
t.Fatalf("Should have rejected")
} else if dep4.Status() != choices.Rejected {
t.Fatalf("Should have rejected")
}
}
func DivergedVotingTest(t *testing.T, factory Factory) {
sm := factory.New()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
}
sm.Initialize(snow.DefaultContextTest(), params, Genesis.ID())
2020-04-04 15:07:14 -07:00
dep0 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.NewID([32]byte{0x0f}), // 0b1111
}
sm.Add(dep0)
2020-04-04 15:07:14 -07:00
dep1 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.NewID([32]byte{0x08}), // 0b1000
}
sm.Add(dep1)
dep0_1 := ids.Bag{}
dep0_1.AddCount(dep0.id, 1)
sm.RecordPoll(dep0_1)
2020-04-04 15:07:14 -07:00
dep2 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.NewID([32]byte{0x01}), // 0b0001
}
sm.Add(dep2)
// dep2 is already rejected.
2020-04-04 15:07:14 -07:00
dep3 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: dep2,
id: ids.Empty.Prefix(3),
}
sm.Add(dep3)
if dep0.Status() == choices.Accepted {
t.Fatalf("Shouldn't be accepted yet")
}
// Transitively increases dep2. However, dep2 shares the first bit with
// dep0. Because dep2 is already rejected, this will accept dep0.
dep3_1 := ids.Bag{}
dep3_1.AddCount(dep3.id, 1)
sm.RecordPoll(dep3_1)
if !sm.Finalized() {
t.Fatalf("Finalized too late")
} else if dep0.Status() != choices.Accepted {
t.Fatalf("Should be accepted")
}
}
func IssuedTest(t *testing.T, factory Factory) {
sm := factory.New()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
}
sm.Initialize(snow.DefaultContextTest(), params, Genesis.ID())
2020-04-04 15:07:14 -07:00
dep0 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.NewID([32]byte{0}),
status: choices.Processing,
}
if sm.Issued(dep0) {
t.Fatalf("Hasn't been issued yet")
}
sm.Add(dep0)
if !sm.Issued(dep0) {
t.Fatalf("Has been issued")
}
2020-04-04 15:07:14 -07:00
dep1 := &TestBlock{
2020-03-10 12:20:34 -07:00
parent: Genesis,
id: ids.NewID([32]byte{0x1}), // 0b0001
status: choices.Accepted,
}
if !sm.Issued(dep1) {
t.Fatalf("Has accepted status")
}
}
func MetricsErrorTest(t *testing.T, factory Factory) {
sm := factory.New()
ctx := snow.DefaultContextTest()
params := snowball.Parameters{
Namespace: fmt.Sprintf("gecko_%s", ctx.ChainID),
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
}
numProcessing := prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: params.Namespace,
Name: "processing",
})
numAccepted := prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: params.Namespace,
Name: "accepted",
})
numRejected := prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: params.Namespace,
Name: "rejected",
})
if err := params.Metrics.Register(numProcessing); err != nil {
t.Fatal(err)
}
if err := params.Metrics.Register(numAccepted); err != nil {
t.Fatal(err)
}
if err := params.Metrics.Register(numRejected); err != nil {
t.Fatal(err)
}
sm.Initialize(ctx, params, Genesis.ID())
}
func ConsistentTest(t *testing.T, factory Factory) {
numColors := 50
numNodes := 100
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 20,
Alpha: 15,
BetaVirtuous: 20,
BetaRogue: 30,
}
seed := int64(0)
rand.Seed(seed)
n := Network{}
n.Initialize(params, numColors)
for i := 0; i < numNodes; i++ {
n.AddNode(factory.New())
}
for !n.Finalized() {
n.Round()
}
if !n.Agreement() {
t.Fatalf("Network agreed on inconsistent values")
}
}