Merge branch 'denali' into limit-outstanding-requests

This commit is contained in:
StephenButtolph 2020-06-23 19:19:19 -04:00
commit 561e021e67
62 changed files with 3775 additions and 1789 deletions

16
.ci/run_e2e_tests.sh Executable file
View File

@ -0,0 +1,16 @@
SCRIPTS_PATH=$(cd $(dirname "${BASH_SOURCE[0]}"); pwd)
SRC_PATH=$(dirname "${SCRIPTS_PATH}")
# Build the runnable Gecko docker image
bash "${SRC_PATH}"/scripts/build_image.sh
GECKO_IMAGE=$(docker image ls --format="{{.Repository}}" | head -n 1)
# Turn off GO111MODULE to pull e2e test source code in order to get run script.
GO111MODULE=off go get -t -v github.com/kurtosis-tech/ava-e2e-tests/...
cd "${GOPATH}"/src/github.com/kurtosis-tech/ava-e2e-tests/ || exit
bash "./scripts/rebuild_initializer_binary.sh"
bash "./scripts/rebuild_controller_image.sh"
# TODO: Make the controller image label a parameter to rebuild_controller_image script
# Standard controller image label used by above scripts.
CONTROLLER_IMAGE="kurtosistech/ava-e2e-tests_controller:latest"
./build/ava-e2e-tests --gecko-image-name="${GECKO_IMAGE}" --test-controller-image-name="${CONTROLLER_IMAGE}" --test-names="fiveStakingNodeGetValidatorsTest,fiveStakingNodeFullyConnectedTest"

View File

@ -14,6 +14,7 @@ env:
global:
- CODECOV_TOKEN="8c18c993-fc6e-4706-998b-01ddc7987804"
- GECKO_HOME=/go/src/github.com/ava-labs/gecko/
- E2E_TEST_HOME=/go/src/github.com/kurtosis-tech/ava-e2e-tests/
- COMMIT=${TRAVIS_COMMIT::8}
- DOCKERHUB_REPO=avaplatform/gecko
- secure: "L/A9+re0NEKP6EV6H9RcTGiDhX3WMvsiWrkRKDYKqnviqbjY30RK6EM4vvjrM4Lrw2QwsO3YKgnku3+zioE/TxEZFkpkbjNUXru0nYBrWAg1TKVsDXnYaIZkHUejfryST3E8N7F4Hx6zCtGEO0sEdUeKuT+MNUIuHezHooTgGzDjMogm70EWMFjQHc7VucTJu7dWU1RBPjovWQ0q9qflrtCpbrvXFIiihQQ1PQha1Q2C4wLakKuLbhhSafue90Mnyss0blaPHy/tyewcASJu4vsGTKRBn0DzttlkNTwuD6+nKrbmJY0ohunnkVFzYjrZAw1gyN+DCDb/lPbz4ZDItKPwrIUPEtL5xuUOrxUZPUh+0io3Q2d6rjaqkdGjd1KQXzbnW1mn0BxX3d3b2UpIqhBn9umYYjHBKnMuoRiTK33b7U9+LF3K84+tEvVDCPeHs/mw6Inp5jGRSravnM6yPQ6feGzogs4+3EMzZXxnkngKFKCsnd67Oe9xfV9amOU2aQAx4jaAwlPjEpBEkUa8YKx3lPznvmUk1QsNCUbLjdSl5JBaXojLJoiuPbj29hp4S5AXXgn+3Hvwk3ndcFCxi6/l1W9mjYSOtFqg3EAUdF4EgnA/ykQg9ZokkoKY0+qgOzG2bKOAYuCDWeGr7P1apToh00ccsQXL81nVPiq7uDw="
@ -26,7 +27,7 @@ install:
script:
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then .ci/runscript_osx.sh; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then .ci/runscript_linux.sh; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then .ci/runscript_linux.sh; .ci/run_e2e_tests.sh; fi
#Need to push to docker hub only from one build
after_success:

View File

@ -10,6 +10,15 @@ import (
"runtime/pprof"
)
const (
// Name of file that CPU profile is written to when StartCPUProfiler called
cpuProfileFile = "cpu.profile"
// Name of file that memory profile is written to when MemoryProfile called
memProfileFile = "mem.profile"
// Name of file that lock profile is written to
lockProfileFile = "lock.profile"
)
var (
errCPUProfilerRunning = errors.New("cpu profiler already running")
errCPUProfilerNotRunning = errors.New("cpu profiler doesn't exist")
@ -20,12 +29,12 @@ var (
type Performance struct{ cpuProfileFile *os.File }
// StartCPUProfiler starts measuring the cpu utilization of this node
func (p *Performance) StartCPUProfiler(filename string) error {
func (p *Performance) StartCPUProfiler() error {
if p.cpuProfileFile != nil {
return errCPUProfilerRunning
}
file, err := os.Create(filename)
file, err := os.Create(cpuProfileFile)
if err != nil {
return err
}
@ -52,8 +61,8 @@ func (p *Performance) StopCPUProfiler() error {
}
// MemoryProfile dumps the current memory utilization of this node
func (p *Performance) MemoryProfile(filename string) error {
file, err := os.Create(filename)
func (p *Performance) MemoryProfile() error {
file, err := os.Create(memProfileFile)
if err != nil {
return err
}
@ -66,8 +75,8 @@ func (p *Performance) MemoryProfile(filename string) error {
}
// LockProfile dumps the current lock statistics of this node
func (p *Performance) LockProfile(filename string) error {
file, err := os.Create(filename)
func (p *Performance) LockProfile() error {
file, err := os.Create(lockProfileFile)
if err != nil {
return err
}

View File

@ -10,38 +10,127 @@ import (
"github.com/ava-labs/gecko/api"
"github.com/ava-labs/gecko/chains"
"github.com/ava-labs/gecko/genesis"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/network"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/version"
cjson "github.com/ava-labs/gecko/utils/json"
)
// Admin is the API service for node admin management
type Admin struct {
version version.Version
nodeID ids.ShortID
networkID uint32
log logging.Logger
networking network.Network
performance Performance
chainManager chains.Manager
httpServer *api.Server
}
// NewService returns a new admin API service
func NewService(log logging.Logger, chainManager chains.Manager, peers network.Network, httpServer *api.Server) *common.HTTPHandler {
func NewService(version version.Version, nodeID ids.ShortID, networkID uint32, log logging.Logger, chainManager chains.Manager, peers network.Network, httpServer *api.Server) *common.HTTPHandler {
newServer := rpc.NewServer()
codec := cjson.NewCodec()
newServer.RegisterCodec(codec, "application/json")
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
newServer.RegisterService(&Admin{
version: version,
nodeID: nodeID,
networkID: networkID,
log: log,
chainManager: chainManager,
networking: peers,
httpServer: httpServer,
}, "admin")
return &common.HTTPHandler{Handler: newServer}
}
// StartCPUProfilerArgs are the arguments for calling StartCPUProfiler
type StartCPUProfilerArgs struct {
Filename string `json:"filename"`
// GetNodeVersionReply are the results from calling GetNodeVersion
type GetNodeVersionReply struct {
Version string `json:"version"`
}
// GetNodeVersion returns the version this node is running
func (service *Admin) GetNodeVersion(_ *http.Request, _ *struct{}, reply *GetNodeVersionReply) error {
service.log.Info("Admin: GetNodeVersion called")
reply.Version = service.version.String()
return nil
}
// GetNodeIDReply are the results from calling GetNodeID
type GetNodeIDReply struct {
NodeID ids.ShortID `json:"nodeID"`
}
// GetNodeID returns the node ID of this node
func (service *Admin) GetNodeID(_ *http.Request, _ *struct{}, reply *GetNodeIDReply) error {
service.log.Info("Admin: GetNodeID called")
reply.NodeID = service.nodeID
return nil
}
// GetNetworkIDReply are the results from calling GetNetworkID
type GetNetworkIDReply struct {
NetworkID cjson.Uint32 `json:"networkID"`
}
// GetNetworkID returns the network ID this node is running on
func (service *Admin) GetNetworkID(_ *http.Request, _ *struct{}, reply *GetNetworkIDReply) error {
service.log.Info("Admin: GetNetworkID called")
reply.NetworkID = cjson.Uint32(service.networkID)
return nil
}
// GetNetworkNameReply is the result from calling GetNetworkName
type GetNetworkNameReply struct {
NetworkName string `json:"networkName"`
}
// GetNetworkName returns the network name this node is running on
func (service *Admin) GetNetworkName(_ *http.Request, _ *struct{}, reply *GetNetworkNameReply) error {
service.log.Info("Admin: GetNetworkName called")
reply.NetworkName = genesis.NetworkName(service.networkID)
return nil
}
// GetBlockchainIDArgs are the arguments for calling GetBlockchainID
type GetBlockchainIDArgs struct {
Alias string `json:"alias"`
}
// GetBlockchainIDReply are the results from calling GetBlockchainID
type GetBlockchainIDReply struct {
BlockchainID string `json:"blockchainID"`
}
// GetBlockchainID returns the blockchain ID that resolves the alias that was supplied
func (service *Admin) GetBlockchainID(_ *http.Request, args *GetBlockchainIDArgs, reply *GetBlockchainIDReply) error {
service.log.Info("Admin: GetBlockchainID called")
bID, err := service.chainManager.Lookup(args.Alias)
reply.BlockchainID = bID.String()
return err
}
// PeersReply are the results from calling Peers
type PeersReply struct {
Peers []network.PeerID `json:"peers"`
}
// Peers returns the list of current validators
func (service *Admin) Peers(_ *http.Request, _ *struct{}, reply *PeersReply) error {
service.log.Info("Admin: Peers called")
reply.Peers = service.networking.Peers()
return nil
}
// StartCPUProfilerReply are the results from calling StartCPUProfiler
@ -50,10 +139,10 @@ type StartCPUProfilerReply struct {
}
// StartCPUProfiler starts a cpu profile writing to the specified file
func (service *Admin) StartCPUProfiler(_ *http.Request, args *StartCPUProfilerArgs, reply *StartCPUProfilerReply) error {
service.log.Info("Admin: StartCPUProfiler called with %s", args.Filename)
func (service *Admin) StartCPUProfiler(_ *http.Request, args *struct{}, reply *StartCPUProfilerReply) error {
service.log.Info("Admin: StartCPUProfiler called")
reply.Success = true
return service.performance.StartCPUProfiler(args.Filename)
return service.performance.StartCPUProfiler()
}
// StopCPUProfilerReply are the results from calling StopCPUProfiler
@ -68,26 +157,16 @@ func (service *Admin) StopCPUProfiler(_ *http.Request, _ *struct{}, reply *StopC
return service.performance.StopCPUProfiler()
}
// MemoryProfileArgs are the arguments for calling MemoryProfile
type MemoryProfileArgs struct {
Filename string `json:"filename"`
}
// MemoryProfileReply are the results from calling MemoryProfile
type MemoryProfileReply struct {
Success bool `json:"success"`
}
// MemoryProfile runs a memory profile writing to the specified file
func (service *Admin) MemoryProfile(_ *http.Request, args *MemoryProfileArgs, reply *MemoryProfileReply) error {
service.log.Info("Admin: MemoryProfile called with %s", args.Filename)
func (service *Admin) MemoryProfile(_ *http.Request, args *struct{}, reply *MemoryProfileReply) error {
service.log.Info("Admin: MemoryProfile called")
reply.Success = true
return service.performance.MemoryProfile(args.Filename)
}
// LockProfileArgs are the arguments for calling LockProfile
type LockProfileArgs struct {
Filename string `json:"filename"`
return service.performance.MemoryProfile()
}
// LockProfileReply are the results from calling LockProfile
@ -96,10 +175,10 @@ type LockProfileReply struct {
}
// LockProfile runs a mutex profile writing to the specified file
func (service *Admin) LockProfile(_ *http.Request, args *LockProfileArgs, reply *LockProfileReply) error {
service.log.Info("Admin: LockProfile called with %s", args.Filename)
func (service *Admin) LockProfile(_ *http.Request, args *struct{}, reply *LockProfileReply) error {
service.log.Info("Admin: LockProfile called")
reply.Success = true
return service.performance.LockProfile(args.Filename)
return service.performance.LockProfile()
}
// AliasArgs are the arguments for calling Alias

14
database/common.go Normal file
View File

@ -0,0 +1,14 @@
package database
const (
// MaxExcessCapacityFactor ...
// If, when a batch is reset, the cap(batch)/len(batch) > MaxExcessCapacityFactor,
// the underlying array's capacity will be reduced by a factor of capacityReductionFactor.
// Higher value for MaxExcessCapacityFactor --> less aggressive array downsizing --> less memory allocations
// but more unnecessary data in the underlying array that can't be garbage collected.
// Higher value for CapacityReductionFactor --> more aggressive array downsizing --> more memory allocations
// but less unnecessary data in the underlying array that can't be garbage collected.
MaxExcessCapacityFactor = 4
// CapacityReductionFactor ...
CapacityReductionFactor = 2
)

View File

@ -201,7 +201,11 @@ func (b *batch) Write() error {
// Reset resets the batch for reuse.
func (b *batch) Reset() {
b.writes = b.writes[:0]
if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor {
b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor)
} else {
b.writes = b.writes[:0]
}
b.Batch.Reset()
}

View File

@ -13,8 +13,10 @@ import (
"github.com/ava-labs/gecko/utils"
)
// DefaultSize is the default initial size of the memory database
const DefaultSize = 1 << 10
const (
// DefaultSize is the default initial size of the memory database
DefaultSize = 1 << 10
)
// Database is an ephemeral key-value store that implements the Database
// interface.
@ -191,7 +193,11 @@ func (b *batch) Write() error {
// Reset implements the Batch interface
func (b *batch) Reset() {
b.writes = b.writes[:0]
if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor {
b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor)
} else {
b.writes = b.writes[:0]
}
b.size = 0
}

View File

@ -199,7 +199,11 @@ func (b *batch) Write() error {
// Reset resets the batch for reuse.
func (b *batch) Reset() {
b.writes = b.writes[:0]
if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor {
b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor)
} else {
b.writes = b.writes[:0]
}
b.Batch.Reset()
}

View File

@ -180,7 +180,11 @@ func (b *batch) Write() error {
}
func (b *batch) Reset() {
b.writes = b.writes[:0]
if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor {
b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor)
} else {
b.writes = b.writes[:0]
}
b.size = 0
}

View File

@ -195,6 +195,7 @@ func (db *Database) Commit() error {
if err := batch.Write(); err != nil {
return err
}
batch.Reset()
db.abort()
return nil
}
@ -209,9 +210,10 @@ func (db *Database) Abort() {
func (db *Database) abort() { db.mem = make(map[string]valueDelete, memdb.DefaultSize) }
// CommitBatch returns a batch that will commit all pending writes to the
// underlying database. The returned batch should be written before future calls
// to this DB unless the batch will never be written.
// CommitBatch returns a batch that contains all uncommitted puts/deletes.
// Calling Write() on the returned batch causes the puts/deletes to be
// written to the underlying database. The returned batch should be written before
// future calls to this DB unless the batch will never be written.
func (db *Database) CommitBatch() (database.Batch, error) {
db.lock.Lock()
defer db.lock.Unlock()
@ -219,6 +221,8 @@ func (db *Database) CommitBatch() (database.Batch, error) {
return db.commitBatch()
}
// Put all of the puts/deletes in memory into db.batch
// and return the batch
func (db *Database) commitBatch() (database.Batch, error) {
if db.mem == nil {
return nil, database.ErrClosed
@ -234,9 +238,6 @@ func (db *Database) commitBatch() (database.Batch, error) {
return nil, err
}
}
if err := db.batch.Write(); err != nil {
return nil, err
}
return db.batch, nil
}
@ -249,6 +250,7 @@ func (db *Database) Close() error {
if db.mem == nil {
return database.ErrClosed
}
db.batch = nil
db.mem = nil
db.db = nil
return nil
@ -303,7 +305,11 @@ func (b *batch) Write() error {
// Reset implements the Database interface
func (b *batch) Reset() {
b.writes = b.writes[:0]
if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor {
b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor)
} else {
b.writes = b.writes[:0]
}
b.size = 0
}

View File

@ -299,6 +299,10 @@ func TestCommitBatch(t *testing.T) {
if err := db.Put(key1, value1); err != nil {
t.Fatalf("Unexpected error on db.Put: %s", err)
} else if has, err := baseDB.Has(key1); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if has {
t.Fatalf("Unexpected result of db.Has: %v", has)
}
batch, err := db.CommitBatch()
@ -307,7 +311,11 @@ func TestCommitBatch(t *testing.T) {
}
db.Abort()
if err := batch.Write(); err != nil {
if has, err := db.Has(key1); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if has {
t.Fatalf("Unexpected result of db.Has: %v", has)
} else if err := batch.Write(); err != nil {
t.Fatalf("Unexpected error on batch.Write: %s", err)
}

View File

@ -57,15 +57,23 @@ func (ids *ShortSet) Remove(idList ...ShortID) {
// Clear empties this set
func (ids *ShortSet) Clear() { *ids = nil }
// CappedList returns a list of length at most [size]. Size should be >= 0
// CappedList returns a list of length at most [size].
// Size should be >= 0. If size < 0, returns nil.
func (ids ShortSet) CappedList(size int) []ShortID {
idList := make([]ShortID, size)[:0]
if size < 0 {
return nil
}
if l := ids.Len(); l < size {
size = l
}
i := 0
idList := make([]ShortID, size)
for id := range ids {
if size <= 0 {
if i >= size {
break
}
size--
idList = append(idList, NewShortID(id))
idList[i] = NewShortID(id)
i++
}
return idList
}

View File

@ -71,8 +71,10 @@ func main() {
mapper := nat.NewDefaultMapper(log, Config.Nat, nat.TCP, "gecko")
defer mapper.UnmapAllPorts()
mapper.MapPort(Config.StakingIP.Port, Config.StakingIP.Port)
mapper.MapPort(Config.HTTPPort, Config.HTTPPort)
mapper.MapPort(Config.StakingIP.Port, Config.StakingIP.Port) // Open staking port
if Config.HTTPHost != "127.0.0.1" && Config.HTTPHost != "localhost" { // Open HTTP port iff HTTP server not listening on localhost
mapper.MapPort(Config.HTTPPort, Config.HTTPPort)
}
node := node.Node{}

View File

@ -35,17 +35,19 @@ const (
// Results of parsing the CLI
var (
Config = node.Config{}
Err error
defaultNetworkName = genesis.TestnetName
defaultDbDir = os.ExpandEnv(filepath.Join("$HOME", ".gecko", "db"))
defaultStakingKeyPath = os.ExpandEnv(filepath.Join("$HOME", ".gecko", "staking", "staker.key"))
defaultStakingCertPath = os.ExpandEnv(filepath.Join("$HOME", ".gecko", "staking", "staker.crt"))
Config = node.Config{}
Err error
defaultNetworkName = genesis.TestnetName
defaultPluginDirs = []string{
"./build/plugins",
"./plugins",
os.ExpandEnv(filepath.Join("$HOME", ".gecko", "plugins")),
homeDir = os.ExpandEnv("$HOME")
defaultDbDir = filepath.Join(homeDir, ".gecko", "db")
defaultStakingKeyPath = filepath.Join(homeDir, ".gecko", "staking", "staker.key")
defaultStakingCertPath = filepath.Join(homeDir, ".gecko", "staking", "staker.crt")
defaultPluginDirs = []string{
filepath.Join(".", "build", "plugins"),
filepath.Join(".", "plugins"),
filepath.Join("/", "usr", "local", "lib", "gecko"),
filepath.Join(homeDir, ".gecko", "plugins"),
}
)
@ -190,7 +192,7 @@ func init() {
consensusIP := fs.String("public-ip", "", "Public IP of this node")
// HTTP Server:
httpHost := fs.String("http-host", "", "Address of the HTTP server")
httpHost := fs.String("http-host", "127.0.0.1", "Address of the HTTP server")
httpPort := fs.Uint("http-port", 9650, "Port of the HTTP server")
fs.BoolVar(&Config.EnableHTTPS, "http-tls-enabled", false, "Upgrade the HTTP server to HTTPs")
fs.StringVar(&Config.HTTPSKeyFile, "http-tls-key-file", "", "TLS private key file for the HTTPs server")
@ -225,7 +227,7 @@ func init() {
fs.IntVar(&Config.ConsensusParams.ConcurrentRepolls, "snow-concurrent-repolls", 1, "Minimum number of concurrent polls for finalizing consensus")
// Enable/Disable APIs:
fs.BoolVar(&Config.AdminAPIEnabled, "api-admin-enabled", true, "If true, this node exposes the Admin API")
fs.BoolVar(&Config.AdminAPIEnabled, "api-admin-enabled", false, "If true, this node exposes the Admin API")
fs.BoolVar(&Config.InfoAPIEnabled, "api-info-enabled", true, "If true, this node exposes the Info API")
fs.BoolVar(&Config.KeystoreAPIEnabled, "api-keystore-enabled", true, "If true, this node exposes the Keystore API")
fs.BoolVar(&Config.MetricsAPIEnabled, "api-metrics-enabled", true, "If true, this node exposes the Metrics API")

View File

@ -57,7 +57,7 @@ var (
genesisHashKey = []byte("genesisID")
// Version is the version of this code
Version = version.NewDefaultVersion("avalanche", 0, 5, 5)
Version = version.NewDefaultVersion("avalanche", 0, 5, 6)
versionParser = version.NewDefaultParser()
)
@ -462,7 +462,7 @@ func (n *Node) initMetricsAPI() {
func (n *Node) initAdminAPI() {
if n.Config.AdminAPIEnabled {
n.Log.Info("initializing Admin API")
service := admin.NewService(n.Log, n.chainManager, n.Net, &n.APIServer)
service := admin.NewService(Version, n.ID, n.Config.NetworkID, n.Log, n.chainManager, n.Net, &n.APIServer)
n.APIServer.AddRoute(service, &sync.RWMutex{}, "admin", "", n.HTTPLog)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,10 @@ import (
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
)
const (
minMapSize = 16
)
// TopologicalFactory implements Factory by returning a topological struct
type TopologicalFactory struct{}
@ -65,12 +69,12 @@ func (ta *Topological) Initialize(ctx *snow.Context, params Parameters, frontier
ta.ctx.Log.Error("%s", err)
}
ta.nodes = make(map[[32]byte]Vertex)
ta.nodes = make(map[[32]byte]Vertex, minMapSize)
ta.cg = &snowstorm.Directed{}
ta.cg.Initialize(ctx, params.Parameters)
ta.frontier = make(map[[32]byte]Vertex)
ta.frontier = make(map[[32]byte]Vertex, minMapSize)
for _, vtx := range frontier {
ta.frontier[vtx.ID().Key()] = vtx
}
@ -159,7 +163,7 @@ func (ta *Topological) Finalized() bool { return ta.cg.Finalized() }
// the non-transitively applied votes. Also returns the list of leaf nodes.
func (ta *Topological) calculateInDegree(
responses ids.UniqueBag) (map[[32]byte]kahnNode, []ids.ID) {
kahns := make(map[[32]byte]kahnNode)
kahns := make(map[[32]byte]kahnNode, minMapSize)
leaves := ids.Set{}
for _, vote := range responses.List() {
@ -233,7 +237,7 @@ func (ta *Topological) pushVotes(
kahnNodes map[[32]byte]kahnNode,
leaves []ids.ID) ids.Bag {
votes := make(ids.UniqueBag)
txConflicts := make(map[[32]byte]ids.Set)
txConflicts := make(map[[32]byte]ids.Set, minMapSize)
for len(leaves) > 0 {
newLeavesSize := len(leaves) - 1
@ -443,9 +447,9 @@ func (ta *Topological) updateFrontiers() error {
ta.preferred.Clear()
ta.virtuous.Clear()
ta.orphans.Clear()
ta.frontier = make(map[[32]byte]Vertex)
ta.preferenceCache = make(map[[32]byte]bool)
ta.virtuousCache = make(map[[32]byte]bool)
ta.frontier = make(map[[32]byte]Vertex, minMapSize)
ta.preferenceCache = make(map[[32]byte]bool, minMapSize)
ta.virtuousCache = make(map[[32]byte]bool, minMapSize)
ta.orphans.Union(ta.cg.Virtuous()) // Initially, nothing is preferred

View File

@ -4,830 +4,7 @@
package avalanche
import (
"math"
"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"
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
)
func TestTopologicalParams(t *testing.T) { ParamsTest(t, TopologicalFactory{}) }
func TestTopologicalAdd(t *testing.T) { AddTest(t, TopologicalFactory{}) }
func TestTopologicalVertexIssued(t *testing.T) { VertexIssuedTest(t, TopologicalFactory{}) }
func TestTopologicalTxIssued(t *testing.T) { TxIssuedTest(t, TopologicalFactory{}) }
func TestAvalancheVoting(t *testing.T) {
params := Parameters{
Parameters: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2,
Alpha: 2,
BetaVirtuous: 1,
BetaRogue: 2,
ConcurrentRepolls: 1,
},
Parents: 2,
BatchSize: 1,
}
vts := []Vertex{&Vtx{
id: GenerateID(),
status: choices.Accepted,
}, &Vtx{
id: GenerateID(),
status: choices.Accepted,
}}
utxos := []ids.ID{GenerateID()}
ta := Topological{}
ta.Initialize(snow.DefaultContextTest(), params, vts)
tx0 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx0.Ins.Add(utxos[0])
vtx0 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
tx1 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx1.Ins.Add(utxos[0])
vtx1 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 1,
status: choices.Processing,
}
ta.Add(vtx0)
ta.Add(vtx1)
sm := make(ids.UniqueBag)
sm.Add(0, vtx1.id)
sm.Add(1, vtx1.id)
ta.RecordPoll(sm)
if ta.Finalized() {
t.Fatalf("An avalanche instance finalized too early")
} else if !ids.UnsortedEquals([]ids.ID{vtx1.id}, ta.Preferences().List()) {
t.Fatalf("Initial frontier failed to be set")
}
ta.RecordPoll(sm)
if !ta.Finalized() {
t.Fatalf("An avalanche instance finalized too late")
} else if !ids.UnsortedEquals([]ids.ID{vtx1.id}, ta.Preferences().List()) {
t.Fatalf("Initial frontier failed to be set")
} else if tx0.Status() != choices.Rejected {
t.Fatalf("Tx should have been rejected")
} else if tx1.Status() != choices.Accepted {
t.Fatalf("Tx should have been accepted")
}
}
func TestAvalancheIgnoreInvalidVoting(t *testing.T) {
params := Parameters{
Parameters: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 3,
Alpha: 2,
BetaVirtuous: 1,
BetaRogue: 1,
},
Parents: 2,
BatchSize: 1,
}
vts := []Vertex{&Vtx{
id: GenerateID(),
status: choices.Accepted,
}, &Vtx{
id: GenerateID(),
status: choices.Accepted,
}}
utxos := []ids.ID{GenerateID()}
ta := Topological{}
ta.Initialize(snow.DefaultContextTest(), params, vts)
tx0 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx0.Ins.Add(utxos[0])
vtx0 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
tx1 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx1.Ins.Add(utxos[0])
vtx1 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 1,
status: choices.Processing,
}
ta.Add(vtx0)
ta.Add(vtx1)
sm := make(ids.UniqueBag)
sm.Add(0, vtx0.id)
sm.Add(1, vtx1.id)
// Add Illegal Vote cast by Response 2
sm.Add(2, vtx0.id)
sm.Add(2, vtx1.id)
ta.RecordPoll(sm)
if ta.Finalized() {
t.Fatalf("An avalanche instance finalized too early")
}
}
func TestAvalancheTransitiveVoting(t *testing.T) {
params := Parameters{
Parameters: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2,
Alpha: 2,
BetaVirtuous: 1,
BetaRogue: 2,
ConcurrentRepolls: 1,
},
Parents: 2,
BatchSize: 1,
}
vts := []Vertex{&Vtx{
id: GenerateID(),
status: choices.Accepted,
}, &Vtx{
id: GenerateID(),
status: choices.Accepted,
}}
utxos := []ids.ID{GenerateID(), GenerateID()}
ta := Topological{}
ta.Initialize(snow.DefaultContextTest(), params, vts)
tx0 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx0.Ins.Add(utxos[0])
vtx0 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
tx1 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx1.Ins.Add(utxos[1])
vtx1 := &Vtx{
dependencies: []Vertex{vtx0},
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 2,
status: choices.Processing,
}
vtx2 := &Vtx{
dependencies: []Vertex{vtx1},
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 3,
status: choices.Processing,
}
ta.Add(vtx0)
ta.Add(vtx1)
ta.Add(vtx2)
sm1 := make(ids.UniqueBag)
sm1.Add(0, vtx0.id)
sm1.Add(1, vtx2.id)
ta.RecordPoll(sm1)
if ta.Finalized() {
t.Fatalf("An avalanche instance finalized too early")
} else if !ids.UnsortedEquals([]ids.ID{vtx2.id}, ta.Preferences().List()) {
t.Fatalf("Initial frontier failed to be set")
} else if tx0.Status() != choices.Accepted {
t.Fatalf("Tx should have been accepted")
}
sm2 := make(ids.UniqueBag)
sm2.Add(0, vtx2.id)
sm2.Add(1, vtx2.id)
ta.RecordPoll(sm2)
if !ta.Finalized() {
t.Fatalf("An avalanche instance finalized too late")
} else if !ids.UnsortedEquals([]ids.ID{vtx2.id}, ta.Preferences().List()) {
t.Fatalf("Initial frontier failed to be set")
} else if tx0.Status() != choices.Accepted {
t.Fatalf("Tx should have been accepted")
} else if tx1.Status() != choices.Accepted {
t.Fatalf("Tx should have been accepted")
}
}
func TestAvalancheSplitVoting(t *testing.T) {
params := Parameters{
Parameters: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2,
Alpha: 2,
BetaVirtuous: 1,
BetaRogue: 2,
ConcurrentRepolls: 1,
},
Parents: 2,
BatchSize: 1,
}
vts := []Vertex{&Vtx{
id: GenerateID(),
status: choices.Accepted,
}, &Vtx{
id: GenerateID(),
status: choices.Accepted,
}}
utxos := []ids.ID{GenerateID()}
ta := Topological{}
ta.Initialize(snow.DefaultContextTest(), params, vts)
tx0 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx0.Ins.Add(utxos[0])
vtx0 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
vtx1 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
ta.Add(vtx0)
ta.Add(vtx1)
sm1 := make(ids.UniqueBag)
sm1.Add(0, vtx0.id)
sm1.Add(1, vtx1.id)
ta.RecordPoll(sm1)
if !ta.Finalized() {
t.Fatalf("An avalanche instance finalized too late")
} else if !ids.UnsortedEquals([]ids.ID{vtx0.id, vtx1.id}, ta.Preferences().List()) {
t.Fatalf("Initial frontier failed to be set")
} else if tx0.Status() != choices.Accepted {
t.Fatalf("Tx should have been accepted")
}
}
func TestAvalancheTransitiveRejection(t *testing.T) {
params := Parameters{
Parameters: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2,
Alpha: 2,
BetaVirtuous: 1,
BetaRogue: 2,
ConcurrentRepolls: 1,
},
Parents: 2,
BatchSize: 1,
}
vts := []Vertex{&Vtx{
id: GenerateID(),
status: choices.Accepted,
}, &Vtx{
id: GenerateID(),
status: choices.Accepted,
}}
utxos := []ids.ID{GenerateID(), GenerateID()}
ta := Topological{}
ta.Initialize(snow.DefaultContextTest(), params, vts)
tx0 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx0.Ins.Add(utxos[0])
vtx0 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
tx1 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx1.Ins.Add(utxos[0])
vtx1 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 1,
status: choices.Processing,
}
tx2 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx2.Ins.Add(utxos[1])
vtx2 := &Vtx{
dependencies: []Vertex{vtx0},
id: GenerateID(),
txs: []snowstorm.Tx{tx2},
height: 2,
status: choices.Processing,
}
ta.Add(vtx0)
ta.Add(vtx1)
ta.Add(vtx2)
sm := make(ids.UniqueBag)
sm.Add(0, vtx1.id)
sm.Add(1, vtx1.id)
ta.RecordPoll(sm)
if ta.Finalized() {
t.Fatalf("An avalanche instance finalized too early")
} else if !ids.UnsortedEquals([]ids.ID{vtx1.id}, ta.Preferences().List()) {
t.Fatalf("Initial frontier failed to be set")
}
ta.RecordPoll(sm)
if ta.Finalized() {
t.Fatalf("An avalanche instance finalized too early")
} else if !ids.UnsortedEquals([]ids.ID{vtx1.id}, ta.Preferences().List()) {
t.Fatalf("Initial frontier failed to be set")
} else if tx0.Status() != choices.Rejected {
t.Fatalf("Tx should have been rejected")
} else if tx1.Status() != choices.Accepted {
t.Fatalf("Tx should have been accepted")
} else if tx2.Status() != choices.Processing {
t.Fatalf("Tx should not have been decided")
}
ta.preferenceCache = make(map[[32]byte]bool)
ta.virtuousCache = make(map[[32]byte]bool)
ta.update(vtx2)
}
func TestAvalancheVirtuous(t *testing.T) {
params := Parameters{
Parameters: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2,
Alpha: 2,
BetaVirtuous: 1,
BetaRogue: 2,
ConcurrentRepolls: 1,
},
Parents: 2,
BatchSize: 1,
}
vts := []Vertex{&Vtx{
id: GenerateID(),
status: choices.Accepted,
}, &Vtx{
id: GenerateID(),
status: choices.Accepted,
}}
utxos := []ids.ID{GenerateID(), GenerateID()}
ta := Topological{}
ta.Initialize(snow.DefaultContextTest(), params, vts)
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(vts[0].ID()) {
t.Fatalf("Wrong virtuous")
} else if !virtuous.Contains(vts[1].ID()) {
t.Fatalf("Wrong virtuous")
}
tx0 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx0.Ins.Add(utxos[0])
vtx0 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
tx1 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx1.Ins.Add(utxos[0])
vtx1 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 1,
status: choices.Processing,
}
tx2 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx2.Ins.Add(utxos[1])
vtx2 := &Vtx{
dependencies: []Vertex{vtx0},
id: GenerateID(),
txs: []snowstorm.Tx{tx2},
height: 2,
status: choices.Processing,
}
ta.Add(vtx0)
if virtuous := ta.Virtuous(); virtuous.Len() != 1 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(vtx0.id) {
t.Fatalf("Wrong virtuous")
}
ta.Add(vtx1)
if virtuous := ta.Virtuous(); virtuous.Len() != 1 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(vtx0.id) {
t.Fatalf("Wrong virtuous")
}
ta.updateFrontiers()
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(vts[0].ID()) {
t.Fatalf("Wrong virtuous")
} else if !virtuous.Contains(vts[1].ID()) {
t.Fatalf("Wrong virtuous")
}
ta.Add(vtx2)
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(vts[0].ID()) {
t.Fatalf("Wrong virtuous")
} else if !virtuous.Contains(vts[1].ID()) {
t.Fatalf("Wrong virtuous")
}
ta.updateFrontiers()
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(vts[0].ID()) {
t.Fatalf("Wrong virtuous")
} else if !virtuous.Contains(vts[1].ID()) {
t.Fatalf("Wrong virtuous")
}
}
func TestAvalancheIsVirtuous(t *testing.T) {
params := Parameters{
Parameters: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2,
Alpha: 2,
BetaVirtuous: 1,
BetaRogue: 2,
ConcurrentRepolls: 1,
},
Parents: 2,
BatchSize: 1,
}
vts := []Vertex{&Vtx{
id: GenerateID(),
status: choices.Accepted,
}, &Vtx{
id: GenerateID(),
status: choices.Accepted,
}}
utxos := []ids.ID{GenerateID(), GenerateID()}
ta := Topological{}
ta.Initialize(snow.DefaultContextTest(), params, vts)
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(vts[0].ID()) {
t.Fatalf("Wrong virtuous")
} else if !virtuous.Contains(vts[1].ID()) {
t.Fatalf("Wrong virtuous")
}
tx0 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx0.Ins.Add(utxos[0])
vtx0 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
tx1 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx1.Ins.Add(utxos[0])
vtx1 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 1,
status: choices.Processing,
}
if !ta.IsVirtuous(tx0) {
t.Fatalf("Should be virtuous.")
} else if !ta.IsVirtuous(tx1) {
t.Fatalf("Should be virtuous.")
}
ta.Add(vtx0)
if !ta.IsVirtuous(tx0) {
t.Fatalf("Should be virtuous.")
} else if ta.IsVirtuous(tx1) {
t.Fatalf("Should not be virtuous.")
}
ta.Add(vtx1)
if ta.IsVirtuous(tx0) {
t.Fatalf("Should not be virtuous.")
} else if ta.IsVirtuous(tx1) {
t.Fatalf("Should not be virtuous.")
}
}
func TestAvalancheQuiesce(t *testing.T) {
params := Parameters{
Parameters: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1,
Alpha: 1,
BetaVirtuous: 1,
BetaRogue: 1,
ConcurrentRepolls: 1,
},
Parents: 2,
BatchSize: 1,
}
vts := []Vertex{&Vtx{
id: GenerateID(),
status: choices.Accepted,
}, &Vtx{
id: GenerateID(),
status: choices.Accepted,
}}
utxos := []ids.ID{GenerateID(), GenerateID()}
ta := Topological{}
ta.Initialize(snow.DefaultContextTest(), params, vts)
tx0 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx0.Ins.Add(utxos[0])
vtx0 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
tx1 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx1.Ins.Add(utxos[0])
vtx1 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 1,
status: choices.Processing,
}
tx2 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx2.Ins.Add(utxos[1])
vtx2 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx2},
height: 2,
status: choices.Processing,
}
ta.Add(vtx0)
if ta.Quiesce() {
t.Fatalf("Shouldn't quiesce")
}
ta.Add(vtx1)
if !ta.Quiesce() {
t.Fatalf("Should quiesce")
}
ta.Add(vtx2)
if ta.Quiesce() {
t.Fatalf("Shouldn't quiesce")
}
sm := make(ids.UniqueBag)
sm.Add(0, vtx2.id)
ta.RecordPoll(sm)
if !ta.Quiesce() {
t.Fatalf("Should quiesce")
}
}
func TestAvalancheOrphans(t *testing.T) {
params := Parameters{
Parameters: snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1,
Alpha: 1,
BetaVirtuous: math.MaxInt32,
BetaRogue: math.MaxInt32,
ConcurrentRepolls: 1,
},
Parents: 2,
BatchSize: 1,
}
vts := []Vertex{&Vtx{
id: GenerateID(),
status: choices.Accepted,
}, &Vtx{
id: GenerateID(),
status: choices.Accepted,
}}
utxos := []ids.ID{GenerateID(), GenerateID()}
ta := Topological{}
ta.Initialize(snow.DefaultContextTest(), params, vts)
tx0 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx0.Ins.Add(utxos[0])
vtx0 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
tx1 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx1.Ins.Add(utxos[0])
vtx1 := &Vtx{
dependencies: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 1,
status: choices.Processing,
}
tx2 := &snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
}
tx2.Ins.Add(utxos[1])
vtx2 := &Vtx{
dependencies: []Vertex{vtx0},
id: GenerateID(),
txs: []snowstorm.Tx{tx2},
height: 2,
status: choices.Processing,
}
ta.Add(vtx0)
if orphans := ta.Orphans(); orphans.Len() != 0 {
t.Fatalf("Wrong number of orphans")
}
ta.Add(vtx1)
if orphans := ta.Orphans(); orphans.Len() != 0 {
t.Fatalf("Wrong number of orphans")
}
ta.Add(vtx2)
if orphans := ta.Orphans(); orphans.Len() != 0 {
t.Fatalf("Wrong number of orphans")
}
sm := make(ids.UniqueBag)
sm.Add(0, vtx1.id)
ta.RecordPoll(sm)
if orphans := ta.Orphans(); orphans.Len() != 1 {
t.Fatalf("Wrong number of orphans")
} else if !orphans.Contains(tx2.ID()) {
t.Fatalf("Wrong orphan")
}
}
func TestTopological(t *testing.T) { ConsensusTest(t, TopologicalFactory{}) }

View File

@ -19,7 +19,8 @@ type Vtx struct {
height uint64
status choices.Status
bytes []byte
Validity error
bytes []byte
}
func (v *Vtx) ID() ids.ID { return v.id }
@ -28,9 +29,8 @@ func (v *Vtx) Parents() []Vertex { return v.dependencies }
func (v *Vtx) Height() uint64 { return v.height }
func (v *Vtx) Txs() []snowstorm.Tx { return v.txs }
func (v *Vtx) Status() choices.Status { return v.status }
func (v *Vtx) Live() {}
func (v *Vtx) Accept() error { v.status = choices.Accepted; return nil }
func (v *Vtx) Reject() error { v.status = choices.Rejected; return nil }
func (v *Vtx) Accept() error { v.status = choices.Accepted; return v.Validity }
func (v *Vtx) Reject() error { v.status = choices.Rejected; return v.Validity }
func (v *Vtx) Bytes() []byte { return v.bytes }
type sortVts []*Vtx

View File

@ -1,48 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package snowball
import (
"github.com/ava-labs/gecko/ids"
)
// ByzantineFactory implements Factory by returning a byzantine struct
type ByzantineFactory struct{}
// New implements Factory
func (ByzantineFactory) New() Consensus { return &Byzantine{} }
// Byzantine is a naive implementation of a multi-choice snowball instance
type Byzantine struct {
// params contains all the configurations of a snowball instance
params Parameters
// Hardcode the preference
preference ids.ID
}
// Initialize implements the Consensus interface
func (b *Byzantine) Initialize(params Parameters, choice ids.ID) {
b.params = params
b.preference = choice
}
// Parameters implements the Consensus interface
func (b *Byzantine) Parameters() Parameters { return b.params }
// Add implements the Consensus interface
func (b *Byzantine) Add(choice ids.ID) {}
// Preference implements the Consensus interface
func (b *Byzantine) Preference() ids.ID { return b.preference }
// RecordPoll implements the Consensus interface
func (b *Byzantine) RecordPoll(votes ids.Bag) {}
// RecordUnsuccessfulPoll implements the Consensus interface
func (b *Byzantine) RecordUnsuccessfulPoll() {}
// Finalized implements the Consensus interface
func (b *Byzantine) Finalized() bool { return true }
func (b *Byzantine) String() string { return b.preference.String() }

View File

@ -1,54 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package snowball
import (
"testing"
"github.com/ava-labs/gecko/ids"
"github.com/prometheus/client_golang/prometheus"
)
func TestByzantine(t *testing.T) {
params := Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 3, BetaRogue: 5,
}
byzFactory := ByzantineFactory{}
byz := byzFactory.New()
byz.Initialize(params, Blue)
if ret := byz.Parameters(); ret != params {
t.Fatalf("Should have returned the correct params")
}
byz.Add(Green)
if pref := byz.Preference(); !pref.Equals(Blue) {
t.Fatalf("Wrong preference, expected %s returned %s", Blue, pref)
}
oneGreen := ids.Bag{}
oneGreen.Add(Green)
byz.RecordPoll(oneGreen)
if pref := byz.Preference(); !pref.Equals(Blue) {
t.Fatalf("Wrong preference, expected %s returned %s", Blue, pref)
}
byz.RecordUnsuccessfulPoll()
if pref := byz.Preference(); !pref.Equals(Blue) {
t.Fatalf("Wrong preference, expected %s returned %s", Blue, pref)
}
if final := byz.Finalized(); !final {
t.Fatalf("Should be marked as accepted")
}
if str := byz.String(); str != Blue.String() {
t.Fatalf("Wrong string, expected %s returned %s", Blue, str)
}
}

View File

@ -11,6 +11,46 @@ import (
"github.com/ava-labs/gecko/ids"
)
// ByzantineFactory implements Factory by returning a byzantine struct
type ByzantineFactory struct{}
// New implements Factory
func (ByzantineFactory) New() Consensus { return &Byzantine{} }
// Byzantine is a naive implementation of a multi-choice snowball instance
type Byzantine struct {
// params contains all the configurations of a snowball instance
params Parameters
// Hardcode the preference
preference ids.ID
}
// Initialize implements the Consensus interface
func (b *Byzantine) Initialize(params Parameters, choice ids.ID) {
b.params = params
b.preference = choice
}
// Parameters implements the Consensus interface
func (b *Byzantine) Parameters() Parameters { return b.params }
// Add implements the Consensus interface
func (b *Byzantine) Add(choice ids.ID) {}
// Preference implements the Consensus interface
func (b *Byzantine) Preference() ids.ID { return b.preference }
// RecordPoll implements the Consensus interface
func (b *Byzantine) RecordPoll(votes ids.Bag) {}
// RecordUnsuccessfulPoll implements the Consensus interface
func (b *Byzantine) RecordUnsuccessfulPoll() {}
// Finalized implements the Consensus interface
func (b *Byzantine) Finalized() bool { return true }
func (b *Byzantine) String() string { return b.preference.String() }
var (
Red = ids.Empty.Prefix(0)
Blue = ids.Empty.Prefix(1)

View File

@ -34,7 +34,7 @@ func (f *Flat) Parameters() Parameters { return f.params }
// RecordPoll implements the Consensus interface
func (f *Flat) RecordPoll(votes ids.Bag) {
if pollMode, numVotes := votes.Mode(); numVotes >= f.params.Alpha {
f.nnarySnowball.RecordSuccessfulPoll(pollMode)
f.RecordSuccessfulPoll(pollMode)
} else {
f.RecordUnsuccessfulPoll()
}

View File

@ -51,7 +51,7 @@ func (sf *nnarySnowflake) RecordSuccessfulPoll(choice ids.ID) {
return // This instace is already decided.
}
if preference := sf.nnarySlush.Preference(); preference.Equals(choice) {
if preference := sf.Preference(); preference.Equals(choice) {
sf.confidence++
} else {
// confidence is set to 1 because there has already been 1 successful

View File

@ -17,6 +17,7 @@ type TestBlock struct {
height int
status choices.Status
bytes []byte
err error
}
func (b *TestBlock) Parent() Block { return b.parent }
@ -27,16 +28,16 @@ func (b *TestBlock) Accept() error {
return errors.New("Dis-agreement")
}
b.status = choices.Accepted
return nil
return b.err
}
func (b *TestBlock) Reject() error {
if b.status.Decided() && b.status != choices.Rejected {
return errors.New("Dis-agreement")
}
b.status = choices.Rejected
return nil
return b.err
}
func (b *TestBlock) Verify() error { return nil }
func (b *TestBlock) Verify() error { return b.err }
func (b *TestBlock) Bytes() []byte { return b.bytes }
type sortBlocks []*TestBlock

View File

@ -4,6 +4,7 @@
package snowman
import (
"errors"
"math/rand"
"testing"
@ -42,6 +43,10 @@ var (
MetricsProcessingErrorTest,
MetricsAcceptedErrorTest,
MetricsRejectedErrorTest,
ErrorOnInitialRejectionTest,
ErrorOnAcceptTest,
ErrorOnRejectSiblingTest,
ErrorOnTransitiveRejectionTest,
RandomizedConsistencyTest,
}
)
@ -71,11 +76,9 @@ func InitializeTest(t *testing.T, factory Factory) {
if p := sm.Parameters(); p != params {
t.Fatalf("Wrong returned parameters")
}
if pref := sm.Preference(); !pref.Equals(GenesisID) {
} else if pref := sm.Preference(); !pref.Equals(GenesisID) {
t.Fatalf("Wrong preference returned")
}
if !sm.Finalized() {
} else if !sm.Finalized() {
t.Fatalf("Wrong should have marked the instance as being finalized")
}
}
@ -101,9 +104,9 @@ func AddToTailTest(t *testing.T, factory Factory) {
}
// Adding to the previous preference will update the preference
sm.Add(block)
if pref := sm.Preference(); !pref.Equals(block.id) {
if err := sm.Add(block); err != nil {
t.Fatal(err)
} else if pref := sm.Preference(); !pref.Equals(block.id) {
t.Fatalf("Wrong preference. Expected %s, got %s", block.id, pref)
}
}
@ -133,17 +136,17 @@ func AddToNonTailTest(t *testing.T, factory Factory) {
}
// Adding to the previous preference will update the preference
sm.Add(firstBlock)
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
if err := sm.Add(firstBlock); err != nil {
t.Fatal(err)
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
t.Fatalf("Wrong preference. Expected %s, got %s", firstBlock.id, pref)
}
// Adding to something other than the previous preference won't update the
// preference
sm.Add(secondBlock)
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
if err := sm.Add(secondBlock); err != nil {
t.Fatal(err)
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
t.Fatalf("Wrong preference. Expected %s, got %s", firstBlock.id, pref)
}
}
@ -171,9 +174,9 @@ func AddToUnknownTest(t *testing.T, factory Factory) {
// Adding a block with an unknown parent means the parent must have already
// been rejected. Therefore the block should be immediately rejected
sm.Add(block)
if pref := sm.Preference(); !pref.Equals(GenesisID) {
if err := sm.Add(block); err != nil {
t.Fatal(err)
} else if pref := sm.Preference(); !pref.Equals(GenesisID) {
t.Fatalf("Wrong preference. Expected %s, got %s", GenesisID, pref)
} else if status := block.Status(); status != choices.Rejected {
t.Fatalf("Should have rejected the block")
@ -269,9 +272,9 @@ func IssuedIssuedTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(block)
if !sm.Issued(block) {
if err := sm.Add(block); err != nil {
t.Fatal(err)
} else if !sm.Issued(block) {
t.Fatalf("Should have marked a pending block as having been issued")
}
}
@ -296,24 +299,23 @@ func RecordPollAcceptSingleBlockTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(block)
if err := sm.Add(block); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(block.id)
sm.RecordPoll(votes)
if pref := sm.Preference(); !pref.Equals(block.id) {
if err := sm.RecordPoll(votes); err != nil {
t.Fatal(err)
} else if pref := sm.Preference(); !pref.Equals(block.id) {
t.Fatalf("Preference returned the wrong block")
} else if sm.Finalized() {
t.Fatalf("Snowman instance finalized too soon")
} else if status := block.Status(); status != choices.Processing {
t.Fatalf("Block's status changed unexpectedly")
}
sm.RecordPoll(votes)
if pref := sm.Preference(); !pref.Equals(block.id) {
} else if err := sm.RecordPoll(votes); err != nil {
t.Fatal(err)
} else if pref := sm.Preference(); !pref.Equals(block.id) {
t.Fatalf("Preference returned the wrong block")
} else if !sm.Finalized() {
t.Fatalf("Snowman instance didn't finalize")
@ -347,15 +349,18 @@ func RecordPollAcceptAndRejectTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(firstBlock)
sm.Add(secondBlock)
if err := sm.Add(firstBlock); err != nil {
t.Fatal(err)
} else if err := sm.Add(secondBlock); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(firstBlock.id)
sm.RecordPoll(votes)
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
if err := sm.RecordPoll(votes); err != nil {
t.Fatal(err)
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
t.Fatalf("Preference returned the wrong block")
} else if sm.Finalized() {
t.Fatalf("Snowman instance finalized too soon")
@ -363,11 +368,9 @@ func RecordPollAcceptAndRejectTest(t *testing.T, factory Factory) {
t.Fatalf("Block's status changed unexpectedly")
} else if status := secondBlock.Status(); status != choices.Processing {
t.Fatalf("Block's status changed unexpectedly")
}
sm.RecordPoll(votes)
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
} else if err := sm.RecordPoll(votes); err != nil {
t.Fatal(err)
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
t.Fatalf("Preference returned the wrong block")
} else if !sm.Finalized() {
t.Fatalf("Snowman instance didn't finalize")
@ -394,9 +397,9 @@ func RecordPollWhenFinalizedTest(t *testing.T, factory Factory) {
votes := ids.Bag{}
votes.Add(GenesisID)
sm.RecordPoll(votes)
if !sm.Finalized() {
if err := sm.RecordPoll(votes); err != nil {
t.Fatal(err)
} else if !sm.Finalized() {
t.Fatalf("Consensus should still be finalized")
} else if pref := sm.Preference(); !GenesisID.Equals(pref) {
t.Fatalf("Wrong preference listed")
@ -433,9 +436,13 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(block0)
sm.Add(block1)
sm.Add(block2)
if err := sm.Add(block0); err != nil {
t.Fatal(err)
} else if err := sm.Add(block1); err != nil {
t.Fatal(err)
} else if err := sm.Add(block2); err != nil {
t.Fatal(err)
}
// Current graph structure:
// G
@ -447,7 +454,9 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) {
votes := ids.Bag{}
votes.Add(block0.id)
sm.RecordPoll(votes)
if err := sm.RecordPoll(votes); err != nil {
t.Fatal(err)
}
// Current graph structure:
// 0
@ -457,9 +466,7 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) {
t.Fatalf("Finalized too late")
} else if pref := sm.Preference(); !block0.id.Equals(pref) {
t.Fatalf("Wrong preference listed")
}
if status := block0.Status(); status != choices.Accepted {
} else if status := block0.Status(); status != choices.Accepted {
t.Fatalf("Wrong status returned")
} else if status := block1.Status(); status != choices.Rejected {
t.Fatalf("Wrong status returned")
@ -503,10 +510,15 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(block0)
sm.Add(block1)
sm.Add(block2)
sm.Add(block3)
if err := sm.Add(block0); err != nil {
t.Fatal(err)
} else if err := sm.Add(block1); err != nil {
t.Fatal(err)
} else if err := sm.Add(block2); err != nil {
t.Fatal(err)
} else if err := sm.Add(block3); err != nil {
t.Fatal(err)
}
// Current graph structure:
// G
@ -517,26 +529,24 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) {
votesFor2 := ids.Bag{}
votesFor2.Add(block2.id)
sm.RecordPoll(votesFor2)
if sm.Finalized() {
if err := sm.RecordPoll(votesFor2); err != nil {
t.Fatal(err)
} else if sm.Finalized() {
t.Fatalf("Finalized too early")
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
t.Fatalf("Wrong preference listed")
}
emptyVotes := ids.Bag{}
sm.RecordPoll(emptyVotes)
if sm.Finalized() {
if err := sm.RecordPoll(emptyVotes); err != nil {
t.Fatal(err)
} else if sm.Finalized() {
t.Fatalf("Finalized too early")
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
t.Fatalf("Wrong preference listed")
}
sm.RecordPoll(votesFor2)
if sm.Finalized() {
} else if err := sm.RecordPoll(votesFor2); err != nil {
t.Fatal(err)
} else if sm.Finalized() {
t.Fatalf("Finalized too early")
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
t.Fatalf("Wrong preference listed")
@ -544,23 +554,19 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) {
votesFor3 := ids.Bag{}
votesFor3.Add(block3.id)
sm.RecordPoll(votesFor3)
if sm.Finalized() {
if err := sm.RecordPoll(votesFor3); err != nil {
t.Fatal(err)
} else if sm.Finalized() {
t.Fatalf("Finalized too early")
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
t.Fatalf("Wrong preference listed")
}
sm.RecordPoll(votesFor3)
if !sm.Finalized() {
} else if err := sm.RecordPoll(votesFor3); err != nil {
t.Fatal(err)
} else if !sm.Finalized() {
t.Fatalf("Finalized too late")
} else if pref := sm.Preference(); !block3.id.Equals(pref) {
t.Fatalf("Wrong preference listed")
}
if status := block0.Status(); status != choices.Rejected {
} else if status := block0.Status(); status != choices.Rejected {
t.Fatalf("Wrong status returned")
} else if status := block1.Status(); status != choices.Accepted {
t.Fatalf("Wrong status returned")
@ -592,19 +598,23 @@ func RecordPollInvalidVoteTest(t *testing.T, factory Factory) {
}
unknownBlockID := ids.Empty.Prefix(2)
sm.Add(block)
if err := sm.Add(block); err != nil {
t.Fatal(err)
}
validVotes := ids.Bag{}
validVotes.Add(block.id)
sm.RecordPoll(validVotes)
if err := sm.RecordPoll(validVotes); err != nil {
t.Fatal(err)
}
invalidVotes := ids.Bag{}
invalidVotes.Add(unknownBlockID)
sm.RecordPoll(invalidVotes)
sm.RecordPoll(validVotes)
if sm.Finalized() {
if err := sm.RecordPoll(invalidVotes); err != nil {
t.Fatal(err)
} else if err := sm.RecordPoll(validVotes); err != nil {
t.Fatal(err)
} else if sm.Finalized() {
t.Fatalf("Finalized too early")
} else if pref := sm.Preference(); !block.id.Equals(pref) {
t.Fatalf("Wrong preference listed")
@ -651,11 +661,17 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(block0)
sm.Add(block1)
sm.Add(block2)
sm.Add(block3)
sm.Add(block4)
if err := sm.Add(block0); err != nil {
t.Fatal(err)
} else if err := sm.Add(block1); err != nil {
t.Fatal(err)
} else if err := sm.Add(block2); err != nil {
t.Fatal(err)
} else if err := sm.Add(block3); err != nil {
t.Fatal(err)
} else if err := sm.Add(block4); err != nil {
t.Fatal(err)
}
// Current graph structure:
// G
@ -668,10 +684,14 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) {
// Tail = 2
votes0_2_4 := ids.Bag{}
votes0_2_4.Add(block0.id)
votes0_2_4.Add(block2.id)
votes0_2_4.Add(block4.id)
sm.RecordPoll(votes0_2_4)
votes0_2_4.Add(
block0.id,
block2.id,
block4.id,
)
if err := sm.RecordPoll(votes0_2_4); err != nil {
t.Fatal(err)
}
// Current graph structure:
// 0
@ -699,7 +719,9 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) {
dep2_2_2 := ids.Bag{}
dep2_2_2.AddCount(block2.id, 3)
sm.RecordPoll(dep2_2_2)
if err := sm.RecordPoll(dep2_2_2); err != nil {
t.Fatal(err)
}
// Current graph structure:
// 2
@ -757,20 +779,25 @@ func RecordPollDivergedVotingTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(block0)
sm.Add(block1)
if err := sm.Add(block0); err != nil {
t.Fatal(err)
} else if err := sm.Add(block1); err != nil {
t.Fatal(err)
}
votes0 := ids.Bag{}
votes0.Add(block0.id)
sm.RecordPoll(votes0)
sm.Add(block2)
if err := sm.RecordPoll(votes0); err != nil {
t.Fatal(err)
} else if err := sm.Add(block2); err != nil {
t.Fatal(err)
}
// dep2 is already rejected.
sm.Add(block3)
if status := block0.Status(); status == choices.Accepted {
if err := sm.Add(block3); err != nil {
t.Fatal(err)
} else if status := block0.Status(); status == choices.Accepted {
t.Fatalf("Shouldn't be accepted yet")
}
@ -778,9 +805,9 @@ func RecordPollDivergedVotingTest(t *testing.T, factory Factory) {
// dep0. Because dep2 is already rejected, this will accept dep0.
votes3 := ids.Bag{}
votes3.Add(block3.id)
sm.RecordPoll(votes3)
if !sm.Finalized() {
if err := sm.RecordPoll(votes3); err != nil {
t.Fatal(err)
} else if !sm.Finalized() {
t.Fatalf("Finalized too late")
} else if status := block0.Status(); status != choices.Accepted {
t.Fatalf("Should be accepted")
@ -818,14 +845,15 @@ func MetricsProcessingErrorTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(block)
if err := sm.Add(block); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(block.id)
sm.RecordPoll(votes)
if !sm.Finalized() {
if err := sm.RecordPoll(votes); err != nil {
t.Fatal(err)
} else if !sm.Finalized() {
t.Fatalf("Snowman instance didn't finalize")
}
}
@ -861,14 +889,15 @@ func MetricsAcceptedErrorTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(block)
if err := sm.Add(block); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(block.id)
sm.RecordPoll(votes)
if !sm.Finalized() {
if err := sm.RecordPoll(votes); err != nil {
t.Fatal(err)
} else if !sm.Finalized() {
t.Fatalf("Snowman instance didn't finalize")
}
}
@ -904,18 +933,171 @@ func MetricsRejectedErrorTest(t *testing.T, factory Factory) {
status: choices.Processing,
}
sm.Add(block)
if err := sm.Add(block); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(block.id)
sm.RecordPoll(votes)
if !sm.Finalized() {
if err := sm.RecordPoll(votes); err != nil {
t.Fatal(err)
} else if !sm.Finalized() {
t.Fatalf("Snowman instance didn't finalize")
}
}
func ErrorOnInitialRejectionTest(t *testing.T, factory Factory) {
sm := factory.New()
ctx := snow.DefaultContextTest()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1,
Alpha: 1,
BetaVirtuous: 1,
BetaRogue: 1,
ConcurrentRepolls: 1,
}
sm.Initialize(ctx, params, GenesisID)
rejectedBlock := &TestBlock{
id: ids.Empty.Prefix(1),
status: choices.Rejected,
}
block := &TestBlock{
parent: rejectedBlock,
id: ids.Empty.Prefix(2),
status: choices.Processing,
err: errors.New(""),
}
if err := sm.Add(block); err == nil {
t.Fatalf("Should have errored on rejecting the rejectable block")
}
}
func ErrorOnAcceptTest(t *testing.T, factory Factory) {
sm := factory.New()
ctx := snow.DefaultContextTest()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1,
Alpha: 1,
BetaVirtuous: 1,
BetaRogue: 1,
ConcurrentRepolls: 1,
}
sm.Initialize(ctx, params, GenesisID)
block := &TestBlock{
parent: Genesis,
id: ids.Empty.Prefix(1),
status: choices.Processing,
err: errors.New(""),
}
if err := sm.Add(block); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(block.id)
if err := sm.RecordPoll(votes); err == nil {
t.Fatalf("Should have errored on accepted the block")
}
}
func ErrorOnRejectSiblingTest(t *testing.T, factory Factory) {
sm := factory.New()
ctx := snow.DefaultContextTest()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1,
Alpha: 1,
BetaVirtuous: 1,
BetaRogue: 1,
ConcurrentRepolls: 1,
}
sm.Initialize(ctx, params, GenesisID)
block0 := &TestBlock{
parent: Genesis,
id: ids.Empty.Prefix(1),
status: choices.Processing,
}
block1 := &TestBlock{
parent: Genesis,
id: ids.Empty.Prefix(2),
status: choices.Processing,
err: errors.New(""),
}
if err := sm.Add(block0); err != nil {
t.Fatal(err)
} else if err := sm.Add(block1); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(block0.id)
if err := sm.RecordPoll(votes); err == nil {
t.Fatalf("Should have errored on rejecting the block's sibling")
}
}
func ErrorOnTransitiveRejectionTest(t *testing.T, factory Factory) {
sm := factory.New()
ctx := snow.DefaultContextTest()
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1,
Alpha: 1,
BetaVirtuous: 1,
BetaRogue: 1,
ConcurrentRepolls: 1,
}
sm.Initialize(ctx, params, GenesisID)
block0 := &TestBlock{
parent: Genesis,
id: ids.Empty.Prefix(1),
status: choices.Processing,
}
block1 := &TestBlock{
parent: Genesis,
id: ids.Empty.Prefix(2),
status: choices.Processing,
}
block2 := &TestBlock{
parent: block1,
id: ids.Empty.Prefix(3),
status: choices.Processing,
err: errors.New(""),
}
if err := sm.Add(block0); err != nil {
t.Fatal(err)
} else if err := sm.Add(block1); err != nil {
t.Fatal(err)
} else if err := sm.Add(block2); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(block0.id)
if err := sm.RecordPoll(votes); err == nil {
t.Fatalf("Should have errored on transitively rejecting the block")
}
}
func RandomizedConsistencyTest(t *testing.T, factory Factory) {
numColors := 50
numNodes := 100

View File

@ -9,6 +9,10 @@ import (
"github.com/ava-labs/gecko/snow/consensus/snowball"
)
const (
minMapSize = 16
)
// TopologicalFactory implements Factory by returning a topological struct
type TopologicalFactory struct{}
@ -183,7 +187,7 @@ func (ts *Topological) Finalized() bool { return len(ts.blocks) == 1 }
// the non-transitively applied votes. Also returns the list of leaf blocks.
func (ts *Topological) calculateInDegree(
votes ids.Bag) (map[[32]byte]kahnNode, []ids.ID) {
kahns := make(map[[32]byte]kahnNode)
kahns := make(map[[32]byte]kahnNode, minMapSize)
leaves := ids.Set{}
for _, vote := range votes.List() {

View File

@ -4,6 +4,7 @@
package snowstorm
import (
"errors"
"testing"
"github.com/prometheus/client_golang/prometheus"
@ -19,6 +20,28 @@ var (
Green = &TestTx{Identifier: ids.Empty.Prefix(1)}
Blue = &TestTx{Identifier: ids.Empty.Prefix(2)}
Alpha = &TestTx{Identifier: ids.Empty.Prefix(3)}
Tests = []func(*testing.T, Factory){
MetricsTest,
ParamsTest,
IssuedTest,
LeftoverInputTest,
LowerConfidenceTest,
MiddleConfidenceTest,
IndependentTest,
VirtuousTest,
IsVirtuousTest,
QuiesceTest,
AcceptingDependencyTest,
RejectingDependencyTest,
VacuouslyAcceptedTest,
ConflictsTest,
VirtuousDependsOnRogueTest,
ErrorOnVacuouslyAcceptedTest,
ErrorOnAcceptedTest,
ErrorOnRejectingLowerConfidenceConflictTest,
ErrorOnRejectingHigherConfidenceConflictTest,
}
)
// R - G - B - A
@ -46,6 +69,52 @@ func Setup() {
Alpha.Reset()
}
// Execute all tests against a consensus implementation
func ConsensusTest(t *testing.T, factory Factory, prefix string) {
for _, test := range Tests {
test(t, factory)
}
StringTest(t, factory, prefix)
}
func MetricsTest(t *testing.T, factory Factory) {
Setup()
{
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
}
params.Metrics.Register(prometheus.NewCounter(prometheus.CounterOpts{
Name: "tx_processing",
}))
graph := factory.New()
graph.Initialize(snow.DefaultContextTest(), params)
}
{
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
}
params.Metrics.Register(prometheus.NewCounter(prometheus.CounterOpts{
Name: "tx_accepted",
}))
graph := factory.New()
graph.Initialize(snow.DefaultContextTest(), params)
}
{
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
}
params.Metrics.Register(prometheus.NewCounter(prometheus.CounterOpts{
Name: "tx_rejected",
}))
graph := factory.New()
graph.Initialize(snow.DefaultContextTest(), params)
}
}
func ParamsTest(t *testing.T, factory Factory) {
Setup()
@ -81,15 +150,13 @@ func IssuedTest(t *testing.T, factory Factory) {
if issued := graph.Issued(Red); issued {
t.Fatalf("Haven't issued anything yet.")
}
graph.Add(Red)
if issued := graph.Issued(Red); !issued {
} else if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if issued := graph.Issued(Red); !issued {
t.Fatalf("Have already issued.")
}
Blue.Accept()
_ = Blue.Accept()
if issued := graph.Issued(Blue); !issued {
t.Fatalf("Have already accepted.")
@ -106,10 +173,12 @@ func LeftoverInputTest(t *testing.T, factory Factory) {
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
}
graph.Initialize(snow.DefaultContextTest(), params)
graph.Add(Red)
graph.Add(Green)
if prefs := graph.Preferences(); prefs.Len() != 1 {
if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if err := graph.Add(Green); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Red.ID()) {
t.Fatalf("Wrong preference. Expected %s got %s", Red.ID(), prefs.List()[0])
@ -120,15 +189,13 @@ func LeftoverInputTest(t *testing.T, factory Factory) {
r := ids.Bag{}
r.SetThreshold(2)
r.AddCount(Red.ID(), 2)
graph.RecordPoll(r)
if prefs := graph.Preferences(); prefs.Len() != 0 {
if err := graph.RecordPoll(r); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
t.Fatalf("Wrong number of preferences.")
} else if !graph.Finalized() {
t.Fatalf("Finalized too late")
}
if Red.Status() != choices.Accepted {
} else if Red.Status() != choices.Accepted {
t.Fatalf("%s should have been accepted", Red.ID())
} else if Green.Status() != choices.Rejected {
t.Fatalf("%s should have been rejected", Green.ID())
@ -145,11 +212,14 @@ func LowerConfidenceTest(t *testing.T, factory Factory) {
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
}
graph.Initialize(snow.DefaultContextTest(), params)
graph.Add(Red)
graph.Add(Green)
graph.Add(Blue)
if prefs := graph.Preferences(); prefs.Len() != 1 {
if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if err := graph.Add(Green); err != nil {
t.Fatal(err)
} else if err := graph.Add(Blue); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Red.ID()) {
t.Fatalf("Wrong preference. Expected %s got %s", Red.ID(), prefs.List()[0])
@ -160,9 +230,9 @@ func LowerConfidenceTest(t *testing.T, factory Factory) {
r := ids.Bag{}
r.SetThreshold(2)
r.AddCount(Red.ID(), 2)
graph.RecordPoll(r)
if prefs := graph.Preferences(); prefs.Len() != 1 {
if err := graph.RecordPoll(r); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Blue.ID()) {
t.Fatalf("Wrong preference. Expected %s", Blue.ID())
@ -181,12 +251,16 @@ func MiddleConfidenceTest(t *testing.T, factory Factory) {
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
}
graph.Initialize(snow.DefaultContextTest(), params)
graph.Add(Red)
graph.Add(Green)
graph.Add(Alpha)
graph.Add(Blue)
if prefs := graph.Preferences(); prefs.Len() != 2 {
if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if err := graph.Add(Green); err != nil {
t.Fatal(err)
} else if err := graph.Add(Alpha); err != nil {
t.Fatal(err)
} else if err := graph.Add(Blue); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Red.ID()) {
t.Fatalf("Wrong preference. Expected %s", Red.ID())
@ -199,9 +273,9 @@ func MiddleConfidenceTest(t *testing.T, factory Factory) {
r := ids.Bag{}
r.SetThreshold(2)
r.AddCount(Red.ID(), 2)
graph.RecordPoll(r)
if prefs := graph.Preferences(); prefs.Len() != 1 {
if err := graph.RecordPoll(r); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Alpha.ID()) {
t.Fatalf("Wrong preference. Expected %s", Alpha.ID())
@ -209,6 +283,7 @@ func MiddleConfidenceTest(t *testing.T, factory Factory) {
t.Fatalf("Finalized too early")
}
}
func IndependentTest(t *testing.T, factory Factory) {
Setup()
@ -219,10 +294,12 @@ func IndependentTest(t *testing.T, factory Factory) {
K: 2, Alpha: 2, BetaVirtuous: 2, BetaRogue: 2,
}
graph.Initialize(snow.DefaultContextTest(), params)
graph.Add(Red)
graph.Add(Alpha)
if prefs := graph.Preferences(); prefs.Len() != 2 {
if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if err := graph.Add(Alpha); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Red.ID()) {
t.Fatalf("Wrong preference. Expected %s", Red.ID())
@ -236,9 +313,9 @@ func IndependentTest(t *testing.T, factory Factory) {
ra.SetThreshold(2)
ra.AddCount(Red.ID(), 2)
ra.AddCount(Alpha.ID(), 2)
graph.RecordPoll(ra)
if prefs := graph.Preferences(); prefs.Len() != 2 {
if err := graph.RecordPoll(ra); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Red.ID()) {
t.Fatalf("Wrong preference. Expected %s", Red.ID())
@ -246,11 +323,9 @@ func IndependentTest(t *testing.T, factory Factory) {
t.Fatalf("Wrong preference. Expected %s", Alpha.ID())
} else if graph.Finalized() {
t.Fatalf("Finalized too early")
}
graph.RecordPoll(ra)
if prefs := graph.Preferences(); prefs.Len() != 0 {
} else if err := graph.RecordPoll(ra); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
t.Fatalf("Wrong number of preferences.")
} else if !graph.Finalized() {
t.Fatalf("Finalized too late")
@ -267,35 +342,30 @@ func VirtuousTest(t *testing.T, factory Factory) {
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
}
graph.Initialize(snow.DefaultContextTest(), params)
graph.Add(Red)
if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(Red.ID()) {
t.Fatalf("Wrong virtuous. Expected %s", Red.ID())
}
graph.Add(Alpha)
if virtuous := graph.Virtuous(); virtuous.Len() != 2 {
} else if err := graph.Add(Alpha); err != nil {
t.Fatal(err)
} else if virtuous := graph.Virtuous(); virtuous.Len() != 2 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(Red.ID()) {
t.Fatalf("Wrong virtuous. Expected %s", Red.ID())
} else if !virtuous.Contains(Alpha.ID()) {
t.Fatalf("Wrong virtuous. Expected %s", Alpha.ID())
}
graph.Add(Green)
if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
} else if err := graph.Add(Green); err != nil {
t.Fatal(err)
} else if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
t.Fatalf("Wrong number of virtuous.")
} else if !virtuous.Contains(Alpha.ID()) {
t.Fatalf("Wrong virtuous. Expected %s", Alpha.ID())
}
graph.Add(Blue)
if virtuous := graph.Virtuous(); virtuous.Len() != 0 {
} else if err := graph.Add(Blue); err != nil {
t.Fatal(err)
} else if virtuous := graph.Virtuous(); virtuous.Len() != 0 {
t.Fatalf("Wrong number of virtuous.")
}
}
@ -319,11 +389,9 @@ func IsVirtuousTest(t *testing.T, factory Factory) {
t.Fatalf("Should be virtuous")
} else if !graph.IsVirtuous(Alpha) {
t.Fatalf("Should be virtuous")
}
graph.Add(Red)
if !graph.IsVirtuous(Red) {
} else if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if !graph.IsVirtuous(Red) {
t.Fatalf("Should be virtuous")
} else if graph.IsVirtuous(Green) {
t.Fatalf("Should not be virtuous")
@ -331,11 +399,9 @@ func IsVirtuousTest(t *testing.T, factory Factory) {
t.Fatalf("Should be virtuous")
} else if !graph.IsVirtuous(Alpha) {
t.Fatalf("Should be virtuous")
}
graph.Add(Green)
if graph.IsVirtuous(Red) {
} else if err := graph.Add(Green); err != nil {
t.Fatal(err)
} else if graph.IsVirtuous(Red) {
t.Fatalf("Should not be virtuous")
} else if graph.IsVirtuous(Green) {
t.Fatalf("Should not be virtuous")
@ -357,17 +423,13 @@ func QuiesceTest(t *testing.T, factory Factory) {
if !graph.Quiesce() {
t.Fatalf("Should quiesce")
}
graph.Add(Red)
if graph.Quiesce() {
} else if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if graph.Quiesce() {
t.Fatalf("Shouldn't quiesce")
}
graph.Add(Green)
if !graph.Quiesce() {
} else if err := graph.Add(Green); err != nil {
t.Fatal(err)
} else if !graph.Quiesce() {
t.Fatalf("Should quiesce")
}
}
@ -390,11 +452,13 @@ func AcceptingDependencyTest(t *testing.T, factory Factory) {
}
graph.Initialize(snow.DefaultContextTest(), params)
graph.Add(Red)
graph.Add(Green)
graph.Add(purple)
if prefs := graph.Preferences(); prefs.Len() != 2 {
if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if err := graph.Add(Green); err != nil {
t.Fatal(err)
} else if err := graph.Add(purple); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Red.ID()) {
t.Fatalf("Wrong preference. Expected %s", Red.ID())
@ -410,10 +474,9 @@ func AcceptingDependencyTest(t *testing.T, factory Factory) {
g := ids.Bag{}
g.Add(Green.ID())
graph.RecordPoll(g)
if prefs := graph.Preferences(); prefs.Len() != 2 {
if err := graph.RecordPoll(g); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Green.ID()) {
t.Fatalf("Wrong preference. Expected %s", Green.ID())
@ -429,10 +492,9 @@ func AcceptingDependencyTest(t *testing.T, factory Factory) {
rp := ids.Bag{}
rp.Add(Red.ID(), purple.ID())
graph.RecordPoll(rp)
if prefs := graph.Preferences(); prefs.Len() != 2 {
if err := graph.RecordPoll(rp); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Green.ID()) {
t.Fatalf("Wrong preference. Expected %s", Green.ID())
@ -448,10 +510,9 @@ func AcceptingDependencyTest(t *testing.T, factory Factory) {
r := ids.Bag{}
r.Add(Red.ID())
graph.RecordPoll(r)
if prefs := graph.Preferences(); prefs.Len() != 0 {
if err := graph.RecordPoll(r); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
t.Fatalf("Wrong number of preferences.")
} else if Red.Status() != choices.Accepted {
t.Fatalf("Wrong status. %s should be %s", Red.ID(), choices.Accepted)
@ -480,12 +541,15 @@ func RejectingDependencyTest(t *testing.T, factory Factory) {
}
graph.Initialize(snow.DefaultContextTest(), params)
graph.Add(Red)
graph.Add(Green)
graph.Add(Blue)
graph.Add(purple)
if prefs := graph.Preferences(); prefs.Len() != 2 {
if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if err := graph.Add(Green); err != nil {
t.Fatal(err)
} else if err := graph.Add(Blue); err != nil {
t.Fatal(err)
} else if err := graph.Add(purple); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Red.ID()) {
t.Fatalf("Wrong preference. Expected %s", Red.ID())
@ -503,10 +567,9 @@ func RejectingDependencyTest(t *testing.T, factory Factory) {
gp := ids.Bag{}
gp.Add(Green.ID(), purple.ID())
graph.RecordPoll(gp)
if prefs := graph.Preferences(); prefs.Len() != 2 {
if err := graph.RecordPoll(gp); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Green.ID()) {
t.Fatalf("Wrong preference. Expected %s", Green.ID())
@ -520,11 +583,9 @@ func RejectingDependencyTest(t *testing.T, factory Factory) {
t.Fatalf("Wrong status. %s should be %s", Blue.ID(), choices.Processing)
} else if purple.Status() != choices.Processing {
t.Fatalf("Wrong status. %s should be %s", purple.ID(), choices.Processing)
}
graph.RecordPoll(gp)
if prefs := graph.Preferences(); prefs.Len() != 0 {
} else if err := graph.RecordPoll(gp); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
t.Fatalf("Wrong number of preferences.")
} else if Red.Status() != choices.Rejected {
t.Fatalf("Wrong status. %s should be %s", Red.ID(), choices.Rejected)
@ -553,9 +614,9 @@ func VacuouslyAcceptedTest(t *testing.T, factory Factory) {
}
graph.Initialize(snow.DefaultContextTest(), params)
graph.Add(purple)
if prefs := graph.Preferences(); prefs.Len() != 0 {
if err := graph.Add(purple); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
t.Fatalf("Wrong number of preferences.")
} else if status := purple.Status(); status != choices.Accepted {
t.Fatalf("Wrong status. %s should be %s", purple.ID(), choices.Accepted)
@ -593,17 +654,15 @@ func ConflictsTest(t *testing.T, factory Factory) {
Ins: insPurple,
}
graph.Add(purple)
if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
if err := graph.Add(purple); err != nil {
t.Fatal(err)
} else if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
t.Fatalf("Wrong number of conflicts")
} else if !orangeConflicts.Contains(purple.Identifier) {
t.Fatalf("Conflicts does not contain the right transaction")
}
graph.Add(orange)
if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
} else if err := graph.Add(orange); err != nil {
t.Fatal(err)
} else if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
t.Fatalf("Wrong number of conflicts")
} else if !orangeConflicts.Contains(purple.Identifier) {
t.Fatalf("Conflicts does not contain the right transaction")
@ -643,17 +702,20 @@ func VirtuousDependsOnRogueTest(t *testing.T, factory Factory) {
virtuous.Ins.Add(input2)
graph.Add(rogue1)
graph.Add(rogue2)
graph.Add(virtuous)
if err := graph.Add(rogue1); err != nil {
t.Fatal(err)
} else if err := graph.Add(rogue2); err != nil {
t.Fatal(err)
} else if err := graph.Add(virtuous); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(rogue1.ID())
votes.Add(virtuous.ID())
graph.RecordPoll(votes)
if status := rogue1.Status(); status != choices.Processing {
if err := graph.RecordPoll(votes); err != nil {
t.Fatal(err)
} else if status := rogue1.Status(); status != choices.Processing {
t.Fatalf("Rogue Tx is %s expected %s", status, choices.Processing)
} else if status := rogue2.Status(); status != choices.Processing {
t.Fatalf("Rogue Tx is %s expected %s", status, choices.Processing)
@ -664,6 +726,135 @@ func VirtuousDependsOnRogueTest(t *testing.T, factory Factory) {
}
}
func ErrorOnVacuouslyAcceptedTest(t *testing.T, factory Factory) {
Setup()
graph := factory.New()
purple := &TestTx{
Identifier: ids.Empty.Prefix(7),
Stat: choices.Processing,
Validity: errors.New(""),
}
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
}
graph.Initialize(snow.DefaultContextTest(), params)
if err := graph.Add(purple); err == nil {
t.Fatalf("Should have errored on acceptance")
}
}
func ErrorOnAcceptedTest(t *testing.T, factory Factory) {
Setup()
graph := factory.New()
purple := &TestTx{
Identifier: ids.Empty.Prefix(7),
Stat: choices.Processing,
Validity: errors.New(""),
}
purple.Ins.Add(ids.Empty.Prefix(4))
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
}
graph.Initialize(snow.DefaultContextTest(), params)
if err := graph.Add(purple); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(purple.ID())
if err := graph.RecordPoll(votes); err == nil {
t.Fatalf("Should have errored on accepting an invalid tx")
}
}
func ErrorOnRejectingLowerConfidenceConflictTest(t *testing.T, factory Factory) {
Setup()
graph := factory.New()
X := ids.Empty.Prefix(4)
purple := &TestTx{
Identifier: ids.Empty.Prefix(7),
Stat: choices.Processing,
}
purple.Ins.Add(X)
pink := &TestTx{
Identifier: ids.Empty.Prefix(8),
Stat: choices.Processing,
Validity: errors.New(""),
}
pink.Ins.Add(X)
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 1,
}
graph.Initialize(snow.DefaultContextTest(), params)
if err := graph.Add(purple); err != nil {
t.Fatal(err)
} else if err := graph.Add(pink); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(purple.ID())
if err := graph.RecordPoll(votes); err == nil {
t.Fatalf("Should have errored on rejecting an invalid tx")
}
}
func ErrorOnRejectingHigherConfidenceConflictTest(t *testing.T, factory Factory) {
Setup()
graph := factory.New()
X := ids.Empty.Prefix(4)
purple := &TestTx{
Identifier: ids.Empty.Prefix(7),
Stat: choices.Processing,
}
purple.Ins.Add(X)
pink := &TestTx{
Identifier: ids.Empty.Prefix(8),
Stat: choices.Processing,
Validity: errors.New(""),
}
pink.Ins.Add(X)
params := snowball.Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 1,
}
graph.Initialize(snow.DefaultContextTest(), params)
if err := graph.Add(pink); err != nil {
t.Fatal(err)
} else if err := graph.Add(purple); err != nil {
t.Fatal(err)
}
votes := ids.Bag{}
votes.Add(purple.ID())
if err := graph.RecordPoll(votes); err == nil {
t.Fatalf("Should have errored on rejecting an invalid tx")
}
}
func StringTest(t *testing.T, factory Factory, prefix string) {
Setup()
@ -674,12 +865,16 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
}
graph.Initialize(snow.DefaultContextTest(), params)
graph.Add(Red)
graph.Add(Green)
graph.Add(Blue)
graph.Add(Alpha)
if prefs := graph.Preferences(); prefs.Len() != 1 {
if err := graph.Add(Red); err != nil {
t.Fatal(err)
} else if err := graph.Add(Green); err != nil {
t.Fatal(err)
} else if err := graph.Add(Blue); err != nil {
t.Fatal(err)
} else if err := graph.Add(Alpha); err != nil {
t.Fatal(err)
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
t.Fatalf("Wrong number of preferences.")
} else if !prefs.Contains(Red.ID()) {
t.Fatalf("Wrong preference. Expected %s got %s", Red.ID(), prefs.List()[0])
@ -691,8 +886,11 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
rb.SetThreshold(2)
rb.AddCount(Red.ID(), 2)
rb.AddCount(Blue.ID(), 2)
graph.RecordPoll(rb)
graph.Add(Blue)
if err := graph.RecordPoll(rb); err != nil {
t.Fatal(err)
} else if err := graph.Add(Blue); err != nil {
t.Fatal(err)
}
{
expected := prefix + "(\n" +
@ -720,7 +918,9 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
ga.SetThreshold(2)
ga.AddCount(Green.ID(), 2)
ga.AddCount(Alpha.ID(), 2)
graph.RecordPoll(ga)
if err := graph.RecordPoll(ga); err != nil {
t.Fatal(err)
}
{
expected := prefix + "(\n" +
@ -745,7 +945,9 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
}
empty := ids.Bag{}
graph.RecordPoll(empty)
if err := graph.RecordPoll(empty); err != nil {
t.Fatal(err)
}
{
expected := prefix + "(\n" +
@ -767,10 +969,10 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
t.Fatalf("Wrong preference. Expected %s", Blue.ID())
} else if graph.Finalized() {
t.Fatalf("Finalized too early")
} else if err := graph.RecordPoll(ga); err != nil {
t.Fatal(err)
}
graph.RecordPoll(ga)
{
expected := prefix + "(\n" +
" Choice[0] = ID: LUC1cmcxnfNR9LdkACS2ccGKLEK7SYqB4gLLTycQfg1koyfSq Confidence: 0 Bias: 1\n" +
@ -791,10 +993,10 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
t.Fatalf("Wrong preference. Expected %s", Alpha.ID())
} else if graph.Finalized() {
t.Fatalf("Finalized too early")
} else if err := graph.RecordPoll(ga); err != nil {
t.Fatal(err)
}
graph.RecordPoll(ga)
{
expected := prefix + "()"
if str := graph.String(); str != expected {
@ -806,9 +1008,7 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
t.Fatalf("Wrong number of preferences.")
} else if !graph.Finalized() {
t.Fatalf("Finalized too late")
}
if Green.Status() != choices.Accepted {
} else if Green.Status() != choices.Accepted {
t.Fatalf("%s should have been accepted", Green.ID())
} else if Alpha.Status() != choices.Accepted {
t.Fatalf("%s should have been accepted", Alpha.ID())
@ -816,10 +1016,10 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
t.Fatalf("%s should have been rejected", Red.ID())
} else if Blue.Status() != choices.Rejected {
t.Fatalf("%s should have been rejected", Blue.ID())
} else if err := graph.RecordPoll(rb); err != nil {
t.Fatal(err)
}
graph.RecordPoll(rb)
{
expected := prefix + "()"
if str := graph.String(); str != expected {
@ -831,9 +1031,7 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
t.Fatalf("Wrong number of preferences.")
} else if !graph.Finalized() {
t.Fatalf("Finalized too late")
}
if Green.Status() != choices.Accepted {
} else if Green.Status() != choices.Accepted {
t.Fatalf("%s should have been accepted", Green.ID())
} else if Alpha.Status() != choices.Accepted {
t.Fatalf("%s should have been accepted", Alpha.ID())

View File

@ -7,34 +7,4 @@ import (
"testing"
)
func TestDirectedParams(t *testing.T) { ParamsTest(t, DirectedFactory{}) }
func TestDirectedIssued(t *testing.T) { IssuedTest(t, DirectedFactory{}) }
func TestDirectedLeftoverInput(t *testing.T) { LeftoverInputTest(t, DirectedFactory{}) }
func TestDirectedLowerConfidence(t *testing.T) { LowerConfidenceTest(t, DirectedFactory{}) }
func TestDirectedMiddleConfidence(t *testing.T) { MiddleConfidenceTest(t, DirectedFactory{}) }
func TestDirectedIndependent(t *testing.T) { IndependentTest(t, DirectedFactory{}) }
func TestDirectedVirtuous(t *testing.T) { VirtuousTest(t, DirectedFactory{}) }
func TestDirectedIsVirtuous(t *testing.T) { IsVirtuousTest(t, DirectedFactory{}) }
func TestDirectedConflicts(t *testing.T) { ConflictsTest(t, DirectedFactory{}) }
func TestDirectedQuiesce(t *testing.T) { QuiesceTest(t, DirectedFactory{}) }
func TestDirectedAcceptingDependency(t *testing.T) { AcceptingDependencyTest(t, DirectedFactory{}) }
func TestDirectedRejectingDependency(t *testing.T) { RejectingDependencyTest(t, DirectedFactory{}) }
func TestDirectedVacuouslyAccepted(t *testing.T) { VacuouslyAcceptedTest(t, DirectedFactory{}) }
func TestDirectedVirtuousDependsOnRogue(t *testing.T) {
VirtuousDependsOnRogueTest(t, DirectedFactory{})
}
func TestDirectedString(t *testing.T) { StringTest(t, DirectedFactory{}, "DG") }
func TestDirectedConsensus(t *testing.T) { ConsensusTest(t, DirectedFactory{}, "DG") }

View File

@ -7,32 +7,4 @@ import (
"testing"
)
func TestInputParams(t *testing.T) { ParamsTest(t, InputFactory{}) }
func TestInputIssued(t *testing.T) { IssuedTest(t, InputFactory{}) }
func TestInputLeftoverInput(t *testing.T) { LeftoverInputTest(t, InputFactory{}) }
func TestInputLowerConfidence(t *testing.T) { LowerConfidenceTest(t, InputFactory{}) }
func TestInputMiddleConfidence(t *testing.T) { MiddleConfidenceTest(t, InputFactory{}) }
func TestInputIndependent(t *testing.T) { IndependentTest(t, InputFactory{}) }
func TestInputVirtuous(t *testing.T) { VirtuousTest(t, InputFactory{}) }
func TestInputIsVirtuous(t *testing.T) { IsVirtuousTest(t, InputFactory{}) }
func TestInputConflicts(t *testing.T) { ConflictsTest(t, InputFactory{}) }
func TestInputQuiesce(t *testing.T) { QuiesceTest(t, InputFactory{}) }
func TestInputAcceptingDependency(t *testing.T) { AcceptingDependencyTest(t, InputFactory{}) }
func TestInputRejectingDependency(t *testing.T) { RejectingDependencyTest(t, InputFactory{}) }
func TestInputVacuouslyAccepted(t *testing.T) { VacuouslyAcceptedTest(t, InputFactory{}) }
func TestInputVirtuousDependsOnRogue(t *testing.T) { VirtuousDependsOnRogueTest(t, InputFactory{}) }
func TestInputString(t *testing.T) { StringTest(t, InputFactory{}, "IG") }
func TestInputConsensus(t *testing.T) { ConsensusTest(t, InputFactory{}, "IG") }

View File

@ -31,10 +31,10 @@ func (tx *TestTx) InputIDs() ids.Set { return tx.Ins }
func (tx *TestTx) Status() choices.Status { return tx.Stat }
// Accept implements the Consumer interface
func (tx *TestTx) Accept() error { tx.Stat = choices.Accepted; return nil }
func (tx *TestTx) Accept() error { tx.Stat = choices.Accepted; return tx.Validity }
// Reject implements the Consumer interface
func (tx *TestTx) Reject() error { tx.Stat = choices.Rejected; return nil }
func (tx *TestTx) Reject() error { tx.Stat = choices.Rejected; return tx.Validity }
// Reset sets the status to pending
func (tx *TestTx) Reset() { tx.Stat = choices.Processing }

View File

@ -14,7 +14,7 @@ type metrics struct {
numBSVtx, numBSDroppedVtx,
numBSTx, numBSDroppedTx prometheus.Counter
numPolls, numVtxRequests, numTxRequests, numPendingVtx prometheus.Gauge
numVtxRequests, numTxRequests, numPendingVtx prometheus.Gauge
}
// Initialize implements the Engine interface
@ -61,12 +61,6 @@ func (m *metrics) Initialize(log logging.Logger, namespace string, registerer pr
Name: "av_bs_dropped_txs",
Help: "Number of dropped txs",
})
m.numPolls = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "av_polls",
Help: "Number of pending network polls",
})
m.numVtxRequests = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: namespace,
@ -107,9 +101,6 @@ func (m *metrics) Initialize(log logging.Logger, namespace string, registerer pr
if err := registerer.Register(m.numBSDroppedTx); err != nil {
log.Error("Failed to register av_bs_dropped_txs statistics due to %s", err)
}
if err := registerer.Register(m.numPolls); err != nil {
log.Error("Failed to register av_polls statistics due to %s", err)
}
if err := registerer.Register(m.numVtxRequests); err != nil {
log.Error("Failed to register av_vtx_requests statistics due to %s", err)
}

View File

@ -0,0 +1,85 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"fmt"
"github.com/ava-labs/gecko/ids"
)
type earlyTermNoTraversalFactory struct {
alpha int
}
// NewEarlyTermNoTraversalFactory returns a factory that returns polls with
// early termination, without doing DAG traversals
func NewEarlyTermNoTraversalFactory(alpha int) Factory {
return &earlyTermNoTraversalFactory{alpha: alpha}
}
func (f *earlyTermNoTraversalFactory) New(vdrs ids.ShortSet) Poll {
return &earlyTermNoTraversalPoll{
polled: vdrs,
alpha: f.alpha,
}
}
// earlyTermNoTraversalPoll finishes when any remaining validators can't change
// the result of the poll. However, does not terminate tightly with this bound.
// It terminates as quickly as it can without performing any DAG traversals.
type earlyTermNoTraversalPoll struct {
votes ids.UniqueBag
polled ids.ShortSet
alpha int
}
// Vote registers a response for this poll
func (p *earlyTermNoTraversalPoll) Vote(vdr ids.ShortID, votes []ids.ID) {
if !p.polled.Contains(vdr) {
// if the validator wasn't polled or already responded to this poll, we
// should just drop the vote
return
}
// make sure that a validator can't respond multiple times
p.polled.Remove(vdr)
// track the votes the validator responded with
p.votes.Add(uint(p.polled.Len()), votes...)
}
// Finished returns true when all validators have voted
func (p *earlyTermNoTraversalPoll) Finished() bool {
// If there are no outstanding queries, the poll is finished
numPending := p.polled.Len()
if numPending == 0 {
return true
}
// If there are still enough pending responses to include another vertex,
// then the poll must wait for more responses
if numPending > p.alpha {
return false
}
// Ignore any vertex that has already received alpha votes. To safely skip
// DAG traversal, assume that all votes for vertices with less than alpha
// votes will be applied to a single shared ancestor. In this case, the poll
// can terminate early, iff there are not enough pending votes for this
// ancestor to receive alpha votes.
partialVotes := ids.BitSet(0)
for _, vote := range p.votes.List() {
if voters := p.votes.GetSet(vote); voters.Len() < p.alpha {
partialVotes.Union(voters)
}
}
return partialVotes.Len()+numPending < p.alpha
}
// Result returns the result of this poll
func (p *earlyTermNoTraversalPoll) Result() ids.UniqueBag { return p.votes }
func (p *earlyTermNoTraversalPoll) String() string {
return fmt.Sprintf("waiting on %s", p.polled)
}

View File

@ -0,0 +1,207 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"testing"
"github.com/ava-labs/gecko/ids"
)
func TestEarlyTermNoTraversalResults(t *testing.T) {
alpha := 1
vtxID := ids.NewID([32]byte{1})
votes := []ids.ID{vtxID}
vdr1 := ids.NewShortID([20]byte{1}) // k = 1
vdrs := ids.ShortSet{}
vdrs.Add(vdr1)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, votes)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving k votes")
}
result := poll.Result()
if list := result.List(); len(list) != 1 {
t.Fatalf("Wrong number of vertices returned")
} else if retVtxID := list[0]; !retVtxID.Equals(vtxID) {
t.Fatalf("Wrong vertex returned")
} else if set := result.GetSet(vtxID); set.Len() != 1 {
t.Fatalf("Wrong number of votes returned")
}
}
func TestEarlyTermNoTraversalString(t *testing.T) {
alpha := 2
vtxID := ids.NewID([32]byte{1})
votes := []ids.ID{vtxID}
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, votes)
expected := "waiting on {BaMPFdqMUQ46BV8iRcwbVfsam55kMqcp}"
if result := poll.String(); expected != result {
t.Fatalf("Poll should have returned %s but returned %s", expected, result)
}
}
func TestEarlyTermNoTraversalDropsDuplicatedVotes(t *testing.T) {
alpha := 2
vtxID := ids.NewID([32]byte{1})
votes := []ids.ID{vtxID}
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, votes)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(vdr1, votes)
if poll.Finished() {
t.Fatalf("Poll finished after getting a duplicated vote")
}
poll.Vote(vdr2, votes)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving k votes")
}
}
func TestEarlyTermNoTraversalTerminatesEarly(t *testing.T) {
alpha := 3
vtxID := ids.NewID([32]byte{1})
votes := []ids.ID{vtxID}
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2})
vdr3 := ids.NewShortID([20]byte{3})
vdr4 := ids.NewShortID([20]byte{4})
vdr5 := ids.NewShortID([20]byte{5}) // k = 5
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
vdr3,
vdr4,
vdr5,
)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, votes)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(vdr2, votes)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(vdr3, votes)
if !poll.Finished() {
t.Fatalf("Poll did not terminate early after receiving alpha votes for one vertex and none for other vertices")
}
}
func TestEarlyTermNoTraversalForSharedAncestor(t *testing.T) {
alpha := 4
vtxA := ids.NewID([32]byte{1})
vtxB := ids.NewID([32]byte{2})
vtxC := ids.NewID([32]byte{3})
vtxD := ids.NewID([32]byte{4})
// If validators 1-3 vote for frontier vertices
// B, C, and D respectively, which all share the common ancestor
// A, then we cannot terminate early with alpha = k = 4
// If the final vote is cast for any of A, B, C, or D, then
// vertex A will have transitively received alpha = 4 votes
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2})
vdr3 := ids.NewShortID([20]byte{3})
vdr4 := ids.NewShortID([20]byte{4})
vdrs := ids.ShortSet{}
vdrs.Add(vdr1)
vdrs.Add(vdr2)
vdrs.Add(vdr3)
vdrs.Add(vdr4)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, []ids.ID{vtxB})
if poll.Finished() {
t.Fatalf("Poll finished early after receiving one vote")
}
poll.Vote(vdr2, []ids.ID{vtxC})
if poll.Finished() {
t.Fatalf("Poll finished early after receiving two votes")
}
poll.Vote(vdr3, []ids.ID{vtxD})
if poll.Finished() {
t.Fatalf("Poll terminated early, when a shared ancestor could have received alpha votes")
}
poll.Vote(vdr4, []ids.ID{vtxA})
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving all outstanding votes")
}
}
func TestEarlyTermNoTraversalWithFastDrops(t *testing.T) {
alpha := 2
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2})
vdr3 := ids.NewShortID([20]byte{3}) // k = 3
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
vdr3,
)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, nil)
if poll.Finished() {
t.Fatalf("Poll finished early after dropping one vote")
}
poll.Vote(vdr2, nil)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after dropping two votes")
}
}

View File

@ -0,0 +1,33 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"fmt"
"github.com/ava-labs/gecko/ids"
)
// Set is a collection of polls
type Set interface {
fmt.Stringer
Add(requestID uint32, vdrs ids.ShortSet) bool
Vote(requestID uint32, vdr ids.ShortID, votes []ids.ID) (ids.UniqueBag, bool)
Len() int
}
// Poll is an outstanding poll
type Poll interface {
fmt.Stringer
Vote(vdr ids.ShortID, votes []ids.ID)
Finished() bool
Result() ids.UniqueBag
}
// Factory creates a new Poll
type Factory interface {
New(vdrs ids.ShortSet) Poll
}

View File

@ -0,0 +1,52 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"fmt"
"github.com/ava-labs/gecko/ids"
)
type noEarlyTermFactory struct{}
// NewNoEarlyTermFactory returns a factory that returns polls with no early
// termination
func NewNoEarlyTermFactory() Factory { return noEarlyTermFactory{} }
func (noEarlyTermFactory) New(vdrs ids.ShortSet) Poll {
return &noEarlyTermPoll{polled: vdrs}
}
// noEarlyTermPoll finishes when all polled validators either respond to the
// query or a timeout occurs
type noEarlyTermPoll struct {
votes ids.UniqueBag
polled ids.ShortSet
}
// Vote registers a response for this poll
func (p *noEarlyTermPoll) Vote(vdr ids.ShortID, votes []ids.ID) {
if !p.polled.Contains(vdr) {
// if the validator wasn't polled or already responded to this poll, we
// should just drop the vote
return
}
// make sure that a validator can't respond multiple times
p.polled.Remove(vdr)
// track the votes the validator responded with
p.votes.Add(uint(p.polled.Len()), votes...)
}
// Finished returns true when all validators have voted
func (p *noEarlyTermPoll) Finished() bool { return p.polled.Len() == 0 }
// Result returns the result of this poll
func (p *noEarlyTermPoll) Result() ids.UniqueBag { return p.votes }
func (p *noEarlyTermPoll) String() string {
return fmt.Sprintf("waiting on %s", p.polled)
}

View File

@ -0,0 +1,91 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"testing"
"github.com/ava-labs/gecko/ids"
)
func TestNoEarlyTermResults(t *testing.T) {
vtxID := ids.NewID([32]byte{1})
votes := []ids.ID{vtxID}
vdr1 := ids.NewShortID([20]byte{1}) // k = 1
vdrs := ids.ShortSet{}
vdrs.Add(vdr1)
factory := NewNoEarlyTermFactory()
poll := factory.New(vdrs)
poll.Vote(vdr1, votes)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving k votes")
}
result := poll.Result()
if list := result.List(); len(list) != 1 {
t.Fatalf("Wrong number of vertices returned")
} else if retVtxID := list[0]; !retVtxID.Equals(vtxID) {
t.Fatalf("Wrong vertex returned")
} else if set := result.GetSet(vtxID); set.Len() != 1 {
t.Fatalf("Wrong number of votes returned")
}
}
func TestNoEarlyTermString(t *testing.T) {
vtxID := ids.NewID([32]byte{1})
votes := []ids.ID{vtxID}
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
factory := NewNoEarlyTermFactory()
poll := factory.New(vdrs)
poll.Vote(vdr1, votes)
expected := "waiting on {BaMPFdqMUQ46BV8iRcwbVfsam55kMqcp}"
if result := poll.String(); expected != result {
t.Fatalf("Poll should have returned %s but returned %s", expected, result)
}
}
func TestNoEarlyTermDropsDuplicatedVotes(t *testing.T) {
vtxID := ids.NewID([32]byte{1})
votes := []ids.ID{vtxID}
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
factory := NewNoEarlyTermFactory()
poll := factory.New(vdrs)
poll.Vote(vdr1, votes)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(vdr1, votes)
if poll.Finished() {
t.Fatalf("Poll finished after getting a duplicated vote")
}
poll.Vote(vdr2, votes)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving k votes")
}
}

View File

@ -0,0 +1,130 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"fmt"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/utils/timer"
)
type poll struct {
Poll
start time.Time
}
type set struct {
log logging.Logger
numPolls prometheus.Gauge
durPolls prometheus.Histogram
factory Factory
polls map[uint32]poll
}
// NewSet returns a new empty set of polls
func NewSet(
factory Factory,
log logging.Logger,
namespace string,
registerer prometheus.Registerer,
) Set {
numPolls := prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "polls",
Help: "Number of pending network polls",
})
if err := registerer.Register(numPolls); err != nil {
log.Error("failed to register polls statistics due to %s", err)
}
durPolls := prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Name: "poll_duration",
Help: "Length of time the poll existed in milliseconds",
Buckets: timer.MillisecondsBuckets,
})
if err := registerer.Register(durPolls); err != nil {
log.Error("failed to register poll_duration statistics due to %s", err)
}
return &set{
log: log,
numPolls: numPolls,
durPolls: durPolls,
factory: factory,
polls: make(map[uint32]poll),
}
}
// Add to the current set of polls
// Returns true if the poll was registered correctly and the network sample
// should be made.
func (s *set) Add(requestID uint32, vdrs ids.ShortSet) bool {
if _, exists := s.polls[requestID]; exists {
s.log.Debug("dropping poll due to duplicated requestID: %d", requestID)
return false
}
s.log.Verbo("creating poll with requestID %d and validators %s",
requestID,
vdrs)
s.polls[requestID] = poll{
Poll: s.factory.New(vdrs), // create the new poll
start: time.Now(),
}
s.numPolls.Inc() // increase the metrics
return true
}
// Vote registers the connections response to a query for [id]. If there was no
// query, or the response has already be registered, nothing is performed.
func (s *set) Vote(
requestID uint32,
vdr ids.ShortID,
votes []ids.ID,
) (ids.UniqueBag, bool) {
poll, exists := s.polls[requestID]
if !exists {
s.log.Verbo("dropping vote from %s to an unknown poll with requestID: %d",
vdr,
requestID)
return nil, false
}
s.log.Verbo("processing vote from %s in the poll with requestID: %d with the votes %v",
vdr,
requestID,
votes)
poll.Vote(vdr, votes)
if !poll.Finished() {
return nil, false
}
s.log.Verbo("poll with requestID %d finished as %s", requestID, poll)
delete(s.polls, requestID) // remove the poll from the current set
s.durPolls.Observe(float64(time.Now().Sub(poll.start).Milliseconds()))
s.numPolls.Dec() // decrease the metrics
return poll.Result(), true
}
// Len returns the number of outstanding polls
func (s *set) Len() int { return len(s.polls) }
func (s *set) String() string {
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("current polls: (Size = %d)", len(s.polls)))
for requestID, poll := range s.polls {
sb.WriteString(fmt.Sprintf("\n %d: %s", requestID, poll))
}
return sb.String()
}

View File

@ -0,0 +1,97 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"testing"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/logging"
"github.com/prometheus/client_golang/prometheus"
)
func TestNewSetErrorOnMetrics(t *testing.T) {
factory := NewNoEarlyTermFactory()
log := logging.NoLog{}
namespace := ""
registerer := prometheus.NewRegistry()
registerer.Register(prometheus.NewCounter(prometheus.CounterOpts{
Name: "polls",
}))
registerer.Register(prometheus.NewCounter(prometheus.CounterOpts{
Name: "poll_duration",
}))
_ = NewSet(factory, log, namespace, registerer)
}
func TestCreateAndFinishPoll(t *testing.T) {
factory := NewNoEarlyTermFactory()
log := logging.NoLog{}
namespace := ""
registerer := prometheus.NewRegistry()
s := NewSet(factory, log, namespace, registerer)
vtxID := ids.NewID([32]byte{1})
votes := []ids.ID{vtxID}
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
if s.Len() != 0 {
t.Fatalf("Shouldn't have any active polls yet")
} else if !s.Add(0, vdrs) {
t.Fatalf("Should have been able to add a new poll")
} else if s.Len() != 1 {
t.Fatalf("Should only have one active poll")
} else if s.Add(0, vdrs) {
t.Fatalf("Shouldn't have been able to add a duplicated poll")
} else if s.Len() != 1 {
t.Fatalf("Should only have one active poll")
} else if _, finished := s.Vote(1, vdr1, votes); finished {
t.Fatalf("Shouldn't have been able to finish a non-existant poll")
} else if _, finished := s.Vote(0, vdr1, votes); finished {
t.Fatalf("Shouldn't have been able to finish an ongoing poll")
} else if _, finished := s.Vote(0, vdr1, votes); finished {
t.Fatalf("Should have dropped a duplicated poll")
} else if result, finished := s.Vote(0, vdr2, votes); !finished {
t.Fatalf("Should have finished the")
} else if list := result.List(); len(list) != 1 {
t.Fatalf("Wrong number of vertices returned")
} else if retVtxID := list[0]; !retVtxID.Equals(vtxID) {
t.Fatalf("Wrong vertex returned")
} else if set := result.GetSet(vtxID); set.Len() != 2 {
t.Fatalf("Wrong number of votes returned")
}
}
func TestSetString(t *testing.T) {
factory := NewNoEarlyTermFactory()
log := logging.NoLog{}
namespace := ""
registerer := prometheus.NewRegistry()
s := NewSet(factory, log, namespace, registerer)
vdr1 := ids.NewShortID([20]byte{1}) // k = 1
vdrs := ids.ShortSet{}
vdrs.Add(vdr1)
expected := "current polls: (Size = 1)\n" +
" 0: waiting on {6HgC8KRBEhXYbF4riJyJFLSHt37UNuRt}"
if !s.Add(0, vdrs) {
t.Fatalf("Should have been able to add a new poll")
} else if str := s.String(); expected != str {
t.Fatalf("Set return wrong string, Expected:\n%s\nReturned:\n%s",
expected,
str)
}
}

View File

@ -1,137 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avalanche
import (
"fmt"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/logging"
)
// TODO: There is a conservative early termination case that doesn't require dag
// traversals we may want to implement. The algorithm would go as follows:
// Keep track of the number of response that reference an ID. If an ID gets >=
// alpha responses, then remove it from all responses and place it into a chit
// list. Remove all empty responses. If the number of responses + the number of
// pending responses is less than alpha, terminate the poll.
// In the synchronous + virtuous case, when everyone returns the same hash, the
// poll now terminates after receiving alpha responses.
// In the rogue case, it is possible that the poll doesn't terminate as quickly
// as possible, because IDs may have the alpha threshold but only when counting
// transitive votes. In this case, we may wait even if it is no longer possible
// for another ID to earn alpha votes.
// Because alpha is typically set close to k, this may not be performance
// critical. However, early termination may be performance critical with crashed
// nodes.
type polls struct {
log logging.Logger
numPolls prometheus.Gauge
alpha int
m map[uint32]poll
}
func newPolls(alpha int, log logging.Logger, numPolls prometheus.Gauge) polls {
return polls{
log: log,
numPolls: numPolls,
alpha: alpha,
m: make(map[uint32]poll),
}
}
// Add to the current set of polls
// Returns true if the poll was registered correctly and the network sample
// should be made.
func (p *polls) Add(requestID uint32, vdrs ids.ShortSet) bool {
poll, exists := p.m[requestID]
if !exists {
poll.polled = vdrs
poll.alpha = p.alpha
p.m[requestID] = poll
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
}
return !exists
}
// Vote registers the connections response to a query for [id]. If there was no
// query, or the response has already be registered, nothing is performed.
func (p *polls) Vote(requestID uint32, vdr ids.ShortID, votes []ids.ID) (ids.UniqueBag, bool) {
p.log.Verbo("Vote. requestID: %d. validatorID: %s.", requestID, vdr)
poll, exists := p.m[requestID]
p.log.Verbo("Poll: %+v", poll)
if !exists {
return nil, false
}
poll.Vote(votes, vdr)
if poll.Finished() {
p.log.Verbo("Poll is finished")
delete(p.m, requestID)
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
return poll.votes, true
}
p.m[requestID] = poll
return nil, false
}
func (p *polls) String() string {
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("Current polls: (Size = %d)", len(p.m)))
for requestID, poll := range p.m {
sb.WriteString(fmt.Sprintf("\n %d: %s", requestID, poll))
}
return sb.String()
}
// poll represents the current state of a network poll for a vertex
type poll struct {
votes ids.UniqueBag
polled ids.ShortSet
alpha int
}
// Vote registers a vote for this poll
func (p *poll) Vote(votes []ids.ID, vdr ids.ShortID) {
if p.polled.Contains(vdr) {
p.polled.Remove(vdr)
p.votes.Add(uint(p.polled.Len()), votes...)
}
}
// Finished returns true if the poll has completed, with no more required
// responses
func (p poll) Finished() bool {
// If there are no outstanding queries, the poll is finished
numPending := p.polled.Len()
if numPending == 0 {
return true
}
// If there are still enough pending responses to include another vertex,
// then the poll must wait for more responses
if numPending > p.alpha {
return false
}
// Ignore any vertex that has already received alpha votes. To safely skip
// DAG traversal, assume that all votes for vertices with less than alpha
// votes will be applied to a single shared ancestor. In this case, the poll
// can terminate early, iff there are not enough pending votes for this
// ancestor to receive alpha votes.
partialVotes := ids.BitSet(0)
for _, vote := range p.votes.List() {
if voters := p.votes.GetSet(vote); voters.Len() < p.alpha {
partialVotes.Union(voters)
}
}
return partialVotes.Len()+numPending < p.alpha
}
func (p poll) String() string { return fmt.Sprintf("Waiting on %d chits", p.polled.Len()) }

View File

@ -1,99 +0,0 @@
package avalanche
import (
"testing"
"github.com/ava-labs/gecko/ids"
)
func TestPollTerminatesEarlyVirtuousCase(t *testing.T) {
alpha := 3
vtxID := GenerateID()
votes := []ids.ID{vtxID}
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2})
vdr3 := ids.NewShortID([20]byte{3})
vdr4 := ids.NewShortID([20]byte{4})
vdr5 := ids.NewShortID([20]byte{5}) // k = 5
vdrs := ids.ShortSet{}
vdrs.Add(vdr1)
vdrs.Add(vdr2)
vdrs.Add(vdr3)
vdrs.Add(vdr4)
vdrs.Add(vdr5)
poll := poll{
votes: make(ids.UniqueBag),
polled: vdrs,
alpha: alpha,
}
poll.Vote(votes, vdr1)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(votes, vdr2)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(votes, vdr3)
if !poll.Finished() {
t.Fatalf("Poll did not terminate early after receiving alpha votes for one vertex and none for other vertices")
}
}
func TestPollAccountsForSharedAncestor(t *testing.T) {
alpha := 4
vtxA := GenerateID()
vtxB := GenerateID()
vtxC := GenerateID()
vtxD := GenerateID()
// If validators 1-3 vote for frontier vertices
// B, C, and D respectively, which all share the common ancestor
// A, then we cannot terminate early with alpha = k = 4
// If the final vote is cast for any of A, B, C, or D, then
// vertex A will have transitively received alpha = 4 votes
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2})
vdr3 := ids.NewShortID([20]byte{3})
vdr4 := ids.NewShortID([20]byte{4})
vdrs := ids.ShortSet{}
vdrs.Add(vdr1)
vdrs.Add(vdr2)
vdrs.Add(vdr3)
vdrs.Add(vdr4)
poll := poll{
votes: make(ids.UniqueBag),
polled: vdrs,
alpha: alpha,
}
votes1 := []ids.ID{vtxB}
poll.Vote(votes1, vdr1)
if poll.Finished() {
t.Fatalf("Poll finished early after receiving one vote")
}
votes2 := []ids.ID{vtxC}
poll.Vote(votes2, vdr2)
if poll.Finished() {
t.Fatalf("Poll finished early after receiving two votes")
}
votes3 := []ids.ID{vtxD}
poll.Vote(votes3, vdr3)
if poll.Finished() {
t.Fatalf("Poll terminated early, when a shared ancestor could have received alpha votes")
}
votes4 := []ids.ID{vtxA}
poll.Vote(votes4, vdr4)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving all outstanding votes")
}
}

View File

@ -12,6 +12,7 @@ import (
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/snow/consensus/avalanche"
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
"github.com/ava-labs/gecko/snow/engine/avalanche/poll"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/snow/events"
"github.com/ava-labs/gecko/utils/formatting"
@ -31,7 +32,7 @@ type Transitive struct {
Config
bootstrapper
polls polls // track people I have asked for their preference
polls poll.Set // track people I have asked for their preference
// vtxReqs prevents asking validators for the same vertex
vtxReqs common.Requests
@ -57,7 +58,12 @@ func (t *Transitive) Initialize(config Config) error {
t.onFinished = t.finishBootstrapping
t.polls = newPolls(int(config.Params.Alpha), config.Context.Log, t.numPolls)
factory := poll.NewEarlyTermNoTraversalFactory(int(config.Params.Alpha))
t.polls = poll.NewSet(factory,
config.Context.Log,
config.Params.Namespace,
config.Params.Metrics,
)
return t.bootstrapper.Initialize(config.BootstrapConfig)
}
@ -309,7 +315,7 @@ func (t *Transitive) Notify(msg common.Message) error {
}
func (t *Transitive) repoll() error {
if len(t.polls.m) >= t.Params.ConcurrentRepolls || t.errs.Errored() {
if t.polls.Len() >= t.Params.ConcurrentRepolls || t.errs.Errored() {
return nil
}
@ -318,7 +324,7 @@ func (t *Transitive) repoll() error {
return err
}
for i := len(t.polls.m); i < t.Params.ConcurrentRepolls; i++ {
for i := t.polls.Len(); i < t.Params.ConcurrentRepolls; i++ {
if err := t.batch(nil, false /*=force*/, true /*=empty*/); err != nil {
return err
}

View File

@ -13,7 +13,7 @@ type metrics struct {
numPendingRequests, numBlocked prometheus.Gauge
numBootstrapped, numDropped prometheus.Counter
numPolls, numBlkRequests, numBlockedBlk prometheus.Gauge
numBlkRequests, numBlockedBlk prometheus.Gauge
}
// Initialize implements the Engine interface
@ -42,12 +42,6 @@ func (m *metrics) Initialize(log logging.Logger, namespace string, registerer pr
Name: "sm_bs_dropped",
Help: "Number of dropped bootstrap blocks",
})
m.numPolls = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "sm_polls",
Help: "Number of pending network polls",
})
m.numBlkRequests = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: namespace,
@ -73,9 +67,6 @@ func (m *metrics) Initialize(log logging.Logger, namespace string, registerer pr
if err := registerer.Register(m.numDropped); err != nil {
log.Error("Failed to register sm_bs_dropped statistics due to %s", err)
}
if err := registerer.Register(m.numPolls); err != nil {
log.Error("Failed to register sm_polls statistics due to %s", err)
}
if err := registerer.Register(m.numBlkRequests); err != nil {
log.Error("Failed to register sm_blk_requests statistics due to %s", err)
}

View File

@ -0,0 +1,73 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"fmt"
"github.com/ava-labs/gecko/ids"
)
type earlyTermNoTraversalFactory struct {
alpha int
}
// NewEarlyTermNoTraversalFactory returns a factory that returns polls with
// early termination, without doing DAG traversals
func NewEarlyTermNoTraversalFactory(alpha int) Factory {
return &earlyTermNoTraversalFactory{alpha: alpha}
}
func (f *earlyTermNoTraversalFactory) New(vdrs ids.ShortSet) Poll {
return &earlyTermNoTraversalPoll{
polled: vdrs,
alpha: f.alpha,
}
}
// earlyTermNoTraversalPoll finishes when any remaining validators can't change
// the result of the poll. However, does not terminate tightly with this bound.
// It terminates as quickly as it can without performing any DAG traversals.
type earlyTermNoTraversalPoll struct {
votes ids.Bag
polled ids.ShortSet
alpha int
}
// Vote registers a response for this poll
func (p *earlyTermNoTraversalPoll) Vote(vdr ids.ShortID, vote ids.ID) {
if !p.polled.Contains(vdr) {
// if the validator wasn't polled or already responded to this poll, we
// should just drop the vote
return
}
// make sure that a validator can't respond multiple times
p.polled.Remove(vdr)
// track the votes the validator responded with
p.votes.Add(vote)
}
// Drop any future response for this poll
func (p *earlyTermNoTraversalPoll) Drop(vdr ids.ShortID) {
p.polled.Remove(vdr)
}
// Finished returns true when all validators have voted
func (p *earlyTermNoTraversalPoll) Finished() bool {
remaining := p.polled.Len()
received := p.votes.Len()
_, freq := p.votes.Mode()
return remaining == 0 || // All k nodes responded
freq >= p.alpha || // An alpha majority has returned
received+remaining < p.alpha // An alpha majority can never return
}
// Result returns the result of this poll
func (p *earlyTermNoTraversalPoll) Result() ids.Bag { return p.votes }
func (p *earlyTermNoTraversalPoll) String() string {
return fmt.Sprintf("waiting on %s", p.polled)
}

View File

@ -0,0 +1,205 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"testing"
"github.com/ava-labs/gecko/ids"
)
func TestEarlyTermNoTraversalResults(t *testing.T) {
alpha := 1
vtxID := ids.NewID([32]byte{1})
vdr1 := ids.NewShortID([20]byte{1}) // k = 1
vdrs := ids.ShortSet{}
vdrs.Add(vdr1)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, vtxID)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving k votes")
}
result := poll.Result()
if list := result.List(); len(list) != 1 {
t.Fatalf("Wrong number of vertices returned")
} else if retVtxID := list[0]; !retVtxID.Equals(vtxID) {
t.Fatalf("Wrong vertex returned")
} else if result.Count(vtxID) != 1 {
t.Fatalf("Wrong number of votes returned")
}
}
func TestEarlyTermNoTraversalString(t *testing.T) {
alpha := 2
vtxID := ids.NewID([32]byte{1})
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, vtxID)
expected := "waiting on {BaMPFdqMUQ46BV8iRcwbVfsam55kMqcp}"
if result := poll.String(); expected != result {
t.Fatalf("Poll should have returned %s but returned %s", expected, result)
}
}
func TestEarlyTermNoTraversalDropsDuplicatedVotes(t *testing.T) {
alpha := 2
vtxID := ids.NewID([32]byte{1})
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, vtxID)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(vdr1, vtxID)
if poll.Finished() {
t.Fatalf("Poll finished after getting a duplicated vote")
}
poll.Vote(vdr2, vtxID)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving k votes")
}
}
func TestEarlyTermNoTraversalTerminatesEarly(t *testing.T) {
alpha := 3
vtxID := ids.NewID([32]byte{1})
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2})
vdr3 := ids.NewShortID([20]byte{3})
vdr4 := ids.NewShortID([20]byte{4})
vdr5 := ids.NewShortID([20]byte{5}) // k = 5
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
vdr3,
vdr4,
vdr5,
)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, vtxID)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(vdr2, vtxID)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(vdr3, vtxID)
if !poll.Finished() {
t.Fatalf("Poll did not terminate early after receiving alpha votes for one vertex and none for other vertices")
}
}
func TestEarlyTermNoTraversalForSharedAncestor(t *testing.T) {
alpha := 4
vtxA := ids.NewID([32]byte{1})
vtxB := ids.NewID([32]byte{2})
vtxC := ids.NewID([32]byte{3})
vtxD := ids.NewID([32]byte{4})
// If validators 1-3 vote for frontier vertices
// B, C, and D respectively, which all share the common ancestor
// A, then we cannot terminate early with alpha = k = 4
// If the final vote is cast for any of A, B, C, or D, then
// vertex A will have transitively received alpha = 4 votes
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2})
vdr3 := ids.NewShortID([20]byte{3})
vdr4 := ids.NewShortID([20]byte{4})
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
vdr3,
vdr4,
)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Vote(vdr1, vtxB)
if poll.Finished() {
t.Fatalf("Poll finished early after receiving one vote")
}
poll.Vote(vdr2, vtxC)
if poll.Finished() {
t.Fatalf("Poll finished early after receiving two votes")
}
poll.Vote(vdr3, vtxD)
if poll.Finished() {
t.Fatalf("Poll terminated early, when a shared ancestor could have received alpha votes")
}
poll.Vote(vdr4, vtxA)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving all outstanding votes")
}
}
func TestEarlyTermNoTraversalWithFastDrops(t *testing.T) {
alpha := 2
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2})
vdr3 := ids.NewShortID([20]byte{3}) // k = 3
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
vdr3,
)
factory := NewEarlyTermNoTraversalFactory(alpha)
poll := factory.New(vdrs)
poll.Drop(vdr1)
if poll.Finished() {
t.Fatalf("Poll finished early after dropping one vote")
}
poll.Drop(vdr2)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after dropping two votes")
}
}

View File

@ -0,0 +1,35 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"fmt"
"github.com/ava-labs/gecko/ids"
)
// Set is a collection of polls
type Set interface {
fmt.Stringer
Add(requestID uint32, vdrs ids.ShortSet) bool
Vote(requestID uint32, vdr ids.ShortID, vote ids.ID) (ids.Bag, bool)
Drop(requestID uint32, vdr ids.ShortID) (ids.Bag, bool)
Len() int
}
// Poll is an outstanding poll
type Poll interface {
fmt.Stringer
Vote(vdr ids.ShortID, vote ids.ID)
Drop(vdr ids.ShortID)
Finished() bool
Result() ids.Bag
}
// Factory creates a new Poll
type Factory interface {
New(vdrs ids.ShortSet) Poll
}

View File

@ -0,0 +1,55 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"fmt"
"github.com/ava-labs/gecko/ids"
)
type noEarlyTermFactory struct{}
// NewNoEarlyTermFactory returns a factory that returns polls with no early
// termination
func NewNoEarlyTermFactory() Factory { return noEarlyTermFactory{} }
func (noEarlyTermFactory) New(vdrs ids.ShortSet) Poll {
return &noEarlyTermPoll{polled: vdrs}
}
// noEarlyTermPoll finishes when all polled validators either respond to the
// query or a timeout occurs
type noEarlyTermPoll struct {
votes ids.Bag
polled ids.ShortSet
}
// Vote registers a response for this poll
func (p *noEarlyTermPoll) Vote(vdr ids.ShortID, vote ids.ID) {
if !p.polled.Contains(vdr) {
// if the validator wasn't polled or already responded to this poll, we
// should just drop the vote
return
}
// make sure that a validator can't respond multiple times
p.polled.Remove(vdr)
// track the votes the validator responded with
p.votes.Add(vote)
}
// Drop any future response for this poll
func (p *noEarlyTermPoll) Drop(vdr ids.ShortID) { p.polled.Remove(vdr) }
// Finished returns true when all validators have voted
func (p *noEarlyTermPoll) Finished() bool { return p.polled.Len() == 0 }
// Result returns the result of this poll
func (p *noEarlyTermPoll) Result() ids.Bag { return p.votes }
func (p *noEarlyTermPoll) String() string {
return fmt.Sprintf("waiting on %s", p.polled)
}

View File

@ -0,0 +1,92 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"testing"
"github.com/ava-labs/gecko/ids"
)
func TestNoEarlyTermResults(t *testing.T) {
vtxID := ids.NewID([32]byte{1})
vdr1 := ids.NewShortID([20]byte{1}) // k = 1
vdrs := ids.ShortSet{}
vdrs.Add(vdr1)
factory := NewNoEarlyTermFactory()
poll := factory.New(vdrs)
poll.Vote(vdr1, vtxID)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving k votes")
}
result := poll.Result()
if list := result.List(); len(list) != 1 {
t.Fatalf("Wrong number of vertices returned")
} else if retVtxID := list[0]; !retVtxID.Equals(vtxID) {
t.Fatalf("Wrong vertex returned")
} else if result.Count(vtxID) != 1 {
t.Fatalf("Wrong number of votes returned")
}
}
func TestNoEarlyTermString(t *testing.T) {
vtxID := ids.NewID([32]byte{1})
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
factory := NewNoEarlyTermFactory()
poll := factory.New(vdrs)
poll.Vote(vdr1, vtxID)
expected := "waiting on {BaMPFdqMUQ46BV8iRcwbVfsam55kMqcp}"
if result := poll.String(); expected != result {
t.Fatalf("Poll should have returned %s but returned %s", expected, result)
}
}
func TestNoEarlyTermDropsDuplicatedVotes(t *testing.T) {
vtxID := ids.NewID([32]byte{1})
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
factory := NewNoEarlyTermFactory()
poll := factory.New(vdrs)
poll.Vote(vdr1, vtxID)
if poll.Finished() {
t.Fatalf("Poll finished after less than alpha votes")
}
poll.Vote(vdr1, vtxID)
if poll.Finished() {
t.Fatalf("Poll finished after getting a duplicated vote")
}
poll.Drop(vdr1)
if poll.Finished() {
t.Fatalf("Poll finished after getting a duplicated vote")
}
poll.Vote(vdr2, vtxID)
if !poll.Finished() {
t.Fatalf("Poll did not terminate after receiving k votes")
}
}

View File

@ -0,0 +1,158 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"fmt"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/utils/timer"
)
type poll struct {
Poll
start time.Time
}
type set struct {
log logging.Logger
numPolls prometheus.Gauge
durPolls prometheus.Histogram
factory Factory
polls map[uint32]poll
}
// NewSet returns a new empty set of polls
func NewSet(
factory Factory,
log logging.Logger,
namespace string,
registerer prometheus.Registerer,
) Set {
numPolls := prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Name: "polls",
Help: "Number of pending network polls",
})
if err := registerer.Register(numPolls); err != nil {
log.Error("failed to register polls statistics due to %s", err)
}
durPolls := prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Name: "poll_duration",
Help: "Length of time the poll existed in milliseconds",
Buckets: timer.MillisecondsBuckets,
})
if err := registerer.Register(durPolls); err != nil {
log.Error("failed to register poll_duration statistics due to %s", err)
}
return &set{
log: log,
numPolls: numPolls,
durPolls: durPolls,
factory: factory,
polls: make(map[uint32]poll),
}
}
// Add to the current set of polls
// Returns true if the poll was registered correctly and the network sample
// should be made.
func (s *set) Add(requestID uint32, vdrs ids.ShortSet) bool {
if _, exists := s.polls[requestID]; exists {
s.log.Debug("dropping poll due to duplicated requestID: %d", requestID)
return false
}
s.log.Verbo("creating poll with requestID %d and validators %s",
requestID,
vdrs)
s.polls[requestID] = poll{
Poll: s.factory.New(vdrs), // create the new poll
start: time.Now(),
}
s.numPolls.Inc() // increase the metrics
return true
}
// Vote registers the connections response to a query for [id]. If there was no
// query, or the response has already be registered, nothing is performed.
func (s *set) Vote(
requestID uint32,
vdr ids.ShortID,
vote ids.ID,
) (ids.Bag, bool) {
poll, exists := s.polls[requestID]
if !exists {
s.log.Verbo("dropping vote from %s to an unknown poll with requestID: %d",
vdr,
requestID)
return ids.Bag{}, false
}
s.log.Verbo("processing vote from %s in the poll with requestID: %d with the vote %s",
vdr,
requestID,
vote)
poll.Vote(vdr, vote)
if !poll.Finished() {
return ids.Bag{}, false
}
s.log.Verbo("poll with requestID %d finished as %s", requestID, poll)
delete(s.polls, requestID) // remove the poll from the current set
s.durPolls.Observe(float64(time.Now().Sub(poll.start).Milliseconds()))
s.numPolls.Dec() // decrease the metrics
return poll.Result(), true
}
// Drop registers the connections response to a query for [id]. If there was no
// query, or the response has already be registered, nothing is performed.
func (s *set) Drop(requestID uint32, vdr ids.ShortID) (ids.Bag, bool) {
poll, exists := s.polls[requestID]
if !exists {
s.log.Verbo("dropping vote from %s to an unknown poll with requestID: %d",
vdr,
requestID)
return ids.Bag{}, false
}
s.log.Verbo("processing dropped vote from %s in the poll with requestID: %d",
vdr,
requestID)
poll.Drop(vdr)
if !poll.Finished() {
return ids.Bag{}, false
}
s.log.Verbo("poll with requestID %d finished as %s", requestID, poll)
delete(s.polls, requestID) // remove the poll from the current set
s.durPolls.Observe(float64(time.Now().Sub(poll.start).Milliseconds()))
s.numPolls.Dec() // decrease the metrics
return poll.Result(), true
}
// Len returns the number of outstanding polls
func (s *set) Len() int { return len(s.polls) }
func (s *set) String() string {
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("current polls: (Size = %d)", len(s.polls)))
for requestID, poll := range s.polls {
sb.WriteString(fmt.Sprintf("\n %d: %s", requestID, poll))
}
return sb.String()
}

View File

@ -0,0 +1,135 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package poll
import (
"testing"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/logging"
"github.com/prometheus/client_golang/prometheus"
)
func TestNewSetErrorOnMetrics(t *testing.T) {
factory := NewNoEarlyTermFactory()
log := logging.NoLog{}
namespace := ""
registerer := prometheus.NewRegistry()
registerer.Register(prometheus.NewCounter(prometheus.CounterOpts{
Name: "polls",
}))
registerer.Register(prometheus.NewCounter(prometheus.CounterOpts{
Name: "poll_duration",
}))
_ = NewSet(factory, log, namespace, registerer)
}
func TestCreateAndFinishSuccessfulPoll(t *testing.T) {
factory := NewNoEarlyTermFactory()
log := logging.NoLog{}
namespace := ""
registerer := prometheus.NewRegistry()
s := NewSet(factory, log, namespace, registerer)
vtxID := ids.NewID([32]byte{1})
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
if s.Len() != 0 {
t.Fatalf("Shouldn't have any active polls yet")
} else if !s.Add(0, vdrs) {
t.Fatalf("Should have been able to add a new poll")
} else if s.Len() != 1 {
t.Fatalf("Should only have one active poll")
} else if s.Add(0, vdrs) {
t.Fatalf("Shouldn't have been able to add a duplicated poll")
} else if s.Len() != 1 {
t.Fatalf("Should only have one active poll")
} else if _, finished := s.Vote(1, vdr1, vtxID); finished {
t.Fatalf("Shouldn't have been able to finish a non-existant poll")
} else if _, finished := s.Vote(0, vdr1, vtxID); finished {
t.Fatalf("Shouldn't have been able to finish an ongoing poll")
} else if _, finished := s.Vote(0, vdr1, vtxID); finished {
t.Fatalf("Should have dropped a duplicated poll")
} else if result, finished := s.Vote(0, vdr2, vtxID); !finished {
t.Fatalf("Should have finished the")
} else if list := result.List(); len(list) != 1 {
t.Fatalf("Wrong number of vertices returned")
} else if retVtxID := list[0]; !retVtxID.Equals(vtxID) {
t.Fatalf("Wrong vertex returned")
} else if result.Count(vtxID) != 2 {
t.Fatalf("Wrong number of votes returned")
}
}
func TestCreateAndFinishFailedPoll(t *testing.T) {
factory := NewNoEarlyTermFactory()
log := logging.NoLog{}
namespace := ""
registerer := prometheus.NewRegistry()
s := NewSet(factory, log, namespace, registerer)
vdr1 := ids.NewShortID([20]byte{1})
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
vdrs := ids.ShortSet{}
vdrs.Add(
vdr1,
vdr2,
)
if s.Len() != 0 {
t.Fatalf("Shouldn't have any active polls yet")
} else if !s.Add(0, vdrs) {
t.Fatalf("Should have been able to add a new poll")
} else if s.Len() != 1 {
t.Fatalf("Should only have one active poll")
} else if s.Add(0, vdrs) {
t.Fatalf("Shouldn't have been able to add a duplicated poll")
} else if s.Len() != 1 {
t.Fatalf("Should only have one active poll")
} else if _, finished := s.Drop(1, vdr1); finished {
t.Fatalf("Shouldn't have been able to finish a non-existant poll")
} else if _, finished := s.Drop(0, vdr1); finished {
t.Fatalf("Shouldn't have been able to finish an ongoing poll")
} else if _, finished := s.Drop(0, vdr1); finished {
t.Fatalf("Should have dropped a duplicated poll")
} else if result, finished := s.Drop(0, vdr2); !finished {
t.Fatalf("Should have finished the")
} else if list := result.List(); len(list) != 0 {
t.Fatalf("Wrong number of vertices returned")
}
}
func TestSetString(t *testing.T) {
factory := NewNoEarlyTermFactory()
log := logging.NoLog{}
namespace := ""
registerer := prometheus.NewRegistry()
s := NewSet(factory, log, namespace, registerer)
vdr1 := ids.NewShortID([20]byte{1}) // k = 1
vdrs := ids.ShortSet{}
vdrs.Add(vdr1)
expected := "current polls: (Size = 1)\n" +
" 0: waiting on {6HgC8KRBEhXYbF4riJyJFLSHt37UNuRt}"
if !s.Add(0, vdrs) {
t.Fatalf("Should have been able to add a new poll")
} else if str := s.String(); expected != str {
t.Fatalf("Set return wrong string, Expected:\n%s\nReturned:\n%s",
expected,
str)
}
}

View File

@ -1,115 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package snowman
import (
"fmt"
"strings"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/logging"
"github.com/prometheus/client_golang/prometheus"
)
type polls struct {
log logging.Logger
numPolls prometheus.Gauge
alpha int
m map[uint32]poll
}
// Add to the current set of polls
// Returns true if the poll was registered correctly and the network sample
// should be made.
func (p *polls) Add(requestID uint32, vdrs ids.ShortSet) bool {
poll, exists := p.m[requestID]
if !exists {
poll.alpha = p.alpha
poll.polled = vdrs
p.m[requestID] = poll
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
}
return !exists
}
// Vote registers the connections response to a query for [id]. If there was no
// query, or the response has already be registered, nothing is performed.
func (p *polls) Vote(requestID uint32, vdr ids.ShortID, vote ids.ID) (ids.Bag, bool) {
p.log.Verbo("[polls.Vote] Vote: requestID: %d. validatorID: %s. Vote: %s", requestID, vdr, vote)
poll, exists := p.m[requestID]
if !exists {
return ids.Bag{}, false
}
poll.Vote(vote, vdr)
if poll.Finished() {
delete(p.m, requestID)
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
return poll.votes, true
}
p.m[requestID] = poll
return ids.Bag{}, false
}
// CancelVote registers the connections failure to respond to a query for [id].
func (p *polls) CancelVote(requestID uint32, vdr ids.ShortID) (ids.Bag, bool) {
p.log.Verbo("CancelVote received. requestID: %d. validatorID: %s. Vote: %s", requestID, vdr)
poll, exists := p.m[requestID]
if !exists {
return ids.Bag{}, false
}
poll.CancelVote(vdr)
if poll.Finished() {
delete(p.m, requestID)
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
return poll.votes, true
}
p.m[requestID] = poll
return ids.Bag{}, false
}
func (p *polls) String() string {
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("Current polls: (Size = %d)", len(p.m)))
for requestID, poll := range p.m {
sb.WriteString(fmt.Sprintf("\n %d: %s", requestID, poll))
}
return sb.String()
}
// poll represents the current state of a network poll for a block
type poll struct {
alpha int
votes ids.Bag
polled ids.ShortSet
}
// Vote registers a vote for this poll
func (p *poll) CancelVote(vdr ids.ShortID) { p.polled.Remove(vdr) }
// Vote registers a vote for this poll
func (p *poll) Vote(vote ids.ID, vdr ids.ShortID) {
if p.polled.Contains(vdr) {
p.polled.Remove(vdr)
p.votes.Add(vote)
}
}
// Finished returns true if the poll has completed, with no more required
// responses
func (p poll) Finished() bool {
remaining := p.polled.Len()
received := p.votes.Len()
_, freq := p.votes.Mode()
return remaining == 0 || // All k nodes responded
freq >= p.alpha || // An alpha majority has returned
received+remaining < p.alpha // An alpha majority can never return
}
func (p poll) String() string {
return fmt.Sprintf("Waiting on %d chits from %s", p.polled.Len(), p.polled)
}

View File

@ -12,6 +12,7 @@ import (
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/snow/consensus/snowman"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/snow/engine/snowman/poll"
"github.com/ava-labs/gecko/snow/events"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/wrappers"
@ -30,7 +31,7 @@ type Transitive struct {
bootstrapper
// track outstanding preference requests
polls polls
polls poll.Set
// blocks that have outstanding get requests
blkReqs common.Requests
@ -64,10 +65,12 @@ func (t *Transitive) Initialize(config Config) error {
t.onFinished = t.finishBootstrapping
t.polls.log = config.Context.Log
t.polls.numPolls = t.numPolls
t.polls.alpha = t.Params.Alpha
t.polls.m = make(map[uint32]poll)
factory := poll.NewEarlyTermNoTraversalFactory(int(config.Params.Alpha))
t.polls = poll.NewSet(factory,
config.Context.Log,
config.Params.Namespace,
config.Params.Metrics,
)
return t.bootstrapper.Initialize(config.BootstrapConfig)
}
@ -409,7 +412,7 @@ func (t *Transitive) repoll() {
// propagate the most likely branch as quickly as possible
prefID := t.Consensus.Preference()
for i := len(t.polls.m); i < t.Params.ConcurrentRepolls; i++ {
for i := t.polls.Len(); i < t.Params.ConcurrentRepolls; i++ {
t.pullSample(prefID)
}
}

View File

@ -810,13 +810,13 @@ func TestVoteCanceling(t *testing.T) {
te.insert(blk)
if len(te.polls.m) != 1 {
if te.polls.Len() != 1 {
t.Fatalf("Shouldn't have finished blocking issue")
}
te.QueryFailed(vdr0.ID(), *queryRequestID)
if len(te.polls.m) != 1 {
if te.polls.Len() != 1 {
t.Fatalf("Shouldn't have finished blocking issue")
}

View File

@ -32,7 +32,7 @@ func (v *voter) Update() {
results := ids.Bag{}
finished := false
if v.response.IsZero() {
results, finished = v.t.polls.CancelVote(v.requestID, v.vdr)
results, finished = v.t.polls.Drop(v.requestID, v.vdr)
} else {
results, finished = v.t.polls.Vote(v.requestID, v.vdr, v.response)
}

View File

@ -54,6 +54,12 @@ func (h *Handler) Initialize(
// Context of this Handler
func (h *Handler) Context() *snow.Context { return h.engine.Context() }
// Engine returns the engine this handler dispatches to
func (h *Handler) Engine() common.Engine { return h.engine }
// SetEngine sets the engine this handler dispatches to
func (h *Handler) SetEngine(engine common.Engine) { h.engine = engine }
// Dispatch waits for incoming messages from the network
// and, when they arrive, sends them to the consensus engine
func (h *Handler) Dispatch() {

View File

@ -13,6 +13,19 @@ import (
"github.com/ava-labs/gecko/utils/random"
)
const (
// maxExcessCapacityFactor ...
// If, when the validator set is reset, cap(set)/len(set) > MaxExcessCapacityFactor,
// the underlying arrays' capacities will be reduced by a factor of capacityReductionFactor.
// Higher value for maxExcessCapacityFactor --> less aggressive array downsizing --> less memory allocations
// but more unnecessary data in the underlying array that can't be garbage collected.
// Higher value for capacityReductionFactor --> more aggressive array downsizing --> more memory allocations
// but less unnecessary data in the underlying array that can't be garbage collected.
maxExcessCapacityFactor = 4
// CapacityReductionFactor ...
capacityReductionFactor = 2
)
// Set of validators that can be sampled
type Set interface {
fmt.Stringer
@ -71,9 +84,21 @@ func (s *set) Set(vdrs []Validator) {
}
func (s *set) set(vdrs []Validator) {
s.vdrMap = make(map[[20]byte]int, len(vdrs))
s.vdrSlice = s.vdrSlice[:0]
s.sampler.Weights = s.sampler.Weights[:0]
lenVdrs := len(vdrs)
// If the underlying arrays are much larger than necessary, resize them to
// allow garbage collection of unused memory
if cap(s.vdrSlice) > len(s.vdrSlice)*maxExcessCapacityFactor {
newCap := cap(s.vdrSlice) / capacityReductionFactor
if newCap < lenVdrs {
newCap = lenVdrs
}
s.vdrSlice = make([]Validator, 0, newCap)
s.sampler.Weights = make([]uint64, 0, newCap)
} else {
s.vdrSlice = s.vdrSlice[:0]
s.sampler.Weights = s.sampler.Weights[:0]
}
s.vdrMap = make(map[[20]byte]int, lenVdrs)
for _, vdr := range vdrs {
s.add(vdr)

View File

@ -40,20 +40,27 @@ func GenerateStakingKeyCert(keyPath, certPath string) error {
return fmt.Errorf("couldn't create certificate: %w", err)
}
// Write cert to disk
if err := os.MkdirAll(filepath.Dir(certPath), 0755); err != nil {
return fmt.Errorf("couldn't create path for key/cert: %w", err)
// Ensure directory where key/cert will live exist
if err := os.MkdirAll(filepath.Dir(certPath), 0700); err != nil {
return fmt.Errorf("couldn't create path for cert: %w", err)
} else if err := os.MkdirAll(filepath.Dir(keyPath), 0700); err != nil {
return fmt.Errorf("couldn't create path for key: %w", err)
}
certOut, err := os.Create(certPath)
// Write cert to disk
certFile, err := os.Create(certPath)
if err != nil {
return fmt.Errorf("couldn't create cert file: %w", err)
}
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil {
if err := pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil {
return fmt.Errorf("couldn't write cert file: %w", err)
}
if err := certOut.Close(); err != nil {
if err := certFile.Close(); err != nil {
return fmt.Errorf("couldn't close cert file: %w", err)
}
if err := os.Chmod(certPath, 0400); err != nil { // Make cert read-only
return fmt.Errorf("couldn't change permissions on cert: %w", err)
}
// Write key to disk
keyOut, err := os.Create(keyPath)
@ -70,5 +77,9 @@ func GenerateStakingKeyCert(keyPath, certPath string) error {
if err := keyOut.Close(); err != nil {
return fmt.Errorf("couldn't close key file: %w", err)
}
if err := os.Chmod(keyPath, 0400); err != nil { // Make key read-only
return fmt.Errorf("couldn't change permissions on key")
}
return nil
}

View File

@ -9,6 +9,7 @@ import (
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/database/prefixdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/engine/common"
@ -117,9 +118,10 @@ func TestIssueExportTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t)
issuer := make(chan common.Message, 1)
baseDB := memdb.New()
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, baseDB))
ctx := snow.DefaultContextTest()
ctx.NetworkID = networkID
@ -138,7 +140,7 @@ func TestIssueExportTx(t *testing.T) {
}
err := vm.Initialize(
ctx,
memdb.New(),
prefixdb.New([]byte{1}, baseDB),
genesisBytes,
issuer,
[]*common.Fx{{
@ -273,9 +275,10 @@ func TestClearForceAcceptedExportTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t)
issuer := make(chan common.Message, 1)
baseDB := memdb.New()
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, baseDB))
ctx := snow.DefaultContextTest()
ctx.NetworkID = networkID
@ -294,7 +297,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) {
}
err := vm.Initialize(
ctx,
memdb.New(),
prefixdb.New([]byte{1}, baseDB),
genesisBytes,
issuer,
[]*common.Fx{{

View File

@ -9,6 +9,7 @@ import (
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/database/prefixdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/engine/common"
@ -106,9 +107,10 @@ func TestIssueImportTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t)
issuer := make(chan common.Message, 1)
baseDB := memdb.New()
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, baseDB))
ctx := snow.DefaultContextTest()
ctx.NetworkID = networkID
@ -127,7 +129,7 @@ func TestIssueImportTx(t *testing.T) {
}
err := vm.Initialize(
ctx,
memdb.New(),
prefixdb.New([]byte{1}, baseDB),
genesisBytes,
issuer,
[]*common.Fx{{
@ -265,9 +267,10 @@ func TestForceAcceptImportTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t)
issuer := make(chan common.Message, 1)
baseDB := memdb.New()
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, baseDB))
ctx := snow.DefaultContextTest()
ctx.NetworkID = networkID
@ -285,7 +288,7 @@ func TestForceAcceptImportTx(t *testing.T) {
err := vm.Initialize(
ctx,
memdb.New(),
prefixdb.New([]byte{1}, baseDB),
genesisBytes,
issuer,
[]*common.Fx{{

View File

@ -492,10 +492,10 @@ func (vm *VM) parseTx(b []byte) (*UniqueTx, error) {
if err := vm.state.SetTx(tx.ID(), tx.Tx); err != nil {
return nil, err
}
if err := tx.setStatus(choices.Processing); err != nil {
return nil, err
}
return tx, vm.db.Commit()
}
return tx, nil

View File

@ -18,6 +18,7 @@ import (
"github.com/ava-labs/gecko/snow/consensus/snowman"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/snow/validators"
"github.com/ava-labs/gecko/utils/codec"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/logging"
@ -26,7 +27,6 @@ import (
"github.com/ava-labs/gecko/utils/units"
"github.com/ava-labs/gecko/utils/wrappers"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/utils/codec"
"github.com/ava-labs/gecko/vms/components/core"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
@ -808,9 +808,11 @@ func (vm *VM) getValidators(validatorEvents *EventHeap) []validators.Validator {
validator.Wght = weight
}
vdrList := make([]validators.Validator, len(vdrMap))[:0]
vdrList := make([]validators.Validator, len(vdrMap))
i := 0
for _, validator := range vdrMap {
vdrList = append(vdrList, validator)
vdrList[i] = validator
i++
}
return vdrList
}

View File

@ -137,7 +137,7 @@ func defaultVM() *VM {
vm.validators.PutValidatorSet(DefaultSubnetID, defaultSubnet)
vm.clock.Set(defaultGenesisTime)
db := memdb.New()
db := prefixdb.New([]byte{0}, memdb.New())
msgChan := make(chan common.Message, 1)
ctx := defaultContext()
ctx.Lock.Lock()
@ -1189,7 +1189,7 @@ func TestAtomicImport(t *testing.T) {
key := keys[0]
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, vm.DB.GetDatabase()))
vm.Ctx.SharedMemory = sm.NewBlockchainSharedMemory(vm.Ctx.ChainID)
@ -1282,7 +1282,7 @@ func TestOptimisticAtomicImport(t *testing.T) {
key := keys[0]
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, vm.DB.GetDatabase()))
vm.Ctx.SharedMemory = sm.NewBlockchainSharedMemory(vm.Ctx.ChainID)