mirror of https://github.com/poanetwork/gecko.git
Merge branch 'master' into ansible-service
This commit is contained in:
commit
658b6f080b
|
@ -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"
|
|
@ -14,6 +14,7 @@ env:
|
||||||
global:
|
global:
|
||||||
- CODECOV_TOKEN="8c18c993-fc6e-4706-998b-01ddc7987804"
|
- CODECOV_TOKEN="8c18c993-fc6e-4706-998b-01ddc7987804"
|
||||||
- GECKO_HOME=/go/src/github.com/ava-labs/gecko/
|
- 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}
|
- COMMIT=${TRAVIS_COMMIT::8}
|
||||||
- DOCKERHUB_REPO=avaplatform/gecko
|
- DOCKERHUB_REPO=avaplatform/gecko
|
||||||
- secure: "L/A9+re0NEKP6EV6H9RcTGiDhX3WMvsiWrkRKDYKqnviqbjY30RK6EM4vvjrM4Lrw2QwsO3YKgnku3+zioE/TxEZFkpkbjNUXru0nYBrWAg1TKVsDXnYaIZkHUejfryST3E8N7F4Hx6zCtGEO0sEdUeKuT+MNUIuHezHooTgGzDjMogm70EWMFjQHc7VucTJu7dWU1RBPjovWQ0q9qflrtCpbrvXFIiihQQ1PQha1Q2C4wLakKuLbhhSafue90Mnyss0blaPHy/tyewcASJu4vsGTKRBn0DzttlkNTwuD6+nKrbmJY0ohunnkVFzYjrZAw1gyN+DCDb/lPbz4ZDItKPwrIUPEtL5xuUOrxUZPUh+0io3Q2d6rjaqkdGjd1KQXzbnW1mn0BxX3d3b2UpIqhBn9umYYjHBKnMuoRiTK33b7U9+LF3K84+tEvVDCPeHs/mw6Inp5jGRSravnM6yPQ6feGzogs4+3EMzZXxnkngKFKCsnd67Oe9xfV9amOU2aQAx4jaAwlPjEpBEkUa8YKx3lPznvmUk1QsNCUbLjdSl5JBaXojLJoiuPbj29hp4S5AXXgn+3Hvwk3ndcFCxi6/l1W9mjYSOtFqg3EAUdF4EgnA/ykQg9ZokkoKY0+qgOzG2bKOAYuCDWeGr7P1apToh00ccsQXL81nVPiq7uDw="
|
- 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:
|
script:
|
||||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then .ci/runscript_osx.sh; fi
|
- 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
|
#Need to push to docker hub only from one build
|
||||||
after_success:
|
after_success:
|
||||||
|
|
|
@ -10,6 +10,15 @@ import (
|
||||||
"runtime/pprof"
|
"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 (
|
var (
|
||||||
errCPUProfilerRunning = errors.New("cpu profiler already running")
|
errCPUProfilerRunning = errors.New("cpu profiler already running")
|
||||||
errCPUProfilerNotRunning = errors.New("cpu profiler doesn't exist")
|
errCPUProfilerNotRunning = errors.New("cpu profiler doesn't exist")
|
||||||
|
@ -20,12 +29,12 @@ var (
|
||||||
type Performance struct{ cpuProfileFile *os.File }
|
type Performance struct{ cpuProfileFile *os.File }
|
||||||
|
|
||||||
// StartCPUProfiler starts measuring the cpu utilization of this node
|
// 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 {
|
if p.cpuProfileFile != nil {
|
||||||
return errCPUProfilerRunning
|
return errCPUProfilerRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Create(filename)
|
file, err := os.Create(cpuProfileFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -52,8 +61,8 @@ func (p *Performance) StopCPUProfiler() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryProfile dumps the current memory utilization of this node
|
// MemoryProfile dumps the current memory utilization of this node
|
||||||
func (p *Performance) MemoryProfile(filename string) error {
|
func (p *Performance) MemoryProfile() error {
|
||||||
file, err := os.Create(filename)
|
file, err := os.Create(memProfileFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -66,8 +75,8 @@ func (p *Performance) MemoryProfile(filename string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LockProfile dumps the current lock statistics of this node
|
// LockProfile dumps the current lock statistics of this node
|
||||||
func (p *Performance) LockProfile(filename string) error {
|
func (p *Performance) LockProfile() error {
|
||||||
file, err := os.Create(filename)
|
file, err := os.Create(lockProfileFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ type GetNodeVersionReply struct {
|
||||||
|
|
||||||
// GetNodeVersion returns the version this node is running
|
// GetNodeVersion returns the version this node is running
|
||||||
func (service *Admin) GetNodeVersion(_ *http.Request, _ *struct{}, reply *GetNodeVersionReply) error {
|
func (service *Admin) GetNodeVersion(_ *http.Request, _ *struct{}, reply *GetNodeVersionReply) error {
|
||||||
service.log.Debug("Admin: GetNodeVersion called")
|
service.log.Info("Admin: GetNodeVersion called")
|
||||||
|
|
||||||
reply.Version = service.version.String()
|
reply.Version = service.version.String()
|
||||||
return nil
|
return nil
|
||||||
|
@ -70,7 +70,7 @@ type GetNodeIDReply struct {
|
||||||
|
|
||||||
// GetNodeID returns the node ID of this node
|
// GetNodeID returns the node ID of this node
|
||||||
func (service *Admin) GetNodeID(_ *http.Request, _ *struct{}, reply *GetNodeIDReply) error {
|
func (service *Admin) GetNodeID(_ *http.Request, _ *struct{}, reply *GetNodeIDReply) error {
|
||||||
service.log.Debug("Admin: GetNodeID called")
|
service.log.Info("Admin: GetNodeID called")
|
||||||
|
|
||||||
reply.NodeID = service.nodeID
|
reply.NodeID = service.nodeID
|
||||||
return nil
|
return nil
|
||||||
|
@ -83,7 +83,7 @@ type GetNetworkIDReply struct {
|
||||||
|
|
||||||
// GetNetworkID returns the network ID this node is running on
|
// GetNetworkID returns the network ID this node is running on
|
||||||
func (service *Admin) GetNetworkID(_ *http.Request, _ *struct{}, reply *GetNetworkIDReply) error {
|
func (service *Admin) GetNetworkID(_ *http.Request, _ *struct{}, reply *GetNetworkIDReply) error {
|
||||||
service.log.Debug("Admin: GetNetworkID called")
|
service.log.Info("Admin: GetNetworkID called")
|
||||||
|
|
||||||
reply.NetworkID = cjson.Uint32(service.networkID)
|
reply.NetworkID = cjson.Uint32(service.networkID)
|
||||||
return nil
|
return nil
|
||||||
|
@ -96,7 +96,7 @@ type GetNetworkNameReply struct {
|
||||||
|
|
||||||
// GetNetworkName returns the network name this node is running on
|
// GetNetworkName returns the network name this node is running on
|
||||||
func (service *Admin) GetNetworkName(_ *http.Request, _ *struct{}, reply *GetNetworkNameReply) error {
|
func (service *Admin) GetNetworkName(_ *http.Request, _ *struct{}, reply *GetNetworkNameReply) error {
|
||||||
service.log.Debug("Admin: GetNetworkName called")
|
service.log.Info("Admin: GetNetworkName called")
|
||||||
|
|
||||||
reply.NetworkName = genesis.NetworkName(service.networkID)
|
reply.NetworkName = genesis.NetworkName(service.networkID)
|
||||||
return nil
|
return nil
|
||||||
|
@ -114,7 +114,7 @@ type GetBlockchainIDReply struct {
|
||||||
|
|
||||||
// GetBlockchainID returns the blockchain ID that resolves the alias that was supplied
|
// GetBlockchainID returns the blockchain ID that resolves the alias that was supplied
|
||||||
func (service *Admin) GetBlockchainID(_ *http.Request, args *GetBlockchainIDArgs, reply *GetBlockchainIDReply) error {
|
func (service *Admin) GetBlockchainID(_ *http.Request, args *GetBlockchainIDArgs, reply *GetBlockchainIDReply) error {
|
||||||
service.log.Debug("Admin: GetBlockchainID called")
|
service.log.Info("Admin: GetBlockchainID called")
|
||||||
|
|
||||||
bID, err := service.chainManager.Lookup(args.Alias)
|
bID, err := service.chainManager.Lookup(args.Alias)
|
||||||
reply.BlockchainID = bID.String()
|
reply.BlockchainID = bID.String()
|
||||||
|
@ -128,26 +128,21 @@ type PeersReply struct {
|
||||||
|
|
||||||
// Peers returns the list of current validators
|
// Peers returns the list of current validators
|
||||||
func (service *Admin) Peers(_ *http.Request, _ *struct{}, reply *PeersReply) error {
|
func (service *Admin) Peers(_ *http.Request, _ *struct{}, reply *PeersReply) error {
|
||||||
service.log.Debug("Admin: Peers called")
|
service.log.Info("Admin: Peers called")
|
||||||
reply.Peers = service.networking.Peers()
|
reply.Peers = service.networking.Peers()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartCPUProfilerArgs are the arguments for calling StartCPUProfiler
|
|
||||||
type StartCPUProfilerArgs struct {
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartCPUProfilerReply are the results from calling StartCPUProfiler
|
// StartCPUProfilerReply are the results from calling StartCPUProfiler
|
||||||
type StartCPUProfilerReply struct {
|
type StartCPUProfilerReply struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartCPUProfiler starts a cpu profile writing to the specified file
|
// StartCPUProfiler starts a cpu profile writing to the specified file
|
||||||
func (service *Admin) StartCPUProfiler(_ *http.Request, args *StartCPUProfilerArgs, reply *StartCPUProfilerReply) error {
|
func (service *Admin) StartCPUProfiler(_ *http.Request, args *struct{}, reply *StartCPUProfilerReply) error {
|
||||||
service.log.Debug("Admin: StartCPUProfiler called with %s", args.Filename)
|
service.log.Info("Admin: StartCPUProfiler called")
|
||||||
reply.Success = true
|
reply.Success = true
|
||||||
return service.performance.StartCPUProfiler(args.Filename)
|
return service.performance.StartCPUProfiler()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopCPUProfilerReply are the results from calling StopCPUProfiler
|
// StopCPUProfilerReply are the results from calling StopCPUProfiler
|
||||||
|
@ -157,31 +152,21 @@ type StopCPUProfilerReply struct {
|
||||||
|
|
||||||
// StopCPUProfiler stops the cpu profile
|
// StopCPUProfiler stops the cpu profile
|
||||||
func (service *Admin) StopCPUProfiler(_ *http.Request, _ *struct{}, reply *StopCPUProfilerReply) error {
|
func (service *Admin) StopCPUProfiler(_ *http.Request, _ *struct{}, reply *StopCPUProfilerReply) error {
|
||||||
service.log.Debug("Admin: StopCPUProfiler called")
|
service.log.Info("Admin: StopCPUProfiler called")
|
||||||
reply.Success = true
|
reply.Success = true
|
||||||
return service.performance.StopCPUProfiler()
|
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
|
// MemoryProfileReply are the results from calling MemoryProfile
|
||||||
type MemoryProfileReply struct {
|
type MemoryProfileReply struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryProfile runs a memory profile writing to the specified file
|
// MemoryProfile runs a memory profile writing to the specified file
|
||||||
func (service *Admin) MemoryProfile(_ *http.Request, args *MemoryProfileArgs, reply *MemoryProfileReply) error {
|
func (service *Admin) MemoryProfile(_ *http.Request, args *struct{}, reply *MemoryProfileReply) error {
|
||||||
service.log.Debug("Admin: MemoryProfile called with %s", args.Filename)
|
service.log.Info("Admin: MemoryProfile called")
|
||||||
reply.Success = true
|
reply.Success = true
|
||||||
return service.performance.MemoryProfile(args.Filename)
|
return service.performance.MemoryProfile()
|
||||||
}
|
|
||||||
|
|
||||||
// LockProfileArgs are the arguments for calling LockProfile
|
|
||||||
type LockProfileArgs struct {
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LockProfileReply are the results from calling LockProfile
|
// LockProfileReply are the results from calling LockProfile
|
||||||
|
@ -190,10 +175,10 @@ type LockProfileReply struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LockProfile runs a mutex profile writing to the specified file
|
// LockProfile runs a mutex profile writing to the specified file
|
||||||
func (service *Admin) LockProfile(_ *http.Request, args *LockProfileArgs, reply *LockProfileReply) error {
|
func (service *Admin) LockProfile(_ *http.Request, args *struct{}, reply *LockProfileReply) error {
|
||||||
service.log.Debug("Admin: LockProfile called with %s", args.Filename)
|
service.log.Info("Admin: LockProfile called")
|
||||||
reply.Success = true
|
reply.Success = true
|
||||||
return service.performance.LockProfile(args.Filename)
|
return service.performance.LockProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AliasArgs are the arguments for calling Alias
|
// AliasArgs are the arguments for calling Alias
|
||||||
|
@ -209,7 +194,7 @@ type AliasReply struct {
|
||||||
|
|
||||||
// Alias attempts to alias an HTTP endpoint to a new name
|
// Alias attempts to alias an HTTP endpoint to a new name
|
||||||
func (service *Admin) Alias(_ *http.Request, args *AliasArgs, reply *AliasReply) error {
|
func (service *Admin) Alias(_ *http.Request, args *AliasArgs, reply *AliasReply) error {
|
||||||
service.log.Debug("Admin: Alias called with URL: %s, Alias: %s", args.Endpoint, args.Alias)
|
service.log.Info("Admin: Alias called with URL: %s, Alias: %s", args.Endpoint, args.Alias)
|
||||||
reply.Success = true
|
reply.Success = true
|
||||||
return service.httpServer.AddAliasesWithReadLock(args.Endpoint, args.Alias)
|
return service.httpServer.AddAliasesWithReadLock(args.Endpoint, args.Alias)
|
||||||
}
|
}
|
||||||
|
@ -227,7 +212,7 @@ type AliasChainReply struct {
|
||||||
|
|
||||||
// AliasChain attempts to alias a chain to a new name
|
// AliasChain attempts to alias a chain to a new name
|
||||||
func (service *Admin) AliasChain(_ *http.Request, args *AliasChainArgs, reply *AliasChainReply) error {
|
func (service *Admin) AliasChain(_ *http.Request, args *AliasChainArgs, reply *AliasChainReply) error {
|
||||||
service.log.Debug("Admin: AliasChain called with Chain: %s, Alias: %s", args.Chain, args.Alias)
|
service.log.Info("Admin: AliasChain called with Chain: %s, Alias: %s", args.Chain, args.Alias)
|
||||||
|
|
||||||
chainID, err := service.chainManager.Lookup(args.Chain)
|
chainID, err := service.chainManager.Lookup(args.Chain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -20,36 +20,66 @@ type CheckFn func() (interface{}, error)
|
||||||
|
|
||||||
// Check defines a single health check that we want to monitor and consider as
|
// Check defines a single health check that we want to monitor and consider as
|
||||||
// part of our wider healthiness
|
// part of our wider healthiness
|
||||||
type Check struct {
|
type Check interface {
|
||||||
// Name is the identifier for this check and must be unique among all Checks
|
// Name is the identifier for this check and must be unique among all Checks
|
||||||
Name string
|
Name() string
|
||||||
|
|
||||||
// CheckFn is the function to call to perform the the health check
|
// Execute performs the health check. It returns nil if the check passes.
|
||||||
CheckFn CheckFn
|
// It can also return additional information to marshal and display to the caller
|
||||||
|
Execute() (interface{}, error)
|
||||||
|
|
||||||
// ExecutionPeriod is the duration to wait between executions of this Check
|
// ExecutionPeriod is the duration to wait between executions of this Check
|
||||||
ExecutionPeriod time.Duration
|
ExecutionPeriod() time.Duration
|
||||||
|
|
||||||
// InitialDelay is the duration to wait before executing the first time
|
// InitialDelay is the duration to wait before executing the first time
|
||||||
InitialDelay time.Duration
|
InitialDelay() time.Duration
|
||||||
|
|
||||||
// InitiallyPassing is whether or not to consider the Check healthy before the
|
// InitiallyPassing is whether or not to consider the Check healthy before the
|
||||||
// initial execution
|
// initial execution
|
||||||
InitiallyPassing bool
|
InitiallyPassing() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// gosundheitCheck implements the health.Check interface backed by a CheckFn
|
// check implements the Check interface
|
||||||
type gosundheitCheck struct {
|
type check struct {
|
||||||
name string
|
name string
|
||||||
checkFn CheckFn
|
checkFn CheckFn
|
||||||
|
executionPeriod, initialDelay time.Duration
|
||||||
|
initiallyPassing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name implements the health.Check interface by returning a unique name
|
// Name is the identifier for this check and must be unique among all Checks
|
||||||
func (c gosundheitCheck) Name() string { return c.name }
|
func (c check) Name() string { return c.name }
|
||||||
|
|
||||||
// Execute implements the health.Check interface by executing the checkFn and
|
// Execute performs the health check. It returns nil if the check passes.
|
||||||
// returning the results
|
// It can also return additional information to marshal and display to the caller
|
||||||
func (c gosundheitCheck) Execute() (interface{}, error) { return c.checkFn() }
|
func (c check) Execute() (interface{}, error) { return c.checkFn() }
|
||||||
|
|
||||||
|
// ExecutionPeriod is the duration to wait between executions of this Check
|
||||||
|
func (c check) ExecutionPeriod() time.Duration { return c.executionPeriod }
|
||||||
|
|
||||||
|
// InitialDelay is the duration to wait before executing the first time
|
||||||
|
func (c check) InitialDelay() time.Duration { return c.initialDelay }
|
||||||
|
|
||||||
|
// InitiallyPassing is whether or not to consider the Check healthy before the initial execution
|
||||||
|
func (c check) InitiallyPassing() bool { return c.initiallyPassing }
|
||||||
|
|
||||||
|
// monotonicCheck is a check that will run until it passes once, and after that it will
|
||||||
|
// always pass without performing any logic. Used for bootstrapping, for example.
|
||||||
|
type monotonicCheck struct {
|
||||||
|
passed bool
|
||||||
|
check
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc monotonicCheck) Execute() (interface{}, error) {
|
||||||
|
if mc.passed {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
details, pass := mc.check.Execute()
|
||||||
|
if pass == nil {
|
||||||
|
mc.passed = true
|
||||||
|
}
|
||||||
|
return details, pass
|
||||||
|
}
|
||||||
|
|
||||||
// Heartbeater provides a getter to the most recently observed heartbeat
|
// Heartbeater provides a getter to the most recently observed heartbeat
|
||||||
type Heartbeater interface {
|
type Heartbeater interface {
|
||||||
|
|
|
@ -7,15 +7,17 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AppsFlyer/go-sundheit"
|
health "github.com/AppsFlyer/go-sundheit"
|
||||||
|
|
||||||
|
"github.com/gorilla/rpc/v2"
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/snow/engine/common"
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
"github.com/ava-labs/gecko/utils/json"
|
"github.com/ava-labs/gecko/utils/json"
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
"github.com/gorilla/rpc/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultCheckOpts is a Check whose properties represent a default Check
|
// defaultCheckOpts is a Check whose properties represent a default Check
|
||||||
var defaultCheckOpts = Check{ExecutionPeriod: time.Minute}
|
var defaultCheckOpts = check{executionPeriod: time.Minute}
|
||||||
|
|
||||||
// Health observes a set of vital signs and makes them available through an HTTP
|
// Health observes a set of vital signs and makes them available through an HTTP
|
||||||
// API.
|
// API.
|
||||||
|
@ -36,7 +38,18 @@ func (h *Health) Handler() *common.HTTPHandler {
|
||||||
newServer.RegisterCodec(codec, "application/json")
|
newServer.RegisterCodec(codec, "application/json")
|
||||||
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
|
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
|
||||||
newServer.RegisterService(h, "health")
|
newServer.RegisterService(h, "health")
|
||||||
return &common.HTTPHandler{LockOptions: common.NoLock, Handler: newServer}
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet { // GET request --> return 200 if getLiveness returns true, else 503
|
||||||
|
if _, healthy := h.health.Results(); healthy {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newServer.ServeHTTP(w, r) // Other request --> use JSON RPC
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return &common.HTTPHandler{LockOptions: common.NoLock, Handler: handler}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterHeartbeat adds a check with default options and a CheckFn that checks
|
// RegisterHeartbeat adds a check with default options and a CheckFn that checks
|
||||||
|
@ -48,18 +61,27 @@ func (h *Health) RegisterHeartbeat(name string, hb Heartbeater, max time.Duratio
|
||||||
// RegisterCheckFunc adds a Check with default options and the given CheckFn
|
// RegisterCheckFunc adds a Check with default options and the given CheckFn
|
||||||
func (h *Health) RegisterCheckFunc(name string, checkFn CheckFn) error {
|
func (h *Health) RegisterCheckFunc(name string, checkFn CheckFn) error {
|
||||||
check := defaultCheckOpts
|
check := defaultCheckOpts
|
||||||
check.Name = name
|
check.name = name
|
||||||
check.CheckFn = checkFn
|
check.checkFn = checkFn
|
||||||
|
return h.RegisterCheck(check)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterMonotonicCheckFunc adds a Check with default options and the given CheckFn
|
||||||
|
// After it passes once, its logic (checkFunc) is never run again; it just passes
|
||||||
|
func (h *Health) RegisterMonotonicCheckFunc(name string, checkFn CheckFn) error {
|
||||||
|
check := monotonicCheck{check: defaultCheckOpts}
|
||||||
|
check.name = name
|
||||||
|
check.checkFn = checkFn
|
||||||
return h.RegisterCheck(check)
|
return h.RegisterCheck(check)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterCheck adds the given Check
|
// RegisterCheck adds the given Check
|
||||||
func (h *Health) RegisterCheck(c Check) error {
|
func (h *Health) RegisterCheck(c Check) error {
|
||||||
return h.health.RegisterCheck(&health.Config{
|
return h.health.RegisterCheck(&health.Config{
|
||||||
InitialDelay: c.InitialDelay,
|
InitialDelay: c.InitialDelay(),
|
||||||
ExecutionPeriod: c.ExecutionPeriod,
|
ExecutionPeriod: c.ExecutionPeriod(),
|
||||||
InitiallyPassing: c.InitiallyPassing,
|
InitiallyPassing: c.InitiallyPassing(),
|
||||||
Check: gosundheitCheck{c.Name, c.CheckFn},
|
Check: c,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +96,7 @@ type GetLivenessReply struct {
|
||||||
|
|
||||||
// GetLiveness returns a summation of the health of the node
|
// GetLiveness returns a summation of the health of the node
|
||||||
func (h *Health) GetLiveness(_ *http.Request, _ *GetLivenessArgs, reply *GetLivenessReply) error {
|
func (h *Health) GetLiveness(_ *http.Request, _ *GetLivenessArgs, reply *GetLivenessReply) error {
|
||||||
h.log.Debug("Health: GetLiveness called")
|
h.log.Info("Health: GetLiveness called")
|
||||||
reply.Checks, reply.Healthy = h.health.Results()
|
reply.Checks, reply.Healthy = h.health.Results()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package info
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/rpc/v2"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Info is the API service for unprivileged info on a node
|
||||||
|
type Info struct {
|
||||||
|
version version.Version
|
||||||
|
nodeID ids.ShortID
|
||||||
|
networkID uint32
|
||||||
|
log logging.Logger
|
||||||
|
networking network.Network
|
||||||
|
chainManager chains.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService returns a new admin API service
|
||||||
|
func NewService(log logging.Logger, version version.Version, nodeID ids.ShortID, networkID uint32, chainManager chains.Manager, peers network.Network) *common.HTTPHandler {
|
||||||
|
newServer := rpc.NewServer()
|
||||||
|
codec := cjson.NewCodec()
|
||||||
|
newServer.RegisterCodec(codec, "application/json")
|
||||||
|
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
|
||||||
|
newServer.RegisterService(&Info{
|
||||||
|
version: version,
|
||||||
|
nodeID: nodeID,
|
||||||
|
networkID: networkID,
|
||||||
|
log: log,
|
||||||
|
chainManager: chainManager,
|
||||||
|
networking: peers,
|
||||||
|
}, "info")
|
||||||
|
return &common.HTTPHandler{Handler: newServer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeVersionReply are the results from calling GetNodeVersion
|
||||||
|
type GetNodeVersionReply struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeVersion returns the version this node is running
|
||||||
|
func (service *Info) GetNodeVersion(_ *http.Request, _ *struct{}, reply *GetNodeVersionReply) error {
|
||||||
|
service.log.Info("Info: 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 *Info) GetNodeID(_ *http.Request, _ *struct{}, reply *GetNodeIDReply) error {
|
||||||
|
service.log.Info("Info: 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 *Info) GetNetworkID(_ *http.Request, _ *struct{}, reply *GetNetworkIDReply) error {
|
||||||
|
service.log.Info("Info: 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 *Info) GetNetworkName(_ *http.Request, _ *struct{}, reply *GetNetworkNameReply) error {
|
||||||
|
service.log.Info("Info: 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 *Info) GetBlockchainID(_ *http.Request, args *GetBlockchainIDArgs, reply *GetBlockchainIDReply) error {
|
||||||
|
service.log.Info("Info: 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 *Info) Peers(_ *http.Request, _ *struct{}, reply *PeersReply) error {
|
||||||
|
service.log.Info("Info: Peers called")
|
||||||
|
|
||||||
|
reply.Peers = service.networking.Peers()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBootstrappedArgs are the arguments for calling IsBootstrapped
|
||||||
|
type IsBootstrappedArgs struct {
|
||||||
|
// Alias of the chain
|
||||||
|
// Can also be the string representation of the chain's ID
|
||||||
|
Chain string `json:"chain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBootstrappedResponse are the results from calling IsBootstrapped
|
||||||
|
type IsBootstrappedResponse struct {
|
||||||
|
// True iff the chain exists and is done bootstrapping
|
||||||
|
IsBootstrapped bool `json:"isBootstrapped"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBootstrapped returns nil and sets [reply.IsBootstrapped] == true iff [args.Chain] exists and is done bootstrapping
|
||||||
|
// Returns an error if the chain doesn't exist
|
||||||
|
func (service *Info) IsBootstrapped(_ *http.Request, args *IsBootstrappedArgs, reply *IsBootstrappedResponse) error {
|
||||||
|
service.log.Info("Info: IsBootstrapped called")
|
||||||
|
if args.Chain == "" {
|
||||||
|
return fmt.Errorf("argument 'chain' not given")
|
||||||
|
}
|
||||||
|
chainID, err := service.chainManager.Lookup(args.Chain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("there is no chain with alias/ID '%s'", args.Chain)
|
||||||
|
}
|
||||||
|
reply.IsBootstrapped = service.chainManager.IsBootstrapped(chainID)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -61,6 +61,7 @@ type PublishBlockchainReply struct {
|
||||||
|
|
||||||
// PublishBlockchain publishes the finalized accepted transactions from the blockchainID over the IPC
|
// PublishBlockchain publishes the finalized accepted transactions from the blockchainID over the IPC
|
||||||
func (ipc *IPCs) PublishBlockchain(r *http.Request, args *PublishBlockchainArgs, reply *PublishBlockchainReply) error {
|
func (ipc *IPCs) PublishBlockchain(r *http.Request, args *PublishBlockchainArgs, reply *PublishBlockchainReply) error {
|
||||||
|
ipc.log.Info("IPCs: PublishBlockchain called with BlockchainID: %s", args.BlockchainID)
|
||||||
chainID, err := ipc.chainManager.Lookup(args.BlockchainID)
|
chainID, err := ipc.chainManager.Lookup(args.BlockchainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ipc.log.Error("unknown blockchainID: %s", err)
|
ipc.log.Error("unknown blockchainID: %s", err)
|
||||||
|
@ -116,6 +117,7 @@ type UnpublishBlockchainReply struct {
|
||||||
|
|
||||||
// UnpublishBlockchain closes publishing of a blockchainID
|
// UnpublishBlockchain closes publishing of a blockchainID
|
||||||
func (ipc *IPCs) UnpublishBlockchain(r *http.Request, args *UnpublishBlockchainArgs, reply *UnpublishBlockchainReply) error {
|
func (ipc *IPCs) UnpublishBlockchain(r *http.Request, args *UnpublishBlockchainArgs, reply *UnpublishBlockchainReply) error {
|
||||||
|
ipc.log.Info("IPCs: UnpublishBlockchain called with BlockchainID: %s", args.BlockchainID)
|
||||||
chainID, err := ipc.chainManager.Lookup(args.BlockchainID)
|
chainID, err := ipc.chainManager.Lookup(args.BlockchainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ipc.log.Error("unknown blockchainID %s: %s", args.BlockchainID, err)
|
ipc.log.Error("unknown blockchainID %s: %s", args.BlockchainID, err)
|
||||||
|
|
|
@ -8,29 +8,41 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/gorilla/rpc/v2"
|
"github.com/gorilla/rpc/v2"
|
||||||
|
|
||||||
|
zxcvbn "github.com/nbutton23/zxcvbn-go"
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/chains/atomic"
|
"github.com/ava-labs/gecko/chains/atomic"
|
||||||
"github.com/ava-labs/gecko/database"
|
"github.com/ava-labs/gecko/database"
|
||||||
"github.com/ava-labs/gecko/database/encdb"
|
"github.com/ava-labs/gecko/database/encdb"
|
||||||
|
"github.com/ava-labs/gecko/database/memdb"
|
||||||
"github.com/ava-labs/gecko/database/prefixdb"
|
"github.com/ava-labs/gecko/database/prefixdb"
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
"github.com/ava-labs/gecko/snow/engine/common"
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
|
"github.com/ava-labs/gecko/utils/codec"
|
||||||
"github.com/ava-labs/gecko/utils/formatting"
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
"github.com/ava-labs/gecko/vms/components/codec"
|
|
||||||
|
|
||||||
jsoncodec "github.com/ava-labs/gecko/utils/json"
|
jsoncodec "github.com/ava-labs/gecko/utils/json"
|
||||||
zxcvbn "github.com/nbutton23/zxcvbn-go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// maxUserPassLen is the maximum length of the username or password allowed
|
// maxUserPassLen is the maximum length of the username or password allowed
|
||||||
maxUserPassLen = 1024
|
maxUserPassLen = 1024
|
||||||
|
|
||||||
// requiredPassScore defines the score a password must achieve to be accepted
|
// maxCheckedPassLen limits the length of the password that should be
|
||||||
// as a password with strong characteristics by the zxcvbn package
|
// strength checked.
|
||||||
|
//
|
||||||
|
// As per issue https://github.com/ava-labs/gecko/issues/195 it was found
|
||||||
|
// the longer the length of password the slower zxcvbn.PasswordStrength()
|
||||||
|
// performs. To avoid performance issues, and a DoS vector, we only check
|
||||||
|
// the first 50 characters of the password.
|
||||||
|
maxCheckedPassLen = 50
|
||||||
|
|
||||||
|
// requiredPassScore defines the score a password must achieve to be
|
||||||
|
// accepted as a password with strong characteristics by the zxcvbn package
|
||||||
//
|
//
|
||||||
// The scoring mechanism defined is as follows;
|
// The scoring mechanism defined is as follows;
|
||||||
//
|
//
|
||||||
|
@ -135,37 +147,11 @@ func (ks *Keystore) CreateUser(_ *http.Request, args *CreateUserArgs, reply *Cre
|
||||||
ks.lock.Lock()
|
ks.lock.Lock()
|
||||||
defer ks.lock.Unlock()
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
ks.log.Verbo("CreateUser called with %.*s", maxUserPassLen, args.Username)
|
ks.log.Info("Keystore: CreateUser called with %.*s", maxUserPassLen, args.Username)
|
||||||
|
if err := ks.AddUser(args.Username, args.Password); err != nil {
|
||||||
if len(args.Username) > maxUserPassLen || len(args.Password) > maxUserPassLen {
|
|
||||||
return errUserPassMaxLength
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.Username == "" {
|
|
||||||
return errEmptyUsername
|
|
||||||
}
|
|
||||||
if usr, err := ks.getUser(args.Username); err == nil || usr != nil {
|
|
||||||
return fmt.Errorf("user already exists: %s", args.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
if zxcvbn.PasswordStrength(args.Password, nil).Score < requiredPassScore {
|
|
||||||
return errWeakPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
usr := &User{}
|
|
||||||
if err := usr.Initialize(args.Password); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
usrBytes, err := ks.codec.Marshal(usr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ks.userDB.Put([]byte(args.Username), usrBytes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ks.users[args.Username] = usr
|
|
||||||
reply.Success = true
|
reply.Success = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -183,7 +169,7 @@ func (ks *Keystore) ListUsers(_ *http.Request, args *ListUsersArgs, reply *ListU
|
||||||
ks.lock.Lock()
|
ks.lock.Lock()
|
||||||
defer ks.lock.Unlock()
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
ks.log.Verbo("ListUsers called")
|
ks.log.Info("Keystore: ListUsers called")
|
||||||
|
|
||||||
reply.Users = []string{}
|
reply.Users = []string{}
|
||||||
|
|
||||||
|
@ -211,7 +197,7 @@ func (ks *Keystore) ExportUser(_ *http.Request, args *ExportUserArgs, reply *Exp
|
||||||
ks.lock.Lock()
|
ks.lock.Lock()
|
||||||
defer ks.lock.Unlock()
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
ks.log.Verbo("ExportUser called for %s", args.Username)
|
ks.log.Info("Keystore: ExportUser called for %s", args.Username)
|
||||||
|
|
||||||
usr, err := ks.getUser(args.Username)
|
usr, err := ks.getUser(args.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -264,7 +250,7 @@ func (ks *Keystore) ImportUser(r *http.Request, args *ImportUserArgs, reply *Imp
|
||||||
ks.lock.Lock()
|
ks.lock.Lock()
|
||||||
defer ks.lock.Unlock()
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
ks.log.Verbo("ImportUser called for %s", args.Username)
|
ks.log.Info("Keystore: ImportUser called for %s", args.Username)
|
||||||
|
|
||||||
if args.Username == "" {
|
if args.Username == "" {
|
||||||
return errEmptyUsername
|
return errEmptyUsername
|
||||||
|
@ -324,7 +310,7 @@ func (ks *Keystore) DeleteUser(_ *http.Request, args *DeleteUserArgs, reply *Del
|
||||||
ks.lock.Lock()
|
ks.lock.Lock()
|
||||||
defer ks.lock.Unlock()
|
defer ks.lock.Unlock()
|
||||||
|
|
||||||
ks.log.Verbo("DeleteUser called with %s", args.Username)
|
ks.log.Info("Keystore: DeleteUser called with %s", args.Username)
|
||||||
|
|
||||||
if args.Username == "" {
|
if args.Username == "" {
|
||||||
return errEmptyUsername
|
return errEmptyUsername
|
||||||
|
@ -403,3 +389,51 @@ func (ks *Keystore) GetDatabase(bID ids.ID, username, password string) (database
|
||||||
|
|
||||||
return encDB, nil
|
return encDB, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddUser attempts to register this username and password as a new user of the
|
||||||
|
// keystore.
|
||||||
|
func (ks *Keystore) AddUser(username, password string) error {
|
||||||
|
if len(username) > maxUserPassLen || len(password) > maxUserPassLen {
|
||||||
|
return errUserPassMaxLength
|
||||||
|
}
|
||||||
|
|
||||||
|
if username == "" {
|
||||||
|
return errEmptyUsername
|
||||||
|
}
|
||||||
|
if usr, err := ks.getUser(username); err == nil || usr != nil {
|
||||||
|
return fmt.Errorf("user already exists: %s", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPass := password
|
||||||
|
if len(password) > maxCheckedPassLen {
|
||||||
|
checkPass = password[:maxCheckedPassLen]
|
||||||
|
}
|
||||||
|
|
||||||
|
if zxcvbn.PasswordStrength(checkPass, nil).Score < requiredPassScore {
|
||||||
|
return errWeakPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
usr := &User{}
|
||||||
|
if err := usr.Initialize(password); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
usrBytes, err := ks.codec.Marshal(usr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ks.userDB.Put([]byte(username), usrBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ks.users[username] = usr
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTestKeystore returns a new keystore that can be utilized for testing
|
||||||
|
func CreateTestKeystore(t *testing.T) *Keystore {
|
||||||
|
ks := &Keystore{}
|
||||||
|
ks.Initialize(logging.NoLog{}, memdb.New())
|
||||||
|
return ks
|
||||||
|
}
|
||||||
|
|
|
@ -10,9 +10,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/database/memdb"
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -22,8 +20,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServiceListNoUsers(t *testing.T) {
|
func TestServiceListNoUsers(t *testing.T) {
|
||||||
ks := Keystore{}
|
ks := CreateTestKeystore(t)
|
||||||
ks.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
reply := ListUsersReply{}
|
reply := ListUsersReply{}
|
||||||
if err := ks.ListUsers(nil, &ListUsersArgs{}, &reply); err != nil {
|
if err := ks.ListUsers(nil, &ListUsersArgs{}, &reply); err != nil {
|
||||||
|
@ -35,8 +32,7 @@ func TestServiceListNoUsers(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceCreateUser(t *testing.T) {
|
func TestServiceCreateUser(t *testing.T) {
|
||||||
ks := Keystore{}
|
ks := CreateTestKeystore(t)
|
||||||
ks.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
{
|
{
|
||||||
reply := CreateUserReply{}
|
reply := CreateUserReply{}
|
||||||
|
@ -75,8 +71,7 @@ func genStr(n int) string {
|
||||||
// TestServiceCreateUserArgsChecks generates excessively long usernames or
|
// TestServiceCreateUserArgsChecks generates excessively long usernames or
|
||||||
// passwords to assure the santity checks on string length are not exceeded
|
// passwords to assure the santity checks on string length are not exceeded
|
||||||
func TestServiceCreateUserArgsCheck(t *testing.T) {
|
func TestServiceCreateUserArgsCheck(t *testing.T) {
|
||||||
ks := Keystore{}
|
ks := CreateTestKeystore(t)
|
||||||
ks.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
{
|
{
|
||||||
reply := CreateUserReply{}
|
reply := CreateUserReply{}
|
||||||
|
@ -117,8 +112,7 @@ func TestServiceCreateUserArgsCheck(t *testing.T) {
|
||||||
// TestServiceCreateUserWeakPassword tests creating a new user with a weak
|
// TestServiceCreateUserWeakPassword tests creating a new user with a weak
|
||||||
// password to ensure the password strength check is working
|
// password to ensure the password strength check is working
|
||||||
func TestServiceCreateUserWeakPassword(t *testing.T) {
|
func TestServiceCreateUserWeakPassword(t *testing.T) {
|
||||||
ks := Keystore{}
|
ks := CreateTestKeystore(t)
|
||||||
ks.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
{
|
{
|
||||||
reply := CreateUserReply{}
|
reply := CreateUserReply{}
|
||||||
|
@ -138,8 +132,7 @@ func TestServiceCreateUserWeakPassword(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceCreateDuplicate(t *testing.T) {
|
func TestServiceCreateDuplicate(t *testing.T) {
|
||||||
ks := Keystore{}
|
ks := CreateTestKeystore(t)
|
||||||
ks.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
{
|
{
|
||||||
reply := CreateUserReply{}
|
reply := CreateUserReply{}
|
||||||
|
@ -166,8 +159,7 @@ func TestServiceCreateDuplicate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceCreateUserNoName(t *testing.T) {
|
func TestServiceCreateUserNoName(t *testing.T) {
|
||||||
ks := Keystore{}
|
ks := CreateTestKeystore(t)
|
||||||
ks.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
reply := CreateUserReply{}
|
reply := CreateUserReply{}
|
||||||
if err := ks.CreateUser(nil, &CreateUserArgs{
|
if err := ks.CreateUser(nil, &CreateUserArgs{
|
||||||
|
@ -178,8 +170,7 @@ func TestServiceCreateUserNoName(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceUseBlockchainDB(t *testing.T) {
|
func TestServiceUseBlockchainDB(t *testing.T) {
|
||||||
ks := Keystore{}
|
ks := CreateTestKeystore(t)
|
||||||
ks.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
{
|
{
|
||||||
reply := CreateUserReply{}
|
reply := CreateUserReply{}
|
||||||
|
@ -218,8 +209,7 @@ func TestServiceUseBlockchainDB(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceExportImport(t *testing.T) {
|
func TestServiceExportImport(t *testing.T) {
|
||||||
ks := Keystore{}
|
ks := CreateTestKeystore(t)
|
||||||
ks.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
{
|
{
|
||||||
reply := CreateUserReply{}
|
reply := CreateUserReply{}
|
||||||
|
@ -252,8 +242,7 @@ func TestServiceExportImport(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newKS := Keystore{}
|
newKS := CreateTestKeystore(t)
|
||||||
newKS.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
{
|
{
|
||||||
reply := ImportUserReply{}
|
reply := ImportUserReply{}
|
||||||
|
@ -358,11 +347,10 @@ func TestServiceDeleteUser(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
ks := Keystore{}
|
ks := CreateTestKeystore(t)
|
||||||
ks.Initialize(logging.NoLog{}, memdb.New())
|
|
||||||
|
|
||||||
if tt.setup != nil {
|
if tt.setup != nil {
|
||||||
if err := tt.setup(&ks); err != nil {
|
if err := tt.setup(ks); err != nil {
|
||||||
t.Fatalf("failed to create user setup in keystore: %v", err)
|
t.Fatalf("failed to create user setup in keystore: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ava-labs/gecko/snow/engine/common"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewService returns a new prometheus service
|
// NewService returns a new prometheus service
|
||||||
|
|
|
@ -10,6 +10,10 @@ import (
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minCacheSize = 32
|
||||||
|
)
|
||||||
|
|
||||||
type entry struct {
|
type entry struct {
|
||||||
Key ids.ID
|
Key ids.ID
|
||||||
Value interface{}
|
Value interface{}
|
||||||
|
@ -59,7 +63,7 @@ func (c *LRU) Flush() {
|
||||||
|
|
||||||
func (c *LRU) init() {
|
func (c *LRU) init() {
|
||||||
if c.entryMap == nil {
|
if c.entryMap == nil {
|
||||||
c.entryMap = make(map[[32]byte]*list.Element)
|
c.entryMap = make(map[[32]byte]*list.Element, minCacheSize)
|
||||||
}
|
}
|
||||||
if c.entryList == nil {
|
if c.entryList == nil {
|
||||||
c.entryList = list.New()
|
c.entryList = list.New()
|
||||||
|
@ -134,6 +138,6 @@ func (c *LRU) evict(key ids.ID) {
|
||||||
func (c *LRU) flush() {
|
func (c *LRU) flush() {
|
||||||
c.init()
|
c.init()
|
||||||
|
|
||||||
c.entryMap = make(map[[32]byte]*list.Element)
|
c.entryMap = make(map[[32]byte]*list.Element, minCacheSize)
|
||||||
c.entryList = list.New()
|
c.entryList = list.New()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkLRUCachePutSmall(b *testing.B) {
|
||||||
|
smallLen := 5
|
||||||
|
cache := &LRU{Size: smallLen}
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
for i := 0; i < smallLen; i++ {
|
||||||
|
var idBytes [32]byte
|
||||||
|
rand.Read(idBytes[:])
|
||||||
|
cache.Put(ids.NewID(idBytes), n)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
cache.Flush()
|
||||||
|
b.StartTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLRUCachePutMedium(b *testing.B) {
|
||||||
|
mediumLen := 250
|
||||||
|
cache := &LRU{Size: mediumLen}
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
for i := 0; i < mediumLen; i++ {
|
||||||
|
var idBytes [32]byte
|
||||||
|
rand.Read(idBytes[:])
|
||||||
|
cache.Put(ids.NewID(idBytes), n)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
cache.Flush()
|
||||||
|
b.StartTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLRUCachePutLarge(b *testing.B) {
|
||||||
|
largeLen := 10000
|
||||||
|
cache := &LRU{Size: largeLen}
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
for i := 0; i < largeLen; i++ {
|
||||||
|
var idBytes [32]byte
|
||||||
|
rand.Read(idBytes[:])
|
||||||
|
cache.Put(ids.NewID(idBytes), n)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
cache.Flush()
|
||||||
|
b.StartTimer()
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"github.com/ava-labs/gecko/database"
|
"github.com/ava-labs/gecko/database"
|
||||||
"github.com/ava-labs/gecko/database/prefixdb"
|
"github.com/ava-labs/gecko/database/prefixdb"
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/utils/codec"
|
||||||
"github.com/ava-labs/gecko/utils/hashing"
|
"github.com/ava-labs/gecko/utils/hashing"
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
"github.com/ava-labs/gecko/vms/components/codec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type rcLock struct {
|
type rcLock struct {
|
||||||
|
|
|
@ -76,6 +76,9 @@ type Manager interface {
|
||||||
// Add an alias to a chain
|
// Add an alias to a chain
|
||||||
Alias(ids.ID, string) error
|
Alias(ids.ID, string) error
|
||||||
|
|
||||||
|
// Returns true iff the chain with the given ID exists and is finished bootstrapping
|
||||||
|
IsBootstrapped(ids.ID) bool
|
||||||
|
|
||||||
Shutdown()
|
Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +117,10 @@ type manager struct {
|
||||||
keystore *keystore.Keystore
|
keystore *keystore.Keystore
|
||||||
sharedMemory *atomic.SharedMemory
|
sharedMemory *atomic.SharedMemory
|
||||||
|
|
||||||
|
// Key: Chain's ID
|
||||||
|
// Value: The chain
|
||||||
|
chains map[[32]byte]*router.Handler
|
||||||
|
|
||||||
unblocked bool
|
unblocked bool
|
||||||
blockedChains []ChainParameters
|
blockedChains []ChainParameters
|
||||||
}
|
}
|
||||||
|
@ -131,7 +138,7 @@ func New(
|
||||||
decisionEvents *triggers.EventDispatcher,
|
decisionEvents *triggers.EventDispatcher,
|
||||||
consensusEvents *triggers.EventDispatcher,
|
consensusEvents *triggers.EventDispatcher,
|
||||||
db database.Database,
|
db database.Database,
|
||||||
router router.Router,
|
rtr router.Router,
|
||||||
net network.Network,
|
net network.Network,
|
||||||
consensusParams avacon.Parameters,
|
consensusParams avacon.Parameters,
|
||||||
validators validators.Manager,
|
validators validators.Manager,
|
||||||
|
@ -145,7 +152,7 @@ func New(
|
||||||
timeoutManager.Initialize(requestTimeout)
|
timeoutManager.Initialize(requestTimeout)
|
||||||
go log.RecoverAndPanic(timeoutManager.Dispatch)
|
go log.RecoverAndPanic(timeoutManager.Dispatch)
|
||||||
|
|
||||||
router.Initialize(log, &timeoutManager, gossipFrequency, shutdownTimeout)
|
rtr.Initialize(log, &timeoutManager, gossipFrequency, shutdownTimeout)
|
||||||
|
|
||||||
m := &manager{
|
m := &manager{
|
||||||
stakingEnabled: stakingEnabled,
|
stakingEnabled: stakingEnabled,
|
||||||
|
@ -155,7 +162,7 @@ func New(
|
||||||
decisionEvents: decisionEvents,
|
decisionEvents: decisionEvents,
|
||||||
consensusEvents: consensusEvents,
|
consensusEvents: consensusEvents,
|
||||||
db: db,
|
db: db,
|
||||||
chainRouter: router,
|
chainRouter: rtr,
|
||||||
net: net,
|
net: net,
|
||||||
timeoutManager: &timeoutManager,
|
timeoutManager: &timeoutManager,
|
||||||
consensusParams: consensusParams,
|
consensusParams: consensusParams,
|
||||||
|
@ -165,6 +172,7 @@ func New(
|
||||||
server: server,
|
server: server,
|
||||||
keystore: keystore,
|
keystore: keystore,
|
||||||
sharedMemory: sharedMemory,
|
sharedMemory: sharedMemory,
|
||||||
|
chains: make(map[[32]byte]*router.Handler),
|
||||||
}
|
}
|
||||||
m.Initialize()
|
m.Initialize()
|
||||||
return m
|
return m
|
||||||
|
@ -454,7 +462,7 @@ func (m *manager) createAvalancheChain(
|
||||||
eng: &engine,
|
eng: &engine,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
m.chains[ctx.ChainID.Key()] = handler
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,9 +554,20 @@ func (m *manager) createSnowmanChain(
|
||||||
eng: &engine,
|
eng: &engine,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
m.chains[ctx.ChainID.Key()] = handler
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *manager) IsBootstrapped(id ids.ID) bool {
|
||||||
|
chain, exists := m.chains[id.Key()]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
chain.Context().Lock.Lock()
|
||||||
|
defer chain.Context().Lock.Unlock()
|
||||||
|
return chain.Engine().IsBootstrapped()
|
||||||
|
}
|
||||||
|
|
||||||
// Shutdown stops all the chains
|
// Shutdown stops all the chains
|
||||||
func (m *manager) Shutdown() { m.chainRouter.Shutdown() }
|
func (m *manager) Shutdown() { m.chainRouter.Shutdown() }
|
||||||
|
|
||||||
|
|
|
@ -35,3 +35,6 @@ func (mm MockManager) Alias(ids.ID, string) error { return nil }
|
||||||
|
|
||||||
// Shutdown ...
|
// Shutdown ...
|
||||||
func (mm MockManager) Shutdown() {}
|
func (mm MockManager) Shutdown() {}
|
||||||
|
|
||||||
|
// IsBootstrapped ...
|
||||||
|
func (mm MockManager) IsBootstrapped(ids.ID) bool { return false }
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -13,8 +13,8 @@ import (
|
||||||
"github.com/ava-labs/gecko/database"
|
"github.com/ava-labs/gecko/database"
|
||||||
"github.com/ava-labs/gecko/database/nodb"
|
"github.com/ava-labs/gecko/database/nodb"
|
||||||
"github.com/ava-labs/gecko/utils"
|
"github.com/ava-labs/gecko/utils"
|
||||||
|
"github.com/ava-labs/gecko/utils/codec"
|
||||||
"github.com/ava-labs/gecko/utils/hashing"
|
"github.com/ava-labs/gecko/utils/hashing"
|
||||||
"github.com/ava-labs/gecko/vms/components/codec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database encrypts all values that are provided
|
// Database encrypts all values that are provided
|
||||||
|
@ -201,7 +201,11 @@ func (b *batch) Write() error {
|
||||||
|
|
||||||
// Reset resets the batch for reuse.
|
// Reset resets the batch for reuse.
|
||||||
func (b *batch) Reset() {
|
func (b *batch) Reset() {
|
||||||
|
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.writes = b.writes[:0]
|
||||||
|
}
|
||||||
b.Batch.Reset()
|
b.Batch.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,15 @@ package leveldb
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/database"
|
|
||||||
"github.com/ava-labs/gecko/utils"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||||
"github.com/syndtr/goleveldb/leveldb/filter"
|
"github.com/syndtr/goleveldb/leveldb/filter"
|
||||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||||
"github.com/syndtr/goleveldb/leveldb/util"
|
"github.com/syndtr/goleveldb/leveldb/util"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/database"
|
||||||
|
"github.com/ava-labs/gecko/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -13,8 +13,10 @@ import (
|
||||||
"github.com/ava-labs/gecko/utils"
|
"github.com/ava-labs/gecko/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultSize is the default initial size of the memory database
|
const (
|
||||||
const DefaultSize = 1 << 10
|
// DefaultSize is the default initial size of the memory database
|
||||||
|
DefaultSize = 1 << 10
|
||||||
|
)
|
||||||
|
|
||||||
// Database is an ephemeral key-value store that implements the Database
|
// Database is an ephemeral key-value store that implements the Database
|
||||||
// interface.
|
// interface.
|
||||||
|
@ -191,7 +193,11 @@ func (b *batch) Write() error {
|
||||||
|
|
||||||
// Reset implements the Batch interface
|
// Reset implements the Batch interface
|
||||||
func (b *batch) Reset() {
|
func (b *batch) Reset() {
|
||||||
|
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.writes = b.writes[:0]
|
||||||
|
}
|
||||||
b.size = 0
|
b.size = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ func (*Database) Has([]byte) (bool, error) { return false, database.ErrClosed }
|
||||||
func (*Database) Get([]byte) ([]byte, error) { return nil, database.ErrClosed }
|
func (*Database) Get([]byte) ([]byte, error) { return nil, database.ErrClosed }
|
||||||
|
|
||||||
// Put returns nil
|
// Put returns nil
|
||||||
func (*Database) Put(_ []byte, _ []byte) error { return database.ErrClosed }
|
func (*Database) Put(_, _ []byte) error { return database.ErrClosed }
|
||||||
|
|
||||||
// Delete returns nil
|
// Delete returns nil
|
||||||
func (*Database) Delete([]byte) error { return database.ErrClosed }
|
func (*Database) Delete([]byte) error { return database.ErrClosed }
|
||||||
|
|
|
@ -199,7 +199,11 @@ func (b *batch) Write() error {
|
||||||
|
|
||||||
// Reset resets the batch for reuse.
|
// Reset resets the batch for reuse.
|
||||||
func (b *batch) Reset() {
|
func (b *batch) Reset() {
|
||||||
|
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.writes = b.writes[:0]
|
||||||
|
}
|
||||||
b.Batch.Reset()
|
b.Batch.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ func NewClient(client rpcdbproto.DatabaseClient) *DatabaseClient {
|
||||||
return &DatabaseClient{client: client}
|
return &DatabaseClient{client: client}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has returns false, nil
|
// Has attempts to return if the database has a key with the provided value.
|
||||||
func (db *DatabaseClient) Has(key []byte) (bool, error) {
|
func (db *DatabaseClient) Has(key []byte) (bool, error) {
|
||||||
resp, err := db.client.Has(context.Background(), &rpcdbproto.HasRequest{
|
resp, err := db.client.Has(context.Background(), &rpcdbproto.HasRequest{
|
||||||
Key: key,
|
Key: key,
|
||||||
|
@ -38,7 +38,7 @@ func (db *DatabaseClient) Has(key []byte) (bool, error) {
|
||||||
return resp.Has, nil
|
return resp.Has, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns nil, error
|
// Get attempts to return the value that was mapped to the key that was provided
|
||||||
func (db *DatabaseClient) Get(key []byte) ([]byte, error) {
|
func (db *DatabaseClient) Get(key []byte) ([]byte, error) {
|
||||||
resp, err := db.client.Get(context.Background(), &rpcdbproto.GetRequest{
|
resp, err := db.client.Get(context.Background(), &rpcdbproto.GetRequest{
|
||||||
Key: key,
|
Key: key,
|
||||||
|
@ -49,7 +49,7 @@ func (db *DatabaseClient) Get(key []byte) ([]byte, error) {
|
||||||
return resp.Value, nil
|
return resp.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put returns nil
|
// Put attempts to set the value this key maps to
|
||||||
func (db *DatabaseClient) Put(key, value []byte) error {
|
func (db *DatabaseClient) Put(key, value []byte) error {
|
||||||
_, err := db.client.Put(context.Background(), &rpcdbproto.PutRequest{
|
_, err := db.client.Put(context.Background(), &rpcdbproto.PutRequest{
|
||||||
Key: key,
|
Key: key,
|
||||||
|
@ -58,7 +58,7 @@ func (db *DatabaseClient) Put(key, value []byte) error {
|
||||||
return updateError(err)
|
return updateError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete returns nil
|
// Delete attempts to remove any mapping from the key
|
||||||
func (db *DatabaseClient) Delete(key []byte) error {
|
func (db *DatabaseClient) Delete(key []byte) error {
|
||||||
_, err := db.client.Delete(context.Background(), &rpcdbproto.DeleteRequest{
|
_, err := db.client.Delete(context.Background(), &rpcdbproto.DeleteRequest{
|
||||||
Key: key,
|
Key: key,
|
||||||
|
@ -99,7 +99,7 @@ func (db *DatabaseClient) NewIteratorWithStartAndPrefix(start, prefix []byte) da
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat returns an error
|
// Stat attempts to return the statistic of this database
|
||||||
func (db *DatabaseClient) Stat(property string) (string, error) {
|
func (db *DatabaseClient) Stat(property string) (string, error) {
|
||||||
resp, err := db.client.Stat(context.Background(), &rpcdbproto.StatRequest{
|
resp, err := db.client.Stat(context.Background(), &rpcdbproto.StatRequest{
|
||||||
Property: property,
|
Property: property,
|
||||||
|
@ -110,7 +110,7 @@ func (db *DatabaseClient) Stat(property string) (string, error) {
|
||||||
return resp.Stat, nil
|
return resp.Stat, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compact returns nil
|
// Compact attempts to optimize the space utilization in the provided range
|
||||||
func (db *DatabaseClient) Compact(start, limit []byte) error {
|
func (db *DatabaseClient) Compact(start, limit []byte) error {
|
||||||
_, err := db.client.Compact(context.Background(), &rpcdbproto.CompactRequest{
|
_, err := db.client.Compact(context.Background(), &rpcdbproto.CompactRequest{
|
||||||
Start: start,
|
Start: start,
|
||||||
|
@ -119,7 +119,7 @@ func (db *DatabaseClient) Compact(start, limit []byte) error {
|
||||||
return updateError(err)
|
return updateError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close returns nil
|
// Close attempts to close the database
|
||||||
func (db *DatabaseClient) Close() error {
|
func (db *DatabaseClient) Close() error {
|
||||||
_, err := db.client.Close(context.Background(), &rpcdbproto.CloseRequest{})
|
_, err := db.client.Close(context.Background(), &rpcdbproto.CloseRequest{})
|
||||||
return updateError(err)
|
return updateError(err)
|
||||||
|
@ -180,7 +180,11 @@ func (b *batch) Write() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *batch) Reset() {
|
func (b *batch) Reset() {
|
||||||
|
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.writes = b.writes[:0]
|
||||||
|
}
|
||||||
b.size = 0
|
b.size = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +211,8 @@ type iterator struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns false
|
// Next attempts to move the iterator to the next element and returns if this
|
||||||
|
// succeeded
|
||||||
func (it *iterator) Next() bool {
|
func (it *iterator) Next() bool {
|
||||||
resp, err := it.db.client.IteratorNext(context.Background(), &rpcdbproto.IteratorNextRequest{
|
resp, err := it.db.client.IteratorNext(context.Background(), &rpcdbproto.IteratorNextRequest{
|
||||||
Id: it.id,
|
Id: it.id,
|
||||||
|
@ -221,7 +226,7 @@ func (it *iterator) Next() bool {
|
||||||
return resp.FoundNext
|
return resp.FoundNext
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns any errors
|
// Error returns any that occurred while iterating
|
||||||
func (it *iterator) Error() error {
|
func (it *iterator) Error() error {
|
||||||
if it.err != nil {
|
if it.err != nil {
|
||||||
return it.err
|
return it.err
|
||||||
|
@ -234,19 +239,21 @@ func (it *iterator) Error() error {
|
||||||
return it.err
|
return it.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key returns nil
|
// Key returns the key of the current element
|
||||||
func (it *iterator) Key() []byte { return it.key }
|
func (it *iterator) Key() []byte { return it.key }
|
||||||
|
|
||||||
// Value returns nil
|
// Value returns the value of the current element
|
||||||
func (it *iterator) Value() []byte { return it.value }
|
func (it *iterator) Value() []byte { return it.value }
|
||||||
|
|
||||||
// Release does nothing
|
// Release frees any resources held by the iterator
|
||||||
func (it *iterator) Release() {
|
func (it *iterator) Release() {
|
||||||
it.db.client.IteratorRelease(context.Background(), &rpcdbproto.IteratorReleaseRequest{
|
it.db.client.IteratorRelease(context.Background(), &rpcdbproto.IteratorReleaseRequest{
|
||||||
Id: it.id,
|
Id: it.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateError sets the error value to the errors required by the Database
|
||||||
|
// interface
|
||||||
func updateError(err error) error {
|
func updateError(err error) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -34,16 +34,16 @@ func NewServer(db database.Database) *DatabaseServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has ...
|
// Has delegates the Has call to the managed database and returns the result
|
||||||
func (db *DatabaseServer) Has(_ context.Context, req *rpcdbproto.HasRequest) (*rpcdbproto.HasResponse, error) {
|
func (db *DatabaseServer) Has(_ context.Context, req *rpcdbproto.HasRequest) (*rpcdbproto.HasResponse, error) {
|
||||||
has, err := db.db.Has(req.Key)
|
has, err := db.db.Has(req.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rpcdbproto.HasResponse{Has: has}, nil
|
return &rpcdbproto.HasResponse{Has: has}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get ...
|
// Get delegates the Get call to the managed database and returns the result
|
||||||
func (db *DatabaseServer) Get(_ context.Context, req *rpcdbproto.GetRequest) (*rpcdbproto.GetResponse, error) {
|
func (db *DatabaseServer) Get(_ context.Context, req *rpcdbproto.GetRequest) (*rpcdbproto.GetResponse, error) {
|
||||||
value, err := db.db.Get(req.Key)
|
value, err := db.db.Get(req.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -52,17 +52,18 @@ func (db *DatabaseServer) Get(_ context.Context, req *rpcdbproto.GetRequest) (*r
|
||||||
return &rpcdbproto.GetResponse{Value: value}, nil
|
return &rpcdbproto.GetResponse{Value: value}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put ...
|
// Put delegates the Put call to the managed database and returns the result
|
||||||
func (db *DatabaseServer) Put(_ context.Context, req *rpcdbproto.PutRequest) (*rpcdbproto.PutResponse, error) {
|
func (db *DatabaseServer) Put(_ context.Context, req *rpcdbproto.PutRequest) (*rpcdbproto.PutResponse, error) {
|
||||||
return &rpcdbproto.PutResponse{}, db.db.Put(req.Key, req.Value)
|
return &rpcdbproto.PutResponse{}, db.db.Put(req.Key, req.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete ...
|
// Delete delegates the Delete call to the managed database and returns the
|
||||||
|
// result
|
||||||
func (db *DatabaseServer) Delete(_ context.Context, req *rpcdbproto.DeleteRequest) (*rpcdbproto.DeleteResponse, error) {
|
func (db *DatabaseServer) Delete(_ context.Context, req *rpcdbproto.DeleteRequest) (*rpcdbproto.DeleteResponse, error) {
|
||||||
return &rpcdbproto.DeleteResponse{}, db.db.Delete(req.Key)
|
return &rpcdbproto.DeleteResponse{}, db.db.Delete(req.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat ...
|
// Stat delegates the Stat call to the managed database and returns the result
|
||||||
func (db *DatabaseServer) Stat(_ context.Context, req *rpcdbproto.StatRequest) (*rpcdbproto.StatResponse, error) {
|
func (db *DatabaseServer) Stat(_ context.Context, req *rpcdbproto.StatRequest) (*rpcdbproto.StatResponse, error) {
|
||||||
stat, err := db.db.Stat(req.Property)
|
stat, err := db.db.Stat(req.Property)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -71,17 +72,19 @@ func (db *DatabaseServer) Stat(_ context.Context, req *rpcdbproto.StatRequest) (
|
||||||
return &rpcdbproto.StatResponse{Stat: stat}, nil
|
return &rpcdbproto.StatResponse{Stat: stat}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compact ...
|
// Compact delegates the Compact call to the managed database and returns the
|
||||||
|
// result
|
||||||
func (db *DatabaseServer) Compact(_ context.Context, req *rpcdbproto.CompactRequest) (*rpcdbproto.CompactResponse, error) {
|
func (db *DatabaseServer) Compact(_ context.Context, req *rpcdbproto.CompactRequest) (*rpcdbproto.CompactResponse, error) {
|
||||||
return &rpcdbproto.CompactResponse{}, db.db.Compact(req.Start, req.Limit)
|
return &rpcdbproto.CompactResponse{}, db.db.Compact(req.Start, req.Limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close ...
|
// Close delegates the Close call to the managed database and returns the result
|
||||||
func (db *DatabaseServer) Close(_ context.Context, _ *rpcdbproto.CloseRequest) (*rpcdbproto.CloseResponse, error) {
|
func (db *DatabaseServer) Close(context.Context, *rpcdbproto.CloseRequest) (*rpcdbproto.CloseResponse, error) {
|
||||||
return &rpcdbproto.CloseResponse{}, db.db.Close()
|
return &rpcdbproto.CloseResponse{}, db.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBatch ...
|
// WriteBatch takes in a set of key-value pairs and atomically writes them to
|
||||||
|
// the internal database
|
||||||
func (db *DatabaseServer) WriteBatch(_ context.Context, req *rpcdbproto.WriteBatchRequest) (*rpcdbproto.WriteBatchResponse, error) {
|
func (db *DatabaseServer) WriteBatch(_ context.Context, req *rpcdbproto.WriteBatchRequest) (*rpcdbproto.WriteBatchResponse, error) {
|
||||||
db.batch.Reset()
|
db.batch.Reset()
|
||||||
|
|
||||||
|
@ -100,7 +103,8 @@ func (db *DatabaseServer) WriteBatch(_ context.Context, req *rpcdbproto.WriteBat
|
||||||
return &rpcdbproto.WriteBatchResponse{}, db.batch.Write()
|
return &rpcdbproto.WriteBatchResponse{}, db.batch.Write()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIteratorWithStartAndPrefix ...
|
// NewIteratorWithStartAndPrefix allocates an iterator and returns the iterator
|
||||||
|
// ID
|
||||||
func (db *DatabaseServer) NewIteratorWithStartAndPrefix(_ context.Context, req *rpcdbproto.NewIteratorWithStartAndPrefixRequest) (*rpcdbproto.NewIteratorWithStartAndPrefixResponse, error) {
|
func (db *DatabaseServer) NewIteratorWithStartAndPrefix(_ context.Context, req *rpcdbproto.NewIteratorWithStartAndPrefixRequest) (*rpcdbproto.NewIteratorWithStartAndPrefixResponse, error) {
|
||||||
id := db.nextIteratorID
|
id := db.nextIteratorID
|
||||||
it := db.db.NewIteratorWithStartAndPrefix(req.Start, req.Prefix)
|
it := db.db.NewIteratorWithStartAndPrefix(req.Start, req.Prefix)
|
||||||
|
@ -110,7 +114,7 @@ func (db *DatabaseServer) NewIteratorWithStartAndPrefix(_ context.Context, req *
|
||||||
return &rpcdbproto.NewIteratorWithStartAndPrefixResponse{Id: id}, nil
|
return &rpcdbproto.NewIteratorWithStartAndPrefixResponse{Id: id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IteratorNext ...
|
// IteratorNext attempts to call next on the requested iterator
|
||||||
func (db *DatabaseServer) IteratorNext(_ context.Context, req *rpcdbproto.IteratorNextRequest) (*rpcdbproto.IteratorNextResponse, error) {
|
func (db *DatabaseServer) IteratorNext(_ context.Context, req *rpcdbproto.IteratorNextRequest) (*rpcdbproto.IteratorNextResponse, error) {
|
||||||
it, exists := db.iterators[req.Id]
|
it, exists := db.iterators[req.Id]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
@ -123,7 +127,7 @@ func (db *DatabaseServer) IteratorNext(_ context.Context, req *rpcdbproto.Iterat
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IteratorError ...
|
// IteratorError attempts to report any errors that occurred during iteration
|
||||||
func (db *DatabaseServer) IteratorError(_ context.Context, req *rpcdbproto.IteratorErrorRequest) (*rpcdbproto.IteratorErrorResponse, error) {
|
func (db *DatabaseServer) IteratorError(_ context.Context, req *rpcdbproto.IteratorErrorRequest) (*rpcdbproto.IteratorErrorResponse, error) {
|
||||||
it, exists := db.iterators[req.Id]
|
it, exists := db.iterators[req.Id]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
@ -132,7 +136,7 @@ func (db *DatabaseServer) IteratorError(_ context.Context, req *rpcdbproto.Itera
|
||||||
return &rpcdbproto.IteratorErrorResponse{}, it.Error()
|
return &rpcdbproto.IteratorErrorResponse{}, it.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IteratorRelease ...
|
// IteratorRelease attempts to release the resources allocated to an iterator
|
||||||
func (db *DatabaseServer) IteratorRelease(_ context.Context, req *rpcdbproto.IteratorReleaseRequest) (*rpcdbproto.IteratorReleaseResponse, error) {
|
func (db *DatabaseServer) IteratorRelease(_ context.Context, req *rpcdbproto.IteratorReleaseRequest) (*rpcdbproto.IteratorReleaseResponse, error) {
|
||||||
it, exists := db.iterators[req.Id]
|
it, exists := db.iterators[req.Id]
|
||||||
if exists {
|
if exists {
|
||||||
|
|
|
@ -21,6 +21,7 @@ type Database struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
mem map[string]valueDelete
|
mem map[string]valueDelete
|
||||||
db database.Database
|
db database.Database
|
||||||
|
batch database.Batch
|
||||||
}
|
}
|
||||||
|
|
||||||
type valueDelete struct {
|
type valueDelete struct {
|
||||||
|
@ -33,6 +34,7 @@ func New(db database.Database) *Database {
|
||||||
return &Database{
|
return &Database{
|
||||||
mem: make(map[string]valueDelete, memdb.DefaultSize),
|
mem: make(map[string]valueDelete, memdb.DefaultSize),
|
||||||
db: db,
|
db: db,
|
||||||
|
batch: db.NewBatch(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,6 +171,7 @@ func (db *Database) SetDatabase(newDB database.Database) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
db.db = newDB
|
db.db = newDB
|
||||||
|
db.batch = newDB.NewBatch()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,6 +195,7 @@ func (db *Database) Commit() error {
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
batch.Reset()
|
||||||
db.abort()
|
db.abort()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -206,7 +210,10 @@ func (db *Database) Abort() {
|
||||||
|
|
||||||
func (db *Database) abort() { db.mem = make(map[string]valueDelete, memdb.DefaultSize) }
|
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
|
// 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) {
|
func (db *Database) CommitBatch() (database.Batch, error) {
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
defer db.lock.Unlock()
|
defer db.lock.Unlock()
|
||||||
|
@ -214,26 +221,25 @@ func (db *Database) CommitBatch() (database.Batch, error) {
|
||||||
return db.commitBatch()
|
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) {
|
func (db *Database) commitBatch() (database.Batch, error) {
|
||||||
if db.mem == nil {
|
if db.mem == nil {
|
||||||
return nil, database.ErrClosed
|
return nil, database.ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
batch := db.db.NewBatch()
|
db.batch.Reset()
|
||||||
for key, value := range db.mem {
|
for key, value := range db.mem {
|
||||||
if value.delete {
|
if value.delete {
|
||||||
if err := batch.Delete([]byte(key)); err != nil {
|
if err := db.batch.Delete([]byte(key)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if err := batch.Put([]byte(key), value.value); err != nil {
|
} else if err := db.batch.Put([]byte(key), value.value); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return batch, nil
|
return db.batch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements the database.Database interface
|
// Close implements the database.Database interface
|
||||||
|
@ -244,6 +250,7 @@ func (db *Database) Close() error {
|
||||||
if db.mem == nil {
|
if db.mem == nil {
|
||||||
return database.ErrClosed
|
return database.ErrClosed
|
||||||
}
|
}
|
||||||
|
db.batch = nil
|
||||||
db.mem = nil
|
db.mem = nil
|
||||||
db.db = nil
|
db.db = nil
|
||||||
return nil
|
return nil
|
||||||
|
@ -298,7 +305,11 @@ func (b *batch) Write() error {
|
||||||
|
|
||||||
// Reset implements the Database interface
|
// Reset implements the Database interface
|
||||||
func (b *batch) Reset() {
|
func (b *batch) Reset() {
|
||||||
|
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.writes = b.writes[:0]
|
||||||
|
}
|
||||||
b.size = 0
|
b.size = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -299,6 +299,10 @@ func TestCommitBatch(t *testing.T) {
|
||||||
|
|
||||||
if err := db.Put(key1, value1); err != nil {
|
if err := db.Put(key1, value1); err != nil {
|
||||||
t.Fatalf("Unexpected error on db.Put: %s", err)
|
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()
|
batch, err := db.CommitBatch()
|
||||||
|
@ -307,7 +311,11 @@ func TestCommitBatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
db.Abort()
|
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)
|
t.Fatalf("Unexpected error on batch.Write: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,27 +18,27 @@ import (
|
||||||
// Aliases returns the default aliases based on the network ID
|
// Aliases returns the default aliases based on the network ID
|
||||||
func Aliases(networkID uint32) (map[string][]string, map[[32]byte][]string, map[[32]byte][]string, error) {
|
func Aliases(networkID uint32) (map[string][]string, map[[32]byte][]string, map[[32]byte][]string, error) {
|
||||||
generalAliases := map[string][]string{
|
generalAliases := map[string][]string{
|
||||||
"vm/" + platformvm.ID.String(): []string{"vm/platform"},
|
"vm/" + platformvm.ID.String(): {"vm/platform"},
|
||||||
"vm/" + avm.ID.String(): []string{"vm/avm"},
|
"vm/" + avm.ID.String(): {"vm/avm"},
|
||||||
"vm/" + EVMID.String(): []string{"vm/evm"},
|
"vm/" + EVMID.String(): {"vm/evm"},
|
||||||
"vm/" + spdagvm.ID.String(): []string{"vm/spdag"},
|
"vm/" + spdagvm.ID.String(): {"vm/spdag"},
|
||||||
"vm/" + spchainvm.ID.String(): []string{"vm/spchain"},
|
"vm/" + spchainvm.ID.String(): {"vm/spchain"},
|
||||||
"vm/" + timestampvm.ID.String(): []string{"vm/timestamp"},
|
"vm/" + timestampvm.ID.String(): {"vm/timestamp"},
|
||||||
"bc/" + ids.Empty.String(): []string{"P", "platform", "bc/P", "bc/platform"},
|
"bc/" + ids.Empty.String(): {"P", "platform", "bc/P", "bc/platform"},
|
||||||
}
|
}
|
||||||
chainAliases := map[[32]byte][]string{
|
chainAliases := map[[32]byte][]string{
|
||||||
ids.Empty.Key(): []string{"P", "platform"},
|
ids.Empty.Key(): {"P", "platform"},
|
||||||
}
|
}
|
||||||
vmAliases := map[[32]byte][]string{
|
vmAliases := map[[32]byte][]string{
|
||||||
platformvm.ID.Key(): []string{"platform"},
|
platformvm.ID.Key(): {"platform"},
|
||||||
avm.ID.Key(): []string{"avm"},
|
avm.ID.Key(): {"avm"},
|
||||||
EVMID.Key(): []string{"evm"},
|
EVMID.Key(): {"evm"},
|
||||||
spdagvm.ID.Key(): []string{"spdag"},
|
spdagvm.ID.Key(): {"spdag"},
|
||||||
spchainvm.ID.Key(): []string{"spchain"},
|
spchainvm.ID.Key(): {"spchain"},
|
||||||
timestampvm.ID.Key(): []string{"timestamp"},
|
timestampvm.ID.Key(): {"timestamp"},
|
||||||
secp256k1fx.ID.Key(): []string{"secp256k1fx"},
|
secp256k1fx.ID.Key(): {"secp256k1fx"},
|
||||||
nftfx.ID.Key(): []string{"nftfx"},
|
nftfx.ID.Key(): {"nftfx"},
|
||||||
propertyfx.ID.Key(): []string{"propertyfx"},
|
propertyfx.ID.Key(): {"propertyfx"},
|
||||||
}
|
}
|
||||||
|
|
||||||
genesisBytes, err := Genesis(networkID)
|
genesisBytes, err := Genesis(networkID)
|
||||||
|
|
|
@ -50,6 +50,122 @@ func (c *Config) init() error {
|
||||||
|
|
||||||
// Hard coded genesis constants
|
// Hard coded genesis constants
|
||||||
var (
|
var (
|
||||||
|
EverestConfig = Config{
|
||||||
|
MintAddresses: []string{
|
||||||
|
"95YUFjhDG892VePMzpwKF9JzewGKvGRi3",
|
||||||
|
},
|
||||||
|
FundedAddresses: []string{
|
||||||
|
"9uKvvA7E35QCwLvAaohXTCfFejbf3Rv17",
|
||||||
|
"JLrYNMYXANGj43BfWXBxMMAEenUBp1Sbn",
|
||||||
|
"7TUTzwrU6nbZtWHjTHEpdneUvjKBxb3EM",
|
||||||
|
"77mPUXBdQKwQpPoX6rckCZGLGGdkuG1G6",
|
||||||
|
"4gGWdFZ4Gax1B466YKXyKRRpWLb42Afdt",
|
||||||
|
"CKTkzAPsRxCreyiDTnjGxLmjMarxF28fi",
|
||||||
|
"4ABm9gFHVtsNdcKSd1xsacFkGneSgzpaa",
|
||||||
|
"DpL8PTsrjtLzv5J8LL3D2A6YcnCTqrNH9",
|
||||||
|
"ZdhZv6oZrmXLyFDy6ovXAu6VxmbTsT2h",
|
||||||
|
"6cesTteH62Y5mLoDBUASaBvCXuL2AthL",
|
||||||
|
},
|
||||||
|
StakerIDs: []string{
|
||||||
|
"LQwRLm4cbJ7T2kxcxp4uXCU5XD8DFrE1C",
|
||||||
|
"hArafGhY2HFTbwaaVh1CSCUCUCiJ2Vfb",
|
||||||
|
"2m38qc95mhHXtrhjyGbe7r2NhniqHHJRB",
|
||||||
|
"4QBwET5o8kUhvt9xArhir4d3R25CtmZho",
|
||||||
|
"NpagUxt6KQiwPch9Sd4osv8kD1TZnkjdk",
|
||||||
|
},
|
||||||
|
EVMBytes: []byte{
|
||||||
|
0x7b, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
|
0x22, 0x3a, 0x7b, 0x22, 0x63, 0x68, 0x61, 0x69,
|
||||||
|
0x6e, 0x49, 0x64, 0x22, 0x3a, 0x34, 0x33, 0x31,
|
||||||
|
0x31, 0x30, 0x2c, 0x22, 0x68, 0x6f, 0x6d, 0x65,
|
||||||
|
0x73, 0x74, 0x65, 0x61, 0x64, 0x42, 0x6c, 0x6f,
|
||||||
|
0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x64,
|
||||||
|
0x61, 0x6f, 0x46, 0x6f, 0x72, 0x6b, 0x42, 0x6c,
|
||||||
|
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22,
|
||||||
|
0x64, 0x61, 0x6f, 0x46, 0x6f, 0x72, 0x6b, 0x53,
|
||||||
|
0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x3a,
|
||||||
|
0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x65, 0x69,
|
||||||
|
0x70, 0x31, 0x35, 0x30, 0x42, 0x6c, 0x6f, 0x63,
|
||||||
|
0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x65, 0x69,
|
||||||
|
0x70, 0x31, 0x35, 0x30, 0x48, 0x61, 0x73, 0x68,
|
||||||
|
0x22, 0x3a, 0x22, 0x30, 0x78, 0x32, 0x30, 0x38,
|
||||||
|
0x36, 0x37, 0x39, 0x39, 0x61, 0x65, 0x65, 0x62,
|
||||||
|
0x65, 0x61, 0x65, 0x31, 0x33, 0x35, 0x63, 0x32,
|
||||||
|
0x34, 0x36, 0x63, 0x36, 0x35, 0x30, 0x32, 0x31,
|
||||||
|
0x63, 0x38, 0x32, 0x62, 0x34, 0x65, 0x31, 0x35,
|
||||||
|
0x61, 0x32, 0x63, 0x34, 0x35, 0x31, 0x33, 0x34,
|
||||||
|
0x30, 0x39, 0x39, 0x33, 0x61, 0x61, 0x63, 0x66,
|
||||||
|
0x64, 0x32, 0x37, 0x35, 0x31, 0x38, 0x38, 0x36,
|
||||||
|
0x35, 0x31, 0x34, 0x66, 0x30, 0x22, 0x2c, 0x22,
|
||||||
|
0x65, 0x69, 0x70, 0x31, 0x35, 0x35, 0x42, 0x6c,
|
||||||
|
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22,
|
||||||
|
0x65, 0x69, 0x70, 0x31, 0x35, 0x38, 0x42, 0x6c,
|
||||||
|
0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22,
|
||||||
|
0x62, 0x79, 0x7a, 0x61, 0x6e, 0x74, 0x69, 0x75,
|
||||||
|
0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a,
|
||||||
|
0x30, 0x2c, 0x22, 0x63, 0x6f, 0x6e, 0x73, 0x74,
|
||||||
|
0x61, 0x6e, 0x74, 0x69, 0x6e, 0x6f, 0x70, 0x6c,
|
||||||
|
0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a,
|
||||||
|
0x30, 0x2c, 0x22, 0x70, 0x65, 0x74, 0x65, 0x72,
|
||||||
|
0x73, 0x62, 0x75, 0x72, 0x67, 0x42, 0x6c, 0x6f,
|
||||||
|
0x63, 0x6b, 0x22, 0x3a, 0x30, 0x7d, 0x2c, 0x22,
|
||||||
|
0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x22,
|
||||||
|
0x30, 0x78, 0x30, 0x22, 0x2c, 0x22, 0x74, 0x69,
|
||||||
|
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22,
|
||||||
|
0x3a, 0x22, 0x30, 0x78, 0x30, 0x22, 0x2c, 0x22,
|
||||||
|
0x65, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61, 0x74,
|
||||||
|
0x61, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30, 0x30,
|
||||||
|
0x22, 0x2c, 0x22, 0x67, 0x61, 0x73, 0x4c, 0x69,
|
||||||
|
0x6d, 0x69, 0x74, 0x22, 0x3a, 0x22, 0x30, 0x78,
|
||||||
|
0x35, 0x66, 0x35, 0x65, 0x31, 0x30, 0x30, 0x22,
|
||||||
|
0x2c, 0x22, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63,
|
||||||
|
0x75, 0x6c, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x30,
|
||||||
|
0x78, 0x30, 0x22, 0x2c, 0x22, 0x6d, 0x69, 0x78,
|
||||||
|
0x48, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x30,
|
||||||
|
0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x22, 0x2c, 0x22, 0x63, 0x6f, 0x69, 0x6e,
|
||||||
|
0x62, 0x61, 0x73, 0x65, 0x22, 0x3a, 0x22, 0x30,
|
||||||
|
0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x22, 0x2c, 0x22, 0x61, 0x6c, 0x6c, 0x6f,
|
||||||
|
0x63, 0x22, 0x3a, 0x7b, 0x22, 0x35, 0x37, 0x32,
|
||||||
|
0x66, 0x34, 0x64, 0x38, 0x30, 0x66, 0x31, 0x30,
|
||||||
|
0x66, 0x36, 0x36, 0x33, 0x62, 0x35, 0x30, 0x34,
|
||||||
|
0x39, 0x66, 0x37, 0x38, 0x39, 0x35, 0x34, 0x36,
|
||||||
|
0x66, 0x32, 0x35, 0x66, 0x37, 0x30, 0x62, 0x62,
|
||||||
|
0x36, 0x32, 0x61, 0x37, 0x66, 0x22, 0x3a, 0x7b,
|
||||||
|
0x22, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
|
||||||
|
0x22, 0x3a, 0x22, 0x30, 0x78, 0x33, 0x33, 0x62,
|
||||||
|
0x32, 0x65, 0x33, 0x63, 0x39, 0x66, 0x64, 0x30,
|
||||||
|
0x38, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x22, 0x7d, 0x7d, 0x2c,
|
||||||
|
0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22,
|
||||||
|
0x3a, 0x22, 0x30, 0x78, 0x30, 0x22, 0x2c, 0x22,
|
||||||
|
0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x22,
|
||||||
|
0x3a, 0x22, 0x30, 0x78, 0x30, 0x22, 0x2c, 0x22,
|
||||||
|
0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61,
|
||||||
|
0x73, 0x68, 0x22, 0x3a, 0x22, 0x30, 0x78, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x22,
|
||||||
|
0x7d,
|
||||||
|
},
|
||||||
|
}
|
||||||
DenaliConfig = Config{
|
DenaliConfig = Config{
|
||||||
MintAddresses: []string{
|
MintAddresses: []string{
|
||||||
"95YUFjhDG892VePMzpwKF9JzewGKvGRi3",
|
"95YUFjhDG892VePMzpwKF9JzewGKvGRi3",
|
||||||
|
@ -393,6 +509,8 @@ var (
|
||||||
// GetConfig ...
|
// GetConfig ...
|
||||||
func GetConfig(networkID uint32) *Config {
|
func GetConfig(networkID uint32) *Config {
|
||||||
switch networkID {
|
switch networkID {
|
||||||
|
case EverestID:
|
||||||
|
return &EverestConfig
|
||||||
case DenaliID:
|
case DenaliID:
|
||||||
return &DenaliConfig
|
return &DenaliConfig
|
||||||
case CascadeID:
|
case CascadeID:
|
||||||
|
|
|
@ -9,12 +9,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/utils/codec"
|
||||||
"github.com/ava-labs/gecko/utils/formatting"
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
"github.com/ava-labs/gecko/utils/json"
|
"github.com/ava-labs/gecko/utils/json"
|
||||||
"github.com/ava-labs/gecko/utils/units"
|
"github.com/ava-labs/gecko/utils/units"
|
||||||
"github.com/ava-labs/gecko/utils/wrappers"
|
"github.com/ava-labs/gecko/utils/wrappers"
|
||||||
"github.com/ava-labs/gecko/vms/avm"
|
"github.com/ava-labs/gecko/vms/avm"
|
||||||
"github.com/ava-labs/gecko/vms/components/codec"
|
|
||||||
"github.com/ava-labs/gecko/vms/nftfx"
|
"github.com/ava-labs/gecko/vms/nftfx"
|
||||||
"github.com/ava-labs/gecko/vms/platformvm"
|
"github.com/ava-labs/gecko/vms/platformvm"
|
||||||
"github.com/ava-labs/gecko/vms/propertyfx"
|
"github.com/ava-labs/gecko/vms/propertyfx"
|
||||||
|
@ -156,7 +156,7 @@ func FromConfig(networkID uint32, config *Config) ([]byte, error) {
|
||||||
|
|
||||||
// Specify the chains that exist upon this network's creation
|
// Specify the chains that exist upon this network's creation
|
||||||
platformvmArgs.Chains = []platformvm.APIChain{
|
platformvmArgs.Chains = []platformvm.APIChain{
|
||||||
platformvm.APIChain{
|
{
|
||||||
GenesisData: avmReply.Bytes,
|
GenesisData: avmReply.Bytes,
|
||||||
SubnetID: platformvm.DefaultSubnetID,
|
SubnetID: platformvm.DefaultSubnetID,
|
||||||
VMID: avm.ID,
|
VMID: avm.ID,
|
||||||
|
@ -167,25 +167,25 @@ func FromConfig(networkID uint32, config *Config) ([]byte, error) {
|
||||||
},
|
},
|
||||||
Name: "X-Chain",
|
Name: "X-Chain",
|
||||||
},
|
},
|
||||||
platformvm.APIChain{
|
{
|
||||||
GenesisData: formatting.CB58{Bytes: config.EVMBytes},
|
GenesisData: formatting.CB58{Bytes: config.EVMBytes},
|
||||||
SubnetID: platformvm.DefaultSubnetID,
|
SubnetID: platformvm.DefaultSubnetID,
|
||||||
VMID: EVMID,
|
VMID: EVMID,
|
||||||
Name: "C-Chain",
|
Name: "C-Chain",
|
||||||
},
|
},
|
||||||
platformvm.APIChain{
|
{
|
||||||
GenesisData: spdagvmReply.Bytes,
|
GenesisData: spdagvmReply.Bytes,
|
||||||
SubnetID: platformvm.DefaultSubnetID,
|
SubnetID: platformvm.DefaultSubnetID,
|
||||||
VMID: spdagvm.ID,
|
VMID: spdagvm.ID,
|
||||||
Name: "Simple DAG Payments",
|
Name: "Simple DAG Payments",
|
||||||
},
|
},
|
||||||
platformvm.APIChain{
|
{
|
||||||
GenesisData: spchainvmReply.Bytes,
|
GenesisData: spchainvmReply.Bytes,
|
||||||
SubnetID: platformvm.DefaultSubnetID,
|
SubnetID: platformvm.DefaultSubnetID,
|
||||||
VMID: spchainvm.ID,
|
VMID: spchainvm.ID,
|
||||||
Name: "Simple Chain Payments",
|
Name: "Simple Chain Payments",
|
||||||
},
|
},
|
||||||
platformvm.APIChain{
|
{
|
||||||
GenesisData: formatting.CB58{Bytes: []byte{}}, // There is no genesis data
|
GenesisData: formatting.CB58{Bytes: []byte{}}, // There is no genesis data
|
||||||
SubnetID: platformvm.DefaultSubnetID,
|
SubnetID: platformvm.DefaultSubnetID,
|
||||||
VMID: timestampvm.ID,
|
VMID: timestampvm.ID,
|
||||||
|
|
|
@ -23,7 +23,10 @@ func TestNetworkName(t *testing.T) {
|
||||||
if name := NetworkName(DenaliID); name != DenaliName {
|
if name := NetworkName(DenaliID); name != DenaliName {
|
||||||
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, DenaliName)
|
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, DenaliName)
|
||||||
}
|
}
|
||||||
if name := NetworkName(TestnetID); name != DenaliName {
|
if name := NetworkName(EverestID); name != EverestName {
|
||||||
|
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, EverestName)
|
||||||
|
}
|
||||||
|
if name := NetworkName(DenaliID); name != DenaliName {
|
||||||
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, DenaliName)
|
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, DenaliName)
|
||||||
}
|
}
|
||||||
if name := NetworkName(4294967295); name != "network-4294967295" {
|
if name := NetworkName(4294967295); name != "network-4294967295" {
|
||||||
|
|
|
@ -16,6 +16,7 @@ var (
|
||||||
MainnetID uint32 = 1
|
MainnetID uint32 = 1
|
||||||
CascadeID uint32 = 2
|
CascadeID uint32 = 2
|
||||||
DenaliID uint32 = 3
|
DenaliID uint32 = 3
|
||||||
|
EverestID uint32 = 4
|
||||||
|
|
||||||
TestnetID uint32 = 3
|
TestnetID uint32 = 3
|
||||||
LocalID uint32 = 12345
|
LocalID uint32 = 12345
|
||||||
|
@ -23,6 +24,7 @@ var (
|
||||||
MainnetName = "mainnet"
|
MainnetName = "mainnet"
|
||||||
CascadeName = "cascade"
|
CascadeName = "cascade"
|
||||||
DenaliName = "denali"
|
DenaliName = "denali"
|
||||||
|
EverestName = "everest"
|
||||||
|
|
||||||
TestnetName = "testnet"
|
TestnetName = "testnet"
|
||||||
LocalName = "local"
|
LocalName = "local"
|
||||||
|
@ -31,6 +33,7 @@ var (
|
||||||
MainnetID: MainnetName,
|
MainnetID: MainnetName,
|
||||||
CascadeID: CascadeName,
|
CascadeID: CascadeName,
|
||||||
DenaliID: DenaliName,
|
DenaliID: DenaliName,
|
||||||
|
EverestID: EverestName,
|
||||||
|
|
||||||
LocalID: LocalName,
|
LocalID: LocalName,
|
||||||
}
|
}
|
||||||
|
@ -38,6 +41,7 @@ var (
|
||||||
MainnetName: MainnetID,
|
MainnetName: MainnetID,
|
||||||
CascadeName: CascadeID,
|
CascadeName: CascadeID,
|
||||||
DenaliName: DenaliID,
|
DenaliName: DenaliID,
|
||||||
|
EverestName: EverestID,
|
||||||
|
|
||||||
TestnetName: TestnetID,
|
TestnetName: TestnetID,
|
||||||
LocalName: LocalID,
|
LocalName: LocalID,
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -6,10 +6,10 @@ require (
|
||||||
github.com/AppsFlyer/go-sundheit v0.2.0
|
github.com/AppsFlyer/go-sundheit v0.2.0
|
||||||
github.com/allegro/bigcache v1.2.1 // indirect
|
github.com/allegro/bigcache v1.2.1 // indirect
|
||||||
github.com/aristanetworks/goarista v0.0.0-20200520141224-0f14e646773f // indirect
|
github.com/aristanetworks/goarista v0.0.0-20200520141224-0f14e646773f // indirect
|
||||||
github.com/ava-labs/coreth v0.2.4 // Added manually; don't delete
|
github.com/ava-labs/coreth v0.2.5 // indirect; Added manually; don't delete
|
||||||
github.com/ava-labs/go-ethereum v1.9.3 // indirect
|
github.com/ava-labs/go-ethereum v1.9.3 // indirect
|
||||||
github.com/deckarep/golang-set v1.7.1 // indirect
|
github.com/deckarep/golang-set v1.7.1 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1 v1.0.3
|
github.com/decred/dcrd/dcrec/secp256k1 v1.0.3 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200526030155-0c6c7ca85d3b
|
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200526030155-0c6c7ca85d3b
|
||||||
github.com/edsrzf/mmap-go v1.0.0 // indirect
|
github.com/edsrzf/mmap-go v1.0.0 // indirect
|
||||||
github.com/elastic/gosigar v0.10.5 // indirect
|
github.com/elastic/gosigar v0.10.5 // indirect
|
||||||
|
@ -20,6 +20,7 @@ require (
|
||||||
github.com/gorilla/mux v1.7.4
|
github.com/gorilla/mux v1.7.4
|
||||||
github.com/gorilla/rpc v1.2.0
|
github.com/gorilla/rpc v1.2.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
|
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd
|
||||||
github.com/hashicorp/go-plugin v1.3.0
|
github.com/hashicorp/go-plugin v1.3.0
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
github.com/huin/goupnp v1.0.0
|
github.com/huin/goupnp v1.0.0
|
||||||
|
@ -28,7 +29,7 @@ require (
|
||||||
github.com/karalabe/usb v0.0.0-20191104083709-911d15fe12a9 // indirect
|
github.com/karalabe/usb v0.0.0-20191104083709-911d15fe12a9 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/mr-tron/base58 v1.1.3
|
github.com/mr-tron/base58 v1.2.0
|
||||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d
|
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d
|
||||||
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
||||||
github.com/pborman/uuid v1.2.0 // indirect
|
github.com/pborman/uuid v1.2.0 // indirect
|
||||||
|
|
26
go.sum
26
go.sum
|
@ -2,13 +2,16 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||||
github.com/AppsFlyer/go-sundheit v0.2.0 h1:FArqX+HbqZ6U32RC3giEAWRUpkggqxHj91KIvxNgwjU=
|
github.com/AppsFlyer/go-sundheit v0.2.0 h1:FArqX+HbqZ6U32RC3giEAWRUpkggqxHj91KIvxNgwjU=
|
||||||
github.com/AppsFlyer/go-sundheit v0.2.0/go.mod h1:rCRkVTMQo7/krF7xQ9X0XEF1an68viFR6/Gy02q+4ds=
|
github.com/AppsFlyer/go-sundheit v0.2.0/go.mod h1:rCRkVTMQo7/krF7xQ9X0XEF1an68viFR6/Gy02q+4ds=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
|
||||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU=
|
github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU=
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
|
github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
|
||||||
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||||
|
@ -19,6 +22,8 @@ github.com/aristanetworks/goarista v0.0.0-20200520141224-0f14e646773f/go.mod h1:
|
||||||
github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc=
|
github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc=
|
||||||
github.com/ava-labs/coreth v0.2.4 h1:MhnbuRyMcij7WU4+frayp40quc44AMPc4IrxXhmucWw=
|
github.com/ava-labs/coreth v0.2.4 h1:MhnbuRyMcij7WU4+frayp40quc44AMPc4IrxXhmucWw=
|
||||||
github.com/ava-labs/coreth v0.2.4/go.mod h1:pGolKipwq5vGIY2IBBcBkMYrqniXMsS5SBn+BBi4+Js=
|
github.com/ava-labs/coreth v0.2.4/go.mod h1:pGolKipwq5vGIY2IBBcBkMYrqniXMsS5SBn+BBi4+Js=
|
||||||
|
github.com/ava-labs/coreth v0.2.5 h1:2Al753rpPHvvZfcz7w96YbKhGFvrcZzsIZ/sIp0A0Ao=
|
||||||
|
github.com/ava-labs/coreth v0.2.5/go.mod h1:pGolKipwq5vGIY2IBBcBkMYrqniXMsS5SBn+BBi4+Js=
|
||||||
github.com/ava-labs/go-ethereum v1.9.3 h1:GmnMZ/dlvVAPFmWBzEpRJX49pUAymPfoASLNRJqR0AY=
|
github.com/ava-labs/go-ethereum v1.9.3 h1:GmnMZ/dlvVAPFmWBzEpRJX49pUAymPfoASLNRJqR0AY=
|
||||||
github.com/ava-labs/go-ethereum v1.9.3/go.mod h1:a+agc6fXfZFsPZCylA3ry4Y8CLCqLKg3Rc23NXZ9aw8=
|
github.com/ava-labs/go-ethereum v1.9.3/go.mod h1:a+agc6fXfZFsPZCylA3ry4Y8CLCqLKg3Rc23NXZ9aw8=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
@ -38,7 +43,9 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
|
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
|
||||||
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||||
github.com/decred/dcrd v1.3.0 h1:EEXm7BdiROfazDtuFsOu9mfotnyy00bgCuVwUqaszFo=
|
github.com/decred/dcrd v1.3.0 h1:EEXm7BdiROfazDtuFsOu9mfotnyy00bgCuVwUqaszFo=
|
||||||
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU=
|
||||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
github.com/decred/dcrd/dcrec v1.0.0 h1:W+z6Es+Rai3MXYVoPAxYr5U1DGis0Co33scJ6uH2J6o=
|
github.com/decred/dcrd/dcrec v1.0.0 h1:W+z6Es+Rai3MXYVoPAxYr5U1DGis0Co33scJ6uH2J6o=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1 v1.0.3 h1:u4XpHqlscRolxPxt2YHrFBDVZYY1AK+KMV02H1r+HmU=
|
github.com/decred/dcrd/dcrec/secp256k1 v1.0.3 h1:u4XpHqlscRolxPxt2YHrFBDVZYY1AK+KMV02H1r+HmU=
|
||||||
|
@ -61,8 +68,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
|
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
|
||||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
||||||
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||||
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
|
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
|
||||||
|
@ -76,6 +85,7 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
@ -97,6 +107,7 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||||
|
@ -119,6 +130,7 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
|
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
|
||||||
github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
|
github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
|
||||||
|
@ -129,6 +141,7 @@ github.com/jackpal/gateway v1.0.6/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQ
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||||
|
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
|
||||||
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
@ -142,8 +155,10 @@ github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||||
|
@ -164,6 +179,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
||||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||||
|
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||||
|
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
|
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
|
||||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||||
|
@ -174,8 +191,10 @@ github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn
|
||||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc=
|
github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc=
|
||||||
github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw=
|
github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw=
|
||||||
|
@ -217,6 +236,7 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 h1:Oo2KZNP70KE0+IUJSidPj/BFS/RXNHmKIJOdckzml2E=
|
github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 h1:Oo2KZNP70KE0+IUJSidPj/BFS/RXNHmKIJOdckzml2E=
|
||||||
|
@ -226,6 +246,7 @@ github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8
|
||||||
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM=
|
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM=
|
||||||
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
|
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
@ -310,6 +331,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
@ -336,10 +358,13 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610=
|
gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
|
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
|
||||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
|
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
|
||||||
|
@ -349,6 +374,7 @@ gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLv
|
||||||
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9 h1:ITeyKbRetrVzqR3U1eY+ywgp7IBspGd1U/bkwd1gWu4=
|
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9 h1:ITeyKbRetrVzqR3U1eY+ywgp7IBspGd1U/bkwd1gWu4=
|
||||||
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
|
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
|
||||||
gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA=
|
gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
|
gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
|
||||||
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
|
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
|
||||||
|
|
17
ids/bag.go
17
ids/bag.go
|
@ -8,6 +8,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minBagSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
// Bag is a multiset of IDs.
|
// Bag is a multiset of IDs.
|
||||||
//
|
//
|
||||||
// A bag has the ability to split and filter on it's bits for ease of use for
|
// A bag has the ability to split and filter on it's bits for ease of use for
|
||||||
|
@ -25,7 +29,7 @@ type Bag struct {
|
||||||
|
|
||||||
func (b *Bag) init() {
|
func (b *Bag) init() {
|
||||||
if b.counts == nil {
|
if b.counts == nil {
|
||||||
b.counts = make(map[[32]byte]int)
|
b.counts = make(map[[32]byte]int, minBagSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,16 +76,21 @@ func (b *Bag) AddCount(id ID, count int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count returns the number of times the id has been added.
|
// Count returns the number of times the id has been added.
|
||||||
func (b *Bag) Count(id ID) int { return b.counts[*id.ID] }
|
func (b *Bag) Count(id ID) int {
|
||||||
|
b.init()
|
||||||
|
return b.counts[*id.ID]
|
||||||
|
}
|
||||||
|
|
||||||
// Len returns the number of times an id has been added.
|
// Len returns the number of times an id has been added.
|
||||||
func (b *Bag) Len() int { return b.size }
|
func (b *Bag) Len() int { return b.size }
|
||||||
|
|
||||||
// List returns a list of all ids that have been added.
|
// List returns a list of all ids that have been added.
|
||||||
func (b *Bag) List() []ID {
|
func (b *Bag) List() []ID {
|
||||||
idList := []ID(nil)
|
idList := make([]ID, len(b.counts), len(b.counts))
|
||||||
|
i := 0
|
||||||
for id := range b.counts {
|
for id := range b.counts {
|
||||||
idList = append(idList, NewID(id))
|
idList[i] = NewID(id)
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
return idList
|
return idList
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
func BenchmarkBagListSmall(b *testing.B) {
|
||||||
|
smallLen := 5
|
||||||
|
bag := Bag{}
|
||||||
|
for i := 0; i < smallLen; i++ {
|
||||||
|
var idBytes [32]byte
|
||||||
|
rand.Read(idBytes[:])
|
||||||
|
NewID(idBytes)
|
||||||
|
bag.Add(NewID(idBytes))
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
bag.List()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBagListMedium(b *testing.B) {
|
||||||
|
mediumLen := 25
|
||||||
|
bag := Bag{}
|
||||||
|
for i := 0; i < mediumLen; i++ {
|
||||||
|
var idBytes [32]byte
|
||||||
|
rand.Read(idBytes[:])
|
||||||
|
NewID(idBytes)
|
||||||
|
bag.Add(NewID(idBytes))
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
bag.List()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBagListLarge(b *testing.B) {
|
||||||
|
largeLen := 100000
|
||||||
|
bag := Bag{}
|
||||||
|
for i := 0; i < largeLen; i++ {
|
||||||
|
var idBytes [32]byte
|
||||||
|
rand.Read(idBytes[:])
|
||||||
|
NewID(idBytes)
|
||||||
|
bag.Add(NewID(idBytes))
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
bag.List()
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,8 @@ func TestBagAdd(t *testing.T) {
|
||||||
} else if count := bag.Count(id1); count != 0 {
|
} else if count := bag.Count(id1); count != 0 {
|
||||||
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
t.Fatalf("Bag.Count returned %d expected %d", count, 0)
|
||||||
} else if size := bag.Len(); size != 0 {
|
} else if size := bag.Len(); size != 0 {
|
||||||
t.Fatalf("Bag.Len returned %d expected %d", count, 0)
|
t.Fatalf("Bag.Len returned %d elements expected %d", count, 0)
|
||||||
} else if list := bag.List(); list != nil {
|
} else if list := bag.List(); len(list) != 0 {
|
||||||
t.Fatalf("Bag.List returned %v expected %v", list, nil)
|
t.Fatalf("Bag.List returned %v expected %v", list, nil)
|
||||||
} else if mode, freq := bag.Mode(); !mode.IsZero() {
|
} else if mode, freq := bag.Mode(); !mode.IsZero() {
|
||||||
t.Fatalf("Bag.Mode[0] returned %s expected %s", mode, ID{})
|
t.Fatalf("Bag.Mode[0] returned %s expected %s", mode, ID{})
|
||||||
|
|
35
ids/set.go
35
ids/set.go
|
@ -7,11 +7,19 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The minimum capacity of a set
|
||||||
|
minSetSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
// Set is a set of IDs
|
// Set is a set of IDs
|
||||||
type Set map[[32]byte]bool
|
type Set map[[32]byte]bool
|
||||||
|
|
||||||
func (ids *Set) init(size int) {
|
func (ids *Set) init(size int) {
|
||||||
if *ids == nil {
|
if *ids == nil {
|
||||||
|
if minSetSize > size {
|
||||||
|
size = minSetSize
|
||||||
|
}
|
||||||
*ids = make(map[[32]byte]bool, size)
|
*ids = make(map[[32]byte]bool, size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,9 +78,32 @@ func (ids *Set) Clear() { *ids = nil }
|
||||||
|
|
||||||
// List converts this set into a list
|
// List converts this set into a list
|
||||||
func (ids Set) List() []ID {
|
func (ids Set) List() []ID {
|
||||||
idList := []ID(nil)
|
idList := make([]ID, ids.Len())
|
||||||
|
i := 0
|
||||||
for id := range ids {
|
for id := range ids {
|
||||||
idList = append(idList, NewID(id))
|
idList[i] = NewID(id)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return idList
|
||||||
|
}
|
||||||
|
|
||||||
|
// CappedList returns a list of length at most [size].
|
||||||
|
// Size should be >= 0. If size < 0, returns nil.
|
||||||
|
func (ids Set) CappedList(size int) []ID {
|
||||||
|
if size < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if l := ids.Len(); l < size {
|
||||||
|
size = l
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
idList := make([]ID, size)
|
||||||
|
for id := range ids {
|
||||||
|
if i >= size {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
idList[i] = NewID(id)
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
return idList
|
return idList
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package ids
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
func BenchmarkSetListSmall(b *testing.B) {
|
||||||
|
smallLen := 5
|
||||||
|
set := Set{}
|
||||||
|
for i := 0; i < smallLen; i++ {
|
||||||
|
var idBytes [32]byte
|
||||||
|
rand.Read(idBytes[:])
|
||||||
|
NewID(idBytes)
|
||||||
|
set.Add(NewID(idBytes))
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
set.List()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSetListMedium(b *testing.B) {
|
||||||
|
mediumLen := 25
|
||||||
|
set := Set{}
|
||||||
|
for i := 0; i < mediumLen; i++ {
|
||||||
|
var idBytes [32]byte
|
||||||
|
rand.Read(idBytes[:])
|
||||||
|
NewID(idBytes)
|
||||||
|
set.Add(NewID(idBytes))
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
set.List()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSetListLarge(b *testing.B) {
|
||||||
|
largeLen := 100000
|
||||||
|
set := Set{}
|
||||||
|
for i := 0; i < largeLen; i++ {
|
||||||
|
var idBytes [32]byte
|
||||||
|
rand.Read(idBytes[:])
|
||||||
|
NewID(idBytes)
|
||||||
|
set.Add(NewID(idBytes))
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
set.List()
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,3 +55,46 @@ func TestSet(t *testing.T) {
|
||||||
t.Fatalf("Sets overlap")
|
t.Fatalf("Sets overlap")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetCappedList(t *testing.T) {
|
||||||
|
set := Set{}
|
||||||
|
|
||||||
|
id := Empty
|
||||||
|
|
||||||
|
if list := set.CappedList(0); len(list) != 0 {
|
||||||
|
t.Fatalf("List should have been empty but was %v", list)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(id)
|
||||||
|
|
||||||
|
if list := set.CappedList(0); len(list) != 0 {
|
||||||
|
t.Fatalf("List should have been empty but was %v", list)
|
||||||
|
} else if list := set.CappedList(1); len(list) != 1 {
|
||||||
|
t.Fatalf("List should have had length %d but had %d", 1, len(list))
|
||||||
|
} else if returnedID := list[0]; !id.Equals(returnedID) {
|
||||||
|
t.Fatalf("List should have been %s but was %s", id, returnedID)
|
||||||
|
} else if list := set.CappedList(2); len(list) != 1 {
|
||||||
|
t.Fatalf("List should have had length %d but had %d", 1, len(list))
|
||||||
|
} else if returnedID := list[0]; !id.Equals(returnedID) {
|
||||||
|
t.Fatalf("List should have been %s but was %s", id, returnedID)
|
||||||
|
}
|
||||||
|
|
||||||
|
id2 := NewID([32]byte{1})
|
||||||
|
set.Add(id2)
|
||||||
|
|
||||||
|
if list := set.CappedList(0); len(list) != 0 {
|
||||||
|
t.Fatalf("List should have been empty but was %v", list)
|
||||||
|
} else if list := set.CappedList(1); len(list) != 1 {
|
||||||
|
t.Fatalf("List should have had length %d but had %d", 1, len(list))
|
||||||
|
} else if returnedID := list[0]; !id.Equals(returnedID) && !id2.Equals(returnedID) {
|
||||||
|
t.Fatalf("List should have been %s but was %s", id, returnedID)
|
||||||
|
} else if list := set.CappedList(2); len(list) != 2 {
|
||||||
|
t.Fatalf("List should have had length %d but had %d", 2, len(list))
|
||||||
|
} else if list := set.CappedList(3); len(list) != 2 {
|
||||||
|
t.Fatalf("List should have had length %d but had %d", 2, len(list))
|
||||||
|
} else if returnedID := list[0]; !id.Equals(returnedID) && !id2.Equals(returnedID) {
|
||||||
|
t.Fatalf("list contains unexpected element %s", returnedID)
|
||||||
|
} else if returnedID := list[1]; !id.Equals(returnedID) && !id2.Equals(returnedID) {
|
||||||
|
t.Fatalf("list contains unexpected element %s", returnedID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,11 +5,18 @@ package ids
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
|
const (
|
||||||
|
minShortSetSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
// ShortSet is a set of ShortIDs
|
// ShortSet is a set of ShortIDs
|
||||||
type ShortSet map[[20]byte]bool
|
type ShortSet map[[20]byte]bool
|
||||||
|
|
||||||
func (ids *ShortSet) init(size int) {
|
func (ids *ShortSet) init(size int) {
|
||||||
if *ids == nil {
|
if *ids == nil {
|
||||||
|
if minShortSetSize > size {
|
||||||
|
size = minShortSetSize
|
||||||
|
}
|
||||||
*ids = make(map[[20]byte]bool, size)
|
*ids = make(map[[20]byte]bool, size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,24 +57,34 @@ func (ids *ShortSet) Remove(idList ...ShortID) {
|
||||||
// Clear empties this set
|
// Clear empties this set
|
||||||
func (ids *ShortSet) Clear() { *ids = nil }
|
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 {
|
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 {
|
for id := range ids {
|
||||||
if size <= 0 {
|
if i >= size {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
size--
|
idList[i] = NewShortID(id)
|
||||||
idList = append(idList, NewShortID(id))
|
i++
|
||||||
}
|
}
|
||||||
return idList
|
return idList
|
||||||
}
|
}
|
||||||
|
|
||||||
// List converts this set into a list
|
// List converts this set into a list
|
||||||
func (ids ShortSet) List() []ShortID {
|
func (ids ShortSet) List() []ShortID {
|
||||||
idList := make([]ShortID, len(ids))[:0]
|
idList := make([]ShortID, len(ids), len(ids))
|
||||||
|
i := 0
|
||||||
for id := range ids {
|
for id := range ids {
|
||||||
idList = append(idList, NewShortID(id))
|
idList[i] = NewShortID(id)
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
return idList
|
return idList
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minUniqueBagSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
// UniqueBag ...
|
// UniqueBag ...
|
||||||
type UniqueBag map[[32]byte]BitSet
|
type UniqueBag map[[32]byte]BitSet
|
||||||
|
|
||||||
func (b *UniqueBag) init() {
|
func (b *UniqueBag) init() {
|
||||||
if *b == nil {
|
if *b == nil {
|
||||||
*b = make(map[[32]byte]BitSet)
|
*b = make(map[[32]byte]BitSet, minUniqueBagSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
main/main.go
23
main/main.go
|
@ -40,12 +40,11 @@ func main() {
|
||||||
defer log.StopOnPanic()
|
defer log.StopOnPanic()
|
||||||
defer Config.DB.Close()
|
defer Config.DB.Close()
|
||||||
|
|
||||||
if Config.StakingIP.IsZero() {
|
|
||||||
log.Warn("NAT traversal has failed. It will be able to connect to less nodes.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track if sybil control is enforced
|
// Track if sybil control is enforced
|
||||||
if !Config.EnableStaking {
|
if !Config.EnableStaking && Config.EnableP2PTLS {
|
||||||
|
log.Warn("Staking is disabled. Sybil control is not enforced.")
|
||||||
|
}
|
||||||
|
if !Config.EnableStaking && !Config.EnableP2PTLS {
|
||||||
log.Warn("Staking and p2p encryption are disabled. Packet spoofing is possible.")
|
log.Warn("Staking and p2p encryption are disabled. Packet spoofing is possible.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,11 +64,19 @@ func main() {
|
||||||
log.Debug("assertions are enabled. This may slow down execution")
|
log.Debug("assertions are enabled. This may slow down execution")
|
||||||
}
|
}
|
||||||
|
|
||||||
mapper := nat.NewDefaultMapper(log, Config.Nat, nat.TCP, "gecko")
|
mapper := nat.NewPortMapper(log, Config.Nat)
|
||||||
defer mapper.UnmapAllPorts()
|
defer mapper.UnmapAllPorts()
|
||||||
|
|
||||||
mapper.MapPort(Config.StakingIP.Port, Config.StakingIP.Port)
|
port, err := mapper.Map("TCP", Config.StakingLocalPort, "gecko-staking") // Open staking port
|
||||||
mapper.MapPort(Config.HTTPPort, Config.HTTPPort)
|
if err == nil {
|
||||||
|
Config.StakingIP.Port = port
|
||||||
|
} else {
|
||||||
|
log.Warn("NAT traversal has failed. The node will be able to connect to less nodes.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Config.HTTPHost != "127.0.0.1" && Config.HTTPHost != "localhost" { // Open HTTP port iff HTTP server not listening on localhost
|
||||||
|
_, _ = mapper.Map("TCP", Config.HTTPPort, "gecko-http")
|
||||||
|
}
|
||||||
|
|
||||||
node := node.Node{}
|
node := node.Node{}
|
||||||
|
|
||||||
|
|
|
@ -37,19 +37,23 @@ const (
|
||||||
var (
|
var (
|
||||||
Config = node.Config{}
|
Config = node.Config{}
|
||||||
Err error
|
Err error
|
||||||
defaultDbDir = os.ExpandEnv(filepath.Join("$HOME", ".gecko", "db"))
|
defaultNetworkName = genesis.TestnetName
|
||||||
defaultStakingKeyPath = os.ExpandEnv(filepath.Join("$HOME", ".gecko", "staking", "staker.key"))
|
|
||||||
defaultStakingCertPath = os.ExpandEnv(filepath.Join("$HOME", ".gecko", "staking", "staker.crt"))
|
|
||||||
|
|
||||||
|
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{
|
defaultPluginDirs = []string{
|
||||||
"./build/plugins",
|
filepath.Join(".", "build", "plugins"),
|
||||||
"./plugins",
|
filepath.Join(".", "plugins"),
|
||||||
os.ExpandEnv(filepath.Join("$HOME", ".gecko", "plugins")),
|
filepath.Join("/", "usr", "local", "lib", "gecko"),
|
||||||
|
filepath.Join(homeDir, ".gecko", "plugins"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errBootstrapMismatch = errors.New("more bootstrap IDs provided than bootstrap IPs")
|
errBootstrapMismatch = errors.New("more bootstrap IDs provided than bootstrap IPs")
|
||||||
|
errStakingRequiresTLS = errors.New("if staking is enabled, network TLS must also be enabled")
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetIPs returns the default IPs for each network
|
// GetIPs returns the default IPs for each network
|
||||||
|
@ -169,7 +173,7 @@ func init() {
|
||||||
version := fs.Bool("version", false, "If true, print version and quit")
|
version := fs.Bool("version", false, "If true, print version and quit")
|
||||||
|
|
||||||
// NetworkID:
|
// NetworkID:
|
||||||
networkName := fs.String("network-id", genesis.TestnetName, "Network ID this node will connect to")
|
networkName := fs.String("network-id", defaultNetworkName, "Network ID this node will connect to")
|
||||||
|
|
||||||
// Ava fees:
|
// Ava fees:
|
||||||
fs.Uint64Var(&Config.AvaTxFee, "ava-tx-fee", 0, "Ava transaction fee, in $nAva")
|
fs.Uint64Var(&Config.AvaTxFee, "ava-tx-fee", 0, "Ava transaction fee, in $nAva")
|
||||||
|
@ -188,7 +192,7 @@ func init() {
|
||||||
consensusIP := fs.String("public-ip", "", "Public IP of this node")
|
consensusIP := fs.String("public-ip", "", "Public IP of this node")
|
||||||
|
|
||||||
// HTTP Server:
|
// 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")
|
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.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")
|
fs.StringVar(&Config.HTTPSKeyFile, "http-tls-key-file", "", "TLS private key file for the HTTPs server")
|
||||||
|
@ -200,7 +204,9 @@ func init() {
|
||||||
|
|
||||||
// Staking:
|
// Staking:
|
||||||
consensusPort := fs.Uint("staking-port", 9651, "Port of the consensus server")
|
consensusPort := fs.Uint("staking-port", 9651, "Port of the consensus server")
|
||||||
fs.BoolVar(&Config.EnableStaking, "staking-tls-enabled", true, "Require TLS to authenticate staking connections")
|
// TODO - keeping same flag for backwards compatibility, should be changed to "staking-enabled"
|
||||||
|
fs.BoolVar(&Config.EnableStaking, "staking-tls-enabled", true, "Enable staking. If enabled, Network TLS is required.")
|
||||||
|
fs.BoolVar(&Config.EnableP2PTLS, "p2p-tls-enabled", true, "Require TLS to authenticate network communication")
|
||||||
fs.StringVar(&Config.StakingKeyFile, "staking-tls-key-file", defaultStakingKeyPath, "TLS private key for staking")
|
fs.StringVar(&Config.StakingKeyFile, "staking-tls-key-file", defaultStakingKeyPath, "TLS private key for staking")
|
||||||
fs.StringVar(&Config.StakingCertFile, "staking-tls-cert-file", defaultStakingCertPath, "TLS certificate for staking")
|
fs.StringVar(&Config.StakingCertFile, "staking-tls-cert-file", defaultStakingCertPath, "TLS certificate for staking")
|
||||||
|
|
||||||
|
@ -221,7 +227,8 @@ func init() {
|
||||||
fs.IntVar(&Config.ConsensusParams.ConcurrentRepolls, "snow-concurrent-repolls", 1, "Minimum number of concurrent polls for finalizing consensus")
|
fs.IntVar(&Config.ConsensusParams.ConcurrentRepolls, "snow-concurrent-repolls", 1, "Minimum number of concurrent polls for finalizing consensus")
|
||||||
|
|
||||||
// Enable/Disable APIs:
|
// 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.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")
|
fs.BoolVar(&Config.MetricsAPIEnabled, "api-metrics-enabled", true, "If true, this node exposes the Metrics API")
|
||||||
fs.BoolVar(&Config.HealthAPIEnabled, "api-health-enabled", true, "If true, this node exposes the Health API")
|
fs.BoolVar(&Config.HealthAPIEnabled, "api-health-enabled", true, "If true, this node exposes the Health API")
|
||||||
|
@ -234,7 +241,15 @@ func init() {
|
||||||
ferr := fs.Parse(os.Args[1:])
|
ferr := fs.Parse(os.Args[1:])
|
||||||
|
|
||||||
if *version { // If --version used, print version and exit
|
if *version { // If --version used, print version and exit
|
||||||
fmt.Println(node.Version.String())
|
networkID, err := genesis.NetworkID(defaultNetworkName)
|
||||||
|
if errs.Add(err); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
networkGeneration := genesis.NetworkName(networkID)
|
||||||
|
fmt.Printf(
|
||||||
|
"%s [database=%s, network=%s/%s]\n",
|
||||||
|
node.Version, dbVersion, defaultNetworkName, networkGeneration,
|
||||||
|
)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,16 +284,16 @@ func init() {
|
||||||
Config.DB = memdb.New()
|
Config.DB = memdb.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.Nat = nat.NewRouter()
|
|
||||||
|
|
||||||
var ip net.IP
|
var ip net.IP
|
||||||
// If public IP is not specified, get it using shell command dig
|
// If public IP is not specified, get it using shell command dig
|
||||||
if *consensusIP == "" {
|
if *consensusIP == "" {
|
||||||
ip, err = Config.Nat.IP()
|
Config.Nat = nat.GetRouter()
|
||||||
|
ip, err = Config.Nat.ExternalIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ip = net.IPv4zero // Couldn't get my IP...set to 0.0.0.0
|
ip = net.IPv4zero // Couldn't get my IP...set to 0.0.0.0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Config.Nat = nat.NewNoRouter()
|
||||||
ip = net.ParseIP(*consensusIP)
|
ip = net.ParseIP(*consensusIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,6 +306,7 @@ func init() {
|
||||||
IP: ip,
|
IP: ip,
|
||||||
Port: uint16(*consensusPort),
|
Port: uint16(*consensusPort),
|
||||||
}
|
}
|
||||||
|
Config.StakingLocalPort = uint16(*consensusPort)
|
||||||
|
|
||||||
defaultBootstrapIPs, defaultBootstrapIDs := GetDefaultBootstraps(networkID, 5)
|
defaultBootstrapIPs, defaultBootstrapIDs := GetDefaultBootstraps(networkID, 5)
|
||||||
|
|
||||||
|
@ -318,7 +334,13 @@ func init() {
|
||||||
*bootstrapIDs = strings.Join(defaultBootstrapIDs, ",")
|
*bootstrapIDs = strings.Join(defaultBootstrapIDs, ",")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if Config.EnableStaking {
|
|
||||||
|
if Config.EnableStaking && !Config.EnableP2PTLS {
|
||||||
|
errs.Add(errStakingRequiresTLS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if Config.EnableP2PTLS {
|
||||||
i := 0
|
i := 0
|
||||||
cb58 := formatting.CB58{}
|
cb58 := formatting.CB58{}
|
||||||
for _, id := range strings.Split(*bootstrapIDs, ",") {
|
for _, id := range strings.Split(*bootstrapIDs, ",") {
|
||||||
|
|
143
nat/mapper.go
143
nat/mapper.go
|
@ -1,143 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package nat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
"github.com/ava-labs/gecko/utils/wrappers"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultMappingTimeout = 30 * time.Minute
|
|
||||||
defaultMappingUpdateInterval = 3 * defaultMappingTimeout / 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mapper maps port
|
|
||||||
type Mapper interface {
|
|
||||||
MapPort(newInternalPort, newExternalPort uint16) error
|
|
||||||
UnmapAllPorts() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type mapper struct {
|
|
||||||
log logging.Logger
|
|
||||||
router Router
|
|
||||||
networkProtocol NetworkProtocol
|
|
||||||
mappingNames string
|
|
||||||
mappingTimeout time.Duration
|
|
||||||
mappingUpdateInterval time.Duration
|
|
||||||
|
|
||||||
closer chan struct{}
|
|
||||||
wg sync.WaitGroup
|
|
||||||
errLock sync.Mutex
|
|
||||||
errs wrappers.Errs
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMapper returns a new mapper that can map ports on a router
|
|
||||||
func NewMapper(
|
|
||||||
log logging.Logger,
|
|
||||||
router Router,
|
|
||||||
networkProtocol NetworkProtocol,
|
|
||||||
mappingNames string,
|
|
||||||
mappingTimeout time.Duration,
|
|
||||||
mappingUpdateInterval time.Duration,
|
|
||||||
) Mapper {
|
|
||||||
return &mapper{
|
|
||||||
log: log,
|
|
||||||
router: router,
|
|
||||||
networkProtocol: networkProtocol,
|
|
||||||
mappingNames: mappingNames,
|
|
||||||
mappingTimeout: mappingTimeout,
|
|
||||||
mappingUpdateInterval: mappingUpdateInterval,
|
|
||||||
closer: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultMapper returns a new mapper that can map ports on a router with
|
|
||||||
// default settings
|
|
||||||
func NewDefaultMapper(
|
|
||||||
log logging.Logger,
|
|
||||||
router Router,
|
|
||||||
networkProtocol NetworkProtocol,
|
|
||||||
mappingNames string,
|
|
||||||
) Mapper {
|
|
||||||
return NewMapper(
|
|
||||||
log,
|
|
||||||
router,
|
|
||||||
networkProtocol,
|
|
||||||
mappingNames,
|
|
||||||
defaultMappingTimeout, // uses the default value
|
|
||||||
defaultMappingUpdateInterval, // uses the default value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapPort maps a local port to a port on the router until UnmapAllPorts is
|
|
||||||
// called.
|
|
||||||
func (m *mapper) MapPort(newInternalPort, newExternalPort uint16) error {
|
|
||||||
m.wg.Add(1)
|
|
||||||
go m.mapPort(newInternalPort, newExternalPort)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mapper) mapPort(newInternalPort, newExternalPort uint16) {
|
|
||||||
// duration is set to 0 here so that the select case will execute
|
|
||||||
// immediately
|
|
||||||
updateTimer := time.NewTimer(0)
|
|
||||||
defer func() {
|
|
||||||
updateTimer.Stop()
|
|
||||||
|
|
||||||
m.errLock.Lock()
|
|
||||||
m.errs.Add(m.router.UnmapPort(
|
|
||||||
m.networkProtocol,
|
|
||||||
newInternalPort,
|
|
||||||
newExternalPort))
|
|
||||||
m.errLock.Unlock()
|
|
||||||
|
|
||||||
m.log.Debug("Unmapped external port %d to internal port %d",
|
|
||||||
newExternalPort,
|
|
||||||
newInternalPort)
|
|
||||||
|
|
||||||
m.wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-updateTimer.C:
|
|
||||||
err := m.router.MapPort(
|
|
||||||
m.networkProtocol,
|
|
||||||
newInternalPort,
|
|
||||||
newExternalPort,
|
|
||||||
m.mappingNames,
|
|
||||||
m.mappingTimeout)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
m.errLock.Lock()
|
|
||||||
m.errs.Add(err)
|
|
||||||
m.errLock.Unlock()
|
|
||||||
|
|
||||||
m.log.Debug("Failed to add mapping from external port %d to internal port %d due to %s",
|
|
||||||
newExternalPort,
|
|
||||||
newInternalPort,
|
|
||||||
err)
|
|
||||||
} else {
|
|
||||||
m.log.Debug("Mapped external port %d to internal port %d",
|
|
||||||
newExternalPort,
|
|
||||||
newInternalPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remap the port in m.mappingUpdateInterval
|
|
||||||
updateTimer.Reset(m.mappingUpdateInterval)
|
|
||||||
case _, _ = <-m.closer:
|
|
||||||
return // only return when all ports are unmapped
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mapper) UnmapAllPorts() error {
|
|
||||||
close(m.closer)
|
|
||||||
m.wg.Wait()
|
|
||||||
return m.errs.Err
|
|
||||||
}
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package nat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mapTimeout = 30 * time.Second
|
||||||
|
mapUpdateTimeout = mapTimeout / 2
|
||||||
|
maxRetries = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
// Router describes the functionality that a network device must support to be
|
||||||
|
// able to open ports to an external IP.
|
||||||
|
type Router interface {
|
||||||
|
MapPort(protocol string, intPort, extPort uint16, desc string, duration time.Duration) error
|
||||||
|
UnmapPort(protocol string, intPort, extPort uint16) error
|
||||||
|
ExternalIP() (net.IP, error)
|
||||||
|
GetPortMappingEntry(extPort uint16, protocol string) (
|
||||||
|
InternalIP string,
|
||||||
|
InternalPort uint16,
|
||||||
|
Description string,
|
||||||
|
err error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRouter returns a router on the current network.
|
||||||
|
func GetRouter() Router {
|
||||||
|
if r := getUPnPRouter(); r != nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
if r := getPMPRouter(); r != nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewNoRouter()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapper attempts to open a set of ports on a router
|
||||||
|
type Mapper struct {
|
||||||
|
log logging.Logger
|
||||||
|
r Router
|
||||||
|
closer chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPortMapper returns an initialized mapper
|
||||||
|
func NewPortMapper(log logging.Logger, r Router) Mapper {
|
||||||
|
return Mapper{
|
||||||
|
log: log,
|
||||||
|
r: r,
|
||||||
|
closer: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map sets up port mapping using given protocol, internal and external ports
|
||||||
|
// and returns the final port mapped. It returns 0 if mapping failed after the
|
||||||
|
// maximun number of retries
|
||||||
|
func (dev *Mapper) Map(protocol string, intPort uint16, desc string) (uint16, error) {
|
||||||
|
mappedPort := make(chan uint16)
|
||||||
|
|
||||||
|
go dev.keepPortMapping(mappedPort, protocol, intPort, desc)
|
||||||
|
|
||||||
|
port := <-mappedPort
|
||||||
|
if port == 0 {
|
||||||
|
return 0, errors.New("failed to map port")
|
||||||
|
}
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// keepPortMapping runs in the background to keep a port mapped. It renews the
|
||||||
|
// the port mapping in mapUpdateTimeout.
|
||||||
|
func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string,
|
||||||
|
intPort uint16, desc string) {
|
||||||
|
updateTimer := time.NewTimer(mapUpdateTimeout)
|
||||||
|
|
||||||
|
for i := 0; i <= maxRetries; i++ {
|
||||||
|
extPort := intPort + uint16(i)
|
||||||
|
if intaddr, intPort, desc, err := dev.r.GetPortMappingEntry(extPort, protocol); err == nil {
|
||||||
|
dev.log.Debug("Port %d is taken by %s:%d: %s, retry with the next port",
|
||||||
|
extPort, intaddr, intPort, desc)
|
||||||
|
} else if err := dev.r.MapPort(protocol, intPort, extPort, desc, mapTimeout); err != nil {
|
||||||
|
dev.log.Debug("Map port failed. Protocol %s Internal %d External %d. %s",
|
||||||
|
protocol, intPort, extPort, err)
|
||||||
|
} else {
|
||||||
|
dev.log.Info("Mapped Protocol %s Internal %d External %d.", protocol,
|
||||||
|
intPort, extPort)
|
||||||
|
|
||||||
|
dev.wg.Add(1)
|
||||||
|
|
||||||
|
mappedPort <- extPort
|
||||||
|
|
||||||
|
defer func(extPort uint16) {
|
||||||
|
updateTimer.Stop()
|
||||||
|
|
||||||
|
dev.log.Debug("Unmap protocol %s external port %d", protocol, extPort)
|
||||||
|
dev.r.UnmapPort(protocol, intPort, extPort)
|
||||||
|
|
||||||
|
dev.wg.Done()
|
||||||
|
}(extPort)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-updateTimer.C:
|
||||||
|
if err := dev.r.MapPort(protocol, intPort, extPort, desc, mapTimeout); err != nil {
|
||||||
|
dev.log.Error("Renewing port mapping from external port %d to internal port %d failed with %s",
|
||||||
|
intPort, extPort, err)
|
||||||
|
} else {
|
||||||
|
dev.log.Debug("Renewed port mapping from external port %d to internal port %d.",
|
||||||
|
intPort, extPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimer.Reset(mapUpdateTimeout)
|
||||||
|
case _, _ = <-dev.closer:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.log.Debug("Unable to map port %d~%d", intPort, intPort+maxRetries)
|
||||||
|
mappedPort <- 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmapAllPorts stops mapping all ports from this mapper and attempts to unmap
|
||||||
|
// them.
|
||||||
|
func (dev *Mapper) UnmapAllPorts() {
|
||||||
|
close(dev.closer)
|
||||||
|
dev.wg.Wait()
|
||||||
|
dev.log.Info("Unmapped all ports")
|
||||||
|
}
|
|
@ -4,25 +4,57 @@
|
||||||
package nat
|
package nat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const googleDNSServer = "8.8.8.8:80"
|
||||||
errNoRouter = errors.New("no nat enabled router was discovered")
|
|
||||||
)
|
|
||||||
|
|
||||||
type noRouter struct{}
|
type noRouter struct {
|
||||||
|
ip net.IP
|
||||||
func (noRouter) MapPort(_ NetworkProtocol, _, _ uint16, _ string, _ time.Duration) error {
|
|
||||||
return errNoRouter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (noRouter) UnmapPort(_ NetworkProtocol, _, _ uint16) error {
|
func (noRouter) MapPort(_ string, intPort, extPort uint16, _ string, _ time.Duration) error {
|
||||||
return errNoRouter
|
if intPort != extPort {
|
||||||
|
return fmt.Errorf("cannot map port %d to %d", intPort, extPort)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (noRouter) IP() (net.IP, error) {
|
func (noRouter) UnmapPort(string, uint16, uint16) error {
|
||||||
return nil, errNoRouter
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r noRouter) ExternalIP() (net.IP, error) {
|
||||||
|
return r.ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noRouter) GetPortMappingEntry(uint16, string) (string, uint16, string, error) {
|
||||||
|
return "", 0, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOutboundIP() (net.IP, error) {
|
||||||
|
conn, err := net.Dial("udp", googleDNSServer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if udpAddr, ok := conn.LocalAddr().(*net.UDPAddr); ok {
|
||||||
|
return udpAddr.IP, conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("getting outbound IP failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNoRouter returns a router that assumes the network is public
|
||||||
|
func NewNoRouter() Router {
|
||||||
|
ip, err := getOutboundIP()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &noRouter{
|
||||||
|
ip: ip,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
28
nat/pmp.go
28
nat/pmp.go
|
@ -4,11 +4,13 @@
|
||||||
package nat
|
package nat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jackpal/gateway"
|
"github.com/jackpal/gateway"
|
||||||
"github.com/jackpal/go-nat-pmp"
|
|
||||||
|
natpmp "github.com/jackpal/go-nat-pmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -17,12 +19,12 @@ var (
|
||||||
|
|
||||||
// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
|
// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
|
||||||
// the common interface.
|
// the common interface.
|
||||||
type pmpClient struct {
|
type pmpRouter struct {
|
||||||
client *natpmp.Client
|
client *natpmp.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pmp *pmpClient) MapPort(
|
func (pmp *pmpRouter) MapPort(
|
||||||
networkProtocol NetworkProtocol,
|
networkProtocol string,
|
||||||
newInternalPort uint16,
|
newInternalPort uint16,
|
||||||
newExternalPort uint16,
|
newExternalPort uint16,
|
||||||
mappingName string,
|
mappingName string,
|
||||||
|
@ -37,8 +39,8 @@ func (pmp *pmpClient) MapPort(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pmp *pmpClient) UnmapPort(
|
func (pmp *pmpRouter) UnmapPort(
|
||||||
networkProtocol NetworkProtocol,
|
networkProtocol string,
|
||||||
internalPort uint16,
|
internalPort uint16,
|
||||||
_ uint16) error {
|
_ uint16) error {
|
||||||
protocol := string(networkProtocol)
|
protocol := string(networkProtocol)
|
||||||
|
@ -48,7 +50,7 @@ func (pmp *pmpClient) UnmapPort(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pmp *pmpClient) IP() (net.IP, error) {
|
func (pmp *pmpRouter) ExternalIP() (net.IP, error) {
|
||||||
response, err := pmp.client.GetExternalAddress()
|
response, err := pmp.client.GetExternalAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -56,14 +58,20 @@ func (pmp *pmpClient) IP() (net.IP, error) {
|
||||||
return response.ExternalIPAddress[:], nil
|
return response.ExternalIPAddress[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPMPRouter() Router {
|
// go-nat-pmp does not support port mapping entry query
|
||||||
|
func (pmp *pmpRouter) GetPortMappingEntry(externalPort uint16, protocol string) (
|
||||||
|
string, uint16, string, error) {
|
||||||
|
return "", 0, "", fmt.Errorf("port mapping entry not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPMPRouter() *pmpRouter {
|
||||||
gatewayIP, err := gateway.DiscoverGateway()
|
gatewayIP, err := gateway.DiscoverGateway()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pmp := &pmpClient{natpmp.NewClientWithTimeout(gatewayIP, pmpClientTimeout)}
|
pmp := &pmpRouter{natpmp.NewClientWithTimeout(gatewayIP, pmpClientTimeout)}
|
||||||
if _, err := pmp.IP(); err != nil {
|
if _, err := pmp.ExternalIP(); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
// Package nat performs network address translation and provides helpers for
|
|
||||||
// routing ports.
|
|
||||||
package nat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NetworkProtocol is a protocol that will be used through a port
|
|
||||||
type NetworkProtocol string
|
|
||||||
|
|
||||||
// Available protocol
|
|
||||||
const (
|
|
||||||
TCP NetworkProtocol = "TCP"
|
|
||||||
UDP NetworkProtocol = "UDP"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Router provides a standard NAT router functions. Specifically, allowing the
|
|
||||||
// fetching of public IPs and port forwarding to this computer.
|
|
||||||
type Router interface {
|
|
||||||
// mapPort creates a mapping between a port on the local computer to an
|
|
||||||
// external port on the router.
|
|
||||||
//
|
|
||||||
// The mappingName is something displayed on the router, so it is included
|
|
||||||
// for completeness.
|
|
||||||
MapPort(
|
|
||||||
networkProtocol NetworkProtocol,
|
|
||||||
newInternalPort uint16,
|
|
||||||
newExternalPort uint16,
|
|
||||||
mappingName string,
|
|
||||||
mappingDuration time.Duration) error
|
|
||||||
|
|
||||||
// UnmapPort clears a mapping that was previous made by a call to MapPort
|
|
||||||
UnmapPort(
|
|
||||||
networkProtocol NetworkProtocol,
|
|
||||||
internalPort uint16,
|
|
||||||
externalPort uint16) error
|
|
||||||
|
|
||||||
// Returns the routers IP address on the network the router considers
|
|
||||||
// external
|
|
||||||
IP() (net.IP, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRouter returns a new router discovered on the local network
|
|
||||||
func NewRouter() Router {
|
|
||||||
routers := make(chan Router)
|
|
||||||
// Because getting a router can take a noticeable amount of time to error,
|
|
||||||
// we run these requests in parallel
|
|
||||||
go func() {
|
|
||||||
routers <- getUPnPRouter()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
routers <- getPMPRouter()
|
|
||||||
}()
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
if router := <-routers; router != nil {
|
|
||||||
return router
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noRouter{}
|
|
||||||
}
|
|
244
nat/upnp.go
244
nat/upnp.go
|
@ -4,7 +4,6 @@
|
||||||
package nat
|
package nat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,11 +14,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
soapTimeout = time.Second
|
soapRequestTimeout = 3 * time.Second
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errNoGateway = errors.New("Failed to connect to any avaliable gateways")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// upnpClient is the interface used by goupnp for their client implementations
|
// upnpClient is the interface used by goupnp for their client implementations
|
||||||
|
@ -47,69 +42,30 @@ type upnpClient interface {
|
||||||
|
|
||||||
// returns if there is rsip available, nat enabled, or an unexpected error.
|
// returns if there is rsip available, nat enabled, or an unexpected error.
|
||||||
GetNATRSIPStatus() (newRSIPAvailable bool, natEnabled bool, err error)
|
GetNATRSIPStatus() (newRSIPAvailable bool, natEnabled bool, err error)
|
||||||
}
|
|
||||||
|
|
||||||
type upnpRouter struct {
|
// attempts to get port mapping information give a external port and protocol
|
||||||
root *goupnp.RootDevice
|
GetSpecificPortMappingEntry(
|
||||||
client upnpClient
|
NewRemoteHost string,
|
||||||
}
|
NewExternalPort uint16,
|
||||||
|
NewProtocol string,
|
||||||
func (n *upnpRouter) MapPort(
|
) (
|
||||||
networkProtocol NetworkProtocol,
|
NewInternalPort uint16,
|
||||||
newInternalPort uint16,
|
NewInternalClient string,
|
||||||
newExternalPort uint16,
|
NewEnabled bool,
|
||||||
mappingName string,
|
NewPortMappingDescription string,
|
||||||
mappingDuration time.Duration,
|
NewLeaseDuration uint32,
|
||||||
) error {
|
err error,
|
||||||
ip, err := n.localAddress()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol := string(networkProtocol)
|
|
||||||
// goupnp uses seconds to denote their lifetime
|
|
||||||
lifetime := uint32(mappingDuration / time.Second)
|
|
||||||
|
|
||||||
// UnmapPort's error is intentionally dropped, because the mapping may not
|
|
||||||
// exist.
|
|
||||||
n.UnmapPort(networkProtocol, newInternalPort, newExternalPort)
|
|
||||||
|
|
||||||
return n.client.AddPortMapping(
|
|
||||||
"", // newRemoteHost isn't used to limit the mapping to a host
|
|
||||||
newExternalPort,
|
|
||||||
protocol,
|
|
||||||
newInternalPort,
|
|
||||||
ip.String(), // newInternalClient is the client traffic should be sent to
|
|
||||||
true, // newEnabled enables port mappings
|
|
||||||
mappingName,
|
|
||||||
lifetime,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *upnpRouter) UnmapPort(networkProtocol NetworkProtocol, _, externalPort uint16) error {
|
type upnpRouter struct {
|
||||||
protocol := string(networkProtocol)
|
dev *goupnp.RootDevice
|
||||||
return n.client.DeletePortMapping(
|
client upnpClient
|
||||||
"", // newRemoteHost isn't used to limit the mapping to a host
|
|
||||||
externalPort,
|
|
||||||
protocol)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *upnpRouter) IP() (net.IP, error) {
|
func (r *upnpRouter) localIP() (net.IP, error) {
|
||||||
ipStr, err := n.client.GetExternalIPAddress()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(ipStr)
|
|
||||||
if ip == nil {
|
|
||||||
return nil, fmt.Errorf("invalid IP %s", ipStr)
|
|
||||||
}
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *upnpRouter) localAddress() (net.IP, error) {
|
|
||||||
// attempt to get an address on the router
|
// attempt to get an address on the router
|
||||||
deviceAddr, err := net.ResolveUDPAddr("udp4", n.root.URLBase.Host)
|
deviceAddr, err := net.ResolveUDPAddr("udp", r.dev.URLBase.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -120,7 +76,7 @@ func (n *upnpRouter) localAddress() (net.IP, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt to find one of my ips that the router would know about
|
// attempt to find one of my IPs that matches router's record
|
||||||
for _, netInterface := range netInterfaces {
|
for _, netInterface := range netInterfaces {
|
||||||
addrs, err := netInterface.Addrs()
|
addrs, err := netInterface.Addrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -128,9 +84,6 @@ func (n *upnpRouter) localAddress() (net.IP, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
// this is pretty janky, but it seems to be the best way to get the
|
|
||||||
// ip mask and properly check if the ip references the device we are
|
|
||||||
// connected to
|
|
||||||
ipNet, ok := addr.(*net.IPNet)
|
ipNet, ok := addr.(*net.IPNet)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
|
@ -144,110 +97,119 @@ func (n *upnpRouter) localAddress() (net.IP, error) {
|
||||||
return nil, fmt.Errorf("couldn't find the local address in the same network as %s", deviceIP)
|
return nil, fmt.Errorf("couldn't find the local address in the same network as %s", deviceIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUPnPRouter searches for all Gateway Devices that have avaliable
|
func (r *upnpRouter) ExternalIP() (net.IP, error) {
|
||||||
// connections in the goupnp library and returns the first connection it can
|
str, err := r.client.GetExternalIPAddress()
|
||||||
// find.
|
if err != nil {
|
||||||
func getUPnPRouter() Router {
|
return nil, err
|
||||||
routers := make(chan *upnpRouter)
|
|
||||||
// Because DiscoverDevices takes a noticeable amount of time to error, we
|
|
||||||
// run these requests in parallel
|
|
||||||
go func() {
|
|
||||||
routers <- connectToGateway(internetgateway1.URN_WANConnectionDevice_1, gateway1)
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
routers <- connectToGateway(internetgateway2.URN_WANConnectionDevice_2, gateway2)
|
|
||||||
}()
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
if router := <-routers; router != nil {
|
|
||||||
return router
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(str)
|
||||||
|
if ip == nil {
|
||||||
|
return nil, fmt.Errorf("invalid IP %s", str)
|
||||||
}
|
}
|
||||||
return nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gateway1(client goupnp.ServiceClient) upnpClient {
|
func (r *upnpRouter) MapPort(protocol string, intPort, extPort uint16,
|
||||||
|
desc string, duration time.Duration) error {
|
||||||
|
ip, err := r.localIP()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lifetime := uint32(duration / time.Second)
|
||||||
|
|
||||||
|
return r.client.AddPortMapping("", extPort, protocol, intPort,
|
||||||
|
ip.String(), true, desc, lifetime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *upnpRouter) UnmapPort(protocol string, _, extPort uint16) error {
|
||||||
|
return r.client.DeletePortMapping("", extPort, protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *upnpRouter) GetPortMappingEntry(extPort uint16, protocol string) (string, uint16, string, error) {
|
||||||
|
intPort, intAddr, _, desc, _, err := r.client.GetSpecificPortMappingEntry("", extPort, protocol)
|
||||||
|
return intAddr, intPort, desc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create UPnP SOAP service client with URN
|
||||||
|
func getUPnPClient(client goupnp.ServiceClient) upnpClient {
|
||||||
switch client.Service.ServiceType {
|
switch client.Service.ServiceType {
|
||||||
case internetgateway1.URN_WANIPConnection_1:
|
case internetgateway1.URN_WANIPConnection_1:
|
||||||
return &internetgateway1.WANIPConnection1{ServiceClient: client}
|
return &internetgateway1.WANIPConnection1{ServiceClient: client}
|
||||||
case internetgateway1.URN_WANPPPConnection_1:
|
case internetgateway1.URN_WANPPPConnection_1:
|
||||||
return &internetgateway1.WANPPPConnection1{ServiceClient: client}
|
return &internetgateway1.WANPPPConnection1{ServiceClient: client}
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func gateway2(client goupnp.ServiceClient) upnpClient {
|
|
||||||
switch client.Service.ServiceType {
|
|
||||||
case internetgateway2.URN_WANIPConnection_1:
|
|
||||||
return &internetgateway2.WANIPConnection1{ServiceClient: client}
|
|
||||||
case internetgateway2.URN_WANIPConnection_2:
|
case internetgateway2.URN_WANIPConnection_2:
|
||||||
return &internetgateway2.WANIPConnection2{ServiceClient: client}
|
return &internetgateway2.WANIPConnection2{ServiceClient: client}
|
||||||
case internetgateway2.URN_WANPPPConnection_1:
|
|
||||||
return &internetgateway2.WANPPPConnection1{ServiceClient: client}
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectToGateway(deviceType string, toClient func(goupnp.ServiceClient) upnpClient) *upnpRouter {
|
// discover() tries to find gateway device
|
||||||
devs, err := goupnp.DiscoverDevices(deviceType)
|
func discover(target string) *upnpRouter {
|
||||||
|
devs, err := goupnp.DiscoverDevices(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// we are iterating over all the network devices, acting a possible roots
|
|
||||||
for i := range devs {
|
router := make(chan *upnpRouter)
|
||||||
dev := &devs[i]
|
for i := 0; i < len(devs); i++ {
|
||||||
if dev.Root == nil {
|
if devs[i].Root == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
go func(dev *goupnp.MaybeRootDevice) {
|
||||||
// the root device may be a router, so attempt to connect to that
|
var r *upnpRouter = nil
|
||||||
rootDevice := &dev.Root.Device
|
dev.Root.Device.VisitServices(func(service *goupnp.Service) {
|
||||||
if upnp := getRouter(dev, rootDevice, toClient); upnp != nil {
|
c := goupnp.ServiceClient{
|
||||||
return upnp
|
SOAPClient: service.NewSOAPClient(),
|
||||||
}
|
RootDevice: dev.Root,
|
||||||
|
Location: dev.Location,
|
||||||
// attempt to connect to any sub devices
|
|
||||||
devices := rootDevice.Devices
|
|
||||||
for i := range devices {
|
|
||||||
if upnp := getRouter(dev, &devices[i], toClient); upnp != nil {
|
|
||||||
return upnp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRouter(rootDevice *goupnp.MaybeRootDevice, device *goupnp.Device, toClient func(goupnp.ServiceClient) upnpClient) *upnpRouter {
|
|
||||||
for i := range device.Services {
|
|
||||||
service := &device.Services[i]
|
|
||||||
|
|
||||||
soapClient := service.NewSOAPClient()
|
|
||||||
// make sure the client times out if needed
|
|
||||||
soapClient.HTTPClient.Timeout = soapTimeout
|
|
||||||
|
|
||||||
// attempt to create a client connection
|
|
||||||
serviceClient := goupnp.ServiceClient{
|
|
||||||
SOAPClient: soapClient,
|
|
||||||
RootDevice: rootDevice.Root,
|
|
||||||
Location: rootDevice.Location,
|
|
||||||
Service: service,
|
Service: service,
|
||||||
}
|
}
|
||||||
client := toClient(serviceClient)
|
c.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
|
||||||
|
client := getUPnPClient(c)
|
||||||
if client == nil {
|
if client == nil {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether port mapping is enabled
|
|
||||||
if _, nat, err := client.GetNATRSIPStatus(); err != nil || !nat {
|
if _, nat, err := client.GetNATRSIPStatus(); err != nil || !nat {
|
||||||
continue
|
return
|
||||||
|
}
|
||||||
|
r = &upnpRouter{dev.Root, client}
|
||||||
|
})
|
||||||
|
router <- r
|
||||||
|
}(&devs[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
// we found a router!
|
for i := 0; i < len(devs); i++ {
|
||||||
return &upnpRouter{
|
if r := <-router; r != nil {
|
||||||
root: rootDevice.Root,
|
return r
|
||||||
client: client,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUPnPRouter searches for internet gateway using both Device Control Protocol
|
||||||
|
// and returns the first one it can find. It returns nil if no UPnP gateway is found
|
||||||
|
func getUPnPRouter() *upnpRouter {
|
||||||
|
targets := []string{
|
||||||
|
internetgateway1.URN_WANConnectionDevice_1,
|
||||||
|
internetgateway2.URN_WANConnectionDevice_2,
|
||||||
|
}
|
||||||
|
|
||||||
|
routers := make(chan *upnpRouter)
|
||||||
|
|
||||||
|
for _, urn := range targets {
|
||||||
|
go func(urn string) {
|
||||||
|
routers <- discover(urn)
|
||||||
|
}(urn)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(targets); i++ {
|
||||||
|
if r := <-routers; r != nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,12 @@ func (m Builder) PeerList(ipDescs []utils.IPDesc) (Msg, error) {
|
||||||
return m.Pack(PeerList, map[Field]interface{}{Peers: ipDescs})
|
return m.Pack(PeerList, map[Field]interface{}{Peers: ipDescs})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ping message
|
||||||
|
func (m Builder) Ping() (Msg, error) { return m.Pack(Ping, nil) }
|
||||||
|
|
||||||
|
// Pong message
|
||||||
|
func (m Builder) Pong() (Msg, error) { return m.Pack(Pong, nil) }
|
||||||
|
|
||||||
// GetAcceptedFrontier message
|
// GetAcceptedFrontier message
|
||||||
func (m Builder) GetAcceptedFrontier(chainID ids.ID, requestID uint32) (Msg, error) {
|
func (m Builder) GetAcceptedFrontier(chainID ids.ID, requestID uint32) (Msg, error) {
|
||||||
return m.Pack(GetAcceptedFrontier, map[Field]interface{}{
|
return m.Pack(GetAcceptedFrontier, map[Field]interface{}{
|
||||||
|
|
|
@ -132,6 +132,10 @@ func (op Op) String() string {
|
||||||
return "get_peerlist"
|
return "get_peerlist"
|
||||||
case PeerList:
|
case PeerList:
|
||||||
return "peerlist"
|
return "peerlist"
|
||||||
|
case Ping:
|
||||||
|
return "ping"
|
||||||
|
case Pong:
|
||||||
|
return "pong"
|
||||||
case GetAcceptedFrontier:
|
case GetAcceptedFrontier:
|
||||||
return "get_accepted_frontier"
|
return "get_accepted_frontier"
|
||||||
case AcceptedFrontier:
|
case AcceptedFrontier:
|
||||||
|
@ -177,11 +181,12 @@ const (
|
||||||
PushQuery
|
PushQuery
|
||||||
PullQuery
|
PullQuery
|
||||||
Chits
|
Chits
|
||||||
// Bootstrapping:
|
|
||||||
// TODO: Move GetAncestors and MultiPut with the rest of the bootstrapping
|
// TODO: Reorder these messages when we transition to everest
|
||||||
// commands when we do non-backwards compatible upgrade
|
|
||||||
GetAncestors
|
GetAncestors
|
||||||
MultiPut
|
MultiPut
|
||||||
|
Ping
|
||||||
|
Pong
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines the messages that can be sent/received with this network
|
// Defines the messages that can be sent/received with this network
|
||||||
|
@ -192,6 +197,8 @@ var (
|
||||||
Version: {NetworkID, NodeID, MyTime, IP, VersionStr},
|
Version: {NetworkID, NodeID, MyTime, IP, VersionStr},
|
||||||
GetPeerList: {},
|
GetPeerList: {},
|
||||||
PeerList: {Peers},
|
PeerList: {Peers},
|
||||||
|
Ping: {},
|
||||||
|
Pong: {},
|
||||||
// Bootstrapping:
|
// Bootstrapping:
|
||||||
GetAcceptedFrontier: {ChainID, RequestID},
|
GetAcceptedFrontier: {ChainID, RequestID},
|
||||||
AcceptedFrontier: {ChainID, RequestID, ContainerIDs},
|
AcceptedFrontier: {ChainID, RequestID, ContainerIDs},
|
||||||
|
|
|
@ -54,6 +54,7 @@ type metrics struct {
|
||||||
|
|
||||||
getVersion, version,
|
getVersion, version,
|
||||||
getPeerlist, peerlist,
|
getPeerlist, peerlist,
|
||||||
|
ping, pong,
|
||||||
getAcceptedFrontier, acceptedFrontier,
|
getAcceptedFrontier, acceptedFrontier,
|
||||||
getAccepted, accepted,
|
getAccepted, accepted,
|
||||||
get, getAncestors, put, multiPut,
|
get, getAncestors, put, multiPut,
|
||||||
|
@ -78,6 +79,8 @@ func (m *metrics) initialize(registerer prometheus.Registerer) error {
|
||||||
errs.Add(m.version.initialize(Version, registerer))
|
errs.Add(m.version.initialize(Version, registerer))
|
||||||
errs.Add(m.getPeerlist.initialize(GetPeerList, registerer))
|
errs.Add(m.getPeerlist.initialize(GetPeerList, registerer))
|
||||||
errs.Add(m.peerlist.initialize(PeerList, registerer))
|
errs.Add(m.peerlist.initialize(PeerList, registerer))
|
||||||
|
errs.Add(m.ping.initialize(Ping, registerer))
|
||||||
|
errs.Add(m.pong.initialize(Pong, registerer))
|
||||||
errs.Add(m.getAcceptedFrontier.initialize(GetAcceptedFrontier, registerer))
|
errs.Add(m.getAcceptedFrontier.initialize(GetAcceptedFrontier, registerer))
|
||||||
errs.Add(m.acceptedFrontier.initialize(AcceptedFrontier, registerer))
|
errs.Add(m.acceptedFrontier.initialize(AcceptedFrontier, registerer))
|
||||||
errs.Add(m.getAccepted.initialize(GetAccepted, registerer))
|
errs.Add(m.getAccepted.initialize(GetAccepted, registerer))
|
||||||
|
@ -103,6 +106,10 @@ func (m *metrics) message(msgType Op) *messageMetrics {
|
||||||
return &m.getPeerlist
|
return &m.getPeerlist
|
||||||
case PeerList:
|
case PeerList:
|
||||||
return &m.peerlist
|
return &m.peerlist
|
||||||
|
case Ping:
|
||||||
|
return &m.ping
|
||||||
|
case Pong:
|
||||||
|
return &m.pong
|
||||||
case GetAcceptedFrontier:
|
case GetAcceptedFrontier:
|
||||||
return &m.getAcceptedFrontier
|
return &m.getAcceptedFrontier
|
||||||
case AcceptedFrontier:
|
case AcceptedFrontier:
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/ava-labs/gecko/snow/triggers"
|
"github.com/ava-labs/gecko/snow/triggers"
|
||||||
"github.com/ava-labs/gecko/snow/validators"
|
"github.com/ava-labs/gecko/snow/validators"
|
||||||
"github.com/ava-labs/gecko/utils"
|
"github.com/ava-labs/gecko/utils"
|
||||||
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
"github.com/ava-labs/gecko/utils/random"
|
"github.com/ava-labs/gecko/utils/random"
|
||||||
"github.com/ava-labs/gecko/utils/timer"
|
"github.com/ava-labs/gecko/utils/timer"
|
||||||
|
@ -42,6 +43,12 @@ const (
|
||||||
defaultGetVersionTimeout = 2 * time.Second
|
defaultGetVersionTimeout = 2 * time.Second
|
||||||
defaultAllowPrivateIPs = true
|
defaultAllowPrivateIPs = true
|
||||||
defaultGossipSize = 50
|
defaultGossipSize = 50
|
||||||
|
defaultPingPongTimeout = time.Minute
|
||||||
|
defaultPingFrequency = 3 * defaultPingPongTimeout / 4
|
||||||
|
|
||||||
|
// Request ID used when sending a Put message to gossip an accepted container
|
||||||
|
// (ie not sent in response to a Get)
|
||||||
|
GossipMsgRequestID = math.MaxUint32
|
||||||
)
|
)
|
||||||
|
|
||||||
// Network defines the functionality of the networking library.
|
// Network defines the functionality of the networking library.
|
||||||
|
@ -98,6 +105,7 @@ type network struct {
|
||||||
serverUpgrader Upgrader
|
serverUpgrader Upgrader
|
||||||
clientUpgrader Upgrader
|
clientUpgrader Upgrader
|
||||||
vdrs validators.Set // set of current validators in the AVAnet
|
vdrs validators.Set // set of current validators in the AVAnet
|
||||||
|
beacons validators.Set // set of beacons in the AVAnet
|
||||||
router router.Router // router must be thread safe
|
router router.Router // router must be thread safe
|
||||||
|
|
||||||
nodeID uint32
|
nodeID uint32
|
||||||
|
@ -118,6 +126,8 @@ type network struct {
|
||||||
getVersionTimeout time.Duration
|
getVersionTimeout time.Duration
|
||||||
allowPrivateIPs bool
|
allowPrivateIPs bool
|
||||||
gossipSize int
|
gossipSize int
|
||||||
|
pingPongTimeout time.Duration
|
||||||
|
pingFrequency time.Duration
|
||||||
|
|
||||||
executor timer.Executor
|
executor timer.Executor
|
||||||
|
|
||||||
|
@ -150,6 +160,7 @@ func NewDefaultNetwork(
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader Upgrader,
|
clientUpgrader Upgrader,
|
||||||
vdrs validators.Set,
|
vdrs validators.Set,
|
||||||
|
beacons validators.Set,
|
||||||
router router.Router,
|
router router.Router,
|
||||||
) Network {
|
) Network {
|
||||||
return NewNetwork(
|
return NewNetwork(
|
||||||
|
@ -165,6 +176,7 @@ func NewDefaultNetwork(
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
beacons,
|
||||||
router,
|
router,
|
||||||
defaultInitialReconnectDelay,
|
defaultInitialReconnectDelay,
|
||||||
defaultMaxReconnectDelay,
|
defaultMaxReconnectDelay,
|
||||||
|
@ -179,6 +191,8 @@ func NewDefaultNetwork(
|
||||||
defaultGetVersionTimeout,
|
defaultGetVersionTimeout,
|
||||||
defaultAllowPrivateIPs,
|
defaultAllowPrivateIPs,
|
||||||
defaultGossipSize,
|
defaultGossipSize,
|
||||||
|
defaultPingPongTimeout,
|
||||||
|
defaultPingFrequency,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,6 +210,7 @@ func NewNetwork(
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader Upgrader,
|
clientUpgrader Upgrader,
|
||||||
vdrs validators.Set,
|
vdrs validators.Set,
|
||||||
|
beacons validators.Set,
|
||||||
router router.Router,
|
router router.Router,
|
||||||
initialReconnectDelay,
|
initialReconnectDelay,
|
||||||
maxReconnectDelay time.Duration,
|
maxReconnectDelay time.Duration,
|
||||||
|
@ -210,6 +225,8 @@ func NewNetwork(
|
||||||
getVersionTimeout time.Duration,
|
getVersionTimeout time.Duration,
|
||||||
allowPrivateIPs bool,
|
allowPrivateIPs bool,
|
||||||
gossipSize int,
|
gossipSize int,
|
||||||
|
pingPongTimeout time.Duration,
|
||||||
|
pingFrequency time.Duration,
|
||||||
) Network {
|
) Network {
|
||||||
net := &network{
|
net := &network{
|
||||||
log: log,
|
log: log,
|
||||||
|
@ -223,6 +240,7 @@ func NewNetwork(
|
||||||
serverUpgrader: serverUpgrader,
|
serverUpgrader: serverUpgrader,
|
||||||
clientUpgrader: clientUpgrader,
|
clientUpgrader: clientUpgrader,
|
||||||
vdrs: vdrs,
|
vdrs: vdrs,
|
||||||
|
beacons: beacons,
|
||||||
router: router,
|
router: router,
|
||||||
nodeID: rand.Uint32(),
|
nodeID: rand.Uint32(),
|
||||||
initialReconnectDelay: initialReconnectDelay,
|
initialReconnectDelay: initialReconnectDelay,
|
||||||
|
@ -238,6 +256,8 @@ func NewNetwork(
|
||||||
getVersionTimeout: getVersionTimeout,
|
getVersionTimeout: getVersionTimeout,
|
||||||
allowPrivateIPs: allowPrivateIPs,
|
allowPrivateIPs: allowPrivateIPs,
|
||||||
gossipSize: gossipSize,
|
gossipSize: gossipSize,
|
||||||
|
pingPongTimeout: pingPongTimeout,
|
||||||
|
pingFrequency: pingFrequency,
|
||||||
|
|
||||||
disconnectedIPs: make(map[string]struct{}),
|
disconnectedIPs: make(map[string]struct{}),
|
||||||
connectedIPs: make(map[string]struct{}),
|
connectedIPs: make(map[string]struct{}),
|
||||||
|
@ -278,8 +298,11 @@ func (n *network) GetAcceptedFrontier(validatorIDs ids.ShortSet, chainID ids.ID,
|
||||||
func (n *network) AcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
func (n *network) AcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
||||||
msg, err := n.b.AcceptedFrontier(chainID, requestID, containerIDs)
|
msg, err := n.b.AcceptedFrontier(chainID, requestID, containerIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.log.Error("attempted to pack too large of an AcceptedFrontier message.\nNumber of containerIDs: %d",
|
n.log.Error("failed to build AcceptedFrontier(%s, %d, %s): %s",
|
||||||
containerIDs.Len())
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerIDs,
|
||||||
|
err)
|
||||||
return // Packing message failed
|
return // Packing message failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +314,11 @@ func (n *network) AcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requ
|
||||||
sent = peer.send(msg)
|
sent = peer.send(msg)
|
||||||
}
|
}
|
||||||
if !sent {
|
if !sent {
|
||||||
n.log.Debug("failed to send an AcceptedFrontier message to: %s", validatorID)
|
n.log.Debug("failed to send AcceptedFrontier(%s, %s, %d, %s)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerIDs)
|
||||||
n.acceptedFrontier.numFailed.Inc()
|
n.acceptedFrontier.numFailed.Inc()
|
||||||
} else {
|
} else {
|
||||||
n.acceptedFrontier.numSent.Inc()
|
n.acceptedFrontier.numSent.Inc()
|
||||||
|
@ -302,6 +329,11 @@ func (n *network) AcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requ
|
||||||
func (n *network) GetAccepted(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
func (n *network) GetAccepted(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
||||||
msg, err := n.b.GetAccepted(chainID, requestID, containerIDs)
|
msg, err := n.b.GetAccepted(chainID, requestID, containerIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
n.log.Error("failed to build GetAccepted(%s, %d, %s): %s",
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerIDs,
|
||||||
|
err)
|
||||||
for _, validatorID := range validatorIDs.List() {
|
for _, validatorID := range validatorIDs.List() {
|
||||||
vID := validatorID
|
vID := validatorID
|
||||||
n.executor.Add(func() { n.router.GetAcceptedFailed(vID, chainID, requestID) })
|
n.executor.Add(func() { n.router.GetAcceptedFailed(vID, chainID, requestID) })
|
||||||
|
@ -319,6 +351,11 @@ func (n *network) GetAccepted(validatorIDs ids.ShortSet, chainID ids.ID, request
|
||||||
sent = peer.send(msg)
|
sent = peer.send(msg)
|
||||||
}
|
}
|
||||||
if !sent {
|
if !sent {
|
||||||
|
n.log.Debug("failed to send GetAccepted(%s, %s, %d, %s)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerIDs)
|
||||||
n.executor.Add(func() { n.router.GetAcceptedFailed(vID, chainID, requestID) })
|
n.executor.Add(func() { n.router.GetAcceptedFailed(vID, chainID, requestID) })
|
||||||
n.getAccepted.numFailed.Inc()
|
n.getAccepted.numFailed.Inc()
|
||||||
} else {
|
} else {
|
||||||
|
@ -331,8 +368,11 @@ func (n *network) GetAccepted(validatorIDs ids.ShortSet, chainID ids.ID, request
|
||||||
func (n *network) Accepted(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
func (n *network) Accepted(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
||||||
msg, err := n.b.Accepted(chainID, requestID, containerIDs)
|
msg, err := n.b.Accepted(chainID, requestID, containerIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.log.Error("attempted to pack too large of an Accepted message.\nNumber of containerIDs: %d",
|
n.log.Error("failed to build Accepted(%s, %d, %s): %s",
|
||||||
containerIDs.Len())
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerIDs,
|
||||||
|
err)
|
||||||
return // Packing message failed
|
return // Packing message failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,33 +384,17 @@ func (n *network) Accepted(validatorID ids.ShortID, chainID ids.ID, requestID ui
|
||||||
sent = peer.send(msg)
|
sent = peer.send(msg)
|
||||||
}
|
}
|
||||||
if !sent {
|
if !sent {
|
||||||
n.log.Debug("failed to send an Accepted message to: %s", validatorID)
|
n.log.Debug("failed to send Accepted(%s, %s, %d, %s)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerIDs)
|
||||||
n.accepted.numFailed.Inc()
|
n.accepted.numFailed.Inc()
|
||||||
} else {
|
} else {
|
||||||
n.accepted.numSent.Inc()
|
n.accepted.numSent.Inc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get implements the Sender interface.
|
|
||||||
func (n *network) Get(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
|
|
||||||
msg, err := n.b.Get(chainID, requestID, containerID)
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
|
|
||||||
n.stateLock.Lock()
|
|
||||||
defer n.stateLock.Unlock()
|
|
||||||
|
|
||||||
peer, sent := n.peers[validatorID.Key()]
|
|
||||||
if sent {
|
|
||||||
sent = peer.send(msg)
|
|
||||||
}
|
|
||||||
if !sent {
|
|
||||||
n.log.Debug("failed to send a Get message to: %s", validatorID)
|
|
||||||
n.get.numFailed.Inc()
|
|
||||||
} else {
|
|
||||||
n.get.numSent.Inc()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAncestors implements the Sender interface.
|
// GetAncestors implements the Sender interface.
|
||||||
func (n *network) GetAncestors(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
|
func (n *network) GetAncestors(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
|
||||||
msg, err := n.b.GetAncestors(chainID, requestID, containerID)
|
msg, err := n.b.GetAncestors(chainID, requestID, containerID)
|
||||||
|
@ -387,36 +411,18 @@ func (n *network) GetAncestors(validatorID ids.ShortID, chainID ids.ID, requestI
|
||||||
sent = peer.send(msg)
|
sent = peer.send(msg)
|
||||||
}
|
}
|
||||||
if !sent {
|
if !sent {
|
||||||
|
n.log.Debug("failed to send GetAncestors(%s, %s, %d, %s)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerID)
|
||||||
|
n.executor.Add(func() { n.router.GetAncestorsFailed(validatorID, chainID, requestID) })
|
||||||
n.getAncestors.numFailed.Inc()
|
n.getAncestors.numFailed.Inc()
|
||||||
n.log.Debug("failed to send a GetAncestors message to: %s", validatorID)
|
|
||||||
} else {
|
} else {
|
||||||
n.getAncestors.numSent.Inc()
|
n.getAncestors.numSent.Inc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put implements the Sender interface.
|
|
||||||
func (n *network) Put(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
|
|
||||||
msg, err := n.b.Put(chainID, requestID, containerID, container)
|
|
||||||
if err != nil {
|
|
||||||
n.log.Error("failed to build Put message because of container of size %d", len(container))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n.stateLock.Lock()
|
|
||||||
defer n.stateLock.Unlock()
|
|
||||||
|
|
||||||
peer, sent := n.peers[validatorID.Key()]
|
|
||||||
if sent {
|
|
||||||
sent = peer.send(msg)
|
|
||||||
}
|
|
||||||
if !sent {
|
|
||||||
n.log.Debug("failed to send a Put message to: %s", validatorID)
|
|
||||||
n.put.numFailed.Inc()
|
|
||||||
} else {
|
|
||||||
n.put.numSent.Inc()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiPut implements the Sender interface.
|
// MultiPut implements the Sender interface.
|
||||||
func (n *network) MultiPut(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containers [][]byte) {
|
func (n *network) MultiPut(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containers [][]byte) {
|
||||||
msg, err := n.b.MultiPut(chainID, requestID, containers)
|
msg, err := n.b.MultiPut(chainID, requestID, containers)
|
||||||
|
@ -433,22 +439,90 @@ func (n *network) MultiPut(validatorID ids.ShortID, chainID ids.ID, requestID ui
|
||||||
sent = peer.send(msg)
|
sent = peer.send(msg)
|
||||||
}
|
}
|
||||||
if !sent {
|
if !sent {
|
||||||
n.log.Debug("failed to send a MultiPut message to: %s", validatorID)
|
n.log.Debug("failed to send MultiPut(%s, %s, %d, %d)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
len(containers))
|
||||||
n.multiPut.numFailed.Inc()
|
n.multiPut.numFailed.Inc()
|
||||||
} else {
|
} else {
|
||||||
n.multiPut.numSent.Inc()
|
n.multiPut.numSent.Inc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get implements the Sender interface.
|
||||||
|
func (n *network) Get(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
|
||||||
|
msg, err := n.b.Get(chainID, requestID, containerID)
|
||||||
|
n.log.AssertNoError(err)
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
peer, sent := n.peers[validatorID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.log.Debug("failed to send Get(%s, %s, %d, %s)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerID)
|
||||||
|
n.executor.Add(func() { n.router.GetFailed(validatorID, chainID, requestID) })
|
||||||
|
n.get.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.get.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the Sender interface.
|
||||||
|
func (n *network) Put(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
|
||||||
|
msg, err := n.b.Put(chainID, requestID, containerID, container)
|
||||||
|
if err != nil {
|
||||||
|
n.log.Error("failed to build Put(%s, %d, %s): %s. len(container) : %d",
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerID,
|
||||||
|
err,
|
||||||
|
len(container))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
peer, sent := n.peers[validatorID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.log.Debug("failed to send Put(%s, %s, %d, %s)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerID)
|
||||||
|
n.log.Verbo("container: %s", formatting.DumpBytes{Bytes: container})
|
||||||
|
n.put.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.put.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PushQuery implements the Sender interface.
|
// PushQuery implements the Sender interface.
|
||||||
func (n *network) PushQuery(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
|
func (n *network) PushQuery(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
|
||||||
msg, err := n.b.PushQuery(chainID, requestID, containerID, container)
|
msg, err := n.b.PushQuery(chainID, requestID, containerID, container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
n.log.Error("failed to build PushQuery(%s, %d, %s): %s. len(container): %d",
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerID,
|
||||||
|
err,
|
||||||
|
len(container))
|
||||||
|
n.log.Verbo("container: %s", formatting.DumpBytes{Bytes: container})
|
||||||
for _, validatorID := range validatorIDs.List() {
|
for _, validatorID := range validatorIDs.List() {
|
||||||
vID := validatorID
|
vID := validatorID
|
||||||
n.executor.Add(func() { n.router.QueryFailed(vID, chainID, requestID) })
|
n.executor.Add(func() { n.router.QueryFailed(vID, chainID, requestID) })
|
||||||
}
|
}
|
||||||
n.log.Error("attempted to pack too large of a PushQuery message.\nContainer length: %d", len(container))
|
|
||||||
return // Packing message failed
|
return // Packing message failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,7 +536,12 @@ func (n *network) PushQuery(validatorIDs ids.ShortSet, chainID ids.ID, requestID
|
||||||
sent = peer.send(msg)
|
sent = peer.send(msg)
|
||||||
}
|
}
|
||||||
if !sent {
|
if !sent {
|
||||||
n.log.Debug("failed sending a PushQuery message to: %s", vID)
|
n.log.Debug("failed to send PushQuery(%s, %s, %d, %s)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerID)
|
||||||
|
n.log.Verbo("container: %s", formatting.DumpBytes{Bytes: container})
|
||||||
n.executor.Add(func() { n.router.QueryFailed(vID, chainID, requestID) })
|
n.executor.Add(func() { n.router.QueryFailed(vID, chainID, requestID) })
|
||||||
n.pushQuery.numFailed.Inc()
|
n.pushQuery.numFailed.Inc()
|
||||||
} else {
|
} else {
|
||||||
|
@ -486,7 +565,11 @@ func (n *network) PullQuery(validatorIDs ids.ShortSet, chainID ids.ID, requestID
|
||||||
sent = peer.send(msg)
|
sent = peer.send(msg)
|
||||||
}
|
}
|
||||||
if !sent {
|
if !sent {
|
||||||
n.log.Debug("failed sending a PullQuery message to: %s", vID)
|
n.log.Debug("failed to send PullQuery(%s, %s, %d, %s)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
containerID)
|
||||||
n.executor.Add(func() { n.router.QueryFailed(vID, chainID, requestID) })
|
n.executor.Add(func() { n.router.QueryFailed(vID, chainID, requestID) })
|
||||||
n.pullQuery.numFailed.Inc()
|
n.pullQuery.numFailed.Inc()
|
||||||
} else {
|
} else {
|
||||||
|
@ -499,7 +582,11 @@ func (n *network) PullQuery(validatorIDs ids.ShortSet, chainID ids.ID, requestID
|
||||||
func (n *network) Chits(validatorID ids.ShortID, chainID ids.ID, requestID uint32, votes ids.Set) {
|
func (n *network) Chits(validatorID ids.ShortID, chainID ids.ID, requestID uint32, votes ids.Set) {
|
||||||
msg, err := n.b.Chits(chainID, requestID, votes)
|
msg, err := n.b.Chits(chainID, requestID, votes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.log.Error("failed to build Chits message because of %d votes", votes.Len())
|
n.log.Error("failed to build Chits(%s, %d, %s): %s",
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
votes,
|
||||||
|
err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,7 +598,11 @@ func (n *network) Chits(validatorID ids.ShortID, chainID ids.ID, requestID uint3
|
||||||
sent = peer.send(msg)
|
sent = peer.send(msg)
|
||||||
}
|
}
|
||||||
if !sent {
|
if !sent {
|
||||||
n.log.Debug("failed to send a Chits message to: %s", validatorID)
|
n.log.Debug("failed to send Chits(%s, %s, %d, %s)",
|
||||||
|
validatorID,
|
||||||
|
chainID,
|
||||||
|
requestID,
|
||||||
|
votes)
|
||||||
n.chits.numFailed.Inc()
|
n.chits.numFailed.Inc()
|
||||||
} else {
|
} else {
|
||||||
n.chits.numSent.Inc()
|
n.chits.numSent.Inc()
|
||||||
|
@ -521,7 +612,8 @@ func (n *network) Chits(validatorID ids.ShortID, chainID ids.ID, requestID uint3
|
||||||
// Gossip attempts to gossip the container to the network
|
// Gossip attempts to gossip the container to the network
|
||||||
func (n *network) Gossip(chainID, containerID ids.ID, container []byte) {
|
func (n *network) Gossip(chainID, containerID ids.ID, container []byte) {
|
||||||
if err := n.gossipContainer(chainID, containerID, container); err != nil {
|
if err := n.gossipContainer(chainID, containerID, container); err != nil {
|
||||||
n.log.Error("error gossiping container %s to %s: %s", containerID, chainID, err)
|
n.log.Debug("failed to Gossip(%s, %s): %s", chainID, containerID, err)
|
||||||
|
n.log.Verbo("container:\n%s", formatting.DumpBytes{Bytes: container})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,7 +724,7 @@ func (n *network) Track(ip utils.IPDesc) {
|
||||||
|
|
||||||
// assumes the stateLock is not held.
|
// assumes the stateLock is not held.
|
||||||
func (n *network) gossipContainer(chainID, containerID ids.ID, container []byte) error {
|
func (n *network) gossipContainer(chainID, containerID ids.ID, container []byte) error {
|
||||||
msg, err := n.b.Put(chainID, math.MaxUint32, containerID, container)
|
msg, err := n.b.Put(chainID, GossipMsgRequestID, containerID, container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("attempted to pack too large of a Put message.\nContainer length: %d", len(container))
|
return fmt.Errorf("attempted to pack too large of a Put message.\nContainer length: %d", len(container))
|
||||||
}
|
}
|
||||||
|
@ -695,7 +787,9 @@ func (n *network) gossip() {
|
||||||
}
|
}
|
||||||
msg, err := n.b.PeerList(ips)
|
msg, err := n.b.PeerList(ips)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.log.Warn("failed to gossip PeerList message due to %s", err)
|
n.log.Error("failed to build peer list to gossip: %s. len(ips): %d",
|
||||||
|
err,
|
||||||
|
len(ips))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -197,6 +197,7 @@ func TestNewDefaultNetwork(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net)
|
assert.NotNil(t, net)
|
||||||
|
@ -280,6 +281,7 @@ func TestEstablishConnection(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net0)
|
assert.NotNil(t, net0)
|
||||||
|
@ -297,6 +299,7 @@ func TestEstablishConnection(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net1)
|
assert.NotNil(t, net1)
|
||||||
|
@ -419,6 +422,7 @@ func TestDoubleTrack(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net0)
|
assert.NotNil(t, net0)
|
||||||
|
@ -436,6 +440,7 @@ func TestDoubleTrack(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net1)
|
assert.NotNil(t, net1)
|
||||||
|
@ -559,6 +564,7 @@ func TestDoubleClose(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net0)
|
assert.NotNil(t, net0)
|
||||||
|
@ -576,6 +582,7 @@ func TestDoubleClose(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net1)
|
assert.NotNil(t, net1)
|
||||||
|
@ -704,6 +711,7 @@ func TestRemoveHandlers(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net0)
|
assert.NotNil(t, net0)
|
||||||
|
@ -721,6 +729,7 @@ func TestRemoveHandlers(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net1)
|
assert.NotNil(t, net1)
|
||||||
|
@ -858,6 +867,7 @@ func TestTrackConnected(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net0)
|
assert.NotNil(t, net0)
|
||||||
|
@ -875,6 +885,7 @@ func TestTrackConnected(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net1)
|
assert.NotNil(t, net1)
|
||||||
|
@ -999,6 +1010,7 @@ func TestTrackConnectedRace(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net0)
|
assert.NotNil(t, net0)
|
||||||
|
@ -1016,6 +1028,7 @@ func TestTrackConnectedRace(t *testing.T) {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
vdrs,
|
vdrs,
|
||||||
|
vdrs,
|
||||||
handler,
|
handler,
|
||||||
)
|
)
|
||||||
assert.NotNil(t, net1)
|
assert.NotNil(t, net1)
|
||||||
|
|
|
@ -64,6 +64,24 @@ func (p *peer) Start() {
|
||||||
// Initially send the version to the peer
|
// Initially send the version to the peer
|
||||||
go p.Version()
|
go p.Version()
|
||||||
go p.requestVersion()
|
go p.requestVersion()
|
||||||
|
// go p.sendPings()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *peer) sendPings() {
|
||||||
|
t := time.NewTicker(p.net.pingFrequency)
|
||||||
|
defer t.Stop()
|
||||||
|
|
||||||
|
for range t.C {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
closed := p.closed
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
|
||||||
|
if closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Ping()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// request the version from the peer until we get the version from them
|
// request the version from the peer until we get the version from them
|
||||||
|
@ -80,6 +98,7 @@ func (p *peer) requestVersion() {
|
||||||
if connected || closed {
|
if connected || closed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.GetVersion()
|
p.GetVersion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +107,11 @@ func (p *peer) requestVersion() {
|
||||||
func (p *peer) ReadMessages() {
|
func (p *peer) ReadMessages() {
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
||||||
|
// if err := p.conn.SetReadDeadline(p.net.clock.Time().Add(p.net.pingPongTimeout)); err != nil {
|
||||||
|
// p.net.log.Verbo("error on setting the connection read timeout %s", err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
pendingBuffer := wrappers.Packer{}
|
pendingBuffer := wrappers.Packer{}
|
||||||
readBuffer := make([]byte, 1<<10)
|
readBuffer := make([]byte, 1<<10)
|
||||||
for {
|
for {
|
||||||
|
@ -218,7 +242,15 @@ func (p *peer) send(msg Msg) bool {
|
||||||
// assumes the stateLock is not held
|
// assumes the stateLock is not held
|
||||||
func (p *peer) handle(msg Msg) {
|
func (p *peer) handle(msg Msg) {
|
||||||
p.net.heartbeat()
|
p.net.heartbeat()
|
||||||
atomic.StoreInt64(&p.lastReceived, p.net.clock.Time().Unix())
|
|
||||||
|
currentTime := p.net.clock.Time()
|
||||||
|
atomic.StoreInt64(&p.lastReceived, currentTime.Unix())
|
||||||
|
|
||||||
|
// if err := p.conn.SetReadDeadline(currentTime.Add(p.net.pingPongTimeout)); err != nil {
|
||||||
|
// p.net.log.Verbo("error on setting the connection read timeout %s, closing the connection", err)
|
||||||
|
// p.Close()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
op := msg.Op()
|
op := msg.Op()
|
||||||
msgMetrics := p.net.message(op)
|
msgMetrics := p.net.message(op)
|
||||||
|
@ -235,6 +267,12 @@ func (p *peer) handle(msg Msg) {
|
||||||
case GetVersion:
|
case GetVersion:
|
||||||
p.getVersion(msg)
|
p.getVersion(msg)
|
||||||
return
|
return
|
||||||
|
case Ping:
|
||||||
|
p.ping(msg)
|
||||||
|
return
|
||||||
|
case Pong:
|
||||||
|
p.pong(msg)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if !p.connected {
|
if !p.connected {
|
||||||
p.net.log.Debug("dropping message from %s because the connection hasn't been established yet", p.id)
|
p.net.log.Debug("dropping message from %s because the connection hasn't been established yet", p.id)
|
||||||
|
@ -318,6 +356,12 @@ func (p *peer) GetPeerList() {
|
||||||
p.Send(msg)
|
p.Send(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) SendPeerList() {
|
||||||
|
ips := p.net.validatorIPs()
|
||||||
|
p.PeerList(ips)
|
||||||
|
}
|
||||||
|
|
||||||
// assumes the stateLock is not held
|
// assumes the stateLock is not held
|
||||||
func (p *peer) PeerList(peers []utils.IPDesc) {
|
func (p *peer) PeerList(peers []utils.IPDesc) {
|
||||||
msg, err := p.net.b.PeerList(peers)
|
msg, err := p.net.b.PeerList(peers)
|
||||||
|
@ -326,7 +370,28 @@ func (p *peer) PeerList(peers []utils.IPDesc) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.Send(msg)
|
p.Send(msg)
|
||||||
return
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) Ping() {
|
||||||
|
msg, err := p.net.b.Ping()
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
if p.Send(msg) {
|
||||||
|
p.net.ping.numSent.Inc()
|
||||||
|
} else {
|
||||||
|
p.net.ping.numFailed.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) Pong() {
|
||||||
|
msg, err := p.net.b.Pong()
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
if p.Send(msg) {
|
||||||
|
p.net.pong.numSent.Inc()
|
||||||
|
} else {
|
||||||
|
p.net.pong.numFailed.Inc()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// assumes the stateLock is not held
|
// assumes the stateLock is not held
|
||||||
|
@ -405,8 +470,13 @@ func (p *peer) version(msg Msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.net.version.Before(peerVersion) {
|
if p.net.version.Before(peerVersion) {
|
||||||
p.net.log.Info("peer attempting to connect with newer version %s. You may want to update your client",
|
if p.net.beacons.Contains(p.id) {
|
||||||
|
p.net.log.Info("beacon attempting to connect with newer version %s. You may want to update your client",
|
||||||
peerVersion)
|
peerVersion)
|
||||||
|
} else {
|
||||||
|
p.net.log.Debug("peer attempting to connect with newer version %s. You may want to update your client",
|
||||||
|
peerVersion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.net.version.Compatible(peerVersion); err != nil {
|
if err := p.net.version.Compatible(peerVersion); err != nil {
|
||||||
|
@ -458,17 +528,6 @@ func (p *peer) version(msg Msg) {
|
||||||
p.net.connected(p)
|
p.net.connected(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// assumes the stateLock is not held
|
|
||||||
func (p *peer) SendPeerList() {
|
|
||||||
ips := p.net.validatorIPs()
|
|
||||||
reply, err := p.net.b.PeerList(ips)
|
|
||||||
if err != nil {
|
|
||||||
p.net.log.Warn("failed to send PeerList message due to %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Send(reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
// assumes the stateLock is not held
|
// assumes the stateLock is not held
|
||||||
func (p *peer) getPeerList(_ Msg) { p.SendPeerList() }
|
func (p *peer) getPeerList(_ Msg) { p.SendPeerList() }
|
||||||
|
|
||||||
|
@ -488,6 +547,12 @@ func (p *peer) peerList(msg Msg) {
|
||||||
p.net.stateLock.Unlock()
|
p.net.stateLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) ping(_ Msg) { p.Pong() }
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) pong(_ Msg) {}
|
||||||
|
|
||||||
// assumes the stateLock is not held
|
// assumes the stateLock is not held
|
||||||
func (p *peer) getAcceptedFrontier(msg Msg) {
|
func (p *peer) getAcceptedFrontier(msg Msg) {
|
||||||
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
|
|
@ -34,6 +34,8 @@ type Config struct {
|
||||||
|
|
||||||
// Staking configuration
|
// Staking configuration
|
||||||
StakingIP utils.IPDesc
|
StakingIP utils.IPDesc
|
||||||
|
StakingLocalPort uint16
|
||||||
|
EnableP2PTLS bool
|
||||||
EnableStaking bool
|
EnableStaking bool
|
||||||
StakingKeyFile string
|
StakingKeyFile string
|
||||||
StakingCertFile string
|
StakingCertFile string
|
||||||
|
@ -50,6 +52,7 @@ type Config struct {
|
||||||
|
|
||||||
// Enable/Disable APIs
|
// Enable/Disable APIs
|
||||||
AdminAPIEnabled bool
|
AdminAPIEnabled bool
|
||||||
|
InfoAPIEnabled bool
|
||||||
KeystoreAPIEnabled bool
|
KeystoreAPIEnabled bool
|
||||||
MetricsAPIEnabled bool
|
MetricsAPIEnabled bool
|
||||||
HealthAPIEnabled bool
|
HealthAPIEnabled bool
|
||||||
|
|
161
node/node.go
161
node/node.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
@ -18,6 +19,7 @@ import (
|
||||||
"github.com/ava-labs/gecko/api"
|
"github.com/ava-labs/gecko/api"
|
||||||
"github.com/ava-labs/gecko/api/admin"
|
"github.com/ava-labs/gecko/api/admin"
|
||||||
"github.com/ava-labs/gecko/api/health"
|
"github.com/ava-labs/gecko/api/health"
|
||||||
|
"github.com/ava-labs/gecko/api/info"
|
||||||
"github.com/ava-labs/gecko/api/ipcs"
|
"github.com/ava-labs/gecko/api/ipcs"
|
||||||
"github.com/ava-labs/gecko/api/keystore"
|
"github.com/ava-labs/gecko/api/keystore"
|
||||||
"github.com/ava-labs/gecko/api/metrics"
|
"github.com/ava-labs/gecko/api/metrics"
|
||||||
|
@ -56,7 +58,7 @@ var (
|
||||||
genesisHashKey = []byte("genesisID")
|
genesisHashKey = []byte("genesisID")
|
||||||
|
|
||||||
// Version is the version of this code
|
// Version is the version of this code
|
||||||
Version = version.NewDefaultVersion("avalanche", 0, 5, 5)
|
Version = version.NewDefaultVersion("avalanche", 0, 5, 7)
|
||||||
versionParser = version.NewDefaultParser()
|
versionParser = version.NewDefaultParser()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,6 +94,9 @@ type Node struct {
|
||||||
// Net runs the networking stack
|
// Net runs the networking stack
|
||||||
Net network.Network
|
Net network.Network
|
||||||
|
|
||||||
|
// this node's initial connections to the network
|
||||||
|
beacons validators.Set
|
||||||
|
|
||||||
// current validators of the network
|
// current validators of the network
|
||||||
vdrs validators.Manager
|
vdrs validators.Manager
|
||||||
|
|
||||||
|
@ -112,14 +117,14 @@ type Node struct {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (n *Node) initNetworking() error {
|
func (n *Node) initNetworking() error {
|
||||||
listener, err := net.Listen(TCP, n.Config.StakingIP.PortString())
|
listener, err := net.Listen(TCP, fmt.Sprintf(":%d", n.Config.StakingLocalPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dialer := network.NewDialer(TCP)
|
dialer := network.NewDialer(TCP)
|
||||||
|
|
||||||
var serverUpgrader, clientUpgrader network.Upgrader
|
var serverUpgrader, clientUpgrader network.Upgrader
|
||||||
if n.Config.EnableStaking {
|
if n.Config.EnableP2PTLS {
|
||||||
cert, err := tls.LoadX509KeyPair(n.Config.StakingCertFile, n.Config.StakingKeyFile)
|
cert, err := tls.LoadX509KeyPair(n.Config.StakingCertFile, n.Config.StakingKeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -164,6 +169,7 @@ func (n *Node) initNetworking() error {
|
||||||
serverUpgrader,
|
serverUpgrader,
|
||||||
clientUpgrader,
|
clientUpgrader,
|
||||||
defaultSubnetValidators,
|
defaultSubnetValidators,
|
||||||
|
n.beacons,
|
||||||
n.Config.ConsensusRouter,
|
n.Config.ConsensusRouter,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -253,7 +259,7 @@ func (n *Node) initDatabase() error {
|
||||||
// Otherwise, it is a hash of the TLS certificate that this node
|
// Otherwise, it is a hash of the TLS certificate that this node
|
||||||
// uses for P2P communication
|
// uses for P2P communication
|
||||||
func (n *Node) initNodeID() error {
|
func (n *Node) initNodeID() error {
|
||||||
if !n.Config.EnableStaking {
|
if !n.Config.EnableP2PTLS {
|
||||||
n.ID = ids.NewShortID(hashing.ComputeHash160Array([]byte(n.Config.StakingIP.String())))
|
n.ID = ids.NewShortID(hashing.ComputeHash160Array([]byte(n.Config.StakingIP.String())))
|
||||||
n.Log.Info("Set the node's ID to %s", n.ID)
|
n.Log.Info("Set the node's ID to %s", n.ID)
|
||||||
return nil
|
return nil
|
||||||
|
@ -277,6 +283,14 @@ func (n *Node) initNodeID() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the IDs of the peers this node should first connect to
|
||||||
|
func (n *Node) initBeacons() {
|
||||||
|
n.beacons = validators.NewSet()
|
||||||
|
for _, peer := range n.Config.BootstrapPeers {
|
||||||
|
n.beacons.Add(validators.NewValidator(peer.ID, 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the vmManager and register the following vms:
|
// Create the vmManager and register the following vms:
|
||||||
// AVM, Simple Payments DAG, Simple Payments Chain
|
// AVM, Simple Payments DAG, Simple Payments Chain
|
||||||
// The Platform VM is registered in initStaking because
|
// The Platform VM is registered in initStaking because
|
||||||
|
@ -359,11 +373,6 @@ func (n *Node) initChains() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
beacons := validators.NewSet()
|
|
||||||
for _, peer := range n.Config.BootstrapPeers {
|
|
||||||
beacons.Add(validators.NewValidator(peer.ID, 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
genesisBytes, err := genesis.Genesis(n.Config.NetworkID)
|
genesisBytes, err := genesis.Genesis(n.Config.NetworkID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -375,7 +384,7 @@ func (n *Node) initChains() error {
|
||||||
SubnetID: platformvm.DefaultSubnetID,
|
SubnetID: platformvm.DefaultSubnetID,
|
||||||
GenesisData: genesisBytes, // Specifies other chains to create
|
GenesisData: genesisBytes, // Specifies other chains to create
|
||||||
VMAlias: platformvm.ID.String(),
|
VMAlias: platformvm.ID.String(),
|
||||||
CustomBeacons: beacons,
|
CustomBeacons: n.beacons,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -435,58 +444,105 @@ func (n *Node) initSharedMemory() {
|
||||||
|
|
||||||
// initKeystoreAPI initializes the keystore service
|
// initKeystoreAPI initializes the keystore service
|
||||||
// Assumes n.APIServer is already set
|
// Assumes n.APIServer is already set
|
||||||
func (n *Node) initKeystoreAPI() {
|
func (n *Node) initKeystoreAPI() error {
|
||||||
n.Log.Info("initializing Keystore API")
|
n.Log.Info("initializing keystore")
|
||||||
keystoreDB := prefixdb.New([]byte("keystore"), n.DB)
|
keystoreDB := prefixdb.New([]byte("keystore"), n.DB)
|
||||||
n.keystoreServer.Initialize(n.Log, keystoreDB)
|
n.keystoreServer.Initialize(n.Log, keystoreDB)
|
||||||
keystoreHandler := n.keystoreServer.CreateHandler()
|
keystoreHandler := n.keystoreServer.CreateHandler()
|
||||||
if n.Config.KeystoreAPIEnabled {
|
if !n.Config.KeystoreAPIEnabled {
|
||||||
n.APIServer.AddRoute(keystoreHandler, &sync.RWMutex{}, "keystore", "", n.HTTPLog)
|
n.Log.Info("skipping keystore API initializaion because it has been disabled")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
n.Log.Info("initializing keystore API")
|
||||||
|
return n.APIServer.AddRoute(keystoreHandler, &sync.RWMutex{}, "keystore", "", n.HTTPLog)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initMetricsAPI initializes the Metrics API
|
// initMetricsAPI initializes the Metrics API
|
||||||
// Assumes n.APIServer is already set
|
// Assumes n.APIServer is already set
|
||||||
func (n *Node) initMetricsAPI() {
|
func (n *Node) initMetricsAPI() error {
|
||||||
n.Log.Info("initializing Metrics API")
|
|
||||||
registry, handler := metrics.NewService()
|
registry, handler := metrics.NewService()
|
||||||
if n.Config.MetricsAPIEnabled {
|
// It is assumed by components of the system that the Metrics interface is
|
||||||
n.APIServer.AddRoute(handler, &sync.RWMutex{}, "metrics", "", n.HTTPLog)
|
// non-nil. So, it is set regardless of if the metrics API is available or not.
|
||||||
}
|
|
||||||
n.Config.ConsensusParams.Metrics = registry
|
n.Config.ConsensusParams.Metrics = registry
|
||||||
|
if !n.Config.MetricsAPIEnabled {
|
||||||
|
n.Log.Info("skipping metrics API initialization because it has been disabled")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
n.Log.Info("initializing metrics API")
|
||||||
|
return n.APIServer.AddRoute(handler, &sync.RWMutex{}, "metrics", "", n.HTTPLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initAdminAPI initializes the Admin API service
|
// initAdminAPI initializes the Admin API service
|
||||||
// Assumes n.log, n.chainManager, and n.ValidatorAPI already initialized
|
// Assumes n.log, n.chainManager, and n.ValidatorAPI already initialized
|
||||||
func (n *Node) initAdminAPI() {
|
func (n *Node) initAdminAPI() error {
|
||||||
if n.Config.AdminAPIEnabled {
|
if !n.Config.AdminAPIEnabled {
|
||||||
n.Log.Info("initializing Admin API")
|
n.Log.Info("skipping admin API initializaion because it has been disabled")
|
||||||
service := admin.NewService(Version, n.ID, n.Config.NetworkID, n.Log, n.chainManager, n.Net, &n.APIServer)
|
return nil
|
||||||
n.APIServer.AddRoute(service, &sync.RWMutex{}, "admin", "", n.HTTPLog)
|
|
||||||
}
|
}
|
||||||
|
n.Log.Info("initializing admin API")
|
||||||
|
service := admin.NewService(Version, n.ID, n.Config.NetworkID, n.Log, n.chainManager, n.Net, &n.APIServer)
|
||||||
|
return n.APIServer.AddRoute(service, &sync.RWMutex{}, "admin", "", n.HTTPLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) initInfoAPI() error {
|
||||||
|
if !n.Config.InfoAPIEnabled {
|
||||||
|
n.Log.Info("skipping info API initializaion because it has been disabled")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
n.Log.Info("initializing info API")
|
||||||
|
service := info.NewService(n.Log, Version, n.ID, n.Config.NetworkID, n.chainManager, n.Net)
|
||||||
|
return n.APIServer.AddRoute(service, &sync.RWMutex{}, "info", "", n.HTTPLog)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initHealthAPI initializes the Health API service
|
// initHealthAPI initializes the Health API service
|
||||||
// Assumes n.Log, n.ConsensusAPI, and n.ValidatorAPI already initialized
|
// Assumes n.Log, n.Net, n.APIServer, n.HTTPLog already initialized
|
||||||
func (n *Node) initHealthAPI() {
|
func (n *Node) initHealthAPI() error {
|
||||||
if !n.Config.HealthAPIEnabled {
|
if !n.Config.HealthAPIEnabled {
|
||||||
return
|
n.Log.Info("skipping health API initializaion because it has been disabled")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n.Log.Info("initializing Health API")
|
n.Log.Info("initializing Health API")
|
||||||
service := health.NewService(n.Log)
|
service := health.NewService(n.Log)
|
||||||
service.RegisterHeartbeat("network.validators.heartbeat", n.Net, 5*time.Minute)
|
if err := service.RegisterHeartbeat("network.validators.heartbeat", n.Net, 5*time.Minute); err != nil {
|
||||||
n.APIServer.AddRoute(service.Handler(), &sync.RWMutex{}, "health", "", n.HTTPLog)
|
return fmt.Errorf("couldn't register heartbeat health check: %w", err)
|
||||||
|
}
|
||||||
|
isBootstrappedFunc := func() (interface{}, error) {
|
||||||
|
if pChainID, err := n.chainManager.Lookup("P"); err != nil {
|
||||||
|
return nil, errors.New("P-Chain not created")
|
||||||
|
} else if !n.chainManager.IsBootstrapped(pChainID) {
|
||||||
|
return nil, errors.New("P-Chain not bootstrapped")
|
||||||
|
}
|
||||||
|
if xChainID, err := n.chainManager.Lookup("X"); err != nil {
|
||||||
|
return nil, errors.New("X-Chain not created")
|
||||||
|
} else if !n.chainManager.IsBootstrapped(xChainID) {
|
||||||
|
return nil, errors.New("X-Chain not bootstrapped")
|
||||||
|
}
|
||||||
|
if cChainID, err := n.chainManager.Lookup("C"); err != nil {
|
||||||
|
return nil, errors.New("C-Chain not created")
|
||||||
|
} else if !n.chainManager.IsBootstrapped(cChainID) {
|
||||||
|
return nil, errors.New("C-Chain not bootstrapped")
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// Passes if the P, X and C chains are finished bootstrapping
|
||||||
|
if err := service.RegisterMonotonicCheckFunc("chains.default.bootstrapped", isBootstrappedFunc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return n.APIServer.AddRoute(service.Handler(), &sync.RWMutex{}, "health", "", n.HTTPLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initIPCAPI initializes the IPC API service
|
// initIPCAPI initializes the IPC API service
|
||||||
// Assumes n.log and n.chainManager already initialized
|
// Assumes n.log and n.chainManager already initialized
|
||||||
func (n *Node) initIPCAPI() {
|
func (n *Node) initIPCAPI() error {
|
||||||
if n.Config.IPCEnabled {
|
if !n.Config.IPCEnabled {
|
||||||
n.Log.Info("initializing IPC API")
|
n.Log.Info("skipping ipc API initializaion because it has been disabled")
|
||||||
service := ipcs.NewService(n.Log, n.chainManager, n.DecisionDispatcher, &n.APIServer)
|
return nil
|
||||||
n.APIServer.AddRoute(service, &sync.RWMutex{}, "ipcs", "", n.HTTPLog)
|
|
||||||
}
|
}
|
||||||
|
n.Log.Info("initializing ipc API")
|
||||||
|
service := ipcs.NewService(n.Log, n.chainManager, n.DecisionDispatcher, &n.APIServer)
|
||||||
|
return n.APIServer.AddRoute(service, &sync.RWMutex{}, "ipcs", "", n.HTTPLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give chains and VMs aliases as specified by the genesis information
|
// Give chains and VMs aliases as specified by the genesis information
|
||||||
|
@ -542,10 +598,16 @@ func (n *Node) Initialize(Config *Config, logger logging.Logger, logFactory logg
|
||||||
return fmt.Errorf("problem initializing staker ID: %w", err)
|
return fmt.Errorf("problem initializing staker ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n.initBeacons()
|
||||||
|
|
||||||
// Start HTTP APIs
|
// Start HTTP APIs
|
||||||
n.initAPIServer() // Start the API Server
|
n.initAPIServer() // Start the API Server
|
||||||
n.initKeystoreAPI() // Start the Keystore API
|
if err := n.initKeystoreAPI(); err != nil { // Start the Keystore API
|
||||||
n.initMetricsAPI() // Start the Metrics API
|
return fmt.Errorf("couldn't initialize keystore API: %w", err)
|
||||||
|
}
|
||||||
|
if err := n.initMetricsAPI(); err != nil { // Start the Metrics API
|
||||||
|
return fmt.Errorf("couldn't initialize metrics API: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// initialize shared memory
|
// initialize shared memory
|
||||||
n.initSharedMemory()
|
n.initSharedMemory()
|
||||||
|
@ -561,14 +623,25 @@ func (n *Node) Initialize(Config *Config, logger logging.Logger, logFactory logg
|
||||||
n.initEventDispatcher() // Set up the event dipatcher
|
n.initEventDispatcher() // Set up the event dipatcher
|
||||||
n.initChainManager() // Set up the chain manager
|
n.initChainManager() // Set up the chain manager
|
||||||
|
|
||||||
n.initAdminAPI() // Start the Admin API
|
if err := n.initAdminAPI(); err != nil { // Start the Admin API
|
||||||
n.initHealthAPI() // Start the Health API
|
return fmt.Errorf("couldn't initialize admin API: %w", err)
|
||||||
n.initIPCAPI() // Start the IPC API
|
|
||||||
|
|
||||||
if err := n.initAliases(); err != nil { // Set up aliases
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return n.initChains() // Start the Platform chain
|
if err := n.initInfoAPI(); err != nil { // Start the Info API
|
||||||
|
return fmt.Errorf("couldn't initialize info API: %w", err)
|
||||||
|
}
|
||||||
|
if err := n.initHealthAPI(); err != nil { // Start the Health API
|
||||||
|
return fmt.Errorf("couldn't initialize health API: %w", err)
|
||||||
|
}
|
||||||
|
if err := n.initIPCAPI(); err != nil { // Start the IPC API
|
||||||
|
return fmt.Errorf("couldn't initialize ipc API: %w", err)
|
||||||
|
}
|
||||||
|
if err := n.initAliases(); err != nil { // Set up aliases
|
||||||
|
return fmt.Errorf("couldn't initialize aliases: %w", err)
|
||||||
|
}
|
||||||
|
if err := n.initChains(); err != nil { // Start the Platform chain
|
||||||
|
return fmt.Errorf("couldn't initialize chains: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown this node
|
// Shutdown this node
|
||||||
|
|
|
@ -15,7 +15,7 @@ GECKO_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) # Directory
|
||||||
BUILD_DIR=$GECKO_PATH/build # Where binaries go
|
BUILD_DIR=$GECKO_PATH/build # Where binaries go
|
||||||
PLUGIN_DIR="$BUILD_DIR/plugins" # Where plugin binaries (namely coreth) go
|
PLUGIN_DIR="$BUILD_DIR/plugins" # Where plugin binaries (namely coreth) go
|
||||||
|
|
||||||
CORETH_VER="0.2.4" # Should match coreth version in go.mod
|
CORETH_VER="0.2.5" # Should match coreth version in go.mod
|
||||||
CORETH_PATH="$GOPATH/pkg/mod/github.com/ava-labs/coreth@v$CORETH_VER"
|
CORETH_PATH="$GOPATH/pkg/mod/github.com/ava-labs/coreth@v$CORETH_VER"
|
||||||
|
|
||||||
# Build Gecko
|
# Build Gecko
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,6 +10,10 @@ import (
|
||||||
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
|
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minMapSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
// TopologicalFactory implements Factory by returning a topological struct
|
// TopologicalFactory implements Factory by returning a topological struct
|
||||||
type TopologicalFactory 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.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 = &snowstorm.Directed{}
|
||||||
ta.cg.Initialize(ctx, params.Parameters)
|
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 {
|
for _, vtx := range frontier {
|
||||||
ta.frontier[vtx.ID().Key()] = vtx
|
ta.frontier[vtx.ID().Key()] = vtx
|
||||||
}
|
}
|
||||||
|
@ -141,7 +145,9 @@ func (ta *Topological) RecordPoll(responses ids.UniqueBag) error {
|
||||||
votes := ta.pushVotes(kahns, leaves)
|
votes := ta.pushVotes(kahns, leaves)
|
||||||
// Update the conflict graph: O(|Transactions|)
|
// Update the conflict graph: O(|Transactions|)
|
||||||
ta.ctx.Log.Verbo("Updating consumer confidences based on:\n%s", &votes)
|
ta.ctx.Log.Verbo("Updating consumer confidences based on:\n%s", &votes)
|
||||||
ta.cg.RecordPoll(votes)
|
if err := ta.cg.RecordPoll(votes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Update the dag: O(|Live Set|)
|
// Update the dag: O(|Live Set|)
|
||||||
return ta.updateFrontiers()
|
return ta.updateFrontiers()
|
||||||
}
|
}
|
||||||
|
@ -157,7 +163,7 @@ func (ta *Topological) Finalized() bool { return ta.cg.Finalized() }
|
||||||
// the non-transitively applied votes. Also returns the list of leaf nodes.
|
// the non-transitively applied votes. Also returns the list of leaf nodes.
|
||||||
func (ta *Topological) calculateInDegree(
|
func (ta *Topological) calculateInDegree(
|
||||||
responses ids.UniqueBag) (map[[32]byte]kahnNode, []ids.ID) {
|
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{}
|
leaves := ids.Set{}
|
||||||
|
|
||||||
for _, vote := range responses.List() {
|
for _, vote := range responses.List() {
|
||||||
|
@ -231,6 +237,7 @@ func (ta *Topological) pushVotes(
|
||||||
kahnNodes map[[32]byte]kahnNode,
|
kahnNodes map[[32]byte]kahnNode,
|
||||||
leaves []ids.ID) ids.Bag {
|
leaves []ids.ID) ids.Bag {
|
||||||
votes := make(ids.UniqueBag)
|
votes := make(ids.UniqueBag)
|
||||||
|
txConflicts := make(map[[32]byte]ids.Set, minMapSize)
|
||||||
|
|
||||||
for len(leaves) > 0 {
|
for len(leaves) > 0 {
|
||||||
newLeavesSize := len(leaves) - 1
|
newLeavesSize := len(leaves) - 1
|
||||||
|
@ -245,6 +252,12 @@ func (ta *Topological) pushVotes(
|
||||||
// Give the votes to the consumer
|
// Give the votes to the consumer
|
||||||
txID := tx.ID()
|
txID := tx.ID()
|
||||||
votes.UnionSet(txID, kahn.votes)
|
votes.UnionSet(txID, kahn.votes)
|
||||||
|
|
||||||
|
// Map txID to set of Conflicts
|
||||||
|
txKey := txID.Key()
|
||||||
|
if _, exists := txConflicts[txKey]; !exists {
|
||||||
|
txConflicts[txKey] = ta.cg.Conflicts(tx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dep := range vtx.Parents() {
|
for _, dep := range vtx.Parents() {
|
||||||
|
@ -265,6 +278,18 @@ func (ta *Topological) pushVotes(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create bag of votes for conflicting transactions
|
||||||
|
conflictingVotes := make(ids.UniqueBag)
|
||||||
|
for txHash, conflicts := range txConflicts {
|
||||||
|
txID := ids.NewID(txHash)
|
||||||
|
for conflictTxHash := range conflicts {
|
||||||
|
conflictTxID := ids.NewID(conflictTxHash)
|
||||||
|
conflictingVotes.UnionSet(txID, votes.GetSet(conflictTxID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
votes.Difference(&conflictingVotes)
|
||||||
|
|
||||||
return votes.Bag(ta.params.Alpha)
|
return votes.Bag(ta.params.Alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,9 +447,9 @@ func (ta *Topological) updateFrontiers() error {
|
||||||
ta.preferred.Clear()
|
ta.preferred.Clear()
|
||||||
ta.virtuous.Clear()
|
ta.virtuous.Clear()
|
||||||
ta.orphans.Clear()
|
ta.orphans.Clear()
|
||||||
ta.frontier = make(map[[32]byte]Vertex)
|
ta.frontier = make(map[[32]byte]Vertex, minMapSize)
|
||||||
ta.preferenceCache = make(map[[32]byte]bool)
|
ta.preferenceCache = make(map[[32]byte]bool, minMapSize)
|
||||||
ta.virtuousCache = make(map[[32]byte]bool)
|
ta.virtuousCache = make(map[[32]byte]bool, minMapSize)
|
||||||
|
|
||||||
ta.orphans.Union(ta.cg.Virtuous()) // Initially, nothing is preferred
|
ta.orphans.Union(ta.cg.Virtuous()) // Initially, nothing is preferred
|
||||||
|
|
||||||
|
|
|
@ -4,758 +4,7 @@
|
||||||
package avalanche
|
package avalanche
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"testing"
|
"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 TestTopological(t *testing.T) { ConsensusTest(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 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Vtx struct {
|
||||||
height uint64
|
height uint64
|
||||||
status choices.Status
|
status choices.Status
|
||||||
|
|
||||||
|
Validity error
|
||||||
bytes []byte
|
bytes []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,9 +29,8 @@ func (v *Vtx) Parents() []Vertex { return v.dependencies }
|
||||||
func (v *Vtx) Height() uint64 { return v.height }
|
func (v *Vtx) Height() uint64 { return v.height }
|
||||||
func (v *Vtx) Txs() []snowstorm.Tx { return v.txs }
|
func (v *Vtx) Txs() []snowstorm.Tx { return v.txs }
|
||||||
func (v *Vtx) Status() choices.Status { return v.status }
|
func (v *Vtx) Status() choices.Status { return v.status }
|
||||||
func (v *Vtx) Live() {}
|
func (v *Vtx) Accept() error { v.status = choices.Accepted; return v.Validity }
|
||||||
func (v *Vtx) Accept() error { v.status = choices.Accepted; return nil }
|
func (v *Vtx) Reject() error { v.status = choices.Rejected; return v.Validity }
|
||||||
func (v *Vtx) Reject() error { v.status = choices.Rejected; return nil }
|
|
||||||
func (v *Vtx) Bytes() []byte { return v.bytes }
|
func (v *Vtx) Bytes() []byte { return v.bytes }
|
||||||
|
|
||||||
type sortVts []*Vtx
|
type sortVts []*Vtx
|
||||||
|
|
|
@ -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() }
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,6 +11,46 @@ import (
|
||||||
"github.com/ava-labs/gecko/ids"
|
"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 (
|
var (
|
||||||
Red = ids.Empty.Prefix(0)
|
Red = ids.Empty.Prefix(0)
|
||||||
Blue = ids.Empty.Prefix(1)
|
Blue = ids.Empty.Prefix(1)
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (f *Flat) Parameters() Parameters { return f.params }
|
||||||
// RecordPoll implements the Consensus interface
|
// RecordPoll implements the Consensus interface
|
||||||
func (f *Flat) RecordPoll(votes ids.Bag) {
|
func (f *Flat) RecordPoll(votes ids.Bag) {
|
||||||
if pollMode, numVotes := votes.Mode(); numVotes >= f.params.Alpha {
|
if pollMode, numVotes := votes.Mode(); numVotes >= f.params.Alpha {
|
||||||
f.nnarySnowball.RecordSuccessfulPoll(pollMode)
|
f.RecordSuccessfulPoll(pollMode)
|
||||||
} else {
|
} else {
|
||||||
f.RecordUnsuccessfulPoll()
|
f.RecordUnsuccessfulPoll()
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (sf *nnarySnowflake) RecordSuccessfulPoll(choice ids.ID) {
|
||||||
return // This instace is already decided.
|
return // This instace is already decided.
|
||||||
}
|
}
|
||||||
|
|
||||||
if preference := sf.nnarySlush.Preference(); preference.Equals(choice) {
|
if preference := sf.Preference(); preference.Equals(choice) {
|
||||||
sf.confidence++
|
sf.confidence++
|
||||||
} else {
|
} else {
|
||||||
// confidence is set to 1 because there has already been 1 successful
|
// confidence is set to 1 because there has already been 1 successful
|
||||||
|
|
|
@ -125,14 +125,14 @@ func TestParametersAnotherInvalidBetaRogue(t *testing.T) {
|
||||||
|
|
||||||
func TestParametersInvalidConcurrentRepolls(t *testing.T) {
|
func TestParametersInvalidConcurrentRepolls(t *testing.T) {
|
||||||
tests := []Parameters{
|
tests := []Parameters{
|
||||||
Parameters{
|
{
|
||||||
K: 1,
|
K: 1,
|
||||||
Alpha: 1,
|
Alpha: 1,
|
||||||
BetaVirtuous: 1,
|
BetaVirtuous: 1,
|
||||||
BetaRogue: 1,
|
BetaRogue: 1,
|
||||||
ConcurrentRepolls: 2,
|
ConcurrentRepolls: 2,
|
||||||
},
|
},
|
||||||
Parameters{
|
{
|
||||||
K: 1,
|
K: 1,
|
||||||
Alpha: 1,
|
Alpha: 1,
|
||||||
BetaVirtuous: 1,
|
BetaVirtuous: 1,
|
||||||
|
|
|
@ -27,11 +27,13 @@ func (sb *unarySnowball) Extend(beta int, choice int) BinarySnowball {
|
||||||
bs := &binarySnowball{
|
bs := &binarySnowball{
|
||||||
binarySnowflake: binarySnowflake{
|
binarySnowflake: binarySnowflake{
|
||||||
binarySlush: binarySlush{preference: choice},
|
binarySlush: binarySlush{preference: choice},
|
||||||
|
confidence: sb.confidence,
|
||||||
beta: beta,
|
beta: beta,
|
||||||
finalized: sb.Finalized(),
|
finalized: sb.Finalized(),
|
||||||
},
|
},
|
||||||
preference: choice,
|
preference: choice,
|
||||||
}
|
}
|
||||||
|
bs.numSuccessfulPolls[choice] = sb.numSuccessfulPolls
|
||||||
return bs
|
return bs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,11 +42,32 @@ func TestUnarySnowball(t *testing.T) {
|
||||||
|
|
||||||
binarySnowball := sbClone.Extend(beta, 0)
|
binarySnowball := sbClone.Extend(beta, 0)
|
||||||
|
|
||||||
|
expected := "SB(Preference = 0, NumSuccessfulPolls[0] = 2, NumSuccessfulPolls[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0)))"
|
||||||
|
if result := binarySnowball.String(); result != expected {
|
||||||
|
t.Fatalf("Expected:\n%s\nReturned:\n%s", expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
binarySnowball.RecordUnsuccessfulPoll()
|
binarySnowball.RecordUnsuccessfulPoll()
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
if binarySnowball.Preference() != 0 {
|
||||||
|
t.Fatalf("Wrong preference")
|
||||||
|
} else if binarySnowball.Finalized() {
|
||||||
|
t.Fatalf("Should not have finalized")
|
||||||
|
}
|
||||||
|
binarySnowball.RecordSuccessfulPoll(1)
|
||||||
|
binarySnowball.RecordUnsuccessfulPoll()
|
||||||
|
}
|
||||||
|
|
||||||
|
if binarySnowball.Preference() != 1 {
|
||||||
|
t.Fatalf("Wrong preference")
|
||||||
|
} else if binarySnowball.Finalized() {
|
||||||
|
t.Fatalf("Should not have finalized")
|
||||||
|
}
|
||||||
|
|
||||||
binarySnowball.RecordSuccessfulPoll(1)
|
binarySnowball.RecordSuccessfulPoll(1)
|
||||||
|
if binarySnowball.Preference() != 1 {
|
||||||
if binarySnowball.Finalized() {
|
t.Fatalf("Wrong preference")
|
||||||
|
} else if binarySnowball.Finalized() {
|
||||||
t.Fatalf("Should not have finalized")
|
t.Fatalf("Should not have finalized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,4 +78,9 @@ func TestUnarySnowball(t *testing.T) {
|
||||||
} else if !binarySnowball.Finalized() {
|
} else if !binarySnowball.Finalized() {
|
||||||
t.Fatalf("Should have finalized")
|
t.Fatalf("Should have finalized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expected = "SB(NumSuccessfulPolls = 2, SF(Confidence = 1, Finalized = false))"
|
||||||
|
if str := sb.String(); str != expected {
|
||||||
|
t.Fatalf("Wrong state. Expected:\n%s\nGot:\n%s", expected, str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ type TestBlock struct {
|
||||||
height int
|
height int
|
||||||
status choices.Status
|
status choices.Status
|
||||||
bytes []byte
|
bytes []byte
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *TestBlock) Parent() Block { return b.parent }
|
func (b *TestBlock) Parent() Block { return b.parent }
|
||||||
|
@ -27,16 +28,16 @@ func (b *TestBlock) Accept() error {
|
||||||
return errors.New("Dis-agreement")
|
return errors.New("Dis-agreement")
|
||||||
}
|
}
|
||||||
b.status = choices.Accepted
|
b.status = choices.Accepted
|
||||||
return nil
|
return b.err
|
||||||
}
|
}
|
||||||
func (b *TestBlock) Reject() error {
|
func (b *TestBlock) Reject() error {
|
||||||
if b.status.Decided() && b.status != choices.Rejected {
|
if b.status.Decided() && b.status != choices.Rejected {
|
||||||
return errors.New("Dis-agreement")
|
return errors.New("Dis-agreement")
|
||||||
}
|
}
|
||||||
b.status = choices.Rejected
|
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 }
|
func (b *TestBlock) Bytes() []byte { return b.bytes }
|
||||||
|
|
||||||
type sortBlocks []*TestBlock
|
type sortBlocks []*TestBlock
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package snowman
|
package snowman
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -42,6 +43,10 @@ var (
|
||||||
MetricsProcessingErrorTest,
|
MetricsProcessingErrorTest,
|
||||||
MetricsAcceptedErrorTest,
|
MetricsAcceptedErrorTest,
|
||||||
MetricsRejectedErrorTest,
|
MetricsRejectedErrorTest,
|
||||||
|
ErrorOnInitialRejectionTest,
|
||||||
|
ErrorOnAcceptTest,
|
||||||
|
ErrorOnRejectSiblingTest,
|
||||||
|
ErrorOnTransitiveRejectionTest,
|
||||||
RandomizedConsistencyTest,
|
RandomizedConsistencyTest,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -71,11 +76,9 @@ func InitializeTest(t *testing.T, factory Factory) {
|
||||||
|
|
||||||
if p := sm.Parameters(); p != params {
|
if p := sm.Parameters(); p != params {
|
||||||
t.Fatalf("Wrong returned parameters")
|
t.Fatalf("Wrong returned parameters")
|
||||||
}
|
} else if pref := sm.Preference(); !pref.Equals(GenesisID) {
|
||||||
if pref := sm.Preference(); !pref.Equals(GenesisID) {
|
|
||||||
t.Fatalf("Wrong preference returned")
|
t.Fatalf("Wrong preference returned")
|
||||||
}
|
} else if !sm.Finalized() {
|
||||||
if !sm.Finalized() {
|
|
||||||
t.Fatalf("Wrong should have marked the instance as being 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
|
// Adding to the previous preference will update the preference
|
||||||
sm.Add(block)
|
if err := sm.Add(block); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if pref := sm.Preference(); !pref.Equals(block.id) {
|
} else if pref := sm.Preference(); !pref.Equals(block.id) {
|
||||||
t.Fatalf("Wrong preference. Expected %s, got %s", block.id, pref)
|
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
|
// Adding to the previous preference will update the preference
|
||||||
sm.Add(firstBlock)
|
if err := sm.Add(firstBlock); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||||
t.Fatalf("Wrong preference. Expected %s, got %s", firstBlock.id, pref)
|
t.Fatalf("Wrong preference. Expected %s, got %s", firstBlock.id, pref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding to something other than the previous preference won't update the
|
// Adding to something other than the previous preference won't update the
|
||||||
// preference
|
// preference
|
||||||
sm.Add(secondBlock)
|
if err := sm.Add(secondBlock); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||||
t.Fatalf("Wrong preference. Expected %s, got %s", firstBlock.id, pref)
|
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
|
// Adding a block with an unknown parent means the parent must have already
|
||||||
// been rejected. Therefore the block should be immediately rejected
|
// been rejected. Therefore the block should be immediately rejected
|
||||||
sm.Add(block)
|
if err := sm.Add(block); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if pref := sm.Preference(); !pref.Equals(GenesisID) {
|
} else if pref := sm.Preference(); !pref.Equals(GenesisID) {
|
||||||
t.Fatalf("Wrong preference. Expected %s, got %s", GenesisID, pref)
|
t.Fatalf("Wrong preference. Expected %s, got %s", GenesisID, pref)
|
||||||
} else if status := block.Status(); status != choices.Rejected {
|
} else if status := block.Status(); status != choices.Rejected {
|
||||||
t.Fatalf("Should have rejected the block")
|
t.Fatalf("Should have rejected the block")
|
||||||
|
@ -269,9 +272,9 @@ func IssuedIssuedTest(t *testing.T, factory Factory) {
|
||||||
status: choices.Processing,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(block)
|
if err := sm.Add(block); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if !sm.Issued(block) {
|
} else if !sm.Issued(block) {
|
||||||
t.Fatalf("Should have marked a pending block as having been issued")
|
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,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(block)
|
if err := sm.Add(block); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
votes := ids.Bag{}
|
votes := ids.Bag{}
|
||||||
votes.Add(block.id)
|
votes.Add(block.id)
|
||||||
|
if err := sm.RecordPoll(votes); err != nil {
|
||||||
sm.RecordPoll(votes)
|
t.Fatal(err)
|
||||||
|
} else if pref := sm.Preference(); !pref.Equals(block.id) {
|
||||||
if pref := sm.Preference(); !pref.Equals(block.id) {
|
|
||||||
t.Fatalf("Preference returned the wrong block")
|
t.Fatalf("Preference returned the wrong block")
|
||||||
} else if sm.Finalized() {
|
} else if sm.Finalized() {
|
||||||
t.Fatalf("Snowman instance finalized too soon")
|
t.Fatalf("Snowman instance finalized too soon")
|
||||||
} else if status := block.Status(); status != choices.Processing {
|
} else if status := block.Status(); status != choices.Processing {
|
||||||
t.Fatalf("Block's status changed unexpectedly")
|
t.Fatalf("Block's status changed unexpectedly")
|
||||||
}
|
} else if err := sm.RecordPoll(votes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
sm.RecordPoll(votes)
|
} else if pref := sm.Preference(); !pref.Equals(block.id) {
|
||||||
|
|
||||||
if pref := sm.Preference(); !pref.Equals(block.id) {
|
|
||||||
t.Fatalf("Preference returned the wrong block")
|
t.Fatalf("Preference returned the wrong block")
|
||||||
} else if !sm.Finalized() {
|
} else if !sm.Finalized() {
|
||||||
t.Fatalf("Snowman instance didn't finalize")
|
t.Fatalf("Snowman instance didn't finalize")
|
||||||
|
@ -347,15 +349,18 @@ func RecordPollAcceptAndRejectTest(t *testing.T, factory Factory) {
|
||||||
status: choices.Processing,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(firstBlock)
|
if err := sm.Add(firstBlock); err != nil {
|
||||||
sm.Add(secondBlock)
|
t.Fatal(err)
|
||||||
|
} else if err := sm.Add(secondBlock); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
votes := ids.Bag{}
|
votes := ids.Bag{}
|
||||||
votes.Add(firstBlock.id)
|
votes.Add(firstBlock.id)
|
||||||
|
|
||||||
sm.RecordPoll(votes)
|
if err := sm.RecordPoll(votes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||||
t.Fatalf("Preference returned the wrong block")
|
t.Fatalf("Preference returned the wrong block")
|
||||||
} else if sm.Finalized() {
|
} else if sm.Finalized() {
|
||||||
t.Fatalf("Snowman instance finalized too soon")
|
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")
|
t.Fatalf("Block's status changed unexpectedly")
|
||||||
} else if status := secondBlock.Status(); status != choices.Processing {
|
} else if status := secondBlock.Status(); status != choices.Processing {
|
||||||
t.Fatalf("Block's status changed unexpectedly")
|
t.Fatalf("Block's status changed unexpectedly")
|
||||||
}
|
} else if err := sm.RecordPoll(votes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
sm.RecordPoll(votes)
|
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||||
|
|
||||||
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
|
||||||
t.Fatalf("Preference returned the wrong block")
|
t.Fatalf("Preference returned the wrong block")
|
||||||
} else if !sm.Finalized() {
|
} else if !sm.Finalized() {
|
||||||
t.Fatalf("Snowman instance didn't finalize")
|
t.Fatalf("Snowman instance didn't finalize")
|
||||||
|
@ -394,9 +397,9 @@ func RecordPollWhenFinalizedTest(t *testing.T, factory Factory) {
|
||||||
|
|
||||||
votes := ids.Bag{}
|
votes := ids.Bag{}
|
||||||
votes.Add(GenesisID)
|
votes.Add(GenesisID)
|
||||||
sm.RecordPoll(votes)
|
if err := sm.RecordPoll(votes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if !sm.Finalized() {
|
} else if !sm.Finalized() {
|
||||||
t.Fatalf("Consensus should still be finalized")
|
t.Fatalf("Consensus should still be finalized")
|
||||||
} else if pref := sm.Preference(); !GenesisID.Equals(pref) {
|
} else if pref := sm.Preference(); !GenesisID.Equals(pref) {
|
||||||
t.Fatalf("Wrong preference listed")
|
t.Fatalf("Wrong preference listed")
|
||||||
|
@ -433,9 +436,13 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) {
|
||||||
status: choices.Processing,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(block0)
|
if err := sm.Add(block0); err != nil {
|
||||||
sm.Add(block1)
|
t.Fatal(err)
|
||||||
sm.Add(block2)
|
} 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:
|
// Current graph structure:
|
||||||
// G
|
// G
|
||||||
|
@ -447,7 +454,9 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) {
|
||||||
|
|
||||||
votes := ids.Bag{}
|
votes := ids.Bag{}
|
||||||
votes.Add(block0.id)
|
votes.Add(block0.id)
|
||||||
sm.RecordPoll(votes)
|
if err := sm.RecordPoll(votes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Current graph structure:
|
// Current graph structure:
|
||||||
// 0
|
// 0
|
||||||
|
@ -457,9 +466,7 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) {
|
||||||
t.Fatalf("Finalized too late")
|
t.Fatalf("Finalized too late")
|
||||||
} else if pref := sm.Preference(); !block0.id.Equals(pref) {
|
} else if pref := sm.Preference(); !block0.id.Equals(pref) {
|
||||||
t.Fatalf("Wrong preference listed")
|
t.Fatalf("Wrong preference listed")
|
||||||
}
|
} else if status := block0.Status(); status != choices.Accepted {
|
||||||
|
|
||||||
if status := block0.Status(); status != choices.Accepted {
|
|
||||||
t.Fatalf("Wrong status returned")
|
t.Fatalf("Wrong status returned")
|
||||||
} else if status := block1.Status(); status != choices.Rejected {
|
} else if status := block1.Status(); status != choices.Rejected {
|
||||||
t.Fatalf("Wrong status returned")
|
t.Fatalf("Wrong status returned")
|
||||||
|
@ -503,10 +510,15 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) {
|
||||||
status: choices.Processing,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(block0)
|
if err := sm.Add(block0); err != nil {
|
||||||
sm.Add(block1)
|
t.Fatal(err)
|
||||||
sm.Add(block2)
|
} else if err := sm.Add(block1); err != nil {
|
||||||
sm.Add(block3)
|
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:
|
// Current graph structure:
|
||||||
// G
|
// G
|
||||||
|
@ -517,26 +529,24 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) {
|
||||||
|
|
||||||
votesFor2 := ids.Bag{}
|
votesFor2 := ids.Bag{}
|
||||||
votesFor2.Add(block2.id)
|
votesFor2.Add(block2.id)
|
||||||
sm.RecordPoll(votesFor2)
|
if err := sm.RecordPoll(votesFor2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if sm.Finalized() {
|
} else if sm.Finalized() {
|
||||||
t.Fatalf("Finalized too early")
|
t.Fatalf("Finalized too early")
|
||||||
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
||||||
t.Fatalf("Wrong preference listed")
|
t.Fatalf("Wrong preference listed")
|
||||||
}
|
}
|
||||||
|
|
||||||
emptyVotes := ids.Bag{}
|
emptyVotes := ids.Bag{}
|
||||||
sm.RecordPoll(emptyVotes)
|
if err := sm.RecordPoll(emptyVotes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if sm.Finalized() {
|
} else if sm.Finalized() {
|
||||||
t.Fatalf("Finalized too early")
|
t.Fatalf("Finalized too early")
|
||||||
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
||||||
t.Fatalf("Wrong preference listed")
|
t.Fatalf("Wrong preference listed")
|
||||||
}
|
} else if err := sm.RecordPoll(votesFor2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
sm.RecordPoll(votesFor2)
|
} else if sm.Finalized() {
|
||||||
|
|
||||||
if sm.Finalized() {
|
|
||||||
t.Fatalf("Finalized too early")
|
t.Fatalf("Finalized too early")
|
||||||
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
||||||
t.Fatalf("Wrong preference listed")
|
t.Fatalf("Wrong preference listed")
|
||||||
|
@ -544,23 +554,19 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) {
|
||||||
|
|
||||||
votesFor3 := ids.Bag{}
|
votesFor3 := ids.Bag{}
|
||||||
votesFor3.Add(block3.id)
|
votesFor3.Add(block3.id)
|
||||||
sm.RecordPoll(votesFor3)
|
if err := sm.RecordPoll(votesFor3); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if sm.Finalized() {
|
} else if sm.Finalized() {
|
||||||
t.Fatalf("Finalized too early")
|
t.Fatalf("Finalized too early")
|
||||||
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
||||||
t.Fatalf("Wrong preference listed")
|
t.Fatalf("Wrong preference listed")
|
||||||
}
|
} else if err := sm.RecordPoll(votesFor3); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
sm.RecordPoll(votesFor3)
|
} else if !sm.Finalized() {
|
||||||
|
|
||||||
if !sm.Finalized() {
|
|
||||||
t.Fatalf("Finalized too late")
|
t.Fatalf("Finalized too late")
|
||||||
} else if pref := sm.Preference(); !block3.id.Equals(pref) {
|
} else if pref := sm.Preference(); !block3.id.Equals(pref) {
|
||||||
t.Fatalf("Wrong preference listed")
|
t.Fatalf("Wrong preference listed")
|
||||||
}
|
} else if status := block0.Status(); status != choices.Rejected {
|
||||||
|
|
||||||
if status := block0.Status(); status != choices.Rejected {
|
|
||||||
t.Fatalf("Wrong status returned")
|
t.Fatalf("Wrong status returned")
|
||||||
} else if status := block1.Status(); status != choices.Accepted {
|
} else if status := block1.Status(); status != choices.Accepted {
|
||||||
t.Fatalf("Wrong status returned")
|
t.Fatalf("Wrong status returned")
|
||||||
|
@ -592,19 +598,23 @@ func RecordPollInvalidVoteTest(t *testing.T, factory Factory) {
|
||||||
}
|
}
|
||||||
unknownBlockID := ids.Empty.Prefix(2)
|
unknownBlockID := ids.Empty.Prefix(2)
|
||||||
|
|
||||||
sm.Add(block)
|
if err := sm.Add(block); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
validVotes := ids.Bag{}
|
validVotes := ids.Bag{}
|
||||||
validVotes.Add(block.id)
|
validVotes.Add(block.id)
|
||||||
sm.RecordPoll(validVotes)
|
if err := sm.RecordPoll(validVotes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
invalidVotes := ids.Bag{}
|
invalidVotes := ids.Bag{}
|
||||||
invalidVotes.Add(unknownBlockID)
|
invalidVotes.Add(unknownBlockID)
|
||||||
sm.RecordPoll(invalidVotes)
|
if err := sm.RecordPoll(invalidVotes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
sm.RecordPoll(validVotes)
|
} else if err := sm.RecordPoll(validVotes); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if sm.Finalized() {
|
} else if sm.Finalized() {
|
||||||
t.Fatalf("Finalized too early")
|
t.Fatalf("Finalized too early")
|
||||||
} else if pref := sm.Preference(); !block.id.Equals(pref) {
|
} else if pref := sm.Preference(); !block.id.Equals(pref) {
|
||||||
t.Fatalf("Wrong preference listed")
|
t.Fatalf("Wrong preference listed")
|
||||||
|
@ -651,11 +661,17 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) {
|
||||||
status: choices.Processing,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(block0)
|
if err := sm.Add(block0); err != nil {
|
||||||
sm.Add(block1)
|
t.Fatal(err)
|
||||||
sm.Add(block2)
|
} else if err := sm.Add(block1); err != nil {
|
||||||
sm.Add(block3)
|
t.Fatal(err)
|
||||||
sm.Add(block4)
|
} 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:
|
// Current graph structure:
|
||||||
// G
|
// G
|
||||||
|
@ -668,10 +684,14 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) {
|
||||||
// Tail = 2
|
// Tail = 2
|
||||||
|
|
||||||
votes0_2_4 := ids.Bag{}
|
votes0_2_4 := ids.Bag{}
|
||||||
votes0_2_4.Add(block0.id)
|
votes0_2_4.Add(
|
||||||
votes0_2_4.Add(block2.id)
|
block0.id,
|
||||||
votes0_2_4.Add(block4.id)
|
block2.id,
|
||||||
sm.RecordPoll(votes0_2_4)
|
block4.id,
|
||||||
|
)
|
||||||
|
if err := sm.RecordPoll(votes0_2_4); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Current graph structure:
|
// Current graph structure:
|
||||||
// 0
|
// 0
|
||||||
|
@ -699,7 +719,9 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) {
|
||||||
|
|
||||||
dep2_2_2 := ids.Bag{}
|
dep2_2_2 := ids.Bag{}
|
||||||
dep2_2_2.AddCount(block2.id, 3)
|
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:
|
// Current graph structure:
|
||||||
// 2
|
// 2
|
||||||
|
@ -757,20 +779,25 @@ func RecordPollDivergedVotingTest(t *testing.T, factory Factory) {
|
||||||
status: choices.Processing,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(block0)
|
if err := sm.Add(block0); err != nil {
|
||||||
sm.Add(block1)
|
t.Fatal(err)
|
||||||
|
} else if err := sm.Add(block1); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
votes0 := ids.Bag{}
|
votes0 := ids.Bag{}
|
||||||
votes0.Add(block0.id)
|
votes0.Add(block0.id)
|
||||||
sm.RecordPoll(votes0)
|
if err := sm.RecordPoll(votes0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
sm.Add(block2)
|
} else if err := sm.Add(block2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// dep2 is already rejected.
|
// dep2 is already rejected.
|
||||||
|
|
||||||
sm.Add(block3)
|
if err := sm.Add(block3); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if status := block0.Status(); status == choices.Accepted {
|
} else if status := block0.Status(); status == choices.Accepted {
|
||||||
t.Fatalf("Shouldn't be accepted yet")
|
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.
|
// dep0. Because dep2 is already rejected, this will accept dep0.
|
||||||
votes3 := ids.Bag{}
|
votes3 := ids.Bag{}
|
||||||
votes3.Add(block3.id)
|
votes3.Add(block3.id)
|
||||||
sm.RecordPoll(votes3)
|
if err := sm.RecordPoll(votes3); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if !sm.Finalized() {
|
} else if !sm.Finalized() {
|
||||||
t.Fatalf("Finalized too late")
|
t.Fatalf("Finalized too late")
|
||||||
} else if status := block0.Status(); status != choices.Accepted {
|
} else if status := block0.Status(); status != choices.Accepted {
|
||||||
t.Fatalf("Should be accepted")
|
t.Fatalf("Should be accepted")
|
||||||
|
@ -818,14 +845,15 @@ func MetricsProcessingErrorTest(t *testing.T, factory Factory) {
|
||||||
status: choices.Processing,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(block)
|
if err := sm.Add(block); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
votes := ids.Bag{}
|
votes := ids.Bag{}
|
||||||
votes.Add(block.id)
|
votes.Add(block.id)
|
||||||
|
if err := sm.RecordPoll(votes); err != nil {
|
||||||
sm.RecordPoll(votes)
|
t.Fatal(err)
|
||||||
|
} else if !sm.Finalized() {
|
||||||
if !sm.Finalized() {
|
|
||||||
t.Fatalf("Snowman instance didn't finalize")
|
t.Fatalf("Snowman instance didn't finalize")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -861,14 +889,15 @@ func MetricsAcceptedErrorTest(t *testing.T, factory Factory) {
|
||||||
status: choices.Processing,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(block)
|
if err := sm.Add(block); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
votes := ids.Bag{}
|
votes := ids.Bag{}
|
||||||
votes.Add(block.id)
|
votes.Add(block.id)
|
||||||
|
if err := sm.RecordPoll(votes); err != nil {
|
||||||
sm.RecordPoll(votes)
|
t.Fatal(err)
|
||||||
|
} else if !sm.Finalized() {
|
||||||
if !sm.Finalized() {
|
|
||||||
t.Fatalf("Snowman instance didn't finalize")
|
t.Fatalf("Snowman instance didn't finalize")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -904,18 +933,171 @@ func MetricsRejectedErrorTest(t *testing.T, factory Factory) {
|
||||||
status: choices.Processing,
|
status: choices.Processing,
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.Add(block)
|
if err := sm.Add(block); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
votes := ids.Bag{}
|
votes := ids.Bag{}
|
||||||
votes.Add(block.id)
|
votes.Add(block.id)
|
||||||
|
if err := sm.RecordPoll(votes); err != nil {
|
||||||
sm.RecordPoll(votes)
|
t.Fatal(err)
|
||||||
|
} else if !sm.Finalized() {
|
||||||
if !sm.Finalized() {
|
|
||||||
t.Fatalf("Snowman instance didn't finalize")
|
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) {
|
func RandomizedConsistencyTest(t *testing.T, factory Factory) {
|
||||||
numColors := 50
|
numColors := 50
|
||||||
numNodes := 100
|
numNodes := 100
|
||||||
|
|
|
@ -9,6 +9,10 @@ import (
|
||||||
"github.com/ava-labs/gecko/snow/consensus/snowball"
|
"github.com/ava-labs/gecko/snow/consensus/snowball"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minMapSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
// TopologicalFactory implements Factory by returning a topological struct
|
// TopologicalFactory implements Factory by returning a topological struct
|
||||||
type TopologicalFactory 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.
|
// the non-transitively applied votes. Also returns the list of leaf blocks.
|
||||||
func (ts *Topological) calculateInDegree(
|
func (ts *Topological) calculateInDegree(
|
||||||
votes ids.Bag) (map[[32]byte]kahnNode, []ids.ID) {
|
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{}
|
leaves := ids.Set{}
|
||||||
|
|
||||||
for _, vote := range votes.List() {
|
for _, vote := range votes.List() {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package snowstorm
|
package snowstorm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
@ -19,6 +20,28 @@ var (
|
||||||
Green = &TestTx{Identifier: ids.Empty.Prefix(1)}
|
Green = &TestTx{Identifier: ids.Empty.Prefix(1)}
|
||||||
Blue = &TestTx{Identifier: ids.Empty.Prefix(2)}
|
Blue = &TestTx{Identifier: ids.Empty.Prefix(2)}
|
||||||
Alpha = &TestTx{Identifier: ids.Empty.Prefix(3)}
|
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
|
// R - G - B - A
|
||||||
|
@ -46,6 +69,52 @@ func Setup() {
|
||||||
Alpha.Reset()
|
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) {
|
func ParamsTest(t *testing.T, factory Factory) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
|
@ -81,15 +150,13 @@ func IssuedTest(t *testing.T, factory Factory) {
|
||||||
|
|
||||||
if issued := graph.Issued(Red); issued {
|
if issued := graph.Issued(Red); issued {
|
||||||
t.Fatalf("Haven't issued anything yet.")
|
t.Fatalf("Haven't issued anything yet.")
|
||||||
}
|
} else if err := graph.Add(Red); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.Add(Red)
|
} else if issued := graph.Issued(Red); !issued {
|
||||||
|
|
||||||
if issued := graph.Issued(Red); !issued {
|
|
||||||
t.Fatalf("Have already issued.")
|
t.Fatalf("Have already issued.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Blue.Accept()
|
_ = Blue.Accept()
|
||||||
|
|
||||||
if issued := graph.Issued(Blue); !issued {
|
if issued := graph.Issued(Blue); !issued {
|
||||||
t.Fatalf("Have already accepted.")
|
t.Fatalf("Have already accepted.")
|
||||||
|
@ -106,10 +173,12 @@ func LeftoverInputTest(t *testing.T, factory Factory) {
|
||||||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
||||||
}
|
}
|
||||||
graph.Initialize(snow.DefaultContextTest(), params)
|
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.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Red.ID()) {
|
} else if !prefs.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s got %s", Red.ID(), prefs.List()[0])
|
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 := ids.Bag{}
|
||||||
r.SetThreshold(2)
|
r.SetThreshold(2)
|
||||||
r.AddCount(Red.ID(), 2)
|
r.AddCount(Red.ID(), 2)
|
||||||
graph.RecordPoll(r)
|
if err := graph.RecordPoll(r); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !graph.Finalized() {
|
} else if !graph.Finalized() {
|
||||||
t.Fatalf("Finalized too late")
|
t.Fatalf("Finalized too late")
|
||||||
}
|
} else if Red.Status() != choices.Accepted {
|
||||||
|
|
||||||
if Red.Status() != choices.Accepted {
|
|
||||||
t.Fatalf("%s should have been accepted", Red.ID())
|
t.Fatalf("%s should have been accepted", Red.ID())
|
||||||
} else if Green.Status() != choices.Rejected {
|
} else if Green.Status() != choices.Rejected {
|
||||||
t.Fatalf("%s should have been rejected", Green.ID())
|
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,
|
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
||||||
}
|
}
|
||||||
graph.Initialize(snow.DefaultContextTest(), params)
|
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.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Red.ID()) {
|
} else if !prefs.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s got %s", Red.ID(), prefs.List()[0])
|
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 := ids.Bag{}
|
||||||
r.SetThreshold(2)
|
r.SetThreshold(2)
|
||||||
r.AddCount(Red.ID(), 2)
|
r.AddCount(Red.ID(), 2)
|
||||||
graph.RecordPoll(r)
|
if err := graph.RecordPoll(r); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 1 {
|
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Blue.ID()) {
|
} else if !prefs.Contains(Blue.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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,
|
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
||||||
}
|
}
|
||||||
graph.Initialize(snow.DefaultContextTest(), params)
|
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.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Red.ID()) {
|
} else if !prefs.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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 := ids.Bag{}
|
||||||
r.SetThreshold(2)
|
r.SetThreshold(2)
|
||||||
r.AddCount(Red.ID(), 2)
|
r.AddCount(Red.ID(), 2)
|
||||||
graph.RecordPoll(r)
|
if err := graph.RecordPoll(r); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 1 {
|
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Alpha.ID()) {
|
} else if !prefs.Contains(Alpha.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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")
|
t.Fatalf("Finalized too early")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IndependentTest(t *testing.T, factory Factory) {
|
func IndependentTest(t *testing.T, factory Factory) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
|
@ -219,10 +294,12 @@ func IndependentTest(t *testing.T, factory Factory) {
|
||||||
K: 2, Alpha: 2, BetaVirtuous: 2, BetaRogue: 2,
|
K: 2, Alpha: 2, BetaVirtuous: 2, BetaRogue: 2,
|
||||||
}
|
}
|
||||||
graph.Initialize(snow.DefaultContextTest(), params)
|
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.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Red.ID()) {
|
} else if !prefs.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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.SetThreshold(2)
|
||||||
ra.AddCount(Red.ID(), 2)
|
ra.AddCount(Red.ID(), 2)
|
||||||
ra.AddCount(Alpha.ID(), 2)
|
ra.AddCount(Alpha.ID(), 2)
|
||||||
graph.RecordPoll(ra)
|
if err := graph.RecordPoll(ra); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Red.ID()) {
|
} else if !prefs.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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())
|
t.Fatalf("Wrong preference. Expected %s", Alpha.ID())
|
||||||
} else if graph.Finalized() {
|
} else if graph.Finalized() {
|
||||||
t.Fatalf("Finalized too early")
|
t.Fatalf("Finalized too early")
|
||||||
}
|
} else if err := graph.RecordPoll(ra); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.RecordPoll(ra)
|
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||||
|
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !graph.Finalized() {
|
} else if !graph.Finalized() {
|
||||||
t.Fatalf("Finalized too late")
|
t.Fatalf("Finalized too late")
|
||||||
|
@ -267,35 +342,30 @@ func VirtuousTest(t *testing.T, factory Factory) {
|
||||||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
||||||
}
|
}
|
||||||
graph.Initialize(snow.DefaultContextTest(), params)
|
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.")
|
t.Fatalf("Wrong number of virtuous.")
|
||||||
} else if !virtuous.Contains(Red.ID()) {
|
} else if !virtuous.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong virtuous. Expected %s", Red.ID())
|
t.Fatalf("Wrong virtuous. Expected %s", Red.ID())
|
||||||
}
|
} else if err := graph.Add(Alpha); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.Add(Alpha)
|
} else if virtuous := graph.Virtuous(); virtuous.Len() != 2 {
|
||||||
|
|
||||||
if virtuous := graph.Virtuous(); virtuous.Len() != 2 {
|
|
||||||
t.Fatalf("Wrong number of virtuous.")
|
t.Fatalf("Wrong number of virtuous.")
|
||||||
} else if !virtuous.Contains(Red.ID()) {
|
} else if !virtuous.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong virtuous. Expected %s", Red.ID())
|
t.Fatalf("Wrong virtuous. Expected %s", Red.ID())
|
||||||
} else if !virtuous.Contains(Alpha.ID()) {
|
} else if !virtuous.Contains(Alpha.ID()) {
|
||||||
t.Fatalf("Wrong virtuous. Expected %s", Alpha.ID())
|
t.Fatalf("Wrong virtuous. Expected %s", Alpha.ID())
|
||||||
}
|
} else if err := graph.Add(Green); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.Add(Green)
|
} else if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
|
||||||
|
|
||||||
if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
|
|
||||||
t.Fatalf("Wrong number of virtuous.")
|
t.Fatalf("Wrong number of virtuous.")
|
||||||
} else if !virtuous.Contains(Alpha.ID()) {
|
} else if !virtuous.Contains(Alpha.ID()) {
|
||||||
t.Fatalf("Wrong virtuous. Expected %s", Alpha.ID())
|
t.Fatalf("Wrong virtuous. Expected %s", Alpha.ID())
|
||||||
}
|
} else if err := graph.Add(Blue); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.Add(Blue)
|
} else if virtuous := graph.Virtuous(); virtuous.Len() != 0 {
|
||||||
|
|
||||||
if virtuous := graph.Virtuous(); virtuous.Len() != 0 {
|
|
||||||
t.Fatalf("Wrong number of virtuous.")
|
t.Fatalf("Wrong number of virtuous.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,11 +389,9 @@ func IsVirtuousTest(t *testing.T, factory Factory) {
|
||||||
t.Fatalf("Should be virtuous")
|
t.Fatalf("Should be virtuous")
|
||||||
} else if !graph.IsVirtuous(Alpha) {
|
} else if !graph.IsVirtuous(Alpha) {
|
||||||
t.Fatalf("Should be virtuous")
|
t.Fatalf("Should be virtuous")
|
||||||
}
|
} else if err := graph.Add(Red); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.Add(Red)
|
} else if !graph.IsVirtuous(Red) {
|
||||||
|
|
||||||
if !graph.IsVirtuous(Red) {
|
|
||||||
t.Fatalf("Should be virtuous")
|
t.Fatalf("Should be virtuous")
|
||||||
} else if graph.IsVirtuous(Green) {
|
} else if graph.IsVirtuous(Green) {
|
||||||
t.Fatalf("Should not be virtuous")
|
t.Fatalf("Should not be virtuous")
|
||||||
|
@ -331,11 +399,9 @@ func IsVirtuousTest(t *testing.T, factory Factory) {
|
||||||
t.Fatalf("Should be virtuous")
|
t.Fatalf("Should be virtuous")
|
||||||
} else if !graph.IsVirtuous(Alpha) {
|
} else if !graph.IsVirtuous(Alpha) {
|
||||||
t.Fatalf("Should be virtuous")
|
t.Fatalf("Should be virtuous")
|
||||||
}
|
} else if err := graph.Add(Green); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.Add(Green)
|
} else if graph.IsVirtuous(Red) {
|
||||||
|
|
||||||
if graph.IsVirtuous(Red) {
|
|
||||||
t.Fatalf("Should not be virtuous")
|
t.Fatalf("Should not be virtuous")
|
||||||
} else if graph.IsVirtuous(Green) {
|
} else if graph.IsVirtuous(Green) {
|
||||||
t.Fatalf("Should not be virtuous")
|
t.Fatalf("Should not be virtuous")
|
||||||
|
@ -357,17 +423,13 @@ func QuiesceTest(t *testing.T, factory Factory) {
|
||||||
|
|
||||||
if !graph.Quiesce() {
|
if !graph.Quiesce() {
|
||||||
t.Fatalf("Should quiesce")
|
t.Fatalf("Should quiesce")
|
||||||
}
|
} else if err := graph.Add(Red); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.Add(Red)
|
} else if graph.Quiesce() {
|
||||||
|
|
||||||
if graph.Quiesce() {
|
|
||||||
t.Fatalf("Shouldn't quiesce")
|
t.Fatalf("Shouldn't quiesce")
|
||||||
}
|
} else if err := graph.Add(Green); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.Add(Green)
|
} else if !graph.Quiesce() {
|
||||||
|
|
||||||
if !graph.Quiesce() {
|
|
||||||
t.Fatalf("Should quiesce")
|
t.Fatalf("Should quiesce")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,11 +452,13 @@ func AcceptingDependencyTest(t *testing.T, factory Factory) {
|
||||||
}
|
}
|
||||||
graph.Initialize(snow.DefaultContextTest(), params)
|
graph.Initialize(snow.DefaultContextTest(), params)
|
||||||
|
|
||||||
graph.Add(Red)
|
if err := graph.Add(Red); err != nil {
|
||||||
graph.Add(Green)
|
t.Fatal(err)
|
||||||
graph.Add(purple)
|
} else if err := graph.Add(Green); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
} 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.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Red.ID()) {
|
} else if !prefs.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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 := ids.Bag{}
|
||||||
g.Add(Green.ID())
|
g.Add(Green.ID())
|
||||||
|
if err := graph.RecordPoll(g); err != nil {
|
||||||
graph.RecordPoll(g)
|
t.Fatal(err)
|
||||||
|
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Green.ID()) {
|
} else if !prefs.Contains(Green.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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 := ids.Bag{}
|
||||||
rp.Add(Red.ID(), purple.ID())
|
rp.Add(Red.ID(), purple.ID())
|
||||||
|
if err := graph.RecordPoll(rp); err != nil {
|
||||||
graph.RecordPoll(rp)
|
t.Fatal(err)
|
||||||
|
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Green.ID()) {
|
} else if !prefs.Contains(Green.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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 := ids.Bag{}
|
||||||
r.Add(Red.ID())
|
r.Add(Red.ID())
|
||||||
|
if err := graph.RecordPoll(r); err != nil {
|
||||||
graph.RecordPoll(r)
|
t.Fatal(err)
|
||||||
|
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if Red.Status() != choices.Accepted {
|
} else if Red.Status() != choices.Accepted {
|
||||||
t.Fatalf("Wrong status. %s should be %s", Red.ID(), 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.Initialize(snow.DefaultContextTest(), params)
|
||||||
|
|
||||||
graph.Add(Red)
|
if err := graph.Add(Red); err != nil {
|
||||||
graph.Add(Green)
|
t.Fatal(err)
|
||||||
graph.Add(Blue)
|
} else if err := graph.Add(Green); err != nil {
|
||||||
graph.Add(purple)
|
t.Fatal(err)
|
||||||
|
} else if err := graph.Add(Blue); err != nil {
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
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.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Red.ID()) {
|
} else if !prefs.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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 := ids.Bag{}
|
||||||
gp.Add(Green.ID(), purple.ID())
|
gp.Add(Green.ID(), purple.ID())
|
||||||
|
if err := graph.RecordPoll(gp); err != nil {
|
||||||
graph.RecordPoll(gp)
|
t.Fatal(err)
|
||||||
|
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Green.ID()) {
|
} else if !prefs.Contains(Green.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", 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)
|
t.Fatalf("Wrong status. %s should be %s", Blue.ID(), choices.Processing)
|
||||||
} else if purple.Status() != choices.Processing {
|
} else if purple.Status() != choices.Processing {
|
||||||
t.Fatalf("Wrong status. %s should be %s", purple.ID(), choices.Processing)
|
t.Fatalf("Wrong status. %s should be %s", purple.ID(), choices.Processing)
|
||||||
}
|
} else if err := graph.RecordPoll(gp); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.RecordPoll(gp)
|
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||||
|
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if Red.Status() != choices.Rejected {
|
} else if Red.Status() != choices.Rejected {
|
||||||
t.Fatalf("Wrong status. %s should be %s", Red.ID(), 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.Initialize(snow.DefaultContextTest(), params)
|
||||||
|
|
||||||
graph.Add(purple)
|
if err := graph.Add(purple); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||||
t.Fatalf("Wrong number of preferences.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if status := purple.Status(); status != choices.Accepted {
|
} else if status := purple.Status(); status != choices.Accepted {
|
||||||
t.Fatalf("Wrong status. %s should be %s", purple.ID(), 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,
|
Ins: insPurple,
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.Add(purple)
|
if err := graph.Add(purple); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
|
} else if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
|
||||||
t.Fatalf("Wrong number of conflicts")
|
t.Fatalf("Wrong number of conflicts")
|
||||||
} else if !orangeConflicts.Contains(purple.Identifier) {
|
} else if !orangeConflicts.Contains(purple.Identifier) {
|
||||||
t.Fatalf("Conflicts does not contain the right transaction")
|
t.Fatalf("Conflicts does not contain the right transaction")
|
||||||
}
|
} else if err := graph.Add(orange); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
graph.Add(orange)
|
} else if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
|
||||||
|
|
||||||
if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
|
|
||||||
t.Fatalf("Wrong number of conflicts")
|
t.Fatalf("Wrong number of conflicts")
|
||||||
} else if !orangeConflicts.Contains(purple.Identifier) {
|
} else if !orangeConflicts.Contains(purple.Identifier) {
|
||||||
t.Fatalf("Conflicts does not contain the right transaction")
|
t.Fatalf("Conflicts does not contain the right transaction")
|
||||||
|
@ -643,17 +702,20 @@ func VirtuousDependsOnRogueTest(t *testing.T, factory Factory) {
|
||||||
|
|
||||||
virtuous.Ins.Add(input2)
|
virtuous.Ins.Add(input2)
|
||||||
|
|
||||||
graph.Add(rogue1)
|
if err := graph.Add(rogue1); err != nil {
|
||||||
graph.Add(rogue2)
|
t.Fatal(err)
|
||||||
graph.Add(virtuous)
|
} 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 := ids.Bag{}
|
||||||
votes.Add(rogue1.ID())
|
votes.Add(rogue1.ID())
|
||||||
votes.Add(virtuous.ID())
|
votes.Add(virtuous.ID())
|
||||||
|
if err := graph.RecordPoll(votes); err != nil {
|
||||||
graph.RecordPoll(votes)
|
t.Fatal(err)
|
||||||
|
} else if status := rogue1.Status(); status != choices.Processing {
|
||||||
if status := rogue1.Status(); status != choices.Processing {
|
|
||||||
t.Fatalf("Rogue Tx is %s expected %s", status, choices.Processing)
|
t.Fatalf("Rogue Tx is %s expected %s", status, choices.Processing)
|
||||||
} else if status := rogue2.Status(); status != choices.Processing {
|
} else if status := rogue2.Status(); status != choices.Processing {
|
||||||
t.Fatalf("Rogue Tx is %s expected %s", 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) {
|
func StringTest(t *testing.T, factory Factory, prefix string) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
|
@ -674,12 +865,16 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
||||||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
|
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
|
||||||
}
|
}
|
||||||
graph.Initialize(snow.DefaultContextTest(), params)
|
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.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !prefs.Contains(Red.ID()) {
|
} else if !prefs.Contains(Red.ID()) {
|
||||||
t.Fatalf("Wrong preference. Expected %s got %s", Red.ID(), prefs.List()[0])
|
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.SetThreshold(2)
|
||||||
rb.AddCount(Red.ID(), 2)
|
rb.AddCount(Red.ID(), 2)
|
||||||
rb.AddCount(Blue.ID(), 2)
|
rb.AddCount(Blue.ID(), 2)
|
||||||
graph.RecordPoll(rb)
|
if err := graph.RecordPoll(rb); err != nil {
|
||||||
graph.Add(Blue)
|
t.Fatal(err)
|
||||||
|
} else if err := graph.Add(Blue); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
expected := prefix + "(\n" +
|
expected := prefix + "(\n" +
|
||||||
|
@ -720,7 +918,9 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
||||||
ga.SetThreshold(2)
|
ga.SetThreshold(2)
|
||||||
ga.AddCount(Green.ID(), 2)
|
ga.AddCount(Green.ID(), 2)
|
||||||
ga.AddCount(Alpha.ID(), 2)
|
ga.AddCount(Alpha.ID(), 2)
|
||||||
graph.RecordPoll(ga)
|
if err := graph.RecordPoll(ga); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
expected := prefix + "(\n" +
|
expected := prefix + "(\n" +
|
||||||
|
@ -745,7 +945,9 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
empty := ids.Bag{}
|
empty := ids.Bag{}
|
||||||
graph.RecordPoll(empty)
|
if err := graph.RecordPoll(empty); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
expected := prefix + "(\n" +
|
expected := prefix + "(\n" +
|
||||||
|
@ -767,10 +969,10 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
||||||
t.Fatalf("Wrong preference. Expected %s", Blue.ID())
|
t.Fatalf("Wrong preference. Expected %s", Blue.ID())
|
||||||
} else if graph.Finalized() {
|
} else if graph.Finalized() {
|
||||||
t.Fatalf("Finalized too early")
|
t.Fatalf("Finalized too early")
|
||||||
|
} else if err := graph.RecordPoll(ga); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.RecordPoll(ga)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
expected := prefix + "(\n" +
|
expected := prefix + "(\n" +
|
||||||
" Choice[0] = ID: LUC1cmcxnfNR9LdkACS2ccGKLEK7SYqB4gLLTycQfg1koyfSq Confidence: 0 Bias: 1\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())
|
t.Fatalf("Wrong preference. Expected %s", Alpha.ID())
|
||||||
} else if graph.Finalized() {
|
} else if graph.Finalized() {
|
||||||
t.Fatalf("Finalized too early")
|
t.Fatalf("Finalized too early")
|
||||||
|
} else if err := graph.RecordPoll(ga); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.RecordPoll(ga)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
expected := prefix + "()"
|
expected := prefix + "()"
|
||||||
if str := graph.String(); str != expected {
|
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.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !graph.Finalized() {
|
} else if !graph.Finalized() {
|
||||||
t.Fatalf("Finalized too late")
|
t.Fatalf("Finalized too late")
|
||||||
}
|
} else if Green.Status() != choices.Accepted {
|
||||||
|
|
||||||
if Green.Status() != choices.Accepted {
|
|
||||||
t.Fatalf("%s should have been accepted", Green.ID())
|
t.Fatalf("%s should have been accepted", Green.ID())
|
||||||
} else if Alpha.Status() != choices.Accepted {
|
} else if Alpha.Status() != choices.Accepted {
|
||||||
t.Fatalf("%s should have been accepted", Alpha.ID())
|
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())
|
t.Fatalf("%s should have been rejected", Red.ID())
|
||||||
} else if Blue.Status() != choices.Rejected {
|
} else if Blue.Status() != choices.Rejected {
|
||||||
t.Fatalf("%s should have been rejected", Blue.ID())
|
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 + "()"
|
expected := prefix + "()"
|
||||||
if str := graph.String(); str != expected {
|
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.")
|
t.Fatalf("Wrong number of preferences.")
|
||||||
} else if !graph.Finalized() {
|
} else if !graph.Finalized() {
|
||||||
t.Fatalf("Finalized too late")
|
t.Fatalf("Finalized too late")
|
||||||
}
|
} else if Green.Status() != choices.Accepted {
|
||||||
|
|
||||||
if Green.Status() != choices.Accepted {
|
|
||||||
t.Fatalf("%s should have been accepted", Green.ID())
|
t.Fatalf("%s should have been accepted", Green.ID())
|
||||||
} else if Alpha.Status() != choices.Accepted {
|
} else if Alpha.Status() != choices.Accepted {
|
||||||
t.Fatalf("%s should have been accepted", Alpha.ID())
|
t.Fatalf("%s should have been accepted", Alpha.ID())
|
||||||
|
|
|
@ -7,34 +7,4 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDirectedParams(t *testing.T) { ParamsTest(t, DirectedFactory{}) }
|
func TestDirectedConsensus(t *testing.T) { ConsensusTest(t, DirectedFactory{}, "DG") }
|
||||||
|
|
||||||
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") }
|
|
||||||
|
|
|
@ -7,32 +7,4 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInputParams(t *testing.T) { ParamsTest(t, InputFactory{}) }
|
func TestInputConsensus(t *testing.T) { ConsensusTest(t, InputFactory{}, "IG") }
|
||||||
|
|
||||||
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") }
|
|
||||||
|
|
|
@ -31,10 +31,10 @@ func (tx *TestTx) InputIDs() ids.Set { return tx.Ins }
|
||||||
func (tx *TestTx) Status() choices.Status { return tx.Stat }
|
func (tx *TestTx) Status() choices.Status { return tx.Stat }
|
||||||
|
|
||||||
// Accept implements the Consumer interface
|
// 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
|
// 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
|
// Reset sets the status to pending
|
||||||
func (tx *TestTx) Reset() { tx.Stat = choices.Processing }
|
func (tx *TestTx) Reset() { tx.Stat = choices.Processing }
|
||||||
|
|
|
@ -17,6 +17,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// We cache processed vertices where height = c * stripeDistance for c = {1,2,3...}
|
||||||
|
// This forms a "stripe" of cached DAG vertices at height stripeDistance, 2*stripeDistance, etc.
|
||||||
|
// This helps to limit the number of repeated DAG traversals performed
|
||||||
|
stripeDistance = 2000
|
||||||
|
stripeWidth = 5
|
||||||
cacheSize = 100000
|
cacheSize = 100000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,15 +42,16 @@ type bootstrapper struct {
|
||||||
metrics
|
metrics
|
||||||
common.Bootstrapper
|
common.Bootstrapper
|
||||||
|
|
||||||
// true if all of the vertices in the original accepted frontier have been processed
|
|
||||||
processedStartingAcceptedFrontier bool
|
|
||||||
|
|
||||||
// number of vertices fetched so far
|
// number of vertices fetched so far
|
||||||
numFetched uint32
|
numFetched uint32
|
||||||
|
|
||||||
// tracks which validators were asked for which containers in which requests
|
// tracks which validators were asked for which containers in which requests
|
||||||
outstandingRequests common.Requests
|
outstandingRequests common.Requests
|
||||||
|
|
||||||
|
// IDs of vertices that we will send a GetAncestors request for once we are
|
||||||
|
// not at the max number of outstanding requests
|
||||||
|
needToFetch ids.Set
|
||||||
|
|
||||||
// Contains IDs of vertices that have recently been processed
|
// Contains IDs of vertices that have recently been processed
|
||||||
processedCache *cache.LRU
|
processedCache *cache.LRU
|
||||||
|
|
||||||
|
@ -80,14 +86,15 @@ func (b *bootstrapper) Initialize(config BootstrapConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentAcceptedFrontier ...
|
// CurrentAcceptedFrontier returns the set of vertices that this node has accepted
|
||||||
|
// that have no accepted children
|
||||||
func (b *bootstrapper) CurrentAcceptedFrontier() ids.Set {
|
func (b *bootstrapper) CurrentAcceptedFrontier() ids.Set {
|
||||||
acceptedFrontier := ids.Set{}
|
acceptedFrontier := ids.Set{}
|
||||||
acceptedFrontier.Add(b.State.Edge()...)
|
acceptedFrontier.Add(b.State.Edge()...)
|
||||||
return acceptedFrontier
|
return acceptedFrontier
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterAccepted ...
|
// FilterAccepted returns the IDs of vertices in [containerIDs] that this node has accepted
|
||||||
func (b *bootstrapper) FilterAccepted(containerIDs ids.Set) ids.Set {
|
func (b *bootstrapper) FilterAccepted(containerIDs ids.Set) ids.Set {
|
||||||
acceptedVtxIDs := ids.Set{}
|
acceptedVtxIDs := ids.Set{}
|
||||||
for _, vtxID := range containerIDs.List() {
|
for _, vtxID := range containerIDs.List() {
|
||||||
|
@ -98,16 +105,23 @@ func (b *bootstrapper) FilterAccepted(containerIDs ids.Set) ids.Set {
|
||||||
return acceptedVtxIDs
|
return acceptedVtxIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get vertex [vtxID] and its ancestors
|
// Add the vertices in [vtxIDs] to the set of vertices that we need to fetch,
|
||||||
func (b *bootstrapper) fetch(vtxID ids.ID) error {
|
// and then fetch vertices (and their ancestors) until either there are no more
|
||||||
// Make sure we haven't already requested this block
|
// to fetch or we are at the maximum number of outstanding requests.
|
||||||
|
func (b *bootstrapper) fetch(vtxIDs ...ids.ID) error {
|
||||||
|
b.needToFetch.Add(vtxIDs...)
|
||||||
|
for b.needToFetch.Len() > 0 && b.outstandingRequests.Len() < common.MaxOutstandingRequests {
|
||||||
|
vtxID := b.needToFetch.CappedList(1)[0]
|
||||||
|
b.needToFetch.Remove(vtxID)
|
||||||
|
|
||||||
|
// Make sure we haven't already requested this vertex
|
||||||
if b.outstandingRequests.Contains(vtxID) {
|
if b.outstandingRequests.Contains(vtxID) {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we don't already have this vertex
|
// Make sure we don't already have this vertex
|
||||||
if _, err := b.State.GetVertex(vtxID); err == nil {
|
if _, err := b.State.GetVertex(vtxID); err == nil {
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
validators := b.BootstrapConfig.Validators.Sample(1) // validator to send request to
|
validators := b.BootstrapConfig.Validators.Sample(1) // validator to send request to
|
||||||
|
@ -119,29 +133,36 @@ func (b *bootstrapper) fetch(vtxID ids.ID) error {
|
||||||
|
|
||||||
b.outstandingRequests.Add(validatorID, b.RequestID, vtxID)
|
b.outstandingRequests.Add(validatorID, b.RequestID, vtxID)
|
||||||
b.BootstrapConfig.Sender.GetAncestors(validatorID, b.RequestID, vtxID) // request vertex and ancestors
|
b.BootstrapConfig.Sender.GetAncestors(validatorID, b.RequestID, vtxID) // request vertex and ancestors
|
||||||
return nil
|
}
|
||||||
|
return b.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process vertices
|
// Process the vertices in [vtxs].
|
||||||
func (b *bootstrapper) process(vtx avalanche.Vertex) error {
|
func (b *bootstrapper) process(vtxs ...avalanche.Vertex) error {
|
||||||
toProcess := []avalanche.Vertex{vtx}
|
// Vertices that we need to process. Store them in a heap for de-deduplication
|
||||||
for len(toProcess) > 0 {
|
// and so we always process vertices further down in the DAG first. This helps
|
||||||
newLen := len(toProcess) - 1
|
// to reduce the number of repeated DAG traversals.
|
||||||
vtx := toProcess[newLen]
|
toProcess := newMaxVertexHeap()
|
||||||
toProcess = toProcess[:newLen]
|
for _, vtx := range vtxs {
|
||||||
if _, ok := b.processedCache.Get(vtx.ID()); ok { // already processed this
|
if _, ok := b.processedCache.Get(vtx.ID()); !ok { // only process a vertex if we haven't already
|
||||||
continue
|
toProcess.Push(vtx)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for toProcess.Len() > 0 { // While there are unprocessed vertices
|
||||||
|
vtx := toProcess.Pop() // Get an unknown vertex or one furthest down the DAG
|
||||||
|
vtxID := vtx.ID()
|
||||||
|
|
||||||
switch vtx.Status() {
|
switch vtx.Status() {
|
||||||
case choices.Unknown:
|
case choices.Unknown:
|
||||||
if err := b.fetch(vtx.ID()); err != nil {
|
b.needToFetch.Add(vtxID) // We don't have this vertex locally. Mark that we need to fetch it.
|
||||||
return err
|
|
||||||
}
|
|
||||||
case choices.Rejected:
|
case choices.Rejected:
|
||||||
|
b.needToFetch.Remove(vtxID) // We have this vertex locally. Mark that we don't need to fetch it.
|
||||||
return fmt.Errorf("tried to accept %s even though it was previously rejected", vtx.ID())
|
return fmt.Errorf("tried to accept %s even though it was previously rejected", vtx.ID())
|
||||||
case choices.Processing:
|
case choices.Processing:
|
||||||
if err := b.VtxBlocked.Push(&vertexJob{
|
b.needToFetch.Remove(vtxID)
|
||||||
|
|
||||||
|
if err := b.VtxBlocked.Push(&vertexJob{ // Add to queue of vertices to execute when bootstrapping finishes.
|
||||||
log: b.BootstrapConfig.Context.Log,
|
log: b.BootstrapConfig.Context.Log,
|
||||||
numAccepted: b.numBSVtx,
|
numAccepted: b.numBSVtx,
|
||||||
numDropped: b.numBSDroppedVtx,
|
numDropped: b.numBSDroppedVtx,
|
||||||
|
@ -155,7 +176,7 @@ func (b *bootstrapper) process(vtx avalanche.Vertex) error {
|
||||||
} else {
|
} else {
|
||||||
b.BootstrapConfig.Context.Log.Verbo("couldn't push to vtxBlocked: %s", err)
|
b.BootstrapConfig.Context.Log.Verbo("couldn't push to vtxBlocked: %s", err)
|
||||||
}
|
}
|
||||||
for _, tx := range vtx.Txs() {
|
for _, tx := range vtx.Txs() { // Add transactions to queue of transactions to execute when bootstrapping finishes.
|
||||||
if err := b.TxBlocked.Push(&txJob{
|
if err := b.TxBlocked.Push(&txJob{
|
||||||
log: b.BootstrapConfig.Context.Log,
|
log: b.BootstrapConfig.Context.Log,
|
||||||
numAccepted: b.numBSTx,
|
numAccepted: b.numBSTx,
|
||||||
|
@ -167,12 +188,16 @@ func (b *bootstrapper) process(vtx avalanche.Vertex) error {
|
||||||
b.BootstrapConfig.Context.Log.Verbo("couldn't push to txBlocked: %s", err)
|
b.BootstrapConfig.Context.Log.Verbo("couldn't push to txBlocked: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, parent := range vtx.Parents() {
|
for _, parent := range vtx.Parents() { // Process the parents of this vertex (traverse up the DAG)
|
||||||
toProcess = append(toProcess, parent)
|
if _, ok := b.processedCache.Get(parent.ID()); !ok { // But only if we haven't processed the parent
|
||||||
|
toProcess.Push(parent)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if vtx.Height()%stripeDistance < stripeWidth { // See comment for stripeDistance
|
||||||
b.processedCache.Put(vtx.ID(), nil)
|
b.processedCache.Put(vtx.ID(), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := b.VtxBlocked.Commit(); err != nil {
|
if err := b.VtxBlocked.Commit(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -181,10 +206,7 @@ func (b *bootstrapper) process(vtx avalanche.Vertex) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if numPending := b.outstandingRequests.Len(); numPending == 0 && b.processedStartingAcceptedFrontier {
|
return b.fetch()
|
||||||
return b.finish()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultiPut handles the receipt of multiple containers. Should be received in response to a GetAncestors message to [vdr]
|
// MultiPut handles the receipt of multiple containers. Should be received in response to a GetAncestors message to [vdr]
|
||||||
|
@ -200,12 +222,12 @@ func (b *bootstrapper) MultiPut(vdr ids.ShortID, requestID uint32, vtxs [][]byte
|
||||||
|
|
||||||
// Make sure this is in response to a request we made
|
// Make sure this is in response to a request we made
|
||||||
neededVtxID, needed := b.outstandingRequests.Remove(vdr, requestID)
|
neededVtxID, needed := b.outstandingRequests.Remove(vdr, requestID)
|
||||||
if !needed { // this message isn't in response to a request we made
|
if !needed { // this message isn't in response to a request we made, or is in response to a request that timed out
|
||||||
b.BootstrapConfig.Context.Log.Debug("received unexpected MultiPut from %s with ID %d", vdr, requestID)
|
b.BootstrapConfig.Context.Log.Debug("received unexpected MultiPut from %s with ID %d", vdr, requestID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
neededVtx, err := b.State.ParseVertex(vtxs[0]) // the vertex we requested
|
neededVtx, err := b.State.ParseVertex(vtxs[0]) // first vertex should be the one we requested in GetAncestors request
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.BootstrapConfig.Context.Log.Debug("Failed to parse requested vertex %s: %w", neededVtxID, err)
|
b.BootstrapConfig.Context.Log.Debug("Failed to parse requested vertex %s: %w", neededVtxID, err)
|
||||||
b.BootstrapConfig.Context.Log.Verbo("vertex: %s", formatting.DumpBytes{Bytes: vtxs[0]})
|
b.BootstrapConfig.Context.Log.Verbo("vertex: %s", formatting.DumpBytes{Bytes: vtxs[0]})
|
||||||
|
@ -215,14 +237,20 @@ func (b *bootstrapper) MultiPut(vdr ids.ShortID, requestID uint32, vtxs [][]byte
|
||||||
return b.fetch(neededVtxID)
|
return b.fetch(neededVtxID)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, vtxBytes := range vtxs { // Parse/persist all the vertices
|
processVertices := make([]avalanche.Vertex, 1, len(vtxs)) // Process all of the vertices in this message
|
||||||
if _, err := b.State.ParseVertex(vtxBytes); err != nil { // Persists the vtx
|
processVertices[0] = neededVtx
|
||||||
|
|
||||||
|
for _, vtxBytes := range vtxs[1:] { // Parse/persist all the vertices
|
||||||
|
if vtx, err := b.State.ParseVertex(vtxBytes); err != nil { // Persists the vtx
|
||||||
b.BootstrapConfig.Context.Log.Debug("Failed to parse vertex: %w", err)
|
b.BootstrapConfig.Context.Log.Debug("Failed to parse vertex: %w", err)
|
||||||
b.BootstrapConfig.Context.Log.Verbo("vertex: %s", formatting.DumpBytes{Bytes: vtxBytes})
|
b.BootstrapConfig.Context.Log.Verbo("vertex: %s", formatting.DumpBytes{Bytes: vtxBytes})
|
||||||
|
} else {
|
||||||
|
processVertices = append(processVertices, vtx)
|
||||||
|
b.needToFetch.Remove(vtx.ID()) // No need to fetch this vertex since we have it now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.process(neededVtx)
|
return b.process(processVertices...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAncestorsFailed is called when a GetAncestors message we sent fails
|
// GetAncestorsFailed is called when a GetAncestors message we sent fails
|
||||||
|
@ -236,43 +264,38 @@ func (b *bootstrapper) GetAncestorsFailed(vdr ids.ShortID, requestID uint32) err
|
||||||
return b.fetch(vtxID)
|
return b.fetch(vtxID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForceAccepted ...
|
// ForceAccepted starts bootstrapping. Process the vertices in [accepterContainerIDs].
|
||||||
func (b *bootstrapper) ForceAccepted(acceptedContainerIDs ids.Set) error {
|
func (b *bootstrapper) ForceAccepted(acceptedContainerIDs ids.Set) error {
|
||||||
if err := b.VM.Bootstrapping(); err != nil {
|
if err := b.VM.Bootstrapping(); err != nil {
|
||||||
return fmt.Errorf("failed to notify VM that bootstrapping has started: %w",
|
return fmt.Errorf("failed to notify VM that bootstrapping has started: %w",
|
||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toProcess := make([]avalanche.Vertex, 0, acceptedContainerIDs.Len())
|
||||||
for _, vtxID := range acceptedContainerIDs.List() {
|
for _, vtxID := range acceptedContainerIDs.List() {
|
||||||
if vtx, err := b.State.GetVertex(vtxID); err == nil {
|
if vtx, err := b.State.GetVertex(vtxID); err == nil {
|
||||||
if err := b.process(vtx); err != nil {
|
toProcess = append(toProcess, vtx) // Process this vertex.
|
||||||
return err
|
} else {
|
||||||
}
|
b.needToFetch.Add(vtxID) // We don't have this vertex. Mark that we have to fetch it.
|
||||||
} else if err := b.fetch(vtxID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.processedStartingAcceptedFrontier = true
|
return b.process(toProcess...)
|
||||||
|
|
||||||
if numPending := b.outstandingRequests.Len(); numPending == 0 {
|
|
||||||
return b.finish()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finish bootstrapping
|
// Finish bootstrapping
|
||||||
func (b *bootstrapper) finish() error {
|
func (b *bootstrapper) finish() error {
|
||||||
if b.finished {
|
// If there are outstanding requests for vertices or we still need to fetch vertices, we can't finish
|
||||||
|
if b.finished || b.outstandingRequests.Len() > 0 || b.needToFetch.Len() > 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
b.BootstrapConfig.Context.Log.Info("finished fetching vertices. executing transaction state transitions...")
|
|
||||||
|
|
||||||
|
b.BootstrapConfig.Context.Log.Info("finished fetching %d vertices. executing transaction state transitions...",
|
||||||
|
b.numFetched)
|
||||||
if err := b.executeAll(b.TxBlocked, b.numBSBlockedTx); err != nil {
|
if err := b.executeAll(b.TxBlocked, b.numBSBlockedTx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.BootstrapConfig.Context.Log.Info("executing vertex state transitions...")
|
b.BootstrapConfig.Context.Log.Info("executing vertex state transitions...")
|
||||||
|
|
||||||
if err := b.executeAll(b.VtxBlocked, b.numBSBlockedVtx); err != nil {
|
if err := b.executeAll(b.VtxBlocked, b.numBSBlockedVtx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -307,5 +330,6 @@ func (b *bootstrapper) executeAll(jobs *queue.Jobs, numBlocked prometheus.Gauge)
|
||||||
b.BootstrapConfig.Context.Log.Info("executed %d operations", numExecuted)
|
b.BootstrapConfig.Context.Log.Info("executed %d operations", numExecuted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
b.BootstrapConfig.Context.Log.Info("executed %d operations", numExecuted)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -805,3 +805,113 @@ func TestBootstrapperIncompleteMultiPut(t *testing.T) {
|
||||||
t.Fatal("should be accepted")
|
t.Fatal("should be accepted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBootstrapperFinalized(t *testing.T) {
|
||||||
|
config, peerID, sender, state, vm := newConfig(t)
|
||||||
|
|
||||||
|
vtxID0 := ids.Empty.Prefix(0)
|
||||||
|
vtxID1 := ids.Empty.Prefix(1)
|
||||||
|
|
||||||
|
vtxBytes0 := []byte{0}
|
||||||
|
vtxBytes1 := []byte{1}
|
||||||
|
|
||||||
|
vtx0 := &Vtx{
|
||||||
|
id: vtxID0,
|
||||||
|
height: 0,
|
||||||
|
status: choices.Unknown,
|
||||||
|
bytes: vtxBytes0,
|
||||||
|
}
|
||||||
|
vtx1 := &Vtx{
|
||||||
|
id: vtxID1,
|
||||||
|
height: 1,
|
||||||
|
parents: []avalanche.Vertex{vtx0},
|
||||||
|
status: choices.Unknown,
|
||||||
|
bytes: vtxBytes1,
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := bootstrapper{}
|
||||||
|
bs.metrics.Initialize(config.Context.Log, fmt.Sprintf("gecko_%s", config.Context.ChainID), prometheus.NewRegistry())
|
||||||
|
bs.Initialize(config)
|
||||||
|
finished := new(bool)
|
||||||
|
bs.onFinished = func() error { *finished = true; return nil }
|
||||||
|
|
||||||
|
acceptedIDs := ids.Set{}
|
||||||
|
acceptedIDs.Add(vtxID0)
|
||||||
|
acceptedIDs.Add(vtxID1)
|
||||||
|
|
||||||
|
parsedVtx0 := false
|
||||||
|
parsedVtx1 := false
|
||||||
|
state.getVertex = func(vtxID ids.ID) (avalanche.Vertex, error) {
|
||||||
|
switch {
|
||||||
|
case vtxID.Equals(vtxID0):
|
||||||
|
if parsedVtx0 {
|
||||||
|
return vtx0, nil
|
||||||
|
}
|
||||||
|
return nil, errUnknownVertex
|
||||||
|
case vtxID.Equals(vtxID1):
|
||||||
|
if parsedVtx1 {
|
||||||
|
return vtx1, nil
|
||||||
|
}
|
||||||
|
return nil, errUnknownVertex
|
||||||
|
default:
|
||||||
|
t.Fatal(errUnknownVertex)
|
||||||
|
panic(errUnknownVertex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.parseVertex = func(vtxBytes []byte) (avalanche.Vertex, error) {
|
||||||
|
switch {
|
||||||
|
case bytes.Equal(vtxBytes, vtxBytes0):
|
||||||
|
vtx0.status = choices.Processing
|
||||||
|
parsedVtx0 = true
|
||||||
|
return vtx0, nil
|
||||||
|
case bytes.Equal(vtxBytes, vtxBytes1):
|
||||||
|
vtx1.status = choices.Processing
|
||||||
|
parsedVtx1 = true
|
||||||
|
return vtx1, nil
|
||||||
|
}
|
||||||
|
t.Fatal(errUnknownVertex)
|
||||||
|
return nil, errUnknownVertex
|
||||||
|
}
|
||||||
|
|
||||||
|
requestIDs := map[[32]byte]uint32{}
|
||||||
|
sender.GetAncestorsF = func(vdr ids.ShortID, reqID uint32, vtxID ids.ID) {
|
||||||
|
if !vdr.Equals(peerID) {
|
||||||
|
t.Fatalf("Should have requested block from %s, requested from %s", peerID, vdr)
|
||||||
|
}
|
||||||
|
requestIDs[vtxID.Key()] = reqID
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.CantBootstrapping = false
|
||||||
|
|
||||||
|
if err := bs.ForceAccepted(acceptedIDs); err != nil { // should request vtx0 and vtx1
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqID, ok := requestIDs[vtxID1.Key()]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("should have requested vtx1")
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.CantBootstrapped = false
|
||||||
|
|
||||||
|
if err := bs.MultiPut(peerID, reqID, [][]byte{vtxBytes1, vtxBytes0}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqID, ok = requestIDs[vtxID0.Key()]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("should have requested vtx0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bs.GetAncestorsFailed(peerID, reqID); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*finished {
|
||||||
|
t.Fatalf("Bootstrapping should have finished")
|
||||||
|
} else if vtx0.Status() != choices.Accepted {
|
||||||
|
t.Fatalf("Vertex should be accepted")
|
||||||
|
} else if vtx1.Status() != choices.Accepted {
|
||||||
|
t.Fatalf("Vertex should be accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -78,9 +78,12 @@ func (i *issuer) Update() {
|
||||||
vdrSet.Add(vdr.ID())
|
vdrSet.Add(vdr.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toSample := ids.ShortSet{} // Copy to a new variable because we may remove an element in sender.Sender
|
||||||
|
toSample.Union(vdrSet) // and we don't want that to affect the set of validators we wait for [ie vdrSet]
|
||||||
|
|
||||||
i.t.RequestID++
|
i.t.RequestID++
|
||||||
if numVdrs := len(vdrs); numVdrs == p.K && i.t.polls.Add(i.t.RequestID, vdrSet.Len()) {
|
if numVdrs := len(vdrs); numVdrs == p.K && i.t.polls.Add(i.t.RequestID, vdrSet) {
|
||||||
i.t.Config.Sender.PushQuery(vdrSet, i.t.RequestID, vtxID, i.vtx.Bytes())
|
i.t.Config.Sender.PushQuery(toSample, i.t.RequestID, vtxID, i.vtx.Bytes())
|
||||||
} else if numVdrs < p.K {
|
} else if numVdrs < p.K {
|
||||||
i.t.Config.Context.Log.Error("Query for %s was dropped due to an insufficient number of validators", vtxID)
|
i.t.Config.Context.Log.Error("Query for %s was dropped due to an insufficient number of validators", vtxID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ type metrics struct {
|
||||||
numBSVtx, numBSDroppedVtx,
|
numBSVtx, numBSDroppedVtx,
|
||||||
numBSTx, numBSDroppedTx prometheus.Counter
|
numBSTx, numBSDroppedTx prometheus.Counter
|
||||||
|
|
||||||
numPolls, numVtxRequests, numTxRequests, numPendingVtx prometheus.Gauge
|
numVtxRequests, numTxRequests, numPendingVtx prometheus.Gauge
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize implements the Engine interface
|
// 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",
|
Name: "av_bs_dropped_txs",
|
||||||
Help: "Number of 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(
|
m.numVtxRequests = prometheus.NewGauge(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: namespace,
|
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 {
|
if err := registerer.Register(m.numBSDroppedTx); err != nil {
|
||||||
log.Error("Failed to register av_bs_dropped_txs statistics due to %s", err)
|
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 {
|
if err := registerer.Register(m.numVtxRequests); err != nil {
|
||||||
log.Error("Failed to register av_vtx_requests statistics due to %s", err)
|
log.Error("Failed to register av_vtx_requests statistics due to %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,101 +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
|
|
||||||
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, numPolled int) bool {
|
|
||||||
poll, exists := p.m[requestID]
|
|
||||||
if !exists {
|
|
||||||
poll.numPending = numPolled
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
numPending int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vote registers a vote for this poll
|
|
||||||
func (p *poll) Vote(votes []ids.ID) {
|
|
||||||
if p.numPending > 0 {
|
|
||||||
p.numPending--
|
|
||||||
p.votes.Add(uint(p.numPending), votes...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finished returns true if the poll has completed, with no more required
|
|
||||||
// responses
|
|
||||||
func (p poll) Finished() bool { return p.numPending <= 0 }
|
|
||||||
func (p poll) String() string { return fmt.Sprintf("Waiting on %d chits", p.numPending) }
|
|
|
@ -54,6 +54,8 @@ func (vtx *uniqueVertex) refresh() {
|
||||||
func (vtx *uniqueVertex) Evict() {
|
func (vtx *uniqueVertex) Evict() {
|
||||||
if vtx.v != nil {
|
if vtx.v != nil {
|
||||||
vtx.v.unique = false
|
vtx.v.unique = false
|
||||||
|
// make sure the parents are able to be garbage collected
|
||||||
|
vtx.v.parents = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/ava-labs/gecko/snow/choices"
|
"github.com/ava-labs/gecko/snow/choices"
|
||||||
"github.com/ava-labs/gecko/snow/consensus/avalanche"
|
"github.com/ava-labs/gecko/snow/consensus/avalanche"
|
||||||
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
|
"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/engine/common"
|
||||||
"github.com/ava-labs/gecko/snow/events"
|
"github.com/ava-labs/gecko/snow/events"
|
||||||
"github.com/ava-labs/gecko/utils/formatting"
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
|
@ -31,7 +32,7 @@ type Transitive struct {
|
||||||
Config
|
Config
|
||||||
bootstrapper
|
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 prevents asking validators for the same vertex
|
||||||
vtxReqs common.Requests
|
vtxReqs common.Requests
|
||||||
|
@ -57,9 +58,12 @@ func (t *Transitive) Initialize(config Config) error {
|
||||||
|
|
||||||
t.onFinished = t.finishBootstrapping
|
t.onFinished = t.finishBootstrapping
|
||||||
|
|
||||||
t.polls.log = config.Context.Log
|
factory := poll.NewEarlyTermNoTraversalFactory(int(config.Params.Alpha))
|
||||||
t.polls.numPolls = t.numPolls
|
t.polls = poll.NewSet(factory,
|
||||||
t.polls.m = make(map[uint32]poll)
|
config.Context.Log,
|
||||||
|
config.Params.Namespace,
|
||||||
|
config.Params.Metrics,
|
||||||
|
)
|
||||||
|
|
||||||
return t.bootstrapper.Initialize(config.BootstrapConfig)
|
return t.bootstrapper.Initialize(config.BootstrapConfig)
|
||||||
}
|
}
|
||||||
|
@ -169,7 +173,11 @@ func (t *Transitive) Put(vdr ids.ShortID, requestID uint32, vtxID ids.ID, vtxByt
|
||||||
t.Config.Context.Log.Verbo("Put(%s, %d, %s) called", vdr, requestID, vtxID)
|
t.Config.Context.Log.Verbo("Put(%s, %d, %s) called", vdr, requestID, vtxID)
|
||||||
|
|
||||||
if !t.bootstrapped { // Bootstrapping unfinished --> didn't call Get --> this message is invalid
|
if !t.bootstrapped { // Bootstrapping unfinished --> didn't call Get --> this message is invalid
|
||||||
|
if requestID == network.GossipMsgRequestID {
|
||||||
|
t.Config.Context.Log.Verbo("dropping gossip Put(%s, %d, %s) due to bootstrapping", vdr, requestID, vtxID)
|
||||||
|
} else {
|
||||||
t.Config.Context.Log.Debug("dropping Put(%s, %d, %s) due to bootstrapping", vdr, requestID, vtxID)
|
t.Config.Context.Log.Debug("dropping Put(%s, %d, %s) due to bootstrapping", vdr, requestID, vtxID)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +315,7 @@ func (t *Transitive) Notify(msg common.Message) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transitive) repoll() 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +324,7 @@ func (t *Transitive) repoll() error {
|
||||||
return err
|
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 {
|
if err := t.batch(nil, false /*=force*/, true /*=empty*/); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -335,10 +343,10 @@ func (t *Transitive) reinsertFrom(vdr ids.ShortID, vtxID ids.ID) (bool, error) {
|
||||||
|
|
||||||
func (t *Transitive) insertFrom(vdr ids.ShortID, vtx avalanche.Vertex) (bool, error) {
|
func (t *Transitive) insertFrom(vdr ids.ShortID, vtx avalanche.Vertex) (bool, error) {
|
||||||
issued := true
|
issued := true
|
||||||
vts := []avalanche.Vertex{vtx}
|
vertexHeap := newMaxVertexHeap()
|
||||||
for len(vts) > 0 {
|
vertexHeap.Push(vtx)
|
||||||
vtx := vts[0]
|
for vertexHeap.Len() > 0 {
|
||||||
vts = vts[1:]
|
vtx := vertexHeap.Pop()
|
||||||
|
|
||||||
if t.Consensus.VertexIssued(vtx) {
|
if t.Consensus.VertexIssued(vtx) {
|
||||||
continue
|
continue
|
||||||
|
@ -353,7 +361,7 @@ func (t *Transitive) insertFrom(vdr ids.ShortID, vtx avalanche.Vertex) (bool, er
|
||||||
t.sendRequest(vdr, parent.ID())
|
t.sendRequest(vdr, parent.ID())
|
||||||
issued = false
|
issued = false
|
||||||
} else {
|
} else {
|
||||||
vts = append(vts, parent)
|
vertexHeap.Push(parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,8 +479,11 @@ func (t *Transitive) issueRepoll() {
|
||||||
vdrSet.Add(vdr.ID())
|
vdrSet.Add(vdr.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vdrCopy := ids.ShortSet{}
|
||||||
|
vdrCopy.Union((vdrSet))
|
||||||
|
|
||||||
t.RequestID++
|
t.RequestID++
|
||||||
if numVdrs := len(vdrs); numVdrs == p.K && t.polls.Add(t.RequestID, vdrSet.Len()) {
|
if numVdrs := len(vdrs); numVdrs == p.K && t.polls.Add(t.RequestID, vdrCopy) {
|
||||||
t.Config.Sender.PullQuery(vdrSet, t.RequestID, vtxID)
|
t.Config.Sender.PullQuery(vdrSet, t.RequestID, vtxID)
|
||||||
} else if numVdrs < p.K {
|
} else if numVdrs < p.K {
|
||||||
t.Config.Context.Log.Error("re-query for %s was dropped due to an insufficient number of validators", vtxID)
|
t.Config.Context.Log.Error("re-query for %s was dropped due to an insufficient number of validators", vtxID)
|
||||||
|
@ -510,3 +521,8 @@ func (t *Transitive) sendRequest(vdr ids.ShortID, vtxID ids.ID) {
|
||||||
|
|
||||||
t.numVtxRequests.Set(float64(t.vtxReqs.Len())) // Tracks performance statistics
|
t.numVtxRequests.Set(float64(t.vtxReqs.Len())) // Tracks performance statistics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsBootstrapped returns true iff this chain is done bootstrapping
|
||||||
|
func (t *Transitive) IsBootstrapped() bool {
|
||||||
|
return t.bootstrapped
|
||||||
|
}
|
||||||
|
|
|
@ -3085,3 +3085,120 @@ func TestEngineDuplicatedIssuance(t *testing.T) {
|
||||||
|
|
||||||
te.Notify(common.PendingTxs)
|
te.Notify(common.PendingTxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEngineDoubleChit(t *testing.T) {
|
||||||
|
config := DefaultConfig()
|
||||||
|
|
||||||
|
config.Params.Alpha = 2
|
||||||
|
config.Params.K = 2
|
||||||
|
|
||||||
|
vdr0 := validators.GenerateRandomValidator(1)
|
||||||
|
vdr1 := validators.GenerateRandomValidator(1)
|
||||||
|
vals := validators.NewSet()
|
||||||
|
vals.Add(vdr0)
|
||||||
|
vals.Add(vdr1)
|
||||||
|
config.Validators = vals
|
||||||
|
|
||||||
|
sender := &common.SenderTest{}
|
||||||
|
sender.T = t
|
||||||
|
config.Sender = sender
|
||||||
|
|
||||||
|
sender.Default(true)
|
||||||
|
sender.CantGetAcceptedFrontier = false
|
||||||
|
|
||||||
|
st := &stateTest{t: t}
|
||||||
|
config.State = st
|
||||||
|
|
||||||
|
st.Default(true)
|
||||||
|
|
||||||
|
gVtx := &Vtx{
|
||||||
|
id: GenerateID(),
|
||||||
|
status: choices.Accepted,
|
||||||
|
}
|
||||||
|
mVtx := &Vtx{
|
||||||
|
id: GenerateID(),
|
||||||
|
status: choices.Accepted,
|
||||||
|
}
|
||||||
|
|
||||||
|
vts := []avalanche.Vertex{gVtx, mVtx}
|
||||||
|
utxos := []ids.ID{GenerateID()}
|
||||||
|
|
||||||
|
tx := &TestTx{
|
||||||
|
TestTx: snowstorm.TestTx{
|
||||||
|
Identifier: GenerateID(),
|
||||||
|
Stat: choices.Processing,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tx.Ins.Add(utxos[0])
|
||||||
|
|
||||||
|
vtx := &Vtx{
|
||||||
|
parents: vts,
|
||||||
|
id: GenerateID(),
|
||||||
|
txs: []snowstorm.Tx{tx},
|
||||||
|
height: 1,
|
||||||
|
status: choices.Processing,
|
||||||
|
bytes: []byte{1, 1, 2, 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
st.edge = func() []ids.ID { return []ids.ID{vts[0].ID(), vts[1].ID()} }
|
||||||
|
st.getVertex = func(id ids.ID) (avalanche.Vertex, error) {
|
||||||
|
switch {
|
||||||
|
case id.Equals(gVtx.ID()):
|
||||||
|
return gVtx, nil
|
||||||
|
case id.Equals(mVtx.ID()):
|
||||||
|
return mVtx, nil
|
||||||
|
}
|
||||||
|
t.Fatalf("Unknown vertex")
|
||||||
|
panic("Should have errored")
|
||||||
|
}
|
||||||
|
|
||||||
|
te := &Transitive{}
|
||||||
|
te.Initialize(config)
|
||||||
|
te.finishBootstrapping()
|
||||||
|
|
||||||
|
reqID := new(uint32)
|
||||||
|
sender.PushQueryF = func(inVdrs ids.ShortSet, requestID uint32, vtxID ids.ID, _ []byte) {
|
||||||
|
*reqID = requestID
|
||||||
|
if inVdrs.Len() != 2 {
|
||||||
|
t.Fatalf("Wrong number of validators")
|
||||||
|
}
|
||||||
|
if !vtxID.Equals(vtx.ID()) {
|
||||||
|
t.Fatalf("Wrong vertex requested")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st.getVertex = func(id ids.ID) (avalanche.Vertex, error) {
|
||||||
|
switch {
|
||||||
|
case id.Equals(vtx.ID()):
|
||||||
|
return vtx, nil
|
||||||
|
}
|
||||||
|
t.Fatalf("Unknown vertex")
|
||||||
|
panic("Should have errored")
|
||||||
|
}
|
||||||
|
|
||||||
|
te.insert(vtx)
|
||||||
|
|
||||||
|
votes := ids.Set{}
|
||||||
|
votes.Add(vtx.ID())
|
||||||
|
|
||||||
|
if status := tx.Status(); status != choices.Processing {
|
||||||
|
t.Fatalf("Wrong tx status: %s ; expected: %s", status, choices.Processing)
|
||||||
|
}
|
||||||
|
|
||||||
|
te.Chits(vdr0.ID(), *reqID, votes)
|
||||||
|
|
||||||
|
if status := tx.Status(); status != choices.Processing {
|
||||||
|
t.Fatalf("Wrong tx status: %s ; expected: %s", status, choices.Processing)
|
||||||
|
}
|
||||||
|
|
||||||
|
te.Chits(vdr0.ID(), *reqID, votes)
|
||||||
|
|
||||||
|
if status := tx.Status(); status != choices.Processing {
|
||||||
|
t.Fatalf("Wrong tx status: %s ; expected: %s", status, choices.Processing)
|
||||||
|
}
|
||||||
|
|
||||||
|
te.Chits(vdr1.ID(), *reqID, votes)
|
||||||
|
|
||||||
|
if status := tx.Status(); status != choices.Accepted {
|
||||||
|
t.Fatalf("Wrong tx status: %s ; expected: %s", status, choices.Accepted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,11 +17,14 @@ const (
|
||||||
|
|
||||||
// StatusUpdateFrequency ... bootstrapper logs "processed X blocks/vertices" every [statusUpdateFrequency] blocks/vertices
|
// StatusUpdateFrequency ... bootstrapper logs "processed X blocks/vertices" every [statusUpdateFrequency] blocks/vertices
|
||||||
StatusUpdateFrequency = 2500
|
StatusUpdateFrequency = 2500
|
||||||
|
|
||||||
|
// MaxOutstandingRequests is the maximum number of GetAncestors sent but not responsded to/failed
|
||||||
|
MaxOutstandingRequests = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// MaxTimeFetchingAncestors is the maximum amount of time to spend fetching vertices during a call to GetAncestors
|
// MaxTimeFetchingAncestors is the maximum amount of time to spend fetching vertices during a call to GetAncestors
|
||||||
MaxTimeFetchingAncestors = 100 * time.Millisecond
|
MaxTimeFetchingAncestors = 50 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bootstrapper implements the Engine interface.
|
// Bootstrapper implements the Engine interface.
|
||||||
|
|
|
@ -14,6 +14,9 @@ type Engine interface {
|
||||||
|
|
||||||
// Return the context of the chain this engine is working on
|
// Return the context of the chain this engine is working on
|
||||||
Context() *snow.Context
|
Context() *snow.Context
|
||||||
|
|
||||||
|
// Returns true iff the chain is done bootstrapping
|
||||||
|
IsBootstrapped() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler defines the functions that are acted on the node
|
// Handler defines the functions that are acted on the node
|
||||||
|
|
|
@ -7,6 +7,10 @@ import (
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minRequestsSize = 32
|
||||||
|
)
|
||||||
|
|
||||||
type req struct {
|
type req struct {
|
||||||
vdr ids.ShortID
|
vdr ids.ShortID
|
||||||
id uint32
|
id uint32
|
||||||
|
@ -22,7 +26,7 @@ type Requests struct {
|
||||||
// are only in one request at a time.
|
// are only in one request at a time.
|
||||||
func (r *Requests) Add(vdr ids.ShortID, requestID uint32, containerID ids.ID) {
|
func (r *Requests) Add(vdr ids.ShortID, requestID uint32, containerID ids.ID) {
|
||||||
if r.reqsToID == nil {
|
if r.reqsToID == nil {
|
||||||
r.reqsToID = make(map[[20]byte]map[uint32]ids.ID)
|
r.reqsToID = make(map[[20]byte]map[uint32]ids.ID, minRequestsSize)
|
||||||
}
|
}
|
||||||
vdrKey := vdr.Key()
|
vdrKey := vdr.Key()
|
||||||
vdrReqs, ok := r.reqsToID[vdrKey]
|
vdrReqs, ok := r.reqsToID[vdrKey]
|
||||||
|
@ -33,7 +37,7 @@ func (r *Requests) Add(vdr ids.ShortID, requestID uint32, containerID ids.ID) {
|
||||||
vdrReqs[requestID] = containerID
|
vdrReqs[requestID] = containerID
|
||||||
|
|
||||||
if r.idToReq == nil {
|
if r.idToReq == nil {
|
||||||
r.idToReq = make(map[[32]byte]req)
|
r.idToReq = make(map[[32]byte]req, minRequestsSize)
|
||||||
}
|
}
|
||||||
r.idToReq[containerID.Key()] = req{
|
r.idToReq[containerID.Key()] = req{
|
||||||
vdr: vdr,
|
vdr: vdr,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
type EngineTest struct {
|
type EngineTest struct {
|
||||||
T *testing.T
|
T *testing.T
|
||||||
|
|
||||||
|
CantIsBootstrapped,
|
||||||
CantStartup,
|
CantStartup,
|
||||||
CantGossip,
|
CantGossip,
|
||||||
CantShutdown,
|
CantShutdown,
|
||||||
|
@ -43,6 +44,7 @@ type EngineTest struct {
|
||||||
CantQueryFailed,
|
CantQueryFailed,
|
||||||
CantChits bool
|
CantChits bool
|
||||||
|
|
||||||
|
IsBootstrappedF func() bool
|
||||||
ContextF func() *snow.Context
|
ContextF func() *snow.Context
|
||||||
StartupF, GossipF, ShutdownF func() error
|
StartupF, GossipF, ShutdownF func() error
|
||||||
NotifyF func(Message) error
|
NotifyF func(Message) error
|
||||||
|
@ -58,6 +60,8 @@ var _ Engine = &EngineTest{}
|
||||||
|
|
||||||
// Default ...
|
// Default ...
|
||||||
func (e *EngineTest) Default(cant bool) {
|
func (e *EngineTest) Default(cant bool) {
|
||||||
|
e.CantIsBootstrapped = cant
|
||||||
|
|
||||||
e.CantStartup = cant
|
e.CantStartup = cant
|
||||||
e.CantGossip = cant
|
e.CantGossip = cant
|
||||||
e.CantShutdown = cant
|
e.CantShutdown = cant
|
||||||
|
@ -354,3 +358,14 @@ func (e *EngineTest) Chits(validatorID ids.ShortID, requestID uint32, containerI
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsBootstrapped ...
|
||||||
|
func (e *EngineTest) IsBootstrapped() bool {
|
||||||
|
if e.IsBootstrappedF != nil {
|
||||||
|
return e.IsBootstrappedF()
|
||||||
|
}
|
||||||
|
if e.CantIsBootstrapped && e.T != nil {
|
||||||
|
e.T.Fatalf("Unexpectedly called IsBootstrapped")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -115,6 +115,9 @@ func (b *bootstrapper) fetch(blkID ids.ID) error {
|
||||||
|
|
||||||
// Make sure we don't already have this block
|
// Make sure we don't already have this block
|
||||||
if _, err := b.VM.GetBlock(blkID); err == nil {
|
if _, err := b.VM.GetBlock(blkID); err == nil {
|
||||||
|
if numPending := b.outstandingRequests.Len(); numPending == 0 && b.processedStartingAcceptedFrontier {
|
||||||
|
return b.finish()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +227,8 @@ func (b *bootstrapper) finish() error {
|
||||||
if b.finished {
|
if b.finished {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
b.BootstrapConfig.Context.Log.Info("bootstrapping finished fetching blocks. executing state transitions...")
|
b.BootstrapConfig.Context.Log.Info("bootstrapping finished fetching %d blocks. executing state transitions...",
|
||||||
|
b.numFetched)
|
||||||
|
|
||||||
if err := b.executeAll(b.Blocked, b.numBlocked); err != nil {
|
if err := b.executeAll(b.Blocked, b.numBlocked); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -262,5 +266,6 @@ func (b *bootstrapper) executeAll(jobs *queue.Jobs, numBlocked prometheus.Gauge)
|
||||||
b.BootstrapConfig.Context.Log.Info("executed %d blocks", numExecuted)
|
b.BootstrapConfig.Context.Log.Info("executed %d blocks", numExecuted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
b.BootstrapConfig.Context.Log.Info("executed %d blocks", numExecuted)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -622,3 +622,128 @@ func TestBootstrapperFilterAccepted(t *testing.T) {
|
||||||
t.Fatalf("Blk shouldn't be accepted")
|
t.Fatalf("Blk shouldn't be accepted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBootstrapperFinalized(t *testing.T) {
|
||||||
|
config, peerID, sender, vm := newConfig(t)
|
||||||
|
|
||||||
|
blkID0 := ids.Empty.Prefix(0)
|
||||||
|
blkID1 := ids.Empty.Prefix(1)
|
||||||
|
blkID2 := ids.Empty.Prefix(2)
|
||||||
|
|
||||||
|
blkBytes0 := []byte{0}
|
||||||
|
blkBytes1 := []byte{1}
|
||||||
|
blkBytes2 := []byte{2}
|
||||||
|
|
||||||
|
blk0 := &Blk{
|
||||||
|
id: blkID0,
|
||||||
|
height: 0,
|
||||||
|
status: choices.Accepted,
|
||||||
|
bytes: blkBytes0,
|
||||||
|
}
|
||||||
|
blk1 := &Blk{
|
||||||
|
parent: blk0,
|
||||||
|
id: blkID1,
|
||||||
|
height: 1,
|
||||||
|
status: choices.Unknown,
|
||||||
|
bytes: blkBytes1,
|
||||||
|
}
|
||||||
|
blk2 := &Blk{
|
||||||
|
parent: blk1,
|
||||||
|
id: blkID2,
|
||||||
|
height: 2,
|
||||||
|
status: choices.Unknown,
|
||||||
|
bytes: blkBytes2,
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := bootstrapper{}
|
||||||
|
bs.metrics.Initialize(config.Context.Log, fmt.Sprintf("gecko_%s", config.Context.ChainID), prometheus.NewRegistry())
|
||||||
|
bs.Initialize(config)
|
||||||
|
finished := new(bool)
|
||||||
|
bs.onFinished = func() error { *finished = true; return nil }
|
||||||
|
|
||||||
|
acceptedIDs := ids.Set{}
|
||||||
|
acceptedIDs.Add(blkID1)
|
||||||
|
acceptedIDs.Add(blkID2)
|
||||||
|
|
||||||
|
parsedBlk1 := false
|
||||||
|
parsedBlk2 := false
|
||||||
|
vm.GetBlockF = func(blkID ids.ID) (snowman.Block, error) {
|
||||||
|
switch {
|
||||||
|
case blkID.Equals(blkID0):
|
||||||
|
return blk0, nil
|
||||||
|
case blkID.Equals(blkID1):
|
||||||
|
if parsedBlk1 {
|
||||||
|
return blk1, nil
|
||||||
|
}
|
||||||
|
return nil, errUnknownBlock
|
||||||
|
case blkID.Equals(blkID2):
|
||||||
|
if parsedBlk2 {
|
||||||
|
return blk2, nil
|
||||||
|
}
|
||||||
|
return nil, errUnknownBlock
|
||||||
|
default:
|
||||||
|
t.Fatal(errUnknownBlock)
|
||||||
|
panic(errUnknownBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vm.ParseBlockF = func(blkBytes []byte) (snowman.Block, error) {
|
||||||
|
switch {
|
||||||
|
case bytes.Equal(blkBytes, blkBytes0):
|
||||||
|
return blk0, nil
|
||||||
|
case bytes.Equal(blkBytes, blkBytes1):
|
||||||
|
blk1.status = choices.Processing
|
||||||
|
parsedBlk1 = true
|
||||||
|
return blk1, nil
|
||||||
|
case bytes.Equal(blkBytes, blkBytes2):
|
||||||
|
blk2.status = choices.Processing
|
||||||
|
parsedBlk2 = true
|
||||||
|
return blk2, nil
|
||||||
|
}
|
||||||
|
t.Fatal(errUnknownBlock)
|
||||||
|
return nil, errUnknownBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
requestIDs := map[[32]byte]uint32{}
|
||||||
|
sender.GetAncestorsF = func(vdr ids.ShortID, reqID uint32, vtxID ids.ID) {
|
||||||
|
if !vdr.Equals(peerID) {
|
||||||
|
t.Fatalf("Should have requested block from %s, requested from %s", peerID, vdr)
|
||||||
|
}
|
||||||
|
requestIDs[vtxID.Key()] = reqID
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.CantBootstrapping = false
|
||||||
|
|
||||||
|
if err := bs.ForceAccepted(acceptedIDs); err != nil { // should request blk0 and blk1
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqID, ok := requestIDs[blkID2.Key()]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("should have requested blk2")
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.CantBootstrapped = false
|
||||||
|
|
||||||
|
if err := bs.MultiPut(peerID, reqID, [][]byte{blkBytes2, blkBytes1}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqID, ok = requestIDs[blkID1.Key()]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("should have requested blk1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bs.GetAncestorsFailed(peerID, reqID); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*finished {
|
||||||
|
t.Fatalf("Bootstrapping should have finished")
|
||||||
|
} else if blk0.Status() != choices.Accepted {
|
||||||
|
t.Fatalf("Block should be accepted")
|
||||||
|
} else if blk1.Status() != choices.Accepted {
|
||||||
|
t.Fatalf("Block should be accepted")
|
||||||
|
} else if blk2.Status() != choices.Accepted {
|
||||||
|
t.Fatalf("Block should be accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ type metrics struct {
|
||||||
numPendingRequests, numBlocked prometheus.Gauge
|
numPendingRequests, numBlocked prometheus.Gauge
|
||||||
numBootstrapped, numDropped prometheus.Counter
|
numBootstrapped, numDropped prometheus.Counter
|
||||||
|
|
||||||
numPolls, numBlkRequests, numBlockedBlk prometheus.Gauge
|
numBlkRequests, numBlockedBlk prometheus.Gauge
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize implements the Engine interface
|
// Initialize implements the Engine interface
|
||||||
|
@ -42,12 +42,6 @@ func (m *metrics) Initialize(log logging.Logger, namespace string, registerer pr
|
||||||
Name: "sm_bs_dropped",
|
Name: "sm_bs_dropped",
|
||||||
Help: "Number of dropped bootstrap blocks",
|
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(
|
m.numBlkRequests = prometheus.NewGauge(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: namespace,
|
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 {
|
if err := registerer.Register(m.numDropped); err != nil {
|
||||||
log.Error("Failed to register sm_bs_dropped statistics due to %s", err)
|
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 {
|
if err := registerer.Register(m.numBlkRequests); err != nil {
|
||||||
log.Error("Failed to register sm_blk_requests statistics due to %s", err)
|
log.Error("Failed to register sm_blk_requests statistics due to %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue