mirror of https://github.com/poanetwork/gecko.git
Merge branch 'master' of github.com:ava-labs/gecko-internal
This commit is contained in:
commit
369666a0f8
|
@ -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:
|
||||
- CODECOV_TOKEN="8c18c993-fc6e-4706-998b-01ddc7987804"
|
||||
- GECKO_HOME=/go/src/github.com/ava-labs/gecko/
|
||||
- E2E_TEST_HOME=/go/src/github.com/kurtosis-tech/ava-e2e-tests/
|
||||
- COMMIT=${TRAVIS_COMMIT::8}
|
||||
- DOCKERHUB_REPO=avaplatform/gecko
|
||||
- secure: "L/A9+re0NEKP6EV6H9RcTGiDhX3WMvsiWrkRKDYKqnviqbjY30RK6EM4vvjrM4Lrw2QwsO3YKgnku3+zioE/TxEZFkpkbjNUXru0nYBrWAg1TKVsDXnYaIZkHUejfryST3E8N7F4Hx6zCtGEO0sEdUeKuT+MNUIuHezHooTgGzDjMogm70EWMFjQHc7VucTJu7dWU1RBPjovWQ0q9qflrtCpbrvXFIiihQQ1PQha1Q2C4wLakKuLbhhSafue90Mnyss0blaPHy/tyewcASJu4vsGTKRBn0DzttlkNTwuD6+nKrbmJY0ohunnkVFzYjrZAw1gyN+DCDb/lPbz4ZDItKPwrIUPEtL5xuUOrxUZPUh+0io3Q2d6rjaqkdGjd1KQXzbnW1mn0BxX3d3b2UpIqhBn9umYYjHBKnMuoRiTK33b7U9+LF3K84+tEvVDCPeHs/mw6Inp5jGRSravnM6yPQ6feGzogs4+3EMzZXxnkngKFKCsnd67Oe9xfV9amOU2aQAx4jaAwlPjEpBEkUa8YKx3lPznvmUk1QsNCUbLjdSl5JBaXojLJoiuPbj29hp4S5AXXgn+3Hvwk3ndcFCxi6/l1W9mjYSOtFqg3EAUdF4EgnA/ykQg9ZokkoKY0+qgOzG2bKOAYuCDWeGr7P1apToh00ccsQXL81nVPiq7uDw="
|
||||
|
@ -26,7 +27,7 @@ install:
|
|||
|
||||
script:
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then .ci/runscript_osx.sh; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then .ci/runscript_linux.sh; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then .ci/runscript_linux.sh; .ci/run_e2e_tests.sh; fi
|
||||
|
||||
#Need to push to docker hub only from one build
|
||||
after_success:
|
||||
|
|
|
@ -10,6 +10,15 @@ import (
|
|||
"runtime/pprof"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name of file that CPU profile is written to when StartCPUProfiler called
|
||||
cpuProfileFile = "cpu.profile"
|
||||
// Name of file that memory profile is written to when MemoryProfile called
|
||||
memProfileFile = "mem.profile"
|
||||
// Name of file that lock profile is written to
|
||||
lockProfileFile = "lock.profile"
|
||||
)
|
||||
|
||||
var (
|
||||
errCPUProfilerRunning = errors.New("cpu profiler already running")
|
||||
errCPUProfilerNotRunning = errors.New("cpu profiler doesn't exist")
|
||||
|
@ -20,12 +29,12 @@ var (
|
|||
type Performance struct{ cpuProfileFile *os.File }
|
||||
|
||||
// StartCPUProfiler starts measuring the cpu utilization of this node
|
||||
func (p *Performance) StartCPUProfiler(filename string) error {
|
||||
func (p *Performance) StartCPUProfiler() error {
|
||||
if p.cpuProfileFile != nil {
|
||||
return errCPUProfilerRunning
|
||||
}
|
||||
|
||||
file, err := os.Create(filename)
|
||||
file, err := os.Create(cpuProfileFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -52,8 +61,8 @@ func (p *Performance) StopCPUProfiler() error {
|
|||
}
|
||||
|
||||
// MemoryProfile dumps the current memory utilization of this node
|
||||
func (p *Performance) MemoryProfile(filename string) error {
|
||||
file, err := os.Create(filename)
|
||||
func (p *Performance) MemoryProfile() error {
|
||||
file, err := os.Create(memProfileFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -66,8 +75,8 @@ func (p *Performance) MemoryProfile(filename string) error {
|
|||
}
|
||||
|
||||
// LockProfile dumps the current lock statistics of this node
|
||||
func (p *Performance) LockProfile(filename string) error {
|
||||
file, err := os.Create(filename)
|
||||
func (p *Performance) LockProfile() error {
|
||||
file, err := os.Create(lockProfileFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -10,38 +10,127 @@ import (
|
|||
|
||||
"github.com/ava-labs/gecko/api"
|
||||
"github.com/ava-labs/gecko/chains"
|
||||
"github.com/ava-labs/gecko/genesis"
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/network"
|
||||
"github.com/ava-labs/gecko/snow/engine/common"
|
||||
"github.com/ava-labs/gecko/utils/logging"
|
||||
"github.com/ava-labs/gecko/version"
|
||||
|
||||
cjson "github.com/ava-labs/gecko/utils/json"
|
||||
)
|
||||
|
||||
// Admin is the API service for node admin management
|
||||
type Admin struct {
|
||||
version version.Version
|
||||
nodeID ids.ShortID
|
||||
networkID uint32
|
||||
log logging.Logger
|
||||
networking network.Network
|
||||
performance Performance
|
||||
chainManager chains.Manager
|
||||
httpServer *api.Server
|
||||
}
|
||||
|
||||
// NewService returns a new admin API service
|
||||
func NewService(log logging.Logger, chainManager chains.Manager, peers network.Network, httpServer *api.Server) *common.HTTPHandler {
|
||||
func NewService(version version.Version, nodeID ids.ShortID, networkID uint32, log logging.Logger, chainManager chains.Manager, peers network.Network, httpServer *api.Server) *common.HTTPHandler {
|
||||
newServer := rpc.NewServer()
|
||||
codec := cjson.NewCodec()
|
||||
newServer.RegisterCodec(codec, "application/json")
|
||||
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
|
||||
newServer.RegisterService(&Admin{
|
||||
version: version,
|
||||
nodeID: nodeID,
|
||||
networkID: networkID,
|
||||
log: log,
|
||||
chainManager: chainManager,
|
||||
networking: peers,
|
||||
httpServer: httpServer,
|
||||
}, "admin")
|
||||
return &common.HTTPHandler{Handler: newServer}
|
||||
}
|
||||
|
||||
// StartCPUProfilerArgs are the arguments for calling StartCPUProfiler
|
||||
type StartCPUProfilerArgs struct {
|
||||
Filename string `json:"filename"`
|
||||
// GetNodeVersionReply are the results from calling GetNodeVersion
|
||||
type GetNodeVersionReply struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// GetNodeVersion returns the version this node is running
|
||||
func (service *Admin) GetNodeVersion(_ *http.Request, _ *struct{}, reply *GetNodeVersionReply) error {
|
||||
service.log.Info("Admin: GetNodeVersion called")
|
||||
|
||||
reply.Version = service.version.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNodeIDReply are the results from calling GetNodeID
|
||||
type GetNodeIDReply struct {
|
||||
NodeID ids.ShortID `json:"nodeID"`
|
||||
}
|
||||
|
||||
// GetNodeID returns the node ID of this node
|
||||
func (service *Admin) GetNodeID(_ *http.Request, _ *struct{}, reply *GetNodeIDReply) error {
|
||||
service.log.Info("Admin: GetNodeID called")
|
||||
|
||||
reply.NodeID = service.nodeID
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNetworkIDReply are the results from calling GetNetworkID
|
||||
type GetNetworkIDReply struct {
|
||||
NetworkID cjson.Uint32 `json:"networkID"`
|
||||
}
|
||||
|
||||
// GetNetworkID returns the network ID this node is running on
|
||||
func (service *Admin) GetNetworkID(_ *http.Request, _ *struct{}, reply *GetNetworkIDReply) error {
|
||||
service.log.Info("Admin: GetNetworkID called")
|
||||
|
||||
reply.NetworkID = cjson.Uint32(service.networkID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNetworkNameReply is the result from calling GetNetworkName
|
||||
type GetNetworkNameReply struct {
|
||||
NetworkName string `json:"networkName"`
|
||||
}
|
||||
|
||||
// GetNetworkName returns the network name this node is running on
|
||||
func (service *Admin) GetNetworkName(_ *http.Request, _ *struct{}, reply *GetNetworkNameReply) error {
|
||||
service.log.Info("Admin: GetNetworkName called")
|
||||
|
||||
reply.NetworkName = genesis.NetworkName(service.networkID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlockchainIDArgs are the arguments for calling GetBlockchainID
|
||||
type GetBlockchainIDArgs struct {
|
||||
Alias string `json:"alias"`
|
||||
}
|
||||
|
||||
// GetBlockchainIDReply are the results from calling GetBlockchainID
|
||||
type GetBlockchainIDReply struct {
|
||||
BlockchainID string `json:"blockchainID"`
|
||||
}
|
||||
|
||||
// GetBlockchainID returns the blockchain ID that resolves the alias that was supplied
|
||||
func (service *Admin) GetBlockchainID(_ *http.Request, args *GetBlockchainIDArgs, reply *GetBlockchainIDReply) error {
|
||||
service.log.Info("Admin: GetBlockchainID called")
|
||||
|
||||
bID, err := service.chainManager.Lookup(args.Alias)
|
||||
reply.BlockchainID = bID.String()
|
||||
return err
|
||||
}
|
||||
|
||||
// PeersReply are the results from calling Peers
|
||||
type PeersReply struct {
|
||||
Peers []network.PeerID `json:"peers"`
|
||||
}
|
||||
|
||||
// Peers returns the list of current validators
|
||||
func (service *Admin) Peers(_ *http.Request, _ *struct{}, reply *PeersReply) error {
|
||||
service.log.Info("Admin: Peers called")
|
||||
reply.Peers = service.networking.Peers()
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartCPUProfilerReply are the results from calling StartCPUProfiler
|
||||
|
@ -50,10 +139,10 @@ type StartCPUProfilerReply struct {
|
|||
}
|
||||
|
||||
// StartCPUProfiler starts a cpu profile writing to the specified file
|
||||
func (service *Admin) StartCPUProfiler(_ *http.Request, args *StartCPUProfilerArgs, reply *StartCPUProfilerReply) error {
|
||||
service.log.Info("Admin: StartCPUProfiler called with %s", args.Filename)
|
||||
func (service *Admin) StartCPUProfiler(_ *http.Request, args *struct{}, reply *StartCPUProfilerReply) error {
|
||||
service.log.Info("Admin: StartCPUProfiler called")
|
||||
reply.Success = true
|
||||
return service.performance.StartCPUProfiler(args.Filename)
|
||||
return service.performance.StartCPUProfiler()
|
||||
}
|
||||
|
||||
// StopCPUProfilerReply are the results from calling StopCPUProfiler
|
||||
|
@ -68,26 +157,16 @@ func (service *Admin) StopCPUProfiler(_ *http.Request, _ *struct{}, reply *StopC
|
|||
return service.performance.StopCPUProfiler()
|
||||
}
|
||||
|
||||
// MemoryProfileArgs are the arguments for calling MemoryProfile
|
||||
type MemoryProfileArgs struct {
|
||||
Filename string `json:"filename"`
|
||||
}
|
||||
|
||||
// MemoryProfileReply are the results from calling MemoryProfile
|
||||
type MemoryProfileReply struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
// MemoryProfile runs a memory profile writing to the specified file
|
||||
func (service *Admin) MemoryProfile(_ *http.Request, args *MemoryProfileArgs, reply *MemoryProfileReply) error {
|
||||
service.log.Info("Admin: MemoryProfile called with %s", args.Filename)
|
||||
func (service *Admin) MemoryProfile(_ *http.Request, args *struct{}, reply *MemoryProfileReply) error {
|
||||
service.log.Info("Admin: MemoryProfile called")
|
||||
reply.Success = true
|
||||
return service.performance.MemoryProfile(args.Filename)
|
||||
}
|
||||
|
||||
// LockProfileArgs are the arguments for calling LockProfile
|
||||
type LockProfileArgs struct {
|
||||
Filename string `json:"filename"`
|
||||
return service.performance.MemoryProfile()
|
||||
}
|
||||
|
||||
// LockProfileReply are the results from calling LockProfile
|
||||
|
@ -96,10 +175,10 @@ type LockProfileReply struct {
|
|||
}
|
||||
|
||||
// LockProfile runs a mutex profile writing to the specified file
|
||||
func (service *Admin) LockProfile(_ *http.Request, args *LockProfileArgs, reply *LockProfileReply) error {
|
||||
service.log.Info("Admin: LockProfile called with %s", args.Filename)
|
||||
func (service *Admin) LockProfile(_ *http.Request, args *struct{}, reply *LockProfileReply) error {
|
||||
service.log.Info("Admin: LockProfile called")
|
||||
reply.Success = true
|
||||
return service.performance.LockProfile(args.Filename)
|
||||
return service.performance.LockProfile()
|
||||
}
|
||||
|
||||
// AliasArgs are the arguments for calling Alias
|
||||
|
|
|
@ -38,7 +38,18 @@ func (h *Health) Handler() *common.HTTPHandler {
|
|||
newServer.RegisterCodec(codec, "application/json")
|
||||
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
|
||||
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 500
|
||||
if _, healthy := h.health.Results(); healthy {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
} 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
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -201,7 +201,11 @@ func (b *batch) Write() error {
|
|||
|
||||
// Reset resets the batch for reuse.
|
||||
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.Batch.Reset()
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,10 @@ import (
|
|||
"github.com/ava-labs/gecko/utils"
|
||||
)
|
||||
|
||||
// DefaultSize is the default initial size of the memory database
|
||||
const DefaultSize = 1 << 10
|
||||
const (
|
||||
// DefaultSize is the default initial size of the memory database
|
||||
DefaultSize = 1 << 10
|
||||
)
|
||||
|
||||
// Database is an ephemeral key-value store that implements the Database
|
||||
// interface.
|
||||
|
@ -191,7 +193,11 @@ func (b *batch) Write() error {
|
|||
|
||||
// Reset implements the Batch interface
|
||||
func (b *batch) Reset() {
|
||||
if cap(b.writes) > len(b.writes)*database.MaxExcessCapacityFactor {
|
||||
b.writes = make([]keyValue, 0, cap(b.writes)/database.CapacityReductionFactor)
|
||||
} else {
|
||||
b.writes = b.writes[:0]
|
||||
}
|
||||
b.size = 0
|
||||
}
|
||||
|
||||
|
|
|
@ -199,7 +199,11 @@ func (b *batch) Write() error {
|
|||
|
||||
// Reset resets the batch for reuse.
|
||||
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.Batch.Reset()
|
||||
}
|
||||
|
||||
|
|
|
@ -180,7 +180,11 @@ func (b *batch) Write() error {
|
|||
}
|
||||
|
||||
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.size = 0
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ type Database struct {
|
|||
lock sync.RWMutex
|
||||
mem map[string]valueDelete
|
||||
db database.Database
|
||||
batch database.Batch
|
||||
}
|
||||
|
||||
type valueDelete struct {
|
||||
|
@ -33,6 +34,7 @@ func New(db database.Database) *Database {
|
|||
return &Database{
|
||||
mem: make(map[string]valueDelete, memdb.DefaultSize),
|
||||
db: db,
|
||||
batch: db.NewBatch(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,6 +171,7 @@ func (db *Database) SetDatabase(newDB database.Database) error {
|
|||
}
|
||||
|
||||
db.db = newDB
|
||||
db.batch = newDB.NewBatch()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -192,6 +195,7 @@ func (db *Database) Commit() error {
|
|||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
batch.Reset()
|
||||
db.abort()
|
||||
return nil
|
||||
}
|
||||
|
@ -206,7 +210,10 @@ func (db *Database) Abort() {
|
|||
|
||||
func (db *Database) abort() { db.mem = make(map[string]valueDelete, memdb.DefaultSize) }
|
||||
|
||||
// CommitBatch returns a batch that will commit all pending writes to the underlying database
|
||||
// CommitBatch returns a batch that contains all uncommitted puts/deletes.
|
||||
// Calling Write() on the returned batch causes the puts/deletes to be
|
||||
// written to the underlying database. The returned batch should be written before
|
||||
// future calls to this DB unless the batch will never be written.
|
||||
func (db *Database) CommitBatch() (database.Batch, error) {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
@ -214,26 +221,25 @@ func (db *Database) CommitBatch() (database.Batch, error) {
|
|||
return db.commitBatch()
|
||||
}
|
||||
|
||||
// Put all of the puts/deletes in memory into db.batch
|
||||
// and return the batch
|
||||
func (db *Database) commitBatch() (database.Batch, error) {
|
||||
if db.mem == nil {
|
||||
return nil, database.ErrClosed
|
||||
}
|
||||
|
||||
batch := db.db.NewBatch()
|
||||
db.batch.Reset()
|
||||
for key, value := range db.mem {
|
||||
if value.delete {
|
||||
if err := batch.Delete([]byte(key)); err != nil {
|
||||
if err := db.batch.Delete([]byte(key)); err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
if err := batch.Write(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return batch, nil
|
||||
return db.batch, nil
|
||||
}
|
||||
|
||||
// Close implements the database.Database interface
|
||||
|
@ -244,6 +250,7 @@ func (db *Database) Close() error {
|
|||
if db.mem == nil {
|
||||
return database.ErrClosed
|
||||
}
|
||||
db.batch = nil
|
||||
db.mem = nil
|
||||
db.db = nil
|
||||
return nil
|
||||
|
@ -298,7 +305,11 @@ func (b *batch) Write() error {
|
|||
|
||||
// Reset implements the Database interface
|
||||
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.size = 0
|
||||
}
|
||||
|
||||
|
|
|
@ -299,6 +299,10 @@ func TestCommitBatch(t *testing.T) {
|
|||
|
||||
if err := db.Put(key1, value1); err != nil {
|
||||
t.Fatalf("Unexpected error on db.Put: %s", err)
|
||||
} else if has, err := baseDB.Has(key1); err != nil {
|
||||
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||
} else if has {
|
||||
t.Fatalf("Unexpected result of db.Has: %v", has)
|
||||
}
|
||||
|
||||
batch, err := db.CommitBatch()
|
||||
|
@ -307,7 +311,11 @@ func TestCommitBatch(t *testing.T) {
|
|||
}
|
||||
db.Abort()
|
||||
|
||||
if err := batch.Write(); err != nil {
|
||||
if has, err := db.Has(key1); err != nil {
|
||||
t.Fatalf("Unexpected error on db.Has: %s", err)
|
||||
} else if has {
|
||||
t.Fatalf("Unexpected result of db.Has: %v", has)
|
||||
} else if err := batch.Write(); err != nil {
|
||||
t.Fatalf("Unexpected error on batch.Write: %s", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ func TestNetworkName(t *testing.T) {
|
|||
if name := NetworkName(EverestID); name != EverestName {
|
||||
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, EverestName)
|
||||
}
|
||||
if name := NetworkName(TestnetID); 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)
|
||||
}
|
||||
if name := NetworkName(4294967295); name != "network-4294967295" {
|
||||
t.Fatalf("NetworkID was incorrectly named. Result: %s ; Expected: %s", name, "network-4294967295")
|
||||
|
|
|
@ -18,7 +18,7 @@ var (
|
|||
DenaliID uint32 = 3
|
||||
EverestID uint32 = 4
|
||||
|
||||
TestnetID uint32 = 4
|
||||
TestnetID uint32 = 3
|
||||
LocalID uint32 = 12345
|
||||
|
||||
MainnetName = "mainnet"
|
||||
|
|
8
go.mod
8
go.mod
|
@ -6,10 +6,10 @@ require (
|
|||
github.com/AppsFlyer/go-sundheit v0.2.0
|
||||
github.com/allegro/bigcache v1.2.1 // 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/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/edsrzf/mmap-go v1.0.0 // indirect
|
||||
github.com/elastic/gosigar v0.10.5 // indirect
|
||||
|
@ -20,6 +20,7 @@ require (
|
|||
github.com/gorilla/mux v1.7.4
|
||||
github.com/gorilla/rpc v1.2.0
|
||||
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/golang-lru v0.5.4 // indirect
|
||||
github.com/huin/goupnp v1.0.0
|
||||
|
@ -28,12 +29,11 @@ require (
|
|||
github.com/karalabe/usb v0.0.0-20191104083709-911d15fe12a9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||
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/olekukonko/tablewriter v0.0.4 // indirect
|
||||
github.com/pborman/uuid v1.2.0 // indirect
|
||||
github.com/prometheus/client_golang v1.6.0
|
||||
github.com/prometheus/common v0.9.1
|
||||
github.com/prometheus/tsdb v0.10.0 // indirect
|
||||
github.com/rjeczalik/notify v0.9.2 // indirect
|
||||
github.com/rs/cors v1.7.0
|
||||
|
|
14
go.sum
14
go.sum
|
@ -22,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/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.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/go.mod h1:a+agc6fXfZFsPZCylA3ry4Y8CLCqLKg3Rc23NXZ9aw8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
|
@ -41,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/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||
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/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/dcrec v1.0.0 h1:W+z6Es+Rai3MXYVoPAxYr5U1DGis0Co33scJ6uH2J6o=
|
||||
github.com/decred/dcrd/dcrec/secp256k1 v1.0.3 h1:u4XpHqlscRolxPxt2YHrFBDVZYY1AK+KMV02H1r+HmU=
|
||||
|
@ -67,6 +71,7 @@ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+
|
|||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
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/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/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
|
||||
|
@ -80,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/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/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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -101,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.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.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
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/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||
|
@ -134,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/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
@ -147,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/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.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
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/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
|
@ -169,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/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.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/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=
|
||||
|
@ -319,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-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-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
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.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -349,6 +362,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQ
|
|||
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/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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
|
|
23
ids/set.go
23
ids/set.go
|
@ -78,7 +78,7 @@ func (ids *Set) Clear() { *ids = nil }
|
|||
|
||||
// List converts this set into a list
|
||||
func (ids Set) List() []ID {
|
||||
idList := make([]ID, ids.Len(), ids.Len())
|
||||
idList := make([]ID, ids.Len())
|
||||
i := 0
|
||||
for id := range ids {
|
||||
idList[i] = NewID(id)
|
||||
|
@ -87,6 +87,27 @@ func (ids Set) List() []ID {
|
|||
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
|
||||
}
|
||||
|
||||
// Equals returns true if the sets contain the same elements
|
||||
func (ids Set) Equals(oIDs Set) bool {
|
||||
if ids.Len() != oIDs.Len() {
|
||||
|
|
|
@ -55,3 +55,46 @@ func TestSet(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,15 +57,23 @@ func (ids *ShortSet) Remove(idList ...ShortID) {
|
|||
// Clear empties this set
|
||||
func (ids *ShortSet) Clear() { *ids = nil }
|
||||
|
||||
// CappedList returns a list of length at most [size]. Size should be >= 0
|
||||
// CappedList returns a list of length at most [size].
|
||||
// Size should be >= 0. If size < 0, returns nil.
|
||||
func (ids ShortSet) CappedList(size int) []ShortID {
|
||||
idList := make([]ShortID, size)[:0]
|
||||
if size < 0 {
|
||||
return nil
|
||||
}
|
||||
if l := ids.Len(); l < size {
|
||||
size = l
|
||||
}
|
||||
i := 0
|
||||
idList := make([]ShortID, size)
|
||||
for id := range ids {
|
||||
if size <= 0 {
|
||||
if i >= size {
|
||||
break
|
||||
}
|
||||
size--
|
||||
idList = append(idList, NewShortID(id))
|
||||
idList[i] = NewShortID(id)
|
||||
i++
|
||||
}
|
||||
return idList
|
||||
}
|
||||
|
|
18
main/main.go
18
main/main.go
|
@ -40,10 +40,6 @@ func main() {
|
|||
defer log.StopOnPanic()
|
||||
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
|
||||
if !Config.EnableStaking && Config.EnableP2PTLS {
|
||||
log.Warn("Staking is disabled. Sybil control is not enforced.")
|
||||
|
@ -68,11 +64,19 @@ func main() {
|
|||
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()
|
||||
|
||||
mapper.MapPort(Config.StakingIP.Port, Config.StakingIP.Port)
|
||||
mapper.MapPort(Config.HTTPPort, Config.HTTPPort)
|
||||
port, err := mapper.Map("TCP", Config.StakingLocalPort, "gecko-staking") // Open staking port
|
||||
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{}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
dbVersion = "v0.6.0"
|
||||
dbVersion = "v0.5.0"
|
||||
)
|
||||
|
||||
// Results of parsing the CLI
|
||||
|
@ -38,14 +38,16 @@ var (
|
|||
Config = node.Config{}
|
||||
Err error
|
||||
defaultNetworkName = genesis.TestnetName
|
||||
defaultDbDir = os.ExpandEnv(filepath.Join("$HOME", ".gecko", "db"))
|
||||
defaultStakingKeyPath = os.ExpandEnv(filepath.Join("$HOME", ".gecko", "staking", "staker.key"))
|
||||
defaultStakingCertPath = os.ExpandEnv(filepath.Join("$HOME", ".gecko", "staking", "staker.crt"))
|
||||
|
||||
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{
|
||||
"./build/plugins",
|
||||
"./plugins",
|
||||
os.ExpandEnv(filepath.Join("$HOME", ".gecko", "plugins")),
|
||||
filepath.Join(".", "build", "plugins"),
|
||||
filepath.Join(".", "plugins"),
|
||||
filepath.Join("/", "usr", "local", "lib", "gecko"),
|
||||
filepath.Join(homeDir, ".gecko", "plugins"),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -190,7 +192,7 @@ func init() {
|
|||
consensusIP := fs.String("public-ip", "", "Public IP of this node")
|
||||
|
||||
// HTTP Server:
|
||||
httpHost := fs.String("http-host", "", "Address of the HTTP server")
|
||||
httpHost := fs.String("http-host", "127.0.0.1", "Address of the HTTP server")
|
||||
httpPort := fs.Uint("http-port", 9650, "Port of the HTTP server")
|
||||
fs.BoolVar(&Config.EnableHTTPS, "http-tls-enabled", false, "Upgrade the HTTP server to HTTPs")
|
||||
fs.StringVar(&Config.HTTPSKeyFile, "http-tls-key-file", "", "TLS private key file for the HTTPs server")
|
||||
|
@ -225,7 +227,7 @@ func init() {
|
|||
fs.IntVar(&Config.ConsensusParams.ConcurrentRepolls, "snow-concurrent-repolls", 1, "Minimum number of concurrent polls for finalizing consensus")
|
||||
|
||||
// Enable/Disable APIs:
|
||||
fs.BoolVar(&Config.AdminAPIEnabled, "api-admin-enabled", true, "If true, this node exposes the Admin API")
|
||||
fs.BoolVar(&Config.AdminAPIEnabled, "api-admin-enabled", false, "If true, this node exposes the Admin API")
|
||||
fs.BoolVar(&Config.InfoAPIEnabled, "api-info-enabled", true, "If true, this node exposes the Info API")
|
||||
fs.BoolVar(&Config.KeystoreAPIEnabled, "api-keystore-enabled", true, "If true, this node exposes the Keystore API")
|
||||
fs.BoolVar(&Config.MetricsAPIEnabled, "api-metrics-enabled", true, "If true, this node exposes the Metrics API")
|
||||
|
@ -282,16 +284,16 @@ func init() {
|
|||
Config.DB = memdb.New()
|
||||
}
|
||||
|
||||
Config.Nat = nat.NewRouter()
|
||||
|
||||
var ip net.IP
|
||||
// If public IP is not specified, get it using shell command dig
|
||||
if *consensusIP == "" {
|
||||
ip, err = Config.Nat.IP()
|
||||
Config.Nat = nat.GetRouter()
|
||||
ip, err = Config.Nat.ExternalIP()
|
||||
if err != nil {
|
||||
ip = net.IPv4zero // Couldn't get my IP...set to 0.0.0.0
|
||||
}
|
||||
} else {
|
||||
Config.Nat = nat.NewNoRouter()
|
||||
ip = net.ParseIP(*consensusIP)
|
||||
}
|
||||
|
||||
|
@ -304,6 +306,7 @@ func init() {
|
|||
IP: ip,
|
||||
Port: uint16(*consensusPort),
|
||||
}
|
||||
Config.StakingLocalPort = uint16(*consensusPort)
|
||||
|
||||
defaultBootstrapIPs, defaultBootstrapIDs := GetDefaultBootstraps(networkID, 5)
|
||||
|
||||
|
|
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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoRouter = errors.New("no nat enabled router was discovered")
|
||||
)
|
||||
const googleDNSServer = "8.8.8.8:80"
|
||||
|
||||
type noRouter struct{}
|
||||
|
||||
func (noRouter) MapPort(_ NetworkProtocol, _, _ uint16, _ string, _ time.Duration) error {
|
||||
return errNoRouter
|
||||
type noRouter struct {
|
||||
ip net.IP
|
||||
}
|
||||
|
||||
func (noRouter) UnmapPort(_ NetworkProtocol, _, _ uint16) error {
|
||||
return errNoRouter
|
||||
func (noRouter) MapPort(_ string, intPort, extPort uint16, _ string, _ time.Duration) error {
|
||||
if intPort != extPort {
|
||||
return fmt.Errorf("cannot map port %d to %d", intPort, extPort)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (noRouter) IP() (net.IP, error) {
|
||||
return nil, errNoRouter
|
||||
func (noRouter) UnmapPort(string, uint16, uint16) error {
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/jackpal/gateway"
|
||||
"github.com/jackpal/go-nat-pmp"
|
||||
|
||||
natpmp "github.com/jackpal/go-nat-pmp"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -17,12 +19,12 @@ var (
|
|||
|
||||
// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
|
||||
// the common interface.
|
||||
type pmpClient struct {
|
||||
type pmpRouter struct {
|
||||
client *natpmp.Client
|
||||
}
|
||||
|
||||
func (pmp *pmpClient) MapPort(
|
||||
networkProtocol NetworkProtocol,
|
||||
func (pmp *pmpRouter) MapPort(
|
||||
networkProtocol string,
|
||||
newInternalPort uint16,
|
||||
newExternalPort uint16,
|
||||
mappingName string,
|
||||
|
@ -37,8 +39,8 @@ func (pmp *pmpClient) MapPort(
|
|||
return err
|
||||
}
|
||||
|
||||
func (pmp *pmpClient) UnmapPort(
|
||||
networkProtocol NetworkProtocol,
|
||||
func (pmp *pmpRouter) UnmapPort(
|
||||
networkProtocol string,
|
||||
internalPort uint16,
|
||||
_ uint16) error {
|
||||
protocol := string(networkProtocol)
|
||||
|
@ -48,7 +50,7 @@ func (pmp *pmpClient) UnmapPort(
|
|||
return err
|
||||
}
|
||||
|
||||
func (pmp *pmpClient) IP() (net.IP, error) {
|
||||
func (pmp *pmpRouter) ExternalIP() (net.IP, error) {
|
||||
response, err := pmp.client.GetExternalAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -56,14 +58,20 @@ func (pmp *pmpClient) IP() (net.IP, error) {
|
|||
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()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pmp := &pmpClient{natpmp.NewClientWithTimeout(gatewayIP, pmpClientTimeout)}
|
||||
if _, err := pmp.IP(); err != nil {
|
||||
pmp := &pmpRouter{natpmp.NewClientWithTimeout(gatewayIP, pmpClientTimeout)}
|
||||
if _, err := pmp.ExternalIP(); err != 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
@ -15,11 +14,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
soapTimeout = time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
errNoGateway = errors.New("Failed to connect to any avaliable gateways")
|
||||
soapRequestTimeout = 3 * time.Second
|
||||
)
|
||||
|
||||
// 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.
|
||||
GetNATRSIPStatus() (newRSIPAvailable bool, natEnabled bool, err error)
|
||||
}
|
||||
|
||||
type upnpRouter struct {
|
||||
root *goupnp.RootDevice
|
||||
client upnpClient
|
||||
}
|
||||
|
||||
func (n *upnpRouter) MapPort(
|
||||
networkProtocol NetworkProtocol,
|
||||
newInternalPort uint16,
|
||||
newExternalPort uint16,
|
||||
mappingName string,
|
||||
mappingDuration time.Duration,
|
||||
) 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,
|
||||
// attempts to get port mapping information give a external port and protocol
|
||||
GetSpecificPortMappingEntry(
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
NewProtocol string,
|
||||
) (
|
||||
NewInternalPort uint16,
|
||||
NewInternalClient string,
|
||||
NewEnabled bool,
|
||||
NewPortMappingDescription string,
|
||||
NewLeaseDuration uint32,
|
||||
err error,
|
||||
)
|
||||
}
|
||||
|
||||
func (n *upnpRouter) UnmapPort(networkProtocol NetworkProtocol, _, externalPort uint16) error {
|
||||
protocol := string(networkProtocol)
|
||||
return n.client.DeletePortMapping(
|
||||
"", // newRemoteHost isn't used to limit the mapping to a host
|
||||
externalPort,
|
||||
protocol)
|
||||
type upnpRouter struct {
|
||||
dev *goupnp.RootDevice
|
||||
client upnpClient
|
||||
}
|
||||
|
||||
func (n *upnpRouter) IP() (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) {
|
||||
func (r *upnpRouter) localIP() (net.IP, error) {
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -120,7 +76,7 @@ func (n *upnpRouter) localAddress() (net.IP, error) {
|
|||
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 {
|
||||
addrs, err := netInterface.Addrs()
|
||||
if err != nil {
|
||||
|
@ -128,9 +84,6 @@ func (n *upnpRouter) localAddress() (net.IP, error) {
|
|||
}
|
||||
|
||||
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)
|
||||
if !ok {
|
||||
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)
|
||||
}
|
||||
|
||||
// getUPnPRouter searches for all Gateway Devices that have avaliable
|
||||
// connections in the goupnp library and returns the first connection it can
|
||||
// find.
|
||||
func getUPnPRouter() Router {
|
||||
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
|
||||
func (r *upnpRouter) ExternalIP() (net.IP, error) {
|
||||
str, err := r.client.GetExternalIPAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
case internetgateway1.URN_WANIPConnection_1:
|
||||
return &internetgateway1.WANIPConnection1{ServiceClient: client}
|
||||
case internetgateway1.URN_WANPPPConnection_1:
|
||||
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:
|
||||
return &internetgateway2.WANIPConnection2{ServiceClient: client}
|
||||
case internetgateway2.URN_WANPPPConnection_1:
|
||||
return &internetgateway2.WANPPPConnection1{ServiceClient: client}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func connectToGateway(deviceType string, toClient func(goupnp.ServiceClient) upnpClient) *upnpRouter {
|
||||
devs, err := goupnp.DiscoverDevices(deviceType)
|
||||
// discover() tries to find gateway device
|
||||
func discover(target string) *upnpRouter {
|
||||
devs, err := goupnp.DiscoverDevices(target)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// we are iterating over all the network devices, acting a possible roots
|
||||
for i := range devs {
|
||||
dev := &devs[i]
|
||||
if dev.Root == nil {
|
||||
|
||||
router := make(chan *upnpRouter)
|
||||
for i := 0; i < len(devs); i++ {
|
||||
if devs[i].Root == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// the root device may be a router, so attempt to connect to that
|
||||
rootDevice := &dev.Root.Device
|
||||
if upnp := getRouter(dev, rootDevice, toClient); upnp != nil {
|
||||
return upnp
|
||||
}
|
||||
|
||||
// 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,
|
||||
go func(dev *goupnp.MaybeRootDevice) {
|
||||
var r *upnpRouter = nil
|
||||
dev.Root.Device.VisitServices(func(service *goupnp.Service) {
|
||||
c := goupnp.ServiceClient{
|
||||
SOAPClient: service.NewSOAPClient(),
|
||||
RootDevice: dev.Root,
|
||||
Location: dev.Location,
|
||||
Service: service,
|
||||
}
|
||||
client := toClient(serviceClient)
|
||||
c.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
|
||||
client := getUPnPClient(c)
|
||||
if client == nil {
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
// check whether port mapping is enabled
|
||||
if _, nat, err := client.GetNATRSIPStatus(); err != nil || !nat {
|
||||
continue
|
||||
return
|
||||
}
|
||||
r = &upnpRouter{dev.Root, client}
|
||||
})
|
||||
router <- r
|
||||
}(&devs[i])
|
||||
}
|
||||
|
||||
// we found a router!
|
||||
return &upnpRouter{
|
||||
root: rootDevice.Root,
|
||||
client: client,
|
||||
for i := 0; i < len(devs); i++ {
|
||||
if r := <-router; r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -170,21 +170,23 @@ const (
|
|||
Version
|
||||
GetPeerList
|
||||
PeerList
|
||||
Ping
|
||||
Pong
|
||||
// Bootstrapping:
|
||||
GetAcceptedFrontier
|
||||
AcceptedFrontier
|
||||
GetAccepted
|
||||
Accepted
|
||||
GetAncestors
|
||||
MultiPut
|
||||
// Consensus:
|
||||
Get
|
||||
Put
|
||||
PushQuery
|
||||
PullQuery
|
||||
Chits
|
||||
|
||||
// TODO: Reorder these messages when we transition to everest
|
||||
GetAncestors
|
||||
MultiPut
|
||||
Ping
|
||||
Pong
|
||||
)
|
||||
|
||||
// Defines the messages that can be sent/received with this network
|
||||
|
|
|
@ -105,6 +105,7 @@ type network struct {
|
|||
serverUpgrader Upgrader
|
||||
clientUpgrader Upgrader
|
||||
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
|
||||
|
||||
nodeID uint32
|
||||
|
@ -159,6 +160,7 @@ func NewDefaultNetwork(
|
|||
serverUpgrader,
|
||||
clientUpgrader Upgrader,
|
||||
vdrs validators.Set,
|
||||
beacons validators.Set,
|
||||
router router.Router,
|
||||
) Network {
|
||||
return NewNetwork(
|
||||
|
@ -174,6 +176,7 @@ func NewDefaultNetwork(
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
beacons,
|
||||
router,
|
||||
defaultInitialReconnectDelay,
|
||||
defaultMaxReconnectDelay,
|
||||
|
@ -207,6 +210,7 @@ func NewNetwork(
|
|||
serverUpgrader,
|
||||
clientUpgrader Upgrader,
|
||||
vdrs validators.Set,
|
||||
beacons validators.Set,
|
||||
router router.Router,
|
||||
initialReconnectDelay,
|
||||
maxReconnectDelay time.Duration,
|
||||
|
@ -236,6 +240,7 @@ func NewNetwork(
|
|||
serverUpgrader: serverUpgrader,
|
||||
clientUpgrader: clientUpgrader,
|
||||
vdrs: vdrs,
|
||||
beacons: beacons,
|
||||
router: router,
|
||||
nodeID: rand.Uint32(),
|
||||
initialReconnectDelay: initialReconnectDelay,
|
||||
|
|
|
@ -197,6 +197,7 @@ func TestNewDefaultNetwork(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net)
|
||||
|
@ -280,6 +281,7 @@ func TestEstablishConnection(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net0)
|
||||
|
@ -297,6 +299,7 @@ func TestEstablishConnection(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net1)
|
||||
|
@ -419,6 +422,7 @@ func TestDoubleTrack(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net0)
|
||||
|
@ -436,6 +440,7 @@ func TestDoubleTrack(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net1)
|
||||
|
@ -559,6 +564,7 @@ func TestDoubleClose(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net0)
|
||||
|
@ -576,6 +582,7 @@ func TestDoubleClose(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net1)
|
||||
|
@ -704,6 +711,7 @@ func TestRemoveHandlers(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net0)
|
||||
|
@ -721,6 +729,7 @@ func TestRemoveHandlers(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net1)
|
||||
|
@ -858,6 +867,7 @@ func TestTrackConnected(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net0)
|
||||
|
@ -875,6 +885,7 @@ func TestTrackConnected(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net1)
|
||||
|
@ -999,6 +1010,7 @@ func TestTrackConnectedRace(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net0)
|
||||
|
@ -1016,6 +1028,7 @@ func TestTrackConnectedRace(t *testing.T) {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
vdrs,
|
||||
vdrs,
|
||||
handler,
|
||||
)
|
||||
assert.NotNil(t, net1)
|
||||
|
|
|
@ -64,7 +64,7 @@ func (p *peer) Start() {
|
|||
// Initially send the version to the peer
|
||||
go p.Version()
|
||||
go p.requestVersion()
|
||||
go p.sendPings()
|
||||
// go p.sendPings()
|
||||
}
|
||||
|
||||
func (p *peer) sendPings() {
|
||||
|
@ -107,10 +107,10 @@ func (p *peer) requestVersion() {
|
|||
func (p *peer) ReadMessages() {
|
||||
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
|
||||
}
|
||||
// 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{}
|
||||
readBuffer := make([]byte, 1<<10)
|
||||
|
@ -246,11 +246,11 @@ func (p *peer) handle(msg Msg) {
|
|||
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
|
||||
}
|
||||
// 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()
|
||||
msgMetrics := p.net.message(op)
|
||||
|
@ -470,8 +470,13 @@ func (p *peer) version(msg Msg) {
|
|||
}
|
||||
|
||||
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)
|
||||
} 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 {
|
||||
|
|
|
@ -34,6 +34,7 @@ type Config struct {
|
|||
|
||||
// Staking configuration
|
||||
StakingIP utils.IPDesc
|
||||
StakingLocalPort uint16
|
||||
EnableP2PTLS bool
|
||||
EnableStaking bool
|
||||
StakingKeyFile string
|
||||
|
|
27
node/node.go
27
node/node.go
|
@ -57,7 +57,7 @@ var (
|
|||
genesisHashKey = []byte("genesisID")
|
||||
|
||||
// Version is the version of this code
|
||||
Version = version.NewDefaultVersion("avalanche", 0, 6, 0)
|
||||
Version = version.NewDefaultVersion("avalanche", 0, 5, 7)
|
||||
versionParser = version.NewDefaultParser()
|
||||
)
|
||||
|
||||
|
@ -93,6 +93,9 @@ type Node struct {
|
|||
// Net runs the networking stack
|
||||
Net network.Network
|
||||
|
||||
// this node's initial connections to the network
|
||||
beacons validators.Set
|
||||
|
||||
// current validators of the network
|
||||
vdrs validators.Manager
|
||||
|
||||
|
@ -113,7 +116,7 @@ type Node struct {
|
|||
*/
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -165,6 +168,7 @@ func (n *Node) initNetworking() error {
|
|||
serverUpgrader,
|
||||
clientUpgrader,
|
||||
defaultSubnetValidators,
|
||||
n.beacons,
|
||||
n.Config.ConsensusRouter,
|
||||
)
|
||||
|
||||
|
@ -278,6 +282,14 @@ func (n *Node) initNodeID() error {
|
|||
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:
|
||||
// AVM, Simple Payments DAG, Simple Payments Chain
|
||||
// The Platform VM is registered in initStaking because
|
||||
|
@ -360,11 +372,6 @@ func (n *Node) initChains() error {
|
|||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -376,7 +383,7 @@ func (n *Node) initChains() error {
|
|||
SubnetID: platformvm.DefaultSubnetID,
|
||||
GenesisData: genesisBytes, // Specifies other chains to create
|
||||
VMAlias: platformvm.ID.String(),
|
||||
CustomBeacons: beacons,
|
||||
CustomBeacons: n.beacons,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -462,7 +469,7 @@ func (n *Node) initMetricsAPI() {
|
|||
func (n *Node) initAdminAPI() {
|
||||
if n.Config.AdminAPIEnabled {
|
||||
n.Log.Info("initializing Admin API")
|
||||
service := admin.NewService(n.Log, n.chainManager, n.Net, &n.APIServer)
|
||||
service := admin.NewService(Version, n.ID, n.Config.NetworkID, n.Log, n.chainManager, n.Net, &n.APIServer)
|
||||
n.APIServer.AddRoute(service, &sync.RWMutex{}, "admin", "", n.HTTPLog)
|
||||
}
|
||||
}
|
||||
|
@ -551,6 +558,8 @@ func (n *Node) Initialize(Config *Config, logger logging.Logger, logFactory logg
|
|||
return fmt.Errorf("problem initializing staker ID: %w", err)
|
||||
}
|
||||
|
||||
n.initBeacons()
|
||||
|
||||
// Start HTTP APIs
|
||||
n.initAPIServer() // Start the API Server
|
||||
n.initKeystoreAPI() // Start the Keystore API
|
||||
|
|
|
@ -15,7 +15,7 @@ GECKO_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) # Directory
|
|||
BUILD_DIR=$GECKO_PATH/build # Where binaries 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"
|
||||
|
||||
# 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"
|
||||
)
|
||||
|
||||
const (
|
||||
minMapSize = 16
|
||||
)
|
||||
|
||||
// TopologicalFactory implements Factory by returning a topological struct
|
||||
type TopologicalFactory struct{}
|
||||
|
||||
|
@ -65,12 +69,12 @@ func (ta *Topological) Initialize(ctx *snow.Context, params Parameters, frontier
|
|||
ta.ctx.Log.Error("%s", err)
|
||||
}
|
||||
|
||||
ta.nodes = make(map[[32]byte]Vertex)
|
||||
ta.nodes = make(map[[32]byte]Vertex, minMapSize)
|
||||
|
||||
ta.cg = &snowstorm.Directed{}
|
||||
ta.cg.Initialize(ctx, params.Parameters)
|
||||
|
||||
ta.frontier = make(map[[32]byte]Vertex)
|
||||
ta.frontier = make(map[[32]byte]Vertex, minMapSize)
|
||||
for _, vtx := range frontier {
|
||||
ta.frontier[vtx.ID().Key()] = vtx
|
||||
}
|
||||
|
@ -141,7 +145,9 @@ func (ta *Topological) RecordPoll(responses ids.UniqueBag) error {
|
|||
votes := ta.pushVotes(kahns, leaves)
|
||||
// Update the conflict graph: O(|Transactions|)
|
||||
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|)
|
||||
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.
|
||||
func (ta *Topological) calculateInDegree(
|
||||
responses ids.UniqueBag) (map[[32]byte]kahnNode, []ids.ID) {
|
||||
kahns := make(map[[32]byte]kahnNode)
|
||||
kahns := make(map[[32]byte]kahnNode, minMapSize)
|
||||
leaves := ids.Set{}
|
||||
|
||||
for _, vote := range responses.List() {
|
||||
|
@ -231,7 +237,7 @@ func (ta *Topological) pushVotes(
|
|||
kahnNodes map[[32]byte]kahnNode,
|
||||
leaves []ids.ID) ids.Bag {
|
||||
votes := make(ids.UniqueBag)
|
||||
txConflicts := make(map[[32]byte]ids.Set)
|
||||
txConflicts := make(map[[32]byte]ids.Set, minMapSize)
|
||||
|
||||
for len(leaves) > 0 {
|
||||
newLeavesSize := len(leaves) - 1
|
||||
|
@ -441,9 +447,9 @@ func (ta *Topological) updateFrontiers() error {
|
|||
ta.preferred.Clear()
|
||||
ta.virtuous.Clear()
|
||||
ta.orphans.Clear()
|
||||
ta.frontier = make(map[[32]byte]Vertex)
|
||||
ta.preferenceCache = make(map[[32]byte]bool)
|
||||
ta.virtuousCache = make(map[[32]byte]bool)
|
||||
ta.frontier = make(map[[32]byte]Vertex, minMapSize)
|
||||
ta.preferenceCache = make(map[[32]byte]bool, minMapSize)
|
||||
ta.virtuousCache = make(map[[32]byte]bool, minMapSize)
|
||||
|
||||
ta.orphans.Union(ta.cg.Virtuous()) // Initially, nothing is preferred
|
||||
|
||||
|
|
|
@ -4,830 +4,7 @@
|
|||
package avalanche
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow"
|
||||
"github.com/ava-labs/gecko/snow/choices"
|
||||
"github.com/ava-labs/gecko/snow/consensus/snowball"
|
||||
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
|
||||
)
|
||||
|
||||
func TestTopologicalParams(t *testing.T) { ParamsTest(t, TopologicalFactory{}) }
|
||||
|
||||
func TestTopologicalAdd(t *testing.T) { AddTest(t, TopologicalFactory{}) }
|
||||
|
||||
func TestTopologicalVertexIssued(t *testing.T) { VertexIssuedTest(t, TopologicalFactory{}) }
|
||||
|
||||
func TestTopologicalTxIssued(t *testing.T) { TxIssuedTest(t, TopologicalFactory{}) }
|
||||
|
||||
func TestAvalancheVoting(t *testing.T) {
|
||||
params := Parameters{
|
||||
Parameters: snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 2,
|
||||
Alpha: 2,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 2,
|
||||
ConcurrentRepolls: 1,
|
||||
},
|
||||
Parents: 2,
|
||||
BatchSize: 1,
|
||||
}
|
||||
vts := []Vertex{&Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}, &Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}}
|
||||
utxos := []ids.ID{GenerateID()}
|
||||
|
||||
ta := Topological{}
|
||||
ta.Initialize(snow.DefaultContextTest(), params, vts)
|
||||
|
||||
tx0 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx0.Ins.Add(utxos[0])
|
||||
|
||||
vtx0 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx1 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx1.Ins.Add(utxos[0])
|
||||
|
||||
vtx1 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx1},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
ta.Add(vtx0)
|
||||
ta.Add(vtx1)
|
||||
|
||||
sm := make(ids.UniqueBag)
|
||||
sm.Add(0, vtx1.id)
|
||||
sm.Add(1, vtx1.id)
|
||||
ta.RecordPoll(sm)
|
||||
|
||||
if ta.Finalized() {
|
||||
t.Fatalf("An avalanche instance finalized too early")
|
||||
} else if !ids.UnsortedEquals([]ids.ID{vtx1.id}, ta.Preferences().List()) {
|
||||
t.Fatalf("Initial frontier failed to be set")
|
||||
}
|
||||
|
||||
ta.RecordPoll(sm)
|
||||
|
||||
if !ta.Finalized() {
|
||||
t.Fatalf("An avalanche instance finalized too late")
|
||||
} else if !ids.UnsortedEquals([]ids.ID{vtx1.id}, ta.Preferences().List()) {
|
||||
t.Fatalf("Initial frontier failed to be set")
|
||||
} else if tx0.Status() != choices.Rejected {
|
||||
t.Fatalf("Tx should have been rejected")
|
||||
} else if tx1.Status() != choices.Accepted {
|
||||
t.Fatalf("Tx should have been accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvalancheIgnoreInvalidVoting(t *testing.T) {
|
||||
params := Parameters{
|
||||
Parameters: snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 3,
|
||||
Alpha: 2,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 1,
|
||||
},
|
||||
Parents: 2,
|
||||
BatchSize: 1,
|
||||
}
|
||||
|
||||
vts := []Vertex{&Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}, &Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}}
|
||||
utxos := []ids.ID{GenerateID()}
|
||||
|
||||
ta := Topological{}
|
||||
ta.Initialize(snow.DefaultContextTest(), params, vts)
|
||||
|
||||
tx0 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx0.Ins.Add(utxos[0])
|
||||
|
||||
vtx0 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx1 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx1.Ins.Add(utxos[0])
|
||||
|
||||
vtx1 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx1},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
ta.Add(vtx0)
|
||||
ta.Add(vtx1)
|
||||
|
||||
sm := make(ids.UniqueBag)
|
||||
|
||||
sm.Add(0, vtx0.id)
|
||||
sm.Add(1, vtx1.id)
|
||||
|
||||
// Add Illegal Vote cast by Response 2
|
||||
sm.Add(2, vtx0.id)
|
||||
sm.Add(2, vtx1.id)
|
||||
|
||||
ta.RecordPoll(sm)
|
||||
|
||||
if ta.Finalized() {
|
||||
t.Fatalf("An avalanche instance finalized too early")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvalancheTransitiveVoting(t *testing.T) {
|
||||
params := Parameters{
|
||||
Parameters: snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 2,
|
||||
Alpha: 2,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 2,
|
||||
ConcurrentRepolls: 1,
|
||||
},
|
||||
Parents: 2,
|
||||
BatchSize: 1,
|
||||
}
|
||||
vts := []Vertex{&Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}, &Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}}
|
||||
utxos := []ids.ID{GenerateID(), GenerateID()}
|
||||
|
||||
ta := Topological{}
|
||||
ta.Initialize(snow.DefaultContextTest(), params, vts)
|
||||
|
||||
tx0 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx0.Ins.Add(utxos[0])
|
||||
|
||||
vtx0 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx1 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx1.Ins.Add(utxos[1])
|
||||
|
||||
vtx1 := &Vtx{
|
||||
dependencies: []Vertex{vtx0},
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx1},
|
||||
height: 2,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
vtx2 := &Vtx{
|
||||
dependencies: []Vertex{vtx1},
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx1},
|
||||
height: 3,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
ta.Add(vtx0)
|
||||
ta.Add(vtx1)
|
||||
ta.Add(vtx2)
|
||||
|
||||
sm1 := make(ids.UniqueBag)
|
||||
sm1.Add(0, vtx0.id)
|
||||
sm1.Add(1, vtx2.id)
|
||||
ta.RecordPoll(sm1)
|
||||
|
||||
if ta.Finalized() {
|
||||
t.Fatalf("An avalanche instance finalized too early")
|
||||
} else if !ids.UnsortedEquals([]ids.ID{vtx2.id}, ta.Preferences().List()) {
|
||||
t.Fatalf("Initial frontier failed to be set")
|
||||
} else if tx0.Status() != choices.Accepted {
|
||||
t.Fatalf("Tx should have been accepted")
|
||||
}
|
||||
|
||||
sm2 := make(ids.UniqueBag)
|
||||
sm2.Add(0, vtx2.id)
|
||||
sm2.Add(1, vtx2.id)
|
||||
ta.RecordPoll(sm2)
|
||||
|
||||
if !ta.Finalized() {
|
||||
t.Fatalf("An avalanche instance finalized too late")
|
||||
} else if !ids.UnsortedEquals([]ids.ID{vtx2.id}, ta.Preferences().List()) {
|
||||
t.Fatalf("Initial frontier failed to be set")
|
||||
} else if tx0.Status() != choices.Accepted {
|
||||
t.Fatalf("Tx should have been accepted")
|
||||
} else if tx1.Status() != choices.Accepted {
|
||||
t.Fatalf("Tx should have been accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvalancheSplitVoting(t *testing.T) {
|
||||
params := Parameters{
|
||||
Parameters: snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 2,
|
||||
Alpha: 2,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 2,
|
||||
ConcurrentRepolls: 1,
|
||||
},
|
||||
Parents: 2,
|
||||
BatchSize: 1,
|
||||
}
|
||||
vts := []Vertex{&Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}, &Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}}
|
||||
utxos := []ids.ID{GenerateID()}
|
||||
|
||||
ta := Topological{}
|
||||
ta.Initialize(snow.DefaultContextTest(), params, vts)
|
||||
|
||||
tx0 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx0.Ins.Add(utxos[0])
|
||||
|
||||
vtx0 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
vtx1 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
ta.Add(vtx0)
|
||||
ta.Add(vtx1)
|
||||
|
||||
sm1 := make(ids.UniqueBag)
|
||||
sm1.Add(0, vtx0.id)
|
||||
sm1.Add(1, vtx1.id)
|
||||
ta.RecordPoll(sm1)
|
||||
|
||||
if !ta.Finalized() {
|
||||
t.Fatalf("An avalanche instance finalized too late")
|
||||
} else if !ids.UnsortedEquals([]ids.ID{vtx0.id, vtx1.id}, ta.Preferences().List()) {
|
||||
t.Fatalf("Initial frontier failed to be set")
|
||||
} else if tx0.Status() != choices.Accepted {
|
||||
t.Fatalf("Tx should have been accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvalancheTransitiveRejection(t *testing.T) {
|
||||
params := Parameters{
|
||||
Parameters: snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 2,
|
||||
Alpha: 2,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 2,
|
||||
ConcurrentRepolls: 1,
|
||||
},
|
||||
Parents: 2,
|
||||
BatchSize: 1,
|
||||
}
|
||||
vts := []Vertex{&Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}, &Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}}
|
||||
utxos := []ids.ID{GenerateID(), GenerateID()}
|
||||
|
||||
ta := Topological{}
|
||||
ta.Initialize(snow.DefaultContextTest(), params, vts)
|
||||
|
||||
tx0 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx0.Ins.Add(utxos[0])
|
||||
|
||||
vtx0 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx1 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx1.Ins.Add(utxos[0])
|
||||
|
||||
vtx1 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx1},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx2 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx2.Ins.Add(utxos[1])
|
||||
|
||||
vtx2 := &Vtx{
|
||||
dependencies: []Vertex{vtx0},
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx2},
|
||||
height: 2,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
ta.Add(vtx0)
|
||||
ta.Add(vtx1)
|
||||
ta.Add(vtx2)
|
||||
|
||||
sm := make(ids.UniqueBag)
|
||||
sm.Add(0, vtx1.id)
|
||||
sm.Add(1, vtx1.id)
|
||||
ta.RecordPoll(sm)
|
||||
|
||||
if ta.Finalized() {
|
||||
t.Fatalf("An avalanche instance finalized too early")
|
||||
} else if !ids.UnsortedEquals([]ids.ID{vtx1.id}, ta.Preferences().List()) {
|
||||
t.Fatalf("Initial frontier failed to be set")
|
||||
}
|
||||
|
||||
ta.RecordPoll(sm)
|
||||
|
||||
if ta.Finalized() {
|
||||
t.Fatalf("An avalanche instance finalized too early")
|
||||
} else if !ids.UnsortedEquals([]ids.ID{vtx1.id}, ta.Preferences().List()) {
|
||||
t.Fatalf("Initial frontier failed to be set")
|
||||
} else if tx0.Status() != choices.Rejected {
|
||||
t.Fatalf("Tx should have been rejected")
|
||||
} else if tx1.Status() != choices.Accepted {
|
||||
t.Fatalf("Tx should have been accepted")
|
||||
} else if tx2.Status() != choices.Processing {
|
||||
t.Fatalf("Tx should not have been decided")
|
||||
}
|
||||
|
||||
ta.preferenceCache = make(map[[32]byte]bool)
|
||||
ta.virtuousCache = make(map[[32]byte]bool)
|
||||
|
||||
ta.update(vtx2)
|
||||
}
|
||||
|
||||
func TestAvalancheVirtuous(t *testing.T) {
|
||||
params := Parameters{
|
||||
Parameters: snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 2,
|
||||
Alpha: 2,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 2,
|
||||
ConcurrentRepolls: 1,
|
||||
},
|
||||
Parents: 2,
|
||||
BatchSize: 1,
|
||||
}
|
||||
vts := []Vertex{&Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}, &Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}}
|
||||
utxos := []ids.ID{GenerateID(), GenerateID()}
|
||||
|
||||
ta := Topological{}
|
||||
ta.Initialize(snow.DefaultContextTest(), params, vts)
|
||||
|
||||
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(vts[0].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
} else if !virtuous.Contains(vts[1].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
}
|
||||
|
||||
tx0 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx0.Ins.Add(utxos[0])
|
||||
|
||||
vtx0 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx1 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx1.Ins.Add(utxos[0])
|
||||
|
||||
vtx1 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx1},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx2 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx2.Ins.Add(utxos[1])
|
||||
|
||||
vtx2 := &Vtx{
|
||||
dependencies: []Vertex{vtx0},
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx2},
|
||||
height: 2,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
ta.Add(vtx0)
|
||||
|
||||
if virtuous := ta.Virtuous(); virtuous.Len() != 1 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(vtx0.id) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
}
|
||||
|
||||
ta.Add(vtx1)
|
||||
|
||||
if virtuous := ta.Virtuous(); virtuous.Len() != 1 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(vtx0.id) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
}
|
||||
|
||||
ta.updateFrontiers()
|
||||
|
||||
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(vts[0].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
} else if !virtuous.Contains(vts[1].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
}
|
||||
|
||||
ta.Add(vtx2)
|
||||
|
||||
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(vts[0].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
} else if !virtuous.Contains(vts[1].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
}
|
||||
|
||||
ta.updateFrontiers()
|
||||
|
||||
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(vts[0].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
} else if !virtuous.Contains(vts[1].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvalancheIsVirtuous(t *testing.T) {
|
||||
params := Parameters{
|
||||
Parameters: snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 2,
|
||||
Alpha: 2,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 2,
|
||||
ConcurrentRepolls: 1,
|
||||
},
|
||||
Parents: 2,
|
||||
BatchSize: 1,
|
||||
}
|
||||
vts := []Vertex{&Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}, &Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}}
|
||||
utxos := []ids.ID{GenerateID(), GenerateID()}
|
||||
|
||||
ta := Topological{}
|
||||
ta.Initialize(snow.DefaultContextTest(), params, vts)
|
||||
|
||||
if virtuous := ta.Virtuous(); virtuous.Len() != 2 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(vts[0].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
} else if !virtuous.Contains(vts[1].ID()) {
|
||||
t.Fatalf("Wrong virtuous")
|
||||
}
|
||||
|
||||
tx0 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx0.Ins.Add(utxos[0])
|
||||
|
||||
vtx0 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx1 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx1.Ins.Add(utxos[0])
|
||||
|
||||
vtx1 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx1},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
if !ta.IsVirtuous(tx0) {
|
||||
t.Fatalf("Should be virtuous.")
|
||||
} else if !ta.IsVirtuous(tx1) {
|
||||
t.Fatalf("Should be virtuous.")
|
||||
}
|
||||
|
||||
ta.Add(vtx0)
|
||||
|
||||
if !ta.IsVirtuous(tx0) {
|
||||
t.Fatalf("Should be virtuous.")
|
||||
} else if ta.IsVirtuous(tx1) {
|
||||
t.Fatalf("Should not be virtuous.")
|
||||
}
|
||||
|
||||
ta.Add(vtx1)
|
||||
|
||||
if ta.IsVirtuous(tx0) {
|
||||
t.Fatalf("Should not be virtuous.")
|
||||
} else if ta.IsVirtuous(tx1) {
|
||||
t.Fatalf("Should not be virtuous.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvalancheQuiesce(t *testing.T) {
|
||||
params := Parameters{
|
||||
Parameters: snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1,
|
||||
Alpha: 1,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 1,
|
||||
ConcurrentRepolls: 1,
|
||||
},
|
||||
Parents: 2,
|
||||
BatchSize: 1,
|
||||
}
|
||||
vts := []Vertex{&Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}, &Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}}
|
||||
utxos := []ids.ID{GenerateID(), GenerateID()}
|
||||
|
||||
ta := Topological{}
|
||||
ta.Initialize(snow.DefaultContextTest(), params, vts)
|
||||
|
||||
tx0 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx0.Ins.Add(utxos[0])
|
||||
|
||||
vtx0 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx1 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx1.Ins.Add(utxos[0])
|
||||
|
||||
vtx1 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx1},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx2 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx2.Ins.Add(utxos[1])
|
||||
|
||||
vtx2 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx2},
|
||||
height: 2,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
ta.Add(vtx0)
|
||||
|
||||
if ta.Quiesce() {
|
||||
t.Fatalf("Shouldn't quiesce")
|
||||
}
|
||||
|
||||
ta.Add(vtx1)
|
||||
|
||||
if !ta.Quiesce() {
|
||||
t.Fatalf("Should quiesce")
|
||||
}
|
||||
|
||||
ta.Add(vtx2)
|
||||
|
||||
if ta.Quiesce() {
|
||||
t.Fatalf("Shouldn't quiesce")
|
||||
}
|
||||
|
||||
sm := make(ids.UniqueBag)
|
||||
sm.Add(0, vtx2.id)
|
||||
ta.RecordPoll(sm)
|
||||
|
||||
if !ta.Quiesce() {
|
||||
t.Fatalf("Should quiesce")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvalancheOrphans(t *testing.T) {
|
||||
params := Parameters{
|
||||
Parameters: snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1,
|
||||
Alpha: 1,
|
||||
BetaVirtuous: math.MaxInt32,
|
||||
BetaRogue: math.MaxInt32,
|
||||
ConcurrentRepolls: 1,
|
||||
},
|
||||
Parents: 2,
|
||||
BatchSize: 1,
|
||||
}
|
||||
vts := []Vertex{&Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}, &Vtx{
|
||||
id: GenerateID(),
|
||||
status: choices.Accepted,
|
||||
}}
|
||||
utxos := []ids.ID{GenerateID(), GenerateID()}
|
||||
|
||||
ta := Topological{}
|
||||
ta.Initialize(snow.DefaultContextTest(), params, vts)
|
||||
|
||||
tx0 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx0.Ins.Add(utxos[0])
|
||||
|
||||
vtx0 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx0},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx1 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx1.Ins.Add(utxos[0])
|
||||
|
||||
vtx1 := &Vtx{
|
||||
dependencies: vts,
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx1},
|
||||
height: 1,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
tx2 := &snowstorm.TestTx{
|
||||
Identifier: GenerateID(),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
tx2.Ins.Add(utxos[1])
|
||||
|
||||
vtx2 := &Vtx{
|
||||
dependencies: []Vertex{vtx0},
|
||||
id: GenerateID(),
|
||||
txs: []snowstorm.Tx{tx2},
|
||||
height: 2,
|
||||
status: choices.Processing,
|
||||
}
|
||||
|
||||
ta.Add(vtx0)
|
||||
|
||||
if orphans := ta.Orphans(); orphans.Len() != 0 {
|
||||
t.Fatalf("Wrong number of orphans")
|
||||
}
|
||||
|
||||
ta.Add(vtx1)
|
||||
|
||||
if orphans := ta.Orphans(); orphans.Len() != 0 {
|
||||
t.Fatalf("Wrong number of orphans")
|
||||
}
|
||||
|
||||
ta.Add(vtx2)
|
||||
|
||||
if orphans := ta.Orphans(); orphans.Len() != 0 {
|
||||
t.Fatalf("Wrong number of orphans")
|
||||
}
|
||||
|
||||
sm := make(ids.UniqueBag)
|
||||
sm.Add(0, vtx1.id)
|
||||
ta.RecordPoll(sm)
|
||||
|
||||
if orphans := ta.Orphans(); orphans.Len() != 1 {
|
||||
t.Fatalf("Wrong number of orphans")
|
||||
} else if !orphans.Contains(tx2.ID()) {
|
||||
t.Fatalf("Wrong orphan")
|
||||
}
|
||||
}
|
||||
func TestTopological(t *testing.T) { ConsensusTest(t, TopologicalFactory{}) }
|
||||
|
|
|
@ -19,6 +19,7 @@ type Vtx struct {
|
|||
height uint64
|
||||
status choices.Status
|
||||
|
||||
Validity error
|
||||
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) Txs() []snowstorm.Tx { return v.txs }
|
||||
func (v *Vtx) Status() choices.Status { return v.status }
|
||||
func (v *Vtx) Live() {}
|
||||
func (v *Vtx) Accept() error { v.status = choices.Accepted; return nil }
|
||||
func (v *Vtx) Reject() error { v.status = choices.Rejected; return nil }
|
||||
func (v *Vtx) Accept() error { v.status = choices.Accepted; return v.Validity }
|
||||
func (v *Vtx) Reject() error { v.status = choices.Rejected; return v.Validity }
|
||||
func (v *Vtx) Bytes() []byte { return v.bytes }
|
||||
|
||||
type sortVts []*Vtx
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
// ByzantineFactory implements Factory by returning a byzantine struct
|
||||
type ByzantineFactory struct{}
|
||||
|
||||
// New implements Factory
|
||||
func (ByzantineFactory) New() Consensus { return &Byzantine{} }
|
||||
|
||||
// Byzantine is a naive implementation of a multi-choice snowball instance
|
||||
type Byzantine struct {
|
||||
// params contains all the configurations of a snowball instance
|
||||
params Parameters
|
||||
|
||||
// Hardcode the preference
|
||||
preference ids.ID
|
||||
}
|
||||
|
||||
// Initialize implements the Consensus interface
|
||||
func (b *Byzantine) Initialize(params Parameters, choice ids.ID) {
|
||||
b.params = params
|
||||
b.preference = choice
|
||||
}
|
||||
|
||||
// Parameters implements the Consensus interface
|
||||
func (b *Byzantine) Parameters() Parameters { return b.params }
|
||||
|
||||
// Add implements the Consensus interface
|
||||
func (b *Byzantine) Add(choice ids.ID) {}
|
||||
|
||||
// Preference implements the Consensus interface
|
||||
func (b *Byzantine) Preference() ids.ID { return b.preference }
|
||||
|
||||
// RecordPoll implements the Consensus interface
|
||||
func (b *Byzantine) RecordPoll(votes ids.Bag) {}
|
||||
|
||||
// RecordUnsuccessfulPoll implements the Consensus interface
|
||||
func (b *Byzantine) RecordUnsuccessfulPoll() {}
|
||||
|
||||
// Finalized implements the Consensus interface
|
||||
func (b *Byzantine) Finalized() bool { return true }
|
||||
func (b *Byzantine) String() string { return b.preference.String() }
|
||||
|
||||
var (
|
||||
Red = ids.Empty.Prefix(0)
|
||||
Blue = ids.Empty.Prefix(1)
|
||||
|
|
|
@ -34,7 +34,7 @@ func (f *Flat) Parameters() Parameters { return f.params }
|
|||
// RecordPoll implements the Consensus interface
|
||||
func (f *Flat) RecordPoll(votes ids.Bag) {
|
||||
if pollMode, numVotes := votes.Mode(); numVotes >= f.params.Alpha {
|
||||
f.nnarySnowball.RecordSuccessfulPoll(pollMode)
|
||||
f.RecordSuccessfulPoll(pollMode)
|
||||
} else {
|
||||
f.RecordUnsuccessfulPoll()
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ func (sf *nnarySnowflake) RecordSuccessfulPoll(choice ids.ID) {
|
|||
return // This instace is already decided.
|
||||
}
|
||||
|
||||
if preference := sf.nnarySlush.Preference(); preference.Equals(choice) {
|
||||
if preference := sf.Preference(); preference.Equals(choice) {
|
||||
sf.confidence++
|
||||
} else {
|
||||
// confidence is set to 1 because there has already been 1 successful
|
||||
|
|
|
@ -17,6 +17,7 @@ type TestBlock struct {
|
|||
height int
|
||||
status choices.Status
|
||||
bytes []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (b *TestBlock) Parent() Block { return b.parent }
|
||||
|
@ -27,16 +28,16 @@ func (b *TestBlock) Accept() error {
|
|||
return errors.New("Dis-agreement")
|
||||
}
|
||||
b.status = choices.Accepted
|
||||
return nil
|
||||
return b.err
|
||||
}
|
||||
func (b *TestBlock) Reject() error {
|
||||
if b.status.Decided() && b.status != choices.Rejected {
|
||||
return errors.New("Dis-agreement")
|
||||
}
|
||||
b.status = choices.Rejected
|
||||
return nil
|
||||
return b.err
|
||||
}
|
||||
func (b *TestBlock) Verify() error { return nil }
|
||||
func (b *TestBlock) Verify() error { return b.err }
|
||||
func (b *TestBlock) Bytes() []byte { return b.bytes }
|
||||
|
||||
type sortBlocks []*TestBlock
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package snowman
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
|
@ -42,6 +43,10 @@ var (
|
|||
MetricsProcessingErrorTest,
|
||||
MetricsAcceptedErrorTest,
|
||||
MetricsRejectedErrorTest,
|
||||
ErrorOnInitialRejectionTest,
|
||||
ErrorOnAcceptTest,
|
||||
ErrorOnRejectSiblingTest,
|
||||
ErrorOnTransitiveRejectionTest,
|
||||
RandomizedConsistencyTest,
|
||||
}
|
||||
)
|
||||
|
@ -71,11 +76,9 @@ func InitializeTest(t *testing.T, factory Factory) {
|
|||
|
||||
if p := sm.Parameters(); p != params {
|
||||
t.Fatalf("Wrong returned parameters")
|
||||
}
|
||||
if pref := sm.Preference(); !pref.Equals(GenesisID) {
|
||||
} else if pref := sm.Preference(); !pref.Equals(GenesisID) {
|
||||
t.Fatalf("Wrong preference returned")
|
||||
}
|
||||
if !sm.Finalized() {
|
||||
} else if !sm.Finalized() {
|
||||
t.Fatalf("Wrong should have marked the instance as being finalized")
|
||||
}
|
||||
}
|
||||
|
@ -101,9 +104,9 @@ func AddToTailTest(t *testing.T, factory Factory) {
|
|||
}
|
||||
|
||||
// Adding to the previous preference will update the preference
|
||||
sm.Add(block)
|
||||
|
||||
if pref := sm.Preference(); !pref.Equals(block.id) {
|
||||
if err := sm.Add(block); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pref := sm.Preference(); !pref.Equals(block.id) {
|
||||
t.Fatalf("Wrong preference. Expected %s, got %s", block.id, pref)
|
||||
}
|
||||
}
|
||||
|
@ -133,17 +136,17 @@ func AddToNonTailTest(t *testing.T, factory Factory) {
|
|||
}
|
||||
|
||||
// Adding to the previous preference will update the preference
|
||||
sm.Add(firstBlock)
|
||||
|
||||
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||
if err := sm.Add(firstBlock); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||
t.Fatalf("Wrong preference. Expected %s, got %s", firstBlock.id, pref)
|
||||
}
|
||||
|
||||
// Adding to something other than the previous preference won't update the
|
||||
// preference
|
||||
sm.Add(secondBlock)
|
||||
|
||||
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||
if err := sm.Add(secondBlock); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||
t.Fatalf("Wrong preference. Expected %s, got %s", firstBlock.id, pref)
|
||||
}
|
||||
}
|
||||
|
@ -171,9 +174,9 @@ func AddToUnknownTest(t *testing.T, factory Factory) {
|
|||
|
||||
// Adding a block with an unknown parent means the parent must have already
|
||||
// been rejected. Therefore the block should be immediately rejected
|
||||
sm.Add(block)
|
||||
|
||||
if pref := sm.Preference(); !pref.Equals(GenesisID) {
|
||||
if err := sm.Add(block); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pref := sm.Preference(); !pref.Equals(GenesisID) {
|
||||
t.Fatalf("Wrong preference. Expected %s, got %s", GenesisID, pref)
|
||||
} else if status := block.Status(); status != choices.Rejected {
|
||||
t.Fatalf("Should have rejected the block")
|
||||
|
@ -269,9 +272,9 @@ func IssuedIssuedTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(block)
|
||||
|
||||
if !sm.Issued(block) {
|
||||
if err := sm.Add(block); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !sm.Issued(block) {
|
||||
t.Fatalf("Should have marked a pending block as having been issued")
|
||||
}
|
||||
}
|
||||
|
@ -296,24 +299,23 @@ func RecordPollAcceptSingleBlockTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(block)
|
||||
if err := sm.Add(block); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(block.id)
|
||||
|
||||
sm.RecordPoll(votes)
|
||||
|
||||
if pref := sm.Preference(); !pref.Equals(block.id) {
|
||||
if err := sm.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pref := sm.Preference(); !pref.Equals(block.id) {
|
||||
t.Fatalf("Preference returned the wrong block")
|
||||
} else if sm.Finalized() {
|
||||
t.Fatalf("Snowman instance finalized too soon")
|
||||
} else if status := block.Status(); status != choices.Processing {
|
||||
t.Fatalf("Block's status changed unexpectedly")
|
||||
}
|
||||
|
||||
sm.RecordPoll(votes)
|
||||
|
||||
if pref := sm.Preference(); !pref.Equals(block.id) {
|
||||
} else if err := sm.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pref := sm.Preference(); !pref.Equals(block.id) {
|
||||
t.Fatalf("Preference returned the wrong block")
|
||||
} else if !sm.Finalized() {
|
||||
t.Fatalf("Snowman instance didn't finalize")
|
||||
|
@ -347,15 +349,18 @@ func RecordPollAcceptAndRejectTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(firstBlock)
|
||||
sm.Add(secondBlock)
|
||||
if err := sm.Add(firstBlock); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(secondBlock); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(firstBlock.id)
|
||||
|
||||
sm.RecordPoll(votes)
|
||||
|
||||
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||
if err := sm.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||
t.Fatalf("Preference returned the wrong block")
|
||||
} else if sm.Finalized() {
|
||||
t.Fatalf("Snowman instance finalized too soon")
|
||||
|
@ -363,11 +368,9 @@ func RecordPollAcceptAndRejectTest(t *testing.T, factory Factory) {
|
|||
t.Fatalf("Block's status changed unexpectedly")
|
||||
} else if status := secondBlock.Status(); status != choices.Processing {
|
||||
t.Fatalf("Block's status changed unexpectedly")
|
||||
}
|
||||
|
||||
sm.RecordPoll(votes)
|
||||
|
||||
if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||
} else if err := sm.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if pref := sm.Preference(); !pref.Equals(firstBlock.id) {
|
||||
t.Fatalf("Preference returned the wrong block")
|
||||
} else if !sm.Finalized() {
|
||||
t.Fatalf("Snowman instance didn't finalize")
|
||||
|
@ -394,9 +397,9 @@ func RecordPollWhenFinalizedTest(t *testing.T, factory Factory) {
|
|||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(GenesisID)
|
||||
sm.RecordPoll(votes)
|
||||
|
||||
if !sm.Finalized() {
|
||||
if err := sm.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !sm.Finalized() {
|
||||
t.Fatalf("Consensus should still be finalized")
|
||||
} else if pref := sm.Preference(); !GenesisID.Equals(pref) {
|
||||
t.Fatalf("Wrong preference listed")
|
||||
|
@ -433,9 +436,13 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(block0)
|
||||
sm.Add(block1)
|
||||
sm.Add(block2)
|
||||
if err := sm.Add(block0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block1); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Current graph structure:
|
||||
// G
|
||||
|
@ -447,7 +454,9 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) {
|
|||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(block0.id)
|
||||
sm.RecordPoll(votes)
|
||||
if err := sm.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Current graph structure:
|
||||
// 0
|
||||
|
@ -457,9 +466,7 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) {
|
|||
t.Fatalf("Finalized too late")
|
||||
} else if pref := sm.Preference(); !block0.id.Equals(pref) {
|
||||
t.Fatalf("Wrong preference listed")
|
||||
}
|
||||
|
||||
if status := block0.Status(); status != choices.Accepted {
|
||||
} else if status := block0.Status(); status != choices.Accepted {
|
||||
t.Fatalf("Wrong status returned")
|
||||
} else if status := block1.Status(); status != choices.Rejected {
|
||||
t.Fatalf("Wrong status returned")
|
||||
|
@ -503,10 +510,15 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(block0)
|
||||
sm.Add(block1)
|
||||
sm.Add(block2)
|
||||
sm.Add(block3)
|
||||
if err := sm.Add(block0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block1); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block2); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block3); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Current graph structure:
|
||||
// G
|
||||
|
@ -517,26 +529,24 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) {
|
|||
|
||||
votesFor2 := ids.Bag{}
|
||||
votesFor2.Add(block2.id)
|
||||
sm.RecordPoll(votesFor2)
|
||||
|
||||
if sm.Finalized() {
|
||||
if err := sm.RecordPoll(votesFor2); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if sm.Finalized() {
|
||||
t.Fatalf("Finalized too early")
|
||||
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
||||
t.Fatalf("Wrong preference listed")
|
||||
}
|
||||
|
||||
emptyVotes := ids.Bag{}
|
||||
sm.RecordPoll(emptyVotes)
|
||||
|
||||
if sm.Finalized() {
|
||||
if err := sm.RecordPoll(emptyVotes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if sm.Finalized() {
|
||||
t.Fatalf("Finalized too early")
|
||||
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
||||
t.Fatalf("Wrong preference listed")
|
||||
}
|
||||
|
||||
sm.RecordPoll(votesFor2)
|
||||
|
||||
if sm.Finalized() {
|
||||
} else if err := sm.RecordPoll(votesFor2); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if sm.Finalized() {
|
||||
t.Fatalf("Finalized too early")
|
||||
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
||||
t.Fatalf("Wrong preference listed")
|
||||
|
@ -544,23 +554,19 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) {
|
|||
|
||||
votesFor3 := ids.Bag{}
|
||||
votesFor3.Add(block3.id)
|
||||
sm.RecordPoll(votesFor3)
|
||||
|
||||
if sm.Finalized() {
|
||||
if err := sm.RecordPoll(votesFor3); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if sm.Finalized() {
|
||||
t.Fatalf("Finalized too early")
|
||||
} else if pref := sm.Preference(); !block2.id.Equals(pref) {
|
||||
t.Fatalf("Wrong preference listed")
|
||||
}
|
||||
|
||||
sm.RecordPoll(votesFor3)
|
||||
|
||||
if !sm.Finalized() {
|
||||
} else if err := sm.RecordPoll(votesFor3); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !sm.Finalized() {
|
||||
t.Fatalf("Finalized too late")
|
||||
} else if pref := sm.Preference(); !block3.id.Equals(pref) {
|
||||
t.Fatalf("Wrong preference listed")
|
||||
}
|
||||
|
||||
if status := block0.Status(); status != choices.Rejected {
|
||||
} else if status := block0.Status(); status != choices.Rejected {
|
||||
t.Fatalf("Wrong status returned")
|
||||
} else if status := block1.Status(); status != choices.Accepted {
|
||||
t.Fatalf("Wrong status returned")
|
||||
|
@ -592,19 +598,23 @@ func RecordPollInvalidVoteTest(t *testing.T, factory Factory) {
|
|||
}
|
||||
unknownBlockID := ids.Empty.Prefix(2)
|
||||
|
||||
sm.Add(block)
|
||||
if err := sm.Add(block); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validVotes := ids.Bag{}
|
||||
validVotes.Add(block.id)
|
||||
sm.RecordPoll(validVotes)
|
||||
if err := sm.RecordPoll(validVotes); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
invalidVotes := ids.Bag{}
|
||||
invalidVotes.Add(unknownBlockID)
|
||||
sm.RecordPoll(invalidVotes)
|
||||
|
||||
sm.RecordPoll(validVotes)
|
||||
|
||||
if sm.Finalized() {
|
||||
if err := sm.RecordPoll(invalidVotes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.RecordPoll(validVotes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if sm.Finalized() {
|
||||
t.Fatalf("Finalized too early")
|
||||
} else if pref := sm.Preference(); !block.id.Equals(pref) {
|
||||
t.Fatalf("Wrong preference listed")
|
||||
|
@ -651,11 +661,17 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(block0)
|
||||
sm.Add(block1)
|
||||
sm.Add(block2)
|
||||
sm.Add(block3)
|
||||
sm.Add(block4)
|
||||
if err := sm.Add(block0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block1); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block2); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block3); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block4); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Current graph structure:
|
||||
// G
|
||||
|
@ -668,10 +684,14 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) {
|
|||
// Tail = 2
|
||||
|
||||
votes0_2_4 := ids.Bag{}
|
||||
votes0_2_4.Add(block0.id)
|
||||
votes0_2_4.Add(block2.id)
|
||||
votes0_2_4.Add(block4.id)
|
||||
sm.RecordPoll(votes0_2_4)
|
||||
votes0_2_4.Add(
|
||||
block0.id,
|
||||
block2.id,
|
||||
block4.id,
|
||||
)
|
||||
if err := sm.RecordPoll(votes0_2_4); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Current graph structure:
|
||||
// 0
|
||||
|
@ -699,7 +719,9 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) {
|
|||
|
||||
dep2_2_2 := ids.Bag{}
|
||||
dep2_2_2.AddCount(block2.id, 3)
|
||||
sm.RecordPoll(dep2_2_2)
|
||||
if err := sm.RecordPoll(dep2_2_2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Current graph structure:
|
||||
// 2
|
||||
|
@ -757,20 +779,25 @@ func RecordPollDivergedVotingTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(block0)
|
||||
sm.Add(block1)
|
||||
if err := sm.Add(block0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes0 := ids.Bag{}
|
||||
votes0.Add(block0.id)
|
||||
sm.RecordPoll(votes0)
|
||||
|
||||
sm.Add(block2)
|
||||
if err := sm.RecordPoll(votes0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// dep2 is already rejected.
|
||||
|
||||
sm.Add(block3)
|
||||
|
||||
if status := block0.Status(); status == choices.Accepted {
|
||||
if err := sm.Add(block3); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if status := block0.Status(); status == choices.Accepted {
|
||||
t.Fatalf("Shouldn't be accepted yet")
|
||||
}
|
||||
|
||||
|
@ -778,9 +805,9 @@ func RecordPollDivergedVotingTest(t *testing.T, factory Factory) {
|
|||
// dep0. Because dep2 is already rejected, this will accept dep0.
|
||||
votes3 := ids.Bag{}
|
||||
votes3.Add(block3.id)
|
||||
sm.RecordPoll(votes3)
|
||||
|
||||
if !sm.Finalized() {
|
||||
if err := sm.RecordPoll(votes3); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !sm.Finalized() {
|
||||
t.Fatalf("Finalized too late")
|
||||
} else if status := block0.Status(); status != choices.Accepted {
|
||||
t.Fatalf("Should be accepted")
|
||||
|
@ -818,14 +845,15 @@ func MetricsProcessingErrorTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(block)
|
||||
if err := sm.Add(block); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(block.id)
|
||||
|
||||
sm.RecordPoll(votes)
|
||||
|
||||
if !sm.Finalized() {
|
||||
if err := sm.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !sm.Finalized() {
|
||||
t.Fatalf("Snowman instance didn't finalize")
|
||||
}
|
||||
}
|
||||
|
@ -861,14 +889,15 @@ func MetricsAcceptedErrorTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(block)
|
||||
if err := sm.Add(block); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(block.id)
|
||||
|
||||
sm.RecordPoll(votes)
|
||||
|
||||
if !sm.Finalized() {
|
||||
if err := sm.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !sm.Finalized() {
|
||||
t.Fatalf("Snowman instance didn't finalize")
|
||||
}
|
||||
}
|
||||
|
@ -904,18 +933,171 @@ func MetricsRejectedErrorTest(t *testing.T, factory Factory) {
|
|||
status: choices.Processing,
|
||||
}
|
||||
|
||||
sm.Add(block)
|
||||
if err := sm.Add(block); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(block.id)
|
||||
|
||||
sm.RecordPoll(votes)
|
||||
|
||||
if !sm.Finalized() {
|
||||
if err := sm.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !sm.Finalized() {
|
||||
t.Fatalf("Snowman instance didn't finalize")
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorOnInitialRejectionTest(t *testing.T, factory Factory) {
|
||||
sm := factory.New()
|
||||
|
||||
ctx := snow.DefaultContextTest()
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1,
|
||||
Alpha: 1,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 1,
|
||||
ConcurrentRepolls: 1,
|
||||
}
|
||||
|
||||
sm.Initialize(ctx, params, GenesisID)
|
||||
|
||||
rejectedBlock := &TestBlock{
|
||||
id: ids.Empty.Prefix(1),
|
||||
status: choices.Rejected,
|
||||
}
|
||||
|
||||
block := &TestBlock{
|
||||
parent: rejectedBlock,
|
||||
id: ids.Empty.Prefix(2),
|
||||
status: choices.Processing,
|
||||
err: errors.New(""),
|
||||
}
|
||||
|
||||
if err := sm.Add(block); err == nil {
|
||||
t.Fatalf("Should have errored on rejecting the rejectable block")
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorOnAcceptTest(t *testing.T, factory Factory) {
|
||||
sm := factory.New()
|
||||
|
||||
ctx := snow.DefaultContextTest()
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1,
|
||||
Alpha: 1,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 1,
|
||||
ConcurrentRepolls: 1,
|
||||
}
|
||||
|
||||
sm.Initialize(ctx, params, GenesisID)
|
||||
|
||||
block := &TestBlock{
|
||||
parent: Genesis,
|
||||
id: ids.Empty.Prefix(1),
|
||||
status: choices.Processing,
|
||||
err: errors.New(""),
|
||||
}
|
||||
|
||||
if err := sm.Add(block); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(block.id)
|
||||
if err := sm.RecordPoll(votes); err == nil {
|
||||
t.Fatalf("Should have errored on accepted the block")
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorOnRejectSiblingTest(t *testing.T, factory Factory) {
|
||||
sm := factory.New()
|
||||
|
||||
ctx := snow.DefaultContextTest()
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1,
|
||||
Alpha: 1,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 1,
|
||||
ConcurrentRepolls: 1,
|
||||
}
|
||||
|
||||
sm.Initialize(ctx, params, GenesisID)
|
||||
|
||||
block0 := &TestBlock{
|
||||
parent: Genesis,
|
||||
id: ids.Empty.Prefix(1),
|
||||
status: choices.Processing,
|
||||
}
|
||||
block1 := &TestBlock{
|
||||
parent: Genesis,
|
||||
id: ids.Empty.Prefix(2),
|
||||
status: choices.Processing,
|
||||
err: errors.New(""),
|
||||
}
|
||||
|
||||
if err := sm.Add(block0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(block0.id)
|
||||
if err := sm.RecordPoll(votes); err == nil {
|
||||
t.Fatalf("Should have errored on rejecting the block's sibling")
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorOnTransitiveRejectionTest(t *testing.T, factory Factory) {
|
||||
sm := factory.New()
|
||||
|
||||
ctx := snow.DefaultContextTest()
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1,
|
||||
Alpha: 1,
|
||||
BetaVirtuous: 1,
|
||||
BetaRogue: 1,
|
||||
ConcurrentRepolls: 1,
|
||||
}
|
||||
|
||||
sm.Initialize(ctx, params, GenesisID)
|
||||
|
||||
block0 := &TestBlock{
|
||||
parent: Genesis,
|
||||
id: ids.Empty.Prefix(1),
|
||||
status: choices.Processing,
|
||||
}
|
||||
block1 := &TestBlock{
|
||||
parent: Genesis,
|
||||
id: ids.Empty.Prefix(2),
|
||||
status: choices.Processing,
|
||||
}
|
||||
block2 := &TestBlock{
|
||||
parent: block1,
|
||||
id: ids.Empty.Prefix(3),
|
||||
status: choices.Processing,
|
||||
err: errors.New(""),
|
||||
}
|
||||
|
||||
if err := sm.Add(block0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block1); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := sm.Add(block2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(block0.id)
|
||||
if err := sm.RecordPoll(votes); err == nil {
|
||||
t.Fatalf("Should have errored on transitively rejecting the block")
|
||||
}
|
||||
}
|
||||
|
||||
func RandomizedConsistencyTest(t *testing.T, factory Factory) {
|
||||
numColors := 50
|
||||
numNodes := 100
|
||||
|
|
|
@ -9,6 +9,10 @@ import (
|
|||
"github.com/ava-labs/gecko/snow/consensus/snowball"
|
||||
)
|
||||
|
||||
const (
|
||||
minMapSize = 16
|
||||
)
|
||||
|
||||
// TopologicalFactory implements Factory by returning a topological struct
|
||||
type TopologicalFactory struct{}
|
||||
|
||||
|
@ -183,7 +187,7 @@ func (ts *Topological) Finalized() bool { return len(ts.blocks) == 1 }
|
|||
// the non-transitively applied votes. Also returns the list of leaf blocks.
|
||||
func (ts *Topological) calculateInDegree(
|
||||
votes ids.Bag) (map[[32]byte]kahnNode, []ids.ID) {
|
||||
kahns := make(map[[32]byte]kahnNode)
|
||||
kahns := make(map[[32]byte]kahnNode, minMapSize)
|
||||
leaves := ids.Set{}
|
||||
|
||||
for _, vote := range votes.List() {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package snowstorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
@ -19,6 +20,28 @@ var (
|
|||
Green = &TestTx{Identifier: ids.Empty.Prefix(1)}
|
||||
Blue = &TestTx{Identifier: ids.Empty.Prefix(2)}
|
||||
Alpha = &TestTx{Identifier: ids.Empty.Prefix(3)}
|
||||
|
||||
Tests = []func(*testing.T, Factory){
|
||||
MetricsTest,
|
||||
ParamsTest,
|
||||
IssuedTest,
|
||||
LeftoverInputTest,
|
||||
LowerConfidenceTest,
|
||||
MiddleConfidenceTest,
|
||||
IndependentTest,
|
||||
VirtuousTest,
|
||||
IsVirtuousTest,
|
||||
QuiesceTest,
|
||||
AcceptingDependencyTest,
|
||||
RejectingDependencyTest,
|
||||
VacuouslyAcceptedTest,
|
||||
ConflictsTest,
|
||||
VirtuousDependsOnRogueTest,
|
||||
ErrorOnVacuouslyAcceptedTest,
|
||||
ErrorOnAcceptedTest,
|
||||
ErrorOnRejectingLowerConfidenceConflictTest,
|
||||
ErrorOnRejectingHigherConfidenceConflictTest,
|
||||
}
|
||||
)
|
||||
|
||||
// R - G - B - A
|
||||
|
@ -46,6 +69,52 @@ func Setup() {
|
|||
Alpha.Reset()
|
||||
}
|
||||
|
||||
// Execute all tests against a consensus implementation
|
||||
func ConsensusTest(t *testing.T, factory Factory, prefix string) {
|
||||
for _, test := range Tests {
|
||||
test(t, factory)
|
||||
}
|
||||
StringTest(t, factory, prefix)
|
||||
}
|
||||
|
||||
func MetricsTest(t *testing.T, factory Factory) {
|
||||
Setup()
|
||||
|
||||
{
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
|
||||
}
|
||||
params.Metrics.Register(prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "tx_processing",
|
||||
}))
|
||||
graph := factory.New()
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
}
|
||||
{
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
|
||||
}
|
||||
params.Metrics.Register(prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "tx_accepted",
|
||||
}))
|
||||
graph := factory.New()
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
}
|
||||
{
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
|
||||
}
|
||||
params.Metrics.Register(prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "tx_rejected",
|
||||
}))
|
||||
graph := factory.New()
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
}
|
||||
}
|
||||
|
||||
func ParamsTest(t *testing.T, factory Factory) {
|
||||
Setup()
|
||||
|
||||
|
@ -81,15 +150,13 @@ func IssuedTest(t *testing.T, factory Factory) {
|
|||
|
||||
if issued := graph.Issued(Red); issued {
|
||||
t.Fatalf("Haven't issued anything yet.")
|
||||
}
|
||||
|
||||
graph.Add(Red)
|
||||
|
||||
if issued := graph.Issued(Red); !issued {
|
||||
} else if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if issued := graph.Issued(Red); !issued {
|
||||
t.Fatalf("Have already issued.")
|
||||
}
|
||||
|
||||
Blue.Accept()
|
||||
_ = Blue.Accept()
|
||||
|
||||
if issued := graph.Issued(Blue); !issued {
|
||||
t.Fatalf("Have already accepted.")
|
||||
|
@ -106,10 +173,12 @@ func LeftoverInputTest(t *testing.T, factory Factory) {
|
|||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
graph.Add(Red)
|
||||
graph.Add(Green)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Green); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s got %s", Red.ID(), prefs.List()[0])
|
||||
|
@ -120,15 +189,13 @@ func LeftoverInputTest(t *testing.T, factory Factory) {
|
|||
r := ids.Bag{}
|
||||
r.SetThreshold(2)
|
||||
r.AddCount(Red.ID(), 2)
|
||||
graph.RecordPoll(r)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
if err := graph.RecordPoll(r); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !graph.Finalized() {
|
||||
t.Fatalf("Finalized too late")
|
||||
}
|
||||
|
||||
if Red.Status() != choices.Accepted {
|
||||
} else if Red.Status() != choices.Accepted {
|
||||
t.Fatalf("%s should have been accepted", Red.ID())
|
||||
} else if Green.Status() != choices.Rejected {
|
||||
t.Fatalf("%s should have been rejected", Green.ID())
|
||||
|
@ -145,11 +212,14 @@ func LowerConfidenceTest(t *testing.T, factory Factory) {
|
|||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
graph.Add(Red)
|
||||
graph.Add(Green)
|
||||
graph.Add(Blue)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Green); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Blue); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s got %s", Red.ID(), prefs.List()[0])
|
||||
|
@ -160,9 +230,9 @@ func LowerConfidenceTest(t *testing.T, factory Factory) {
|
|||
r := ids.Bag{}
|
||||
r.SetThreshold(2)
|
||||
r.AddCount(Red.ID(), 2)
|
||||
graph.RecordPoll(r)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
if err := graph.RecordPoll(r); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Blue.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Blue.ID())
|
||||
|
@ -181,12 +251,16 @@ func MiddleConfidenceTest(t *testing.T, factory Factory) {
|
|||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
graph.Add(Red)
|
||||
graph.Add(Green)
|
||||
graph.Add(Alpha)
|
||||
graph.Add(Blue)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Green); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Alpha); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Blue); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Red.ID())
|
||||
|
@ -199,9 +273,9 @@ func MiddleConfidenceTest(t *testing.T, factory Factory) {
|
|||
r := ids.Bag{}
|
||||
r.SetThreshold(2)
|
||||
r.AddCount(Red.ID(), 2)
|
||||
graph.RecordPoll(r)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
if err := graph.RecordPoll(r); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Alpha.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Alpha.ID())
|
||||
|
@ -209,6 +283,7 @@ func MiddleConfidenceTest(t *testing.T, factory Factory) {
|
|||
t.Fatalf("Finalized too early")
|
||||
}
|
||||
}
|
||||
|
||||
func IndependentTest(t *testing.T, factory Factory) {
|
||||
Setup()
|
||||
|
||||
|
@ -219,10 +294,12 @@ func IndependentTest(t *testing.T, factory Factory) {
|
|||
K: 2, Alpha: 2, BetaVirtuous: 2, BetaRogue: 2,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
graph.Add(Red)
|
||||
graph.Add(Alpha)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Alpha); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Red.ID())
|
||||
|
@ -236,9 +313,9 @@ func IndependentTest(t *testing.T, factory Factory) {
|
|||
ra.SetThreshold(2)
|
||||
ra.AddCount(Red.ID(), 2)
|
||||
ra.AddCount(Alpha.ID(), 2)
|
||||
graph.RecordPoll(ra)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
if err := graph.RecordPoll(ra); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Red.ID())
|
||||
|
@ -246,11 +323,9 @@ func IndependentTest(t *testing.T, factory Factory) {
|
|||
t.Fatalf("Wrong preference. Expected %s", Alpha.ID())
|
||||
} else if graph.Finalized() {
|
||||
t.Fatalf("Finalized too early")
|
||||
}
|
||||
|
||||
graph.RecordPoll(ra)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
} else if err := graph.RecordPoll(ra); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !graph.Finalized() {
|
||||
t.Fatalf("Finalized too late")
|
||||
|
@ -267,35 +342,30 @@ func VirtuousTest(t *testing.T, factory Factory) {
|
|||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 1,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
graph.Add(Red)
|
||||
|
||||
if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
|
||||
if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong virtuous. Expected %s", Red.ID())
|
||||
}
|
||||
|
||||
graph.Add(Alpha)
|
||||
|
||||
if virtuous := graph.Virtuous(); virtuous.Len() != 2 {
|
||||
} else if err := graph.Add(Alpha); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if virtuous := graph.Virtuous(); virtuous.Len() != 2 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong virtuous. Expected %s", Red.ID())
|
||||
} else if !virtuous.Contains(Alpha.ID()) {
|
||||
t.Fatalf("Wrong virtuous. Expected %s", Alpha.ID())
|
||||
}
|
||||
|
||||
graph.Add(Green)
|
||||
|
||||
if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
|
||||
} else if err := graph.Add(Green); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if virtuous := graph.Virtuous(); virtuous.Len() != 1 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
} else if !virtuous.Contains(Alpha.ID()) {
|
||||
t.Fatalf("Wrong virtuous. Expected %s", Alpha.ID())
|
||||
}
|
||||
|
||||
graph.Add(Blue)
|
||||
|
||||
if virtuous := graph.Virtuous(); virtuous.Len() != 0 {
|
||||
} else if err := graph.Add(Blue); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if virtuous := graph.Virtuous(); virtuous.Len() != 0 {
|
||||
t.Fatalf("Wrong number of virtuous.")
|
||||
}
|
||||
}
|
||||
|
@ -319,11 +389,9 @@ func IsVirtuousTest(t *testing.T, factory Factory) {
|
|||
t.Fatalf("Should be virtuous")
|
||||
} else if !graph.IsVirtuous(Alpha) {
|
||||
t.Fatalf("Should be virtuous")
|
||||
}
|
||||
|
||||
graph.Add(Red)
|
||||
|
||||
if !graph.IsVirtuous(Red) {
|
||||
} else if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !graph.IsVirtuous(Red) {
|
||||
t.Fatalf("Should be virtuous")
|
||||
} else if graph.IsVirtuous(Green) {
|
||||
t.Fatalf("Should not be virtuous")
|
||||
|
@ -331,11 +399,9 @@ func IsVirtuousTest(t *testing.T, factory Factory) {
|
|||
t.Fatalf("Should be virtuous")
|
||||
} else if !graph.IsVirtuous(Alpha) {
|
||||
t.Fatalf("Should be virtuous")
|
||||
}
|
||||
|
||||
graph.Add(Green)
|
||||
|
||||
if graph.IsVirtuous(Red) {
|
||||
} else if err := graph.Add(Green); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if graph.IsVirtuous(Red) {
|
||||
t.Fatalf("Should not be virtuous")
|
||||
} else if graph.IsVirtuous(Green) {
|
||||
t.Fatalf("Should not be virtuous")
|
||||
|
@ -357,17 +423,13 @@ func QuiesceTest(t *testing.T, factory Factory) {
|
|||
|
||||
if !graph.Quiesce() {
|
||||
t.Fatalf("Should quiesce")
|
||||
}
|
||||
|
||||
graph.Add(Red)
|
||||
|
||||
if graph.Quiesce() {
|
||||
} else if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if graph.Quiesce() {
|
||||
t.Fatalf("Shouldn't quiesce")
|
||||
}
|
||||
|
||||
graph.Add(Green)
|
||||
|
||||
if !graph.Quiesce() {
|
||||
} else if err := graph.Add(Green); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !graph.Quiesce() {
|
||||
t.Fatalf("Should quiesce")
|
||||
}
|
||||
}
|
||||
|
@ -390,11 +452,13 @@ func AcceptingDependencyTest(t *testing.T, factory Factory) {
|
|||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
|
||||
graph.Add(Red)
|
||||
graph.Add(Green)
|
||||
graph.Add(purple)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Green); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(purple); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Red.ID())
|
||||
|
@ -410,10 +474,9 @@ func AcceptingDependencyTest(t *testing.T, factory Factory) {
|
|||
|
||||
g := ids.Bag{}
|
||||
g.Add(Green.ID())
|
||||
|
||||
graph.RecordPoll(g)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
if err := graph.RecordPoll(g); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Green.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Green.ID())
|
||||
|
@ -429,10 +492,9 @@ func AcceptingDependencyTest(t *testing.T, factory Factory) {
|
|||
|
||||
rp := ids.Bag{}
|
||||
rp.Add(Red.ID(), purple.ID())
|
||||
|
||||
graph.RecordPoll(rp)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
if err := graph.RecordPoll(rp); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Green.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Green.ID())
|
||||
|
@ -448,10 +510,9 @@ func AcceptingDependencyTest(t *testing.T, factory Factory) {
|
|||
|
||||
r := ids.Bag{}
|
||||
r.Add(Red.ID())
|
||||
|
||||
graph.RecordPoll(r)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
if err := graph.RecordPoll(r); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if Red.Status() != choices.Accepted {
|
||||
t.Fatalf("Wrong status. %s should be %s", Red.ID(), choices.Accepted)
|
||||
|
@ -480,12 +541,15 @@ func RejectingDependencyTest(t *testing.T, factory Factory) {
|
|||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
|
||||
graph.Add(Red)
|
||||
graph.Add(Green)
|
||||
graph.Add(Blue)
|
||||
graph.Add(purple)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Green); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Blue); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(purple); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Red.ID())
|
||||
|
@ -503,10 +567,9 @@ func RejectingDependencyTest(t *testing.T, factory Factory) {
|
|||
|
||||
gp := ids.Bag{}
|
||||
gp.Add(Green.ID(), purple.ID())
|
||||
|
||||
graph.RecordPoll(gp)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
if err := graph.RecordPoll(gp); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 2 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Green.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s", Green.ID())
|
||||
|
@ -520,11 +583,9 @@ func RejectingDependencyTest(t *testing.T, factory Factory) {
|
|||
t.Fatalf("Wrong status. %s should be %s", Blue.ID(), choices.Processing)
|
||||
} else if purple.Status() != choices.Processing {
|
||||
t.Fatalf("Wrong status. %s should be %s", purple.ID(), choices.Processing)
|
||||
}
|
||||
|
||||
graph.RecordPoll(gp)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
} else if err := graph.RecordPoll(gp); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if Red.Status() != choices.Rejected {
|
||||
t.Fatalf("Wrong status. %s should be %s", Red.ID(), choices.Rejected)
|
||||
|
@ -553,9 +614,9 @@ func VacuouslyAcceptedTest(t *testing.T, factory Factory) {
|
|||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
|
||||
graph.Add(purple)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
if err := graph.Add(purple); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 0 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if status := purple.Status(); status != choices.Accepted {
|
||||
t.Fatalf("Wrong status. %s should be %s", purple.ID(), choices.Accepted)
|
||||
|
@ -593,17 +654,15 @@ func ConflictsTest(t *testing.T, factory Factory) {
|
|||
Ins: insPurple,
|
||||
}
|
||||
|
||||
graph.Add(purple)
|
||||
|
||||
if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
|
||||
if err := graph.Add(purple); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
|
||||
t.Fatalf("Wrong number of conflicts")
|
||||
} else if !orangeConflicts.Contains(purple.Identifier) {
|
||||
t.Fatalf("Conflicts does not contain the right transaction")
|
||||
}
|
||||
|
||||
graph.Add(orange)
|
||||
|
||||
if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
|
||||
} else if err := graph.Add(orange); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if orangeConflicts := graph.Conflicts(orange); orangeConflicts.Len() != 1 {
|
||||
t.Fatalf("Wrong number of conflicts")
|
||||
} else if !orangeConflicts.Contains(purple.Identifier) {
|
||||
t.Fatalf("Conflicts does not contain the right transaction")
|
||||
|
@ -643,17 +702,20 @@ func VirtuousDependsOnRogueTest(t *testing.T, factory Factory) {
|
|||
|
||||
virtuous.Ins.Add(input2)
|
||||
|
||||
graph.Add(rogue1)
|
||||
graph.Add(rogue2)
|
||||
graph.Add(virtuous)
|
||||
if err := graph.Add(rogue1); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(rogue2); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(virtuous); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(rogue1.ID())
|
||||
votes.Add(virtuous.ID())
|
||||
|
||||
graph.RecordPoll(votes)
|
||||
|
||||
if status := rogue1.Status(); status != choices.Processing {
|
||||
if err := graph.RecordPoll(votes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if status := rogue1.Status(); status != choices.Processing {
|
||||
t.Fatalf("Rogue Tx is %s expected %s", status, choices.Processing)
|
||||
} else if status := rogue2.Status(); status != choices.Processing {
|
||||
t.Fatalf("Rogue Tx is %s expected %s", status, choices.Processing)
|
||||
|
@ -664,6 +726,135 @@ func VirtuousDependsOnRogueTest(t *testing.T, factory Factory) {
|
|||
}
|
||||
}
|
||||
|
||||
func ErrorOnVacuouslyAcceptedTest(t *testing.T, factory Factory) {
|
||||
Setup()
|
||||
|
||||
graph := factory.New()
|
||||
|
||||
purple := &TestTx{
|
||||
Identifier: ids.Empty.Prefix(7),
|
||||
Stat: choices.Processing,
|
||||
Validity: errors.New(""),
|
||||
}
|
||||
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
|
||||
if err := graph.Add(purple); err == nil {
|
||||
t.Fatalf("Should have errored on acceptance")
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorOnAcceptedTest(t *testing.T, factory Factory) {
|
||||
Setup()
|
||||
|
||||
graph := factory.New()
|
||||
|
||||
purple := &TestTx{
|
||||
Identifier: ids.Empty.Prefix(7),
|
||||
Stat: choices.Processing,
|
||||
Validity: errors.New(""),
|
||||
}
|
||||
purple.Ins.Add(ids.Empty.Prefix(4))
|
||||
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
|
||||
if err := graph.Add(purple); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(purple.ID())
|
||||
if err := graph.RecordPoll(votes); err == nil {
|
||||
t.Fatalf("Should have errored on accepting an invalid tx")
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorOnRejectingLowerConfidenceConflictTest(t *testing.T, factory Factory) {
|
||||
Setup()
|
||||
|
||||
graph := factory.New()
|
||||
|
||||
X := ids.Empty.Prefix(4)
|
||||
|
||||
purple := &TestTx{
|
||||
Identifier: ids.Empty.Prefix(7),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
purple.Ins.Add(X)
|
||||
|
||||
pink := &TestTx{
|
||||
Identifier: ids.Empty.Prefix(8),
|
||||
Stat: choices.Processing,
|
||||
Validity: errors.New(""),
|
||||
}
|
||||
pink.Ins.Add(X)
|
||||
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 1,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
|
||||
if err := graph.Add(purple); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(pink); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(purple.ID())
|
||||
if err := graph.RecordPoll(votes); err == nil {
|
||||
t.Fatalf("Should have errored on rejecting an invalid tx")
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorOnRejectingHigherConfidenceConflictTest(t *testing.T, factory Factory) {
|
||||
Setup()
|
||||
|
||||
graph := factory.New()
|
||||
|
||||
X := ids.Empty.Prefix(4)
|
||||
|
||||
purple := &TestTx{
|
||||
Identifier: ids.Empty.Prefix(7),
|
||||
Stat: choices.Processing,
|
||||
}
|
||||
purple.Ins.Add(X)
|
||||
|
||||
pink := &TestTx{
|
||||
Identifier: ids.Empty.Prefix(8),
|
||||
Stat: choices.Processing,
|
||||
Validity: errors.New(""),
|
||||
}
|
||||
pink.Ins.Add(X)
|
||||
|
||||
params := snowball.Parameters{
|
||||
Metrics: prometheus.NewRegistry(),
|
||||
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 1,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
|
||||
if err := graph.Add(pink); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(purple); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
votes := ids.Bag{}
|
||||
votes.Add(purple.ID())
|
||||
if err := graph.RecordPoll(votes); err == nil {
|
||||
t.Fatalf("Should have errored on rejecting an invalid tx")
|
||||
}
|
||||
}
|
||||
|
||||
func StringTest(t *testing.T, factory Factory, prefix string) {
|
||||
Setup()
|
||||
|
||||
|
@ -674,12 +865,16 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
|||
K: 2, Alpha: 2, BetaVirtuous: 1, BetaRogue: 2,
|
||||
}
|
||||
graph.Initialize(snow.DefaultContextTest(), params)
|
||||
graph.Add(Red)
|
||||
graph.Add(Green)
|
||||
graph.Add(Blue)
|
||||
graph.Add(Alpha)
|
||||
|
||||
if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
if err := graph.Add(Red); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Green); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Blue); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Alpha); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if prefs := graph.Preferences(); prefs.Len() != 1 {
|
||||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !prefs.Contains(Red.ID()) {
|
||||
t.Fatalf("Wrong preference. Expected %s got %s", Red.ID(), prefs.List()[0])
|
||||
|
@ -691,8 +886,11 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
|||
rb.SetThreshold(2)
|
||||
rb.AddCount(Red.ID(), 2)
|
||||
rb.AddCount(Blue.ID(), 2)
|
||||
graph.RecordPoll(rb)
|
||||
graph.Add(Blue)
|
||||
if err := graph.RecordPoll(rb); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := graph.Add(Blue); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
{
|
||||
expected := prefix + "(\n" +
|
||||
|
@ -720,7 +918,9 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
|||
ga.SetThreshold(2)
|
||||
ga.AddCount(Green.ID(), 2)
|
||||
ga.AddCount(Alpha.ID(), 2)
|
||||
graph.RecordPoll(ga)
|
||||
if err := graph.RecordPoll(ga); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
{
|
||||
expected := prefix + "(\n" +
|
||||
|
@ -745,7 +945,9 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
|||
}
|
||||
|
||||
empty := ids.Bag{}
|
||||
graph.RecordPoll(empty)
|
||||
if err := graph.RecordPoll(empty); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
{
|
||||
expected := prefix + "(\n" +
|
||||
|
@ -767,10 +969,10 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
|||
t.Fatalf("Wrong preference. Expected %s", Blue.ID())
|
||||
} else if graph.Finalized() {
|
||||
t.Fatalf("Finalized too early")
|
||||
} else if err := graph.RecordPoll(ga); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
graph.RecordPoll(ga)
|
||||
|
||||
{
|
||||
expected := prefix + "(\n" +
|
||||
" Choice[0] = ID: LUC1cmcxnfNR9LdkACS2ccGKLEK7SYqB4gLLTycQfg1koyfSq Confidence: 0 Bias: 1\n" +
|
||||
|
@ -791,10 +993,10 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
|||
t.Fatalf("Wrong preference. Expected %s", Alpha.ID())
|
||||
} else if graph.Finalized() {
|
||||
t.Fatalf("Finalized too early")
|
||||
} else if err := graph.RecordPoll(ga); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
graph.RecordPoll(ga)
|
||||
|
||||
{
|
||||
expected := prefix + "()"
|
||||
if str := graph.String(); str != expected {
|
||||
|
@ -806,9 +1008,7 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
|||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !graph.Finalized() {
|
||||
t.Fatalf("Finalized too late")
|
||||
}
|
||||
|
||||
if Green.Status() != choices.Accepted {
|
||||
} else if Green.Status() != choices.Accepted {
|
||||
t.Fatalf("%s should have been accepted", Green.ID())
|
||||
} else if Alpha.Status() != choices.Accepted {
|
||||
t.Fatalf("%s should have been accepted", Alpha.ID())
|
||||
|
@ -816,10 +1016,10 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
|||
t.Fatalf("%s should have been rejected", Red.ID())
|
||||
} else if Blue.Status() != choices.Rejected {
|
||||
t.Fatalf("%s should have been rejected", Blue.ID())
|
||||
} else if err := graph.RecordPoll(rb); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
graph.RecordPoll(rb)
|
||||
|
||||
{
|
||||
expected := prefix + "()"
|
||||
if str := graph.String(); str != expected {
|
||||
|
@ -831,9 +1031,7 @@ func StringTest(t *testing.T, factory Factory, prefix string) {
|
|||
t.Fatalf("Wrong number of preferences.")
|
||||
} else if !graph.Finalized() {
|
||||
t.Fatalf("Finalized too late")
|
||||
}
|
||||
|
||||
if Green.Status() != choices.Accepted {
|
||||
} else if Green.Status() != choices.Accepted {
|
||||
t.Fatalf("%s should have been accepted", Green.ID())
|
||||
} else if Alpha.Status() != choices.Accepted {
|
||||
t.Fatalf("%s should have been accepted", Alpha.ID())
|
||||
|
|
|
@ -7,34 +7,4 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestDirectedParams(t *testing.T) { ParamsTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedIssued(t *testing.T) { IssuedTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedLeftoverInput(t *testing.T) { LeftoverInputTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedLowerConfidence(t *testing.T) { LowerConfidenceTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedMiddleConfidence(t *testing.T) { MiddleConfidenceTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedIndependent(t *testing.T) { IndependentTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedVirtuous(t *testing.T) { VirtuousTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedIsVirtuous(t *testing.T) { IsVirtuousTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedConflicts(t *testing.T) { ConflictsTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedQuiesce(t *testing.T) { QuiesceTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedAcceptingDependency(t *testing.T) { AcceptingDependencyTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedRejectingDependency(t *testing.T) { RejectingDependencyTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedVacuouslyAccepted(t *testing.T) { VacuouslyAcceptedTest(t, DirectedFactory{}) }
|
||||
|
||||
func TestDirectedVirtuousDependsOnRogue(t *testing.T) {
|
||||
VirtuousDependsOnRogueTest(t, DirectedFactory{})
|
||||
}
|
||||
|
||||
func TestDirectedString(t *testing.T) { StringTest(t, DirectedFactory{}, "DG") }
|
||||
func TestDirectedConsensus(t *testing.T) { ConsensusTest(t, DirectedFactory{}, "DG") }
|
||||
|
|
|
@ -7,32 +7,4 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestInputParams(t *testing.T) { ParamsTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputIssued(t *testing.T) { IssuedTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputLeftoverInput(t *testing.T) { LeftoverInputTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputLowerConfidence(t *testing.T) { LowerConfidenceTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputMiddleConfidence(t *testing.T) { MiddleConfidenceTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputIndependent(t *testing.T) { IndependentTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputVirtuous(t *testing.T) { VirtuousTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputIsVirtuous(t *testing.T) { IsVirtuousTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputConflicts(t *testing.T) { ConflictsTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputQuiesce(t *testing.T) { QuiesceTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputAcceptingDependency(t *testing.T) { AcceptingDependencyTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputRejectingDependency(t *testing.T) { RejectingDependencyTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputVacuouslyAccepted(t *testing.T) { VacuouslyAcceptedTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputVirtuousDependsOnRogue(t *testing.T) { VirtuousDependsOnRogueTest(t, InputFactory{}) }
|
||||
|
||||
func TestInputString(t *testing.T) { StringTest(t, InputFactory{}, "IG") }
|
||||
func TestInputConsensus(t *testing.T) { ConsensusTest(t, InputFactory{}, "IG") }
|
||||
|
|
|
@ -31,10 +31,10 @@ func (tx *TestTx) InputIDs() ids.Set { return tx.Ins }
|
|||
func (tx *TestTx) Status() choices.Status { return tx.Stat }
|
||||
|
||||
// Accept implements the Consumer interface
|
||||
func (tx *TestTx) Accept() error { tx.Stat = choices.Accepted; return nil }
|
||||
func (tx *TestTx) Accept() error { tx.Stat = choices.Accepted; return tx.Validity }
|
||||
|
||||
// Reject implements the Consumer interface
|
||||
func (tx *TestTx) Reject() error { tx.Stat = choices.Rejected; return nil }
|
||||
func (tx *TestTx) Reject() error { tx.Stat = choices.Rejected; return tx.Validity }
|
||||
|
||||
// Reset sets the status to pending
|
||||
func (tx *TestTx) Reset() { tx.Stat = choices.Processing }
|
||||
|
|
|
@ -17,6 +17,11 @@ import (
|
|||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
@ -37,15 +42,16 @@ type bootstrapper struct {
|
|||
metrics
|
||||
common.Bootstrapper
|
||||
|
||||
// true if all of the vertices in the original accepted frontier have been processed
|
||||
processedStartingAcceptedFrontier bool
|
||||
|
||||
// number of vertices fetched so far
|
||||
numFetched uint32
|
||||
|
||||
// tracks which validators were asked for which containers in which 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
|
||||
processedCache *cache.LRU
|
||||
|
||||
|
@ -80,14 +86,15 @@ func (b *bootstrapper) Initialize(config BootstrapConfig) error {
|
|||
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 {
|
||||
acceptedFrontier := ids.Set{}
|
||||
acceptedFrontier.Add(b.State.Edge()...)
|
||||
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 {
|
||||
acceptedVtxIDs := ids.Set{}
|
||||
for _, vtxID := range containerIDs.List() {
|
||||
|
@ -98,19 +105,23 @@ func (b *bootstrapper) FilterAccepted(containerIDs ids.Set) ids.Set {
|
|||
return acceptedVtxIDs
|
||||
}
|
||||
|
||||
// Get vertex [vtxID] and its ancestors
|
||||
func (b *bootstrapper) fetch(vtxID ids.ID) error {
|
||||
// Make sure we haven't already requested this block
|
||||
// Add the vertices in [vtxIDs] to the set of vertices that we need to fetch,
|
||||
// and then fetch vertices (and their ancestors) until either there are no more
|
||||
// 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) {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
|
||||
// Make sure we don't already have this vertex
|
||||
if _, err := b.State.GetVertex(vtxID); err == nil {
|
||||
if numPending := b.outstandingRequests.Len(); numPending == 0 && b.processedStartingAcceptedFrontier {
|
||||
return b.finish()
|
||||
}
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
|
||||
validators := b.BootstrapConfig.Validators.Sample(1) // validator to send request to
|
||||
|
@ -122,26 +133,36 @@ func (b *bootstrapper) fetch(vtxID ids.ID) error {
|
|||
|
||||
b.outstandingRequests.Add(validatorID, b.RequestID, vtxID)
|
||||
b.BootstrapConfig.Sender.GetAncestors(validatorID, b.RequestID, vtxID) // request vertex and ancestors
|
||||
return nil
|
||||
}
|
||||
return b.finish()
|
||||
}
|
||||
|
||||
// Process vertices
|
||||
func (b *bootstrapper) process(vtx avalanche.Vertex) error {
|
||||
// Process the vertices in [vtxs].
|
||||
func (b *bootstrapper) process(vtxs ...avalanche.Vertex) error {
|
||||
// Vertices that we need to process. Store them in a heap for de-deduplication
|
||||
// and so we always process vertices further down in the DAG first. This helps
|
||||
// to reduce the number of repeated DAG traversals.
|
||||
toProcess := newMaxVertexHeap()
|
||||
if _, ok := b.processedCache.Get(vtx.ID()); !ok { // only process if we haven't already
|
||||
for _, vtx := range vtxs {
|
||||
if _, ok := b.processedCache.Get(vtx.ID()); !ok { // only process a vertex if we haven't already
|
||||
toProcess.Push(vtx)
|
||||
}
|
||||
for toProcess.Len() > 0 {
|
||||
vtx := toProcess.Pop()
|
||||
}
|
||||
|
||||
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() {
|
||||
case choices.Unknown:
|
||||
if err := b.fetch(vtx.ID()); err != nil {
|
||||
return err
|
||||
}
|
||||
b.needToFetch.Add(vtxID) // We don't have this vertex locally. Mark that we need to fetch it.
|
||||
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())
|
||||
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,
|
||||
numAccepted: b.numBSVtx,
|
||||
numDropped: b.numBSDroppedVtx,
|
||||
|
@ -155,7 +176,7 @@ func (b *bootstrapper) process(vtx avalanche.Vertex) error {
|
|||
} else {
|
||||
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{
|
||||
log: b.BootstrapConfig.Context.Log,
|
||||
numAccepted: b.numBSTx,
|
||||
|
@ -167,14 +188,16 @@ func (b *bootstrapper) process(vtx avalanche.Vertex) error {
|
|||
b.BootstrapConfig.Context.Log.Verbo("couldn't push to txBlocked: %s", err)
|
||||
}
|
||||
}
|
||||
for _, parent := range vtx.Parents() {
|
||||
if _, ok := b.processedCache.Get(parent.ID()); !ok { // already processed this
|
||||
for _, parent := range vtx.Parents() { // Process the parents of this vertex (traverse up the DAG)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.VtxBlocked.Commit(); err != nil {
|
||||
return err
|
||||
|
@ -183,10 +206,7 @@ func (b *bootstrapper) process(vtx avalanche.Vertex) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if numPending := b.outstandingRequests.Len(); numPending == 0 && b.processedStartingAcceptedFrontier {
|
||||
return b.finish()
|
||||
}
|
||||
return nil
|
||||
return b.fetch()
|
||||
}
|
||||
|
||||
// MultiPut handles the receipt of multiple containers. Should be received in response to a GetAncestors message to [vdr]
|
||||
|
@ -202,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
|
||||
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)
|
||||
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 {
|
||||
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]})
|
||||
|
@ -217,14 +237,20 @@ func (b *bootstrapper) MultiPut(vdr ids.ShortID, requestID uint32, vtxs [][]byte
|
|||
return b.fetch(neededVtxID)
|
||||
}
|
||||
|
||||
for _, vtxBytes := range vtxs { // Parse/persist all the vertices
|
||||
if _, err := b.State.ParseVertex(vtxBytes); err != nil { // Persists the vtx
|
||||
processVertices := make([]avalanche.Vertex, 1, len(vtxs)) // Process all of the vertices in this message
|
||||
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.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
|
||||
|
@ -238,43 +264,38 @@ func (b *bootstrapper) GetAncestorsFailed(vdr ids.ShortID, requestID uint32) err
|
|||
return b.fetch(vtxID)
|
||||
}
|
||||
|
||||
// ForceAccepted ...
|
||||
// ForceAccepted starts bootstrapping. Process the vertices in [accepterContainerIDs].
|
||||
func (b *bootstrapper) ForceAccepted(acceptedContainerIDs ids.Set) error {
|
||||
if err := b.VM.Bootstrapping(); err != nil {
|
||||
return fmt.Errorf("failed to notify VM that bootstrapping has started: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
toProcess := make([]avalanche.Vertex, 0, acceptedContainerIDs.Len())
|
||||
for _, vtxID := range acceptedContainerIDs.List() {
|
||||
if vtx, err := b.State.GetVertex(vtxID); err == nil {
|
||||
if err := b.process(vtx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := b.fetch(vtxID); err != nil {
|
||||
return err
|
||||
toProcess = append(toProcess, vtx) // Process this vertex.
|
||||
} else {
|
||||
b.needToFetch.Add(vtxID) // We don't have this vertex. Mark that we have to fetch it.
|
||||
}
|
||||
}
|
||||
b.processedStartingAcceptedFrontier = true
|
||||
|
||||
if numPending := b.outstandingRequests.Len(); numPending == 0 {
|
||||
return b.finish()
|
||||
}
|
||||
return nil
|
||||
return b.process(toProcess...)
|
||||
}
|
||||
|
||||
// Finish bootstrapping
|
||||
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
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
b.BootstrapConfig.Context.Log.Info("executing vertex state transitions...")
|
||||
|
||||
if err := b.executeAll(b.VtxBlocked, b.numBSBlockedVtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -309,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)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ type metrics struct {
|
|||
numBSVtx, numBSDroppedVtx,
|
||||
numBSTx, numBSDroppedTx prometheus.Counter
|
||||
|
||||
numPolls, numVtxRequests, numTxRequests, numPendingVtx prometheus.Gauge
|
||||
numVtxRequests, numTxRequests, numPendingVtx prometheus.Gauge
|
||||
}
|
||||
|
||||
// Initialize implements the Engine interface
|
||||
|
@ -61,12 +61,6 @@ func (m *metrics) Initialize(log logging.Logger, namespace string, registerer pr
|
|||
Name: "av_bs_dropped_txs",
|
||||
Help: "Number of dropped txs",
|
||||
})
|
||||
m.numPolls = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "av_polls",
|
||||
Help: "Number of pending network polls",
|
||||
})
|
||||
m.numVtxRequests = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
|
@ -107,9 +101,6 @@ func (m *metrics) Initialize(log logging.Logger, namespace string, registerer pr
|
|||
if err := registerer.Register(m.numBSDroppedTx); err != nil {
|
||||
log.Error("Failed to register av_bs_dropped_txs statistics due to %s", err)
|
||||
}
|
||||
if err := registerer.Register(m.numPolls); err != nil {
|
||||
log.Error("Failed to register av_polls statistics due to %s", err)
|
||||
}
|
||||
if err := registerer.Register(m.numVtxRequests); err != nil {
|
||||
log.Error("Failed to register av_vtx_requests statistics due to %s", err)
|
||||
}
|
||||
|
|
|
@ -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,137 +0,0 @@
|
|||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package avalanche
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/utils/logging"
|
||||
)
|
||||
|
||||
// TODO: There is a conservative early termination case that doesn't require dag
|
||||
// traversals we may want to implement. The algorithm would go as follows:
|
||||
// Keep track of the number of response that reference an ID. If an ID gets >=
|
||||
// alpha responses, then remove it from all responses and place it into a chit
|
||||
// list. Remove all empty responses. If the number of responses + the number of
|
||||
// pending responses is less than alpha, terminate the poll.
|
||||
// In the synchronous + virtuous case, when everyone returns the same hash, the
|
||||
// poll now terminates after receiving alpha responses.
|
||||
// In the rogue case, it is possible that the poll doesn't terminate as quickly
|
||||
// as possible, because IDs may have the alpha threshold but only when counting
|
||||
// transitive votes. In this case, we may wait even if it is no longer possible
|
||||
// for another ID to earn alpha votes.
|
||||
// Because alpha is typically set close to k, this may not be performance
|
||||
// critical. However, early termination may be performance critical with crashed
|
||||
// nodes.
|
||||
|
||||
type polls struct {
|
||||
log logging.Logger
|
||||
numPolls prometheus.Gauge
|
||||
alpha int
|
||||
m map[uint32]poll
|
||||
}
|
||||
|
||||
func newPolls(alpha int, log logging.Logger, numPolls prometheus.Gauge) polls {
|
||||
return polls{
|
||||
log: log,
|
||||
numPolls: numPolls,
|
||||
alpha: alpha,
|
||||
m: make(map[uint32]poll),
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the current set of polls
|
||||
// Returns true if the poll was registered correctly and the network sample
|
||||
// should be made.
|
||||
func (p *polls) Add(requestID uint32, vdrs ids.ShortSet) bool {
|
||||
poll, exists := p.m[requestID]
|
||||
if !exists {
|
||||
poll.polled = vdrs
|
||||
poll.alpha = p.alpha
|
||||
p.m[requestID] = poll
|
||||
|
||||
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
|
||||
}
|
||||
return !exists
|
||||
}
|
||||
|
||||
// Vote registers the connections response to a query for [id]. If there was no
|
||||
// query, or the response has already be registered, nothing is performed.
|
||||
func (p *polls) Vote(requestID uint32, vdr ids.ShortID, votes []ids.ID) (ids.UniqueBag, bool) {
|
||||
p.log.Verbo("Vote. requestID: %d. validatorID: %s.", requestID, vdr)
|
||||
poll, exists := p.m[requestID]
|
||||
p.log.Verbo("Poll: %+v", poll)
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
poll.Vote(votes, vdr)
|
||||
if poll.Finished() {
|
||||
p.log.Verbo("Poll is finished")
|
||||
delete(p.m, requestID)
|
||||
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
|
||||
return poll.votes, true
|
||||
}
|
||||
p.m[requestID] = poll
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (p *polls) String() string {
|
||||
sb := strings.Builder{}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("Current polls: (Size = %d)", len(p.m)))
|
||||
for requestID, poll := range p.m {
|
||||
sb.WriteString(fmt.Sprintf("\n %d: %s", requestID, poll))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// poll represents the current state of a network poll for a vertex
|
||||
type poll struct {
|
||||
votes ids.UniqueBag
|
||||
polled ids.ShortSet
|
||||
alpha int
|
||||
}
|
||||
|
||||
// Vote registers a vote for this poll
|
||||
func (p *poll) Vote(votes []ids.ID, vdr ids.ShortID) {
|
||||
if p.polled.Contains(vdr) {
|
||||
p.polled.Remove(vdr)
|
||||
p.votes.Add(uint(p.polled.Len()), votes...)
|
||||
}
|
||||
}
|
||||
|
||||
// Finished returns true if the poll has completed, with no more required
|
||||
// responses
|
||||
func (p poll) Finished() bool {
|
||||
// If there are no outstanding queries, the poll is finished
|
||||
numPending := p.polled.Len()
|
||||
if numPending == 0 {
|
||||
return true
|
||||
}
|
||||
// If there are still enough pending responses to include another vertex,
|
||||
// then the poll must wait for more responses
|
||||
if numPending > p.alpha {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore any vertex that has already received alpha votes. To safely skip
|
||||
// DAG traversal, assume that all votes for vertices with less than alpha
|
||||
// votes will be applied to a single shared ancestor. In this case, the poll
|
||||
// can terminate early, iff there are not enough pending votes for this
|
||||
// ancestor to receive alpha votes.
|
||||
partialVotes := ids.BitSet(0)
|
||||
for _, vote := range p.votes.List() {
|
||||
if voters := p.votes.GetSet(vote); voters.Len() < p.alpha {
|
||||
partialVotes.Union(voters)
|
||||
}
|
||||
}
|
||||
return partialVotes.Len()+numPending < p.alpha
|
||||
}
|
||||
func (p poll) String() string { return fmt.Sprintf("Waiting on %d chits", p.polled.Len()) }
|
|
@ -1,99 +0,0 @@
|
|||
package avalanche
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
)
|
||||
|
||||
func TestPollTerminatesEarlyVirtuousCase(t *testing.T) {
|
||||
alpha := 3
|
||||
|
||||
vtxID := GenerateID()
|
||||
votes := []ids.ID{vtxID}
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2})
|
||||
vdr3 := ids.NewShortID([20]byte{3})
|
||||
vdr4 := ids.NewShortID([20]byte{4})
|
||||
vdr5 := ids.NewShortID([20]byte{5}) // k = 5
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(vdr1)
|
||||
vdrs.Add(vdr2)
|
||||
vdrs.Add(vdr3)
|
||||
vdrs.Add(vdr4)
|
||||
vdrs.Add(vdr5)
|
||||
|
||||
poll := poll{
|
||||
votes: make(ids.UniqueBag),
|
||||
polled: vdrs,
|
||||
alpha: alpha,
|
||||
}
|
||||
|
||||
poll.Vote(votes, vdr1)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished after less than alpha votes")
|
||||
}
|
||||
poll.Vote(votes, vdr2)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished after less than alpha votes")
|
||||
}
|
||||
poll.Vote(votes, vdr3)
|
||||
if !poll.Finished() {
|
||||
t.Fatalf("Poll did not terminate early after receiving alpha votes for one vertex and none for other vertices")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollAccountsForSharedAncestor(t *testing.T) {
|
||||
alpha := 4
|
||||
|
||||
vtxA := GenerateID()
|
||||
vtxB := GenerateID()
|
||||
vtxC := GenerateID()
|
||||
vtxD := GenerateID()
|
||||
|
||||
// If validators 1-3 vote for frontier vertices
|
||||
// B, C, and D respectively, which all share the common ancestor
|
||||
// A, then we cannot terminate early with alpha = k = 4
|
||||
// If the final vote is cast for any of A, B, C, or D, then
|
||||
// vertex A will have transitively received alpha = 4 votes
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2})
|
||||
vdr3 := ids.NewShortID([20]byte{3})
|
||||
vdr4 := ids.NewShortID([20]byte{4})
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(vdr1)
|
||||
vdrs.Add(vdr2)
|
||||
vdrs.Add(vdr3)
|
||||
vdrs.Add(vdr4)
|
||||
|
||||
poll := poll{
|
||||
votes: make(ids.UniqueBag),
|
||||
polled: vdrs,
|
||||
alpha: alpha,
|
||||
}
|
||||
|
||||
votes1 := []ids.ID{vtxB}
|
||||
poll.Vote(votes1, vdr1)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished early after receiving one vote")
|
||||
}
|
||||
votes2 := []ids.ID{vtxC}
|
||||
poll.Vote(votes2, vdr2)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished early after receiving two votes")
|
||||
}
|
||||
votes3 := []ids.ID{vtxD}
|
||||
poll.Vote(votes3, vdr3)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll terminated early, when a shared ancestor could have received alpha votes")
|
||||
}
|
||||
|
||||
votes4 := []ids.ID{vtxA}
|
||||
poll.Vote(votes4, vdr4)
|
||||
if !poll.Finished() {
|
||||
t.Fatalf("Poll did not terminate after receiving all outstanding votes")
|
||||
}
|
||||
}
|
|
@ -54,6 +54,8 @@ func (vtx *uniqueVertex) refresh() {
|
|||
func (vtx *uniqueVertex) Evict() {
|
||||
if vtx.v != nil {
|
||||
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/consensus/avalanche"
|
||||
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
|
||||
"github.com/ava-labs/gecko/snow/engine/avalanche/poll"
|
||||
"github.com/ava-labs/gecko/snow/engine/common"
|
||||
"github.com/ava-labs/gecko/snow/events"
|
||||
"github.com/ava-labs/gecko/utils/formatting"
|
||||
|
@ -31,7 +32,7 @@ type Transitive struct {
|
|||
Config
|
||||
bootstrapper
|
||||
|
||||
polls polls // track people I have asked for their preference
|
||||
polls poll.Set // track people I have asked for their preference
|
||||
|
||||
// vtxReqs prevents asking validators for the same vertex
|
||||
vtxReqs common.Requests
|
||||
|
@ -57,7 +58,12 @@ func (t *Transitive) Initialize(config Config) error {
|
|||
|
||||
t.onFinished = t.finishBootstrapping
|
||||
|
||||
t.polls = newPolls(int(config.Alpha), config.Context.Log, t.numPolls)
|
||||
factory := poll.NewEarlyTermNoTraversalFactory(int(config.Params.Alpha))
|
||||
t.polls = poll.NewSet(factory,
|
||||
config.Context.Log,
|
||||
config.Params.Namespace,
|
||||
config.Params.Metrics,
|
||||
)
|
||||
|
||||
return t.bootstrapper.Initialize(config.BootstrapConfig)
|
||||
}
|
||||
|
@ -309,7 +315,7 @@ func (t *Transitive) Notify(msg common.Message) error {
|
|||
}
|
||||
|
||||
func (t *Transitive) repoll() error {
|
||||
if len(t.polls.m) >= t.Params.ConcurrentRepolls || t.errs.Errored() {
|
||||
if t.polls.Len() >= t.Params.ConcurrentRepolls || t.errs.Errored() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -318,7 +324,7 @@ func (t *Transitive) repoll() error {
|
|||
return err
|
||||
}
|
||||
|
||||
for i := len(t.polls.m); i < t.Params.ConcurrentRepolls; i++ {
|
||||
for i := t.polls.Len(); i < t.Params.ConcurrentRepolls; i++ {
|
||||
if err := t.batch(nil, false /*=force*/, true /*=empty*/); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -17,11 +17,14 @@ const (
|
|||
|
||||
// StatusUpdateFrequency ... bootstrapper logs "processed X blocks/vertices" every [statusUpdateFrequency] blocks/vertices
|
||||
StatusUpdateFrequency = 2500
|
||||
|
||||
// MaxOutstandingRequests is the maximum number of GetAncestors sent but not responsded to/failed
|
||||
MaxOutstandingRequests = 8
|
||||
)
|
||||
|
||||
var (
|
||||
// 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.
|
||||
|
|
|
@ -227,7 +227,8 @@ func (b *bootstrapper) finish() error {
|
|||
if b.finished {
|
||||
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 {
|
||||
return err
|
||||
|
@ -265,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)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ type metrics struct {
|
|||
numPendingRequests, numBlocked prometheus.Gauge
|
||||
numBootstrapped, numDropped prometheus.Counter
|
||||
|
||||
numPolls, numBlkRequests, numBlockedBlk prometheus.Gauge
|
||||
numBlkRequests, numBlockedBlk prometheus.Gauge
|
||||
}
|
||||
|
||||
// Initialize implements the Engine interface
|
||||
|
@ -42,12 +42,6 @@ func (m *metrics) Initialize(log logging.Logger, namespace string, registerer pr
|
|||
Name: "sm_bs_dropped",
|
||||
Help: "Number of dropped bootstrap blocks",
|
||||
})
|
||||
m.numPolls = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "sm_polls",
|
||||
Help: "Number of pending network polls",
|
||||
})
|
||||
m.numBlkRequests = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
|
@ -73,9 +67,6 @@ func (m *metrics) Initialize(log logging.Logger, namespace string, registerer pr
|
|||
if err := registerer.Register(m.numDropped); err != nil {
|
||||
log.Error("Failed to register sm_bs_dropped statistics due to %s", err)
|
||||
}
|
||||
if err := registerer.Register(m.numPolls); err != nil {
|
||||
log.Error("Failed to register sm_polls statistics due to %s", err)
|
||||
}
|
||||
if err := registerer.Register(m.numBlkRequests); err != nil {
|
||||
log.Error("Failed to register sm_blk_requests statistics due to %s", err)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package poll
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
)
|
||||
|
||||
func TestEarlyTermNoTraversalResults(t *testing.T) {
|
||||
alpha := 1
|
||||
|
||||
vtxID := ids.NewID([32]byte{1})
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1}) // k = 1
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(vdr1)
|
||||
|
||||
factory := NewEarlyTermNoTraversalFactory(alpha)
|
||||
poll := factory.New(vdrs)
|
||||
|
||||
poll.Vote(vdr1, vtxID)
|
||||
if !poll.Finished() {
|
||||
t.Fatalf("Poll did not terminate after receiving k votes")
|
||||
}
|
||||
|
||||
result := poll.Result()
|
||||
if list := result.List(); len(list) != 1 {
|
||||
t.Fatalf("Wrong number of vertices returned")
|
||||
} else if retVtxID := list[0]; !retVtxID.Equals(vtxID) {
|
||||
t.Fatalf("Wrong vertex returned")
|
||||
} else if result.Count(vtxID) != 1 {
|
||||
t.Fatalf("Wrong number of votes returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEarlyTermNoTraversalString(t *testing.T) {
|
||||
alpha := 2
|
||||
|
||||
vtxID := ids.NewID([32]byte{1})
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(
|
||||
vdr1,
|
||||
vdr2,
|
||||
)
|
||||
|
||||
factory := NewEarlyTermNoTraversalFactory(alpha)
|
||||
poll := factory.New(vdrs)
|
||||
|
||||
poll.Vote(vdr1, vtxID)
|
||||
|
||||
expected := "waiting on {BaMPFdqMUQ46BV8iRcwbVfsam55kMqcp}"
|
||||
if result := poll.String(); expected != result {
|
||||
t.Fatalf("Poll should have returned %s but returned %s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEarlyTermNoTraversalDropsDuplicatedVotes(t *testing.T) {
|
||||
alpha := 2
|
||||
|
||||
vtxID := ids.NewID([32]byte{1})
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(
|
||||
vdr1,
|
||||
vdr2,
|
||||
)
|
||||
|
||||
factory := NewEarlyTermNoTraversalFactory(alpha)
|
||||
poll := factory.New(vdrs)
|
||||
|
||||
poll.Vote(vdr1, vtxID)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished after less than alpha votes")
|
||||
}
|
||||
poll.Vote(vdr1, vtxID)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished after getting a duplicated vote")
|
||||
}
|
||||
poll.Vote(vdr2, vtxID)
|
||||
if !poll.Finished() {
|
||||
t.Fatalf("Poll did not terminate after receiving k votes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEarlyTermNoTraversalTerminatesEarly(t *testing.T) {
|
||||
alpha := 3
|
||||
|
||||
vtxID := ids.NewID([32]byte{1})
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2})
|
||||
vdr3 := ids.NewShortID([20]byte{3})
|
||||
vdr4 := ids.NewShortID([20]byte{4})
|
||||
vdr5 := ids.NewShortID([20]byte{5}) // k = 5
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(
|
||||
vdr1,
|
||||
vdr2,
|
||||
vdr3,
|
||||
vdr4,
|
||||
vdr5,
|
||||
)
|
||||
|
||||
factory := NewEarlyTermNoTraversalFactory(alpha)
|
||||
poll := factory.New(vdrs)
|
||||
|
||||
poll.Vote(vdr1, vtxID)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished after less than alpha votes")
|
||||
}
|
||||
poll.Vote(vdr2, vtxID)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished after less than alpha votes")
|
||||
}
|
||||
poll.Vote(vdr3, vtxID)
|
||||
if !poll.Finished() {
|
||||
t.Fatalf("Poll did not terminate early after receiving alpha votes for one vertex and none for other vertices")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEarlyTermNoTraversalForSharedAncestor(t *testing.T) {
|
||||
alpha := 4
|
||||
|
||||
vtxA := ids.NewID([32]byte{1})
|
||||
vtxB := ids.NewID([32]byte{2})
|
||||
vtxC := ids.NewID([32]byte{3})
|
||||
vtxD := ids.NewID([32]byte{4})
|
||||
|
||||
// If validators 1-3 vote for frontier vertices
|
||||
// B, C, and D respectively, which all share the common ancestor
|
||||
// A, then we cannot terminate early with alpha = k = 4
|
||||
// If the final vote is cast for any of A, B, C, or D, then
|
||||
// vertex A will have transitively received alpha = 4 votes
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2})
|
||||
vdr3 := ids.NewShortID([20]byte{3})
|
||||
vdr4 := ids.NewShortID([20]byte{4})
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(
|
||||
vdr1,
|
||||
vdr2,
|
||||
vdr3,
|
||||
vdr4,
|
||||
)
|
||||
|
||||
factory := NewEarlyTermNoTraversalFactory(alpha)
|
||||
poll := factory.New(vdrs)
|
||||
|
||||
poll.Vote(vdr1, vtxB)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished early after receiving one vote")
|
||||
}
|
||||
poll.Vote(vdr2, vtxC)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished early after receiving two votes")
|
||||
}
|
||||
poll.Vote(vdr3, vtxD)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll terminated early, when a shared ancestor could have received alpha votes")
|
||||
}
|
||||
poll.Vote(vdr4, vtxA)
|
||||
if !poll.Finished() {
|
||||
t.Fatalf("Poll did not terminate after receiving all outstanding votes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEarlyTermNoTraversalWithFastDrops(t *testing.T) {
|
||||
alpha := 2
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2})
|
||||
vdr3 := ids.NewShortID([20]byte{3}) // k = 3
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(
|
||||
vdr1,
|
||||
vdr2,
|
||||
vdr3,
|
||||
)
|
||||
|
||||
factory := NewEarlyTermNoTraversalFactory(alpha)
|
||||
poll := factory.New(vdrs)
|
||||
|
||||
poll.Drop(vdr1)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished early after dropping one vote")
|
||||
}
|
||||
poll.Drop(vdr2)
|
||||
if !poll.Finished() {
|
||||
t.Fatalf("Poll did not terminate after dropping two votes")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package poll
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
)
|
||||
|
||||
// Set is a collection of polls
|
||||
type Set interface {
|
||||
fmt.Stringer
|
||||
|
||||
Add(requestID uint32, vdrs ids.ShortSet) bool
|
||||
Vote(requestID uint32, vdr ids.ShortID, vote ids.ID) (ids.Bag, bool)
|
||||
Drop(requestID uint32, vdr ids.ShortID) (ids.Bag, bool)
|
||||
Len() int
|
||||
}
|
||||
|
||||
// Poll is an outstanding poll
|
||||
type Poll interface {
|
||||
fmt.Stringer
|
||||
|
||||
Vote(vdr ids.ShortID, vote ids.ID)
|
||||
Drop(vdr ids.ShortID)
|
||||
Finished() bool
|
||||
Result() ids.Bag
|
||||
}
|
||||
|
||||
// Factory creates a new Poll
|
||||
type Factory interface {
|
||||
New(vdrs ids.ShortSet) Poll
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package poll
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
)
|
||||
|
||||
type noEarlyTermFactory struct{}
|
||||
|
||||
// NewNoEarlyTermFactory returns a factory that returns polls with no early
|
||||
// termination
|
||||
func NewNoEarlyTermFactory() Factory { return noEarlyTermFactory{} }
|
||||
|
||||
func (noEarlyTermFactory) New(vdrs ids.ShortSet) Poll {
|
||||
return &noEarlyTermPoll{polled: vdrs}
|
||||
}
|
||||
|
||||
// noEarlyTermPoll finishes when all polled validators either respond to the
|
||||
// query or a timeout occurs
|
||||
type noEarlyTermPoll struct {
|
||||
votes ids.Bag
|
||||
polled ids.ShortSet
|
||||
}
|
||||
|
||||
// Vote registers a response for this poll
|
||||
func (p *noEarlyTermPoll) Vote(vdr ids.ShortID, vote ids.ID) {
|
||||
if !p.polled.Contains(vdr) {
|
||||
// if the validator wasn't polled or already responded to this poll, we
|
||||
// should just drop the vote
|
||||
return
|
||||
}
|
||||
|
||||
// make sure that a validator can't respond multiple times
|
||||
p.polled.Remove(vdr)
|
||||
|
||||
// track the votes the validator responded with
|
||||
p.votes.Add(vote)
|
||||
}
|
||||
|
||||
// Drop any future response for this poll
|
||||
func (p *noEarlyTermPoll) Drop(vdr ids.ShortID) { p.polled.Remove(vdr) }
|
||||
|
||||
// Finished returns true when all validators have voted
|
||||
func (p *noEarlyTermPoll) Finished() bool { return p.polled.Len() == 0 }
|
||||
|
||||
// Result returns the result of this poll
|
||||
func (p *noEarlyTermPoll) Result() ids.Bag { return p.votes }
|
||||
|
||||
func (p *noEarlyTermPoll) String() string {
|
||||
return fmt.Sprintf("waiting on %s", p.polled)
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package poll
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
)
|
||||
|
||||
func TestNoEarlyTermResults(t *testing.T) {
|
||||
vtxID := ids.NewID([32]byte{1})
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1}) // k = 1
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(vdr1)
|
||||
|
||||
factory := NewNoEarlyTermFactory()
|
||||
poll := factory.New(vdrs)
|
||||
|
||||
poll.Vote(vdr1, vtxID)
|
||||
if !poll.Finished() {
|
||||
t.Fatalf("Poll did not terminate after receiving k votes")
|
||||
}
|
||||
|
||||
result := poll.Result()
|
||||
if list := result.List(); len(list) != 1 {
|
||||
t.Fatalf("Wrong number of vertices returned")
|
||||
} else if retVtxID := list[0]; !retVtxID.Equals(vtxID) {
|
||||
t.Fatalf("Wrong vertex returned")
|
||||
} else if result.Count(vtxID) != 1 {
|
||||
t.Fatalf("Wrong number of votes returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoEarlyTermString(t *testing.T) {
|
||||
vtxID := ids.NewID([32]byte{1})
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(
|
||||
vdr1,
|
||||
vdr2,
|
||||
)
|
||||
|
||||
factory := NewNoEarlyTermFactory()
|
||||
poll := factory.New(vdrs)
|
||||
|
||||
poll.Vote(vdr1, vtxID)
|
||||
|
||||
expected := "waiting on {BaMPFdqMUQ46BV8iRcwbVfsam55kMqcp}"
|
||||
if result := poll.String(); expected != result {
|
||||
t.Fatalf("Poll should have returned %s but returned %s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoEarlyTermDropsDuplicatedVotes(t *testing.T) {
|
||||
vtxID := ids.NewID([32]byte{1})
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(
|
||||
vdr1,
|
||||
vdr2,
|
||||
)
|
||||
|
||||
factory := NewNoEarlyTermFactory()
|
||||
poll := factory.New(vdrs)
|
||||
|
||||
poll.Vote(vdr1, vtxID)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished after less than alpha votes")
|
||||
}
|
||||
poll.Vote(vdr1, vtxID)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished after getting a duplicated vote")
|
||||
}
|
||||
poll.Drop(vdr1)
|
||||
if poll.Finished() {
|
||||
t.Fatalf("Poll finished after getting a duplicated vote")
|
||||
}
|
||||
poll.Vote(vdr2, vtxID)
|
||||
if !poll.Finished() {
|
||||
t.Fatalf("Poll did not terminate after receiving k votes")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package poll
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/utils/logging"
|
||||
"github.com/ava-labs/gecko/utils/timer"
|
||||
)
|
||||
|
||||
type poll struct {
|
||||
Poll
|
||||
start time.Time
|
||||
}
|
||||
|
||||
type set struct {
|
||||
log logging.Logger
|
||||
numPolls prometheus.Gauge
|
||||
durPolls prometheus.Histogram
|
||||
factory Factory
|
||||
polls map[uint32]poll
|
||||
}
|
||||
|
||||
// NewSet returns a new empty set of polls
|
||||
func NewSet(
|
||||
factory Factory,
|
||||
log logging.Logger,
|
||||
namespace string,
|
||||
registerer prometheus.Registerer,
|
||||
) Set {
|
||||
numPolls := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Name: "polls",
|
||||
Help: "Number of pending network polls",
|
||||
})
|
||||
if err := registerer.Register(numPolls); err != nil {
|
||||
log.Error("failed to register polls statistics due to %s", err)
|
||||
}
|
||||
|
||||
durPolls := prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Name: "poll_duration",
|
||||
Help: "Length of time the poll existed in milliseconds",
|
||||
Buckets: timer.MillisecondsBuckets,
|
||||
})
|
||||
if err := registerer.Register(durPolls); err != nil {
|
||||
log.Error("failed to register poll_duration statistics due to %s", err)
|
||||
}
|
||||
|
||||
return &set{
|
||||
log: log,
|
||||
numPolls: numPolls,
|
||||
durPolls: durPolls,
|
||||
factory: factory,
|
||||
polls: make(map[uint32]poll),
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the current set of polls
|
||||
// Returns true if the poll was registered correctly and the network sample
|
||||
// should be made.
|
||||
func (s *set) Add(requestID uint32, vdrs ids.ShortSet) bool {
|
||||
if _, exists := s.polls[requestID]; exists {
|
||||
s.log.Debug("dropping poll due to duplicated requestID: %d", requestID)
|
||||
return false
|
||||
}
|
||||
|
||||
s.log.Verbo("creating poll with requestID %d and validators %s",
|
||||
requestID,
|
||||
vdrs)
|
||||
|
||||
s.polls[requestID] = poll{
|
||||
Poll: s.factory.New(vdrs), // create the new poll
|
||||
start: time.Now(),
|
||||
}
|
||||
s.numPolls.Inc() // increase the metrics
|
||||
return true
|
||||
}
|
||||
|
||||
// Vote registers the connections response to a query for [id]. If there was no
|
||||
// query, or the response has already be registered, nothing is performed.
|
||||
func (s *set) Vote(
|
||||
requestID uint32,
|
||||
vdr ids.ShortID,
|
||||
vote ids.ID,
|
||||
) (ids.Bag, bool) {
|
||||
poll, exists := s.polls[requestID]
|
||||
if !exists {
|
||||
s.log.Verbo("dropping vote from %s to an unknown poll with requestID: %d",
|
||||
vdr,
|
||||
requestID)
|
||||
return ids.Bag{}, false
|
||||
}
|
||||
|
||||
s.log.Verbo("processing vote from %s in the poll with requestID: %d with the vote %s",
|
||||
vdr,
|
||||
requestID,
|
||||
vote)
|
||||
|
||||
poll.Vote(vdr, vote)
|
||||
if !poll.Finished() {
|
||||
return ids.Bag{}, false
|
||||
}
|
||||
|
||||
s.log.Verbo("poll with requestID %d finished as %s", requestID, poll)
|
||||
|
||||
delete(s.polls, requestID) // remove the poll from the current set
|
||||
s.durPolls.Observe(float64(time.Now().Sub(poll.start).Milliseconds()))
|
||||
s.numPolls.Dec() // decrease the metrics
|
||||
return poll.Result(), true
|
||||
}
|
||||
|
||||
// Drop registers the connections response to a query for [id]. If there was no
|
||||
// query, or the response has already be registered, nothing is performed.
|
||||
func (s *set) Drop(requestID uint32, vdr ids.ShortID) (ids.Bag, bool) {
|
||||
poll, exists := s.polls[requestID]
|
||||
if !exists {
|
||||
s.log.Verbo("dropping vote from %s to an unknown poll with requestID: %d",
|
||||
vdr,
|
||||
requestID)
|
||||
return ids.Bag{}, false
|
||||
}
|
||||
|
||||
s.log.Verbo("processing dropped vote from %s in the poll with requestID: %d",
|
||||
vdr,
|
||||
requestID)
|
||||
|
||||
poll.Drop(vdr)
|
||||
if !poll.Finished() {
|
||||
return ids.Bag{}, false
|
||||
}
|
||||
|
||||
s.log.Verbo("poll with requestID %d finished as %s", requestID, poll)
|
||||
|
||||
delete(s.polls, requestID) // remove the poll from the current set
|
||||
s.durPolls.Observe(float64(time.Now().Sub(poll.start).Milliseconds()))
|
||||
s.numPolls.Dec() // decrease the metrics
|
||||
return poll.Result(), true
|
||||
}
|
||||
|
||||
// Len returns the number of outstanding polls
|
||||
func (s *set) Len() int { return len(s.polls) }
|
||||
|
||||
func (s *set) String() string {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString(fmt.Sprintf("current polls: (Size = %d)", len(s.polls)))
|
||||
for requestID, poll := range s.polls {
|
||||
sb.WriteString(fmt.Sprintf("\n %d: %s", requestID, poll))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package poll
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/utils/logging"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestNewSetErrorOnMetrics(t *testing.T) {
|
||||
factory := NewNoEarlyTermFactory()
|
||||
log := logging.NoLog{}
|
||||
namespace := ""
|
||||
registerer := prometheus.NewRegistry()
|
||||
|
||||
registerer.Register(prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "polls",
|
||||
}))
|
||||
registerer.Register(prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "poll_duration",
|
||||
}))
|
||||
|
||||
_ = NewSet(factory, log, namespace, registerer)
|
||||
}
|
||||
|
||||
func TestCreateAndFinishSuccessfulPoll(t *testing.T) {
|
||||
factory := NewNoEarlyTermFactory()
|
||||
log := logging.NoLog{}
|
||||
namespace := ""
|
||||
registerer := prometheus.NewRegistry()
|
||||
s := NewSet(factory, log, namespace, registerer)
|
||||
|
||||
vtxID := ids.NewID([32]byte{1})
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(
|
||||
vdr1,
|
||||
vdr2,
|
||||
)
|
||||
|
||||
if s.Len() != 0 {
|
||||
t.Fatalf("Shouldn't have any active polls yet")
|
||||
} else if !s.Add(0, vdrs) {
|
||||
t.Fatalf("Should have been able to add a new poll")
|
||||
} else if s.Len() != 1 {
|
||||
t.Fatalf("Should only have one active poll")
|
||||
} else if s.Add(0, vdrs) {
|
||||
t.Fatalf("Shouldn't have been able to add a duplicated poll")
|
||||
} else if s.Len() != 1 {
|
||||
t.Fatalf("Should only have one active poll")
|
||||
} else if _, finished := s.Vote(1, vdr1, vtxID); finished {
|
||||
t.Fatalf("Shouldn't have been able to finish a non-existant poll")
|
||||
} else if _, finished := s.Vote(0, vdr1, vtxID); finished {
|
||||
t.Fatalf("Shouldn't have been able to finish an ongoing poll")
|
||||
} else if _, finished := s.Vote(0, vdr1, vtxID); finished {
|
||||
t.Fatalf("Should have dropped a duplicated poll")
|
||||
} else if result, finished := s.Vote(0, vdr2, vtxID); !finished {
|
||||
t.Fatalf("Should have finished the")
|
||||
} else if list := result.List(); len(list) != 1 {
|
||||
t.Fatalf("Wrong number of vertices returned")
|
||||
} else if retVtxID := list[0]; !retVtxID.Equals(vtxID) {
|
||||
t.Fatalf("Wrong vertex returned")
|
||||
} else if result.Count(vtxID) != 2 {
|
||||
t.Fatalf("Wrong number of votes returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAndFinishFailedPoll(t *testing.T) {
|
||||
factory := NewNoEarlyTermFactory()
|
||||
log := logging.NoLog{}
|
||||
namespace := ""
|
||||
registerer := prometheus.NewRegistry()
|
||||
s := NewSet(factory, log, namespace, registerer)
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1})
|
||||
vdr2 := ids.NewShortID([20]byte{2}) // k = 2
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(
|
||||
vdr1,
|
||||
vdr2,
|
||||
)
|
||||
|
||||
if s.Len() != 0 {
|
||||
t.Fatalf("Shouldn't have any active polls yet")
|
||||
} else if !s.Add(0, vdrs) {
|
||||
t.Fatalf("Should have been able to add a new poll")
|
||||
} else if s.Len() != 1 {
|
||||
t.Fatalf("Should only have one active poll")
|
||||
} else if s.Add(0, vdrs) {
|
||||
t.Fatalf("Shouldn't have been able to add a duplicated poll")
|
||||
} else if s.Len() != 1 {
|
||||
t.Fatalf("Should only have one active poll")
|
||||
} else if _, finished := s.Drop(1, vdr1); finished {
|
||||
t.Fatalf("Shouldn't have been able to finish a non-existant poll")
|
||||
} else if _, finished := s.Drop(0, vdr1); finished {
|
||||
t.Fatalf("Shouldn't have been able to finish an ongoing poll")
|
||||
} else if _, finished := s.Drop(0, vdr1); finished {
|
||||
t.Fatalf("Should have dropped a duplicated poll")
|
||||
} else if result, finished := s.Drop(0, vdr2); !finished {
|
||||
t.Fatalf("Should have finished the")
|
||||
} else if list := result.List(); len(list) != 0 {
|
||||
t.Fatalf("Wrong number of vertices returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetString(t *testing.T) {
|
||||
factory := NewNoEarlyTermFactory()
|
||||
log := logging.NoLog{}
|
||||
namespace := ""
|
||||
registerer := prometheus.NewRegistry()
|
||||
s := NewSet(factory, log, namespace, registerer)
|
||||
|
||||
vdr1 := ids.NewShortID([20]byte{1}) // k = 1
|
||||
|
||||
vdrs := ids.ShortSet{}
|
||||
vdrs.Add(vdr1)
|
||||
|
||||
expected := "current polls: (Size = 1)\n" +
|
||||
" 0: waiting on {6HgC8KRBEhXYbF4riJyJFLSHt37UNuRt}"
|
||||
if !s.Add(0, vdrs) {
|
||||
t.Fatalf("Should have been able to add a new poll")
|
||||
} else if str := s.String(); expected != str {
|
||||
t.Fatalf("Set return wrong string, Expected:\n%s\nReturned:\n%s",
|
||||
expected,
|
||||
str)
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package snowman
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/utils/logging"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type polls struct {
|
||||
log logging.Logger
|
||||
numPolls prometheus.Gauge
|
||||
alpha int
|
||||
m map[uint32]poll
|
||||
}
|
||||
|
||||
// Add to the current set of polls
|
||||
// Returns true if the poll was registered correctly and the network sample
|
||||
// should be made.
|
||||
func (p *polls) Add(requestID uint32, vdrs ids.ShortSet) bool {
|
||||
poll, exists := p.m[requestID]
|
||||
if !exists {
|
||||
poll.alpha = p.alpha
|
||||
poll.polled = vdrs
|
||||
p.m[requestID] = poll
|
||||
|
||||
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
|
||||
}
|
||||
return !exists
|
||||
}
|
||||
|
||||
// Vote registers the connections response to a query for [id]. If there was no
|
||||
// query, or the response has already be registered, nothing is performed.
|
||||
func (p *polls) Vote(requestID uint32, vdr ids.ShortID, vote ids.ID) (ids.Bag, bool) {
|
||||
p.log.Verbo("[polls.Vote] Vote: requestID: %d. validatorID: %s. Vote: %s", requestID, vdr, vote)
|
||||
poll, exists := p.m[requestID]
|
||||
if !exists {
|
||||
return ids.Bag{}, false
|
||||
}
|
||||
poll.Vote(vote, vdr)
|
||||
if poll.Finished() {
|
||||
delete(p.m, requestID)
|
||||
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
|
||||
return poll.votes, true
|
||||
}
|
||||
p.m[requestID] = poll
|
||||
return ids.Bag{}, false
|
||||
}
|
||||
|
||||
// CancelVote registers the connections failure to respond to a query for [id].
|
||||
func (p *polls) CancelVote(requestID uint32, vdr ids.ShortID) (ids.Bag, bool) {
|
||||
p.log.Verbo("CancelVote received. requestID: %d. validatorID: %s. Vote: %s", requestID, vdr)
|
||||
poll, exists := p.m[requestID]
|
||||
if !exists {
|
||||
return ids.Bag{}, false
|
||||
}
|
||||
|
||||
poll.CancelVote(vdr)
|
||||
if poll.Finished() {
|
||||
delete(p.m, requestID)
|
||||
p.numPolls.Set(float64(len(p.m))) // Tracks performance statistics
|
||||
return poll.votes, true
|
||||
}
|
||||
p.m[requestID] = poll
|
||||
return ids.Bag{}, false
|
||||
}
|
||||
|
||||
func (p *polls) String() string {
|
||||
sb := strings.Builder{}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("Current polls: (Size = %d)", len(p.m)))
|
||||
for requestID, poll := range p.m {
|
||||
sb.WriteString(fmt.Sprintf("\n %d: %s", requestID, poll))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// poll represents the current state of a network poll for a block
|
||||
type poll struct {
|
||||
alpha int
|
||||
votes ids.Bag
|
||||
polled ids.ShortSet
|
||||
}
|
||||
|
||||
// Vote registers a vote for this poll
|
||||
func (p *poll) CancelVote(vdr ids.ShortID) { p.polled.Remove(vdr) }
|
||||
|
||||
// Vote registers a vote for this poll
|
||||
func (p *poll) Vote(vote ids.ID, vdr ids.ShortID) {
|
||||
if p.polled.Contains(vdr) {
|
||||
p.polled.Remove(vdr)
|
||||
p.votes.Add(vote)
|
||||
}
|
||||
}
|
||||
|
||||
// Finished returns true if the poll has completed, with no more required
|
||||
// responses
|
||||
func (p poll) Finished() bool {
|
||||
remaining := p.polled.Len()
|
||||
received := p.votes.Len()
|
||||
_, freq := p.votes.Mode()
|
||||
return remaining == 0 || // All k nodes responded
|
||||
freq >= p.alpha || // An alpha majority has returned
|
||||
received+remaining < p.alpha // An alpha majority can never return
|
||||
}
|
||||
|
||||
func (p poll) String() string {
|
||||
return fmt.Sprintf("Waiting on %d chits from %s", p.polled.Len(), p.polled)
|
||||
}
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/ava-labs/gecko/snow/choices"
|
||||
"github.com/ava-labs/gecko/snow/consensus/snowman"
|
||||
"github.com/ava-labs/gecko/snow/engine/common"
|
||||
"github.com/ava-labs/gecko/snow/engine/snowman/poll"
|
||||
"github.com/ava-labs/gecko/snow/events"
|
||||
"github.com/ava-labs/gecko/utils/formatting"
|
||||
"github.com/ava-labs/gecko/utils/wrappers"
|
||||
|
@ -30,7 +31,7 @@ type Transitive struct {
|
|||
bootstrapper
|
||||
|
||||
// track outstanding preference requests
|
||||
polls polls
|
||||
polls poll.Set
|
||||
|
||||
// blocks that have outstanding get requests
|
||||
blkReqs common.Requests
|
||||
|
@ -64,10 +65,12 @@ func (t *Transitive) Initialize(config Config) error {
|
|||
|
||||
t.onFinished = t.finishBootstrapping
|
||||
|
||||
t.polls.log = config.Context.Log
|
||||
t.polls.numPolls = t.numPolls
|
||||
t.polls.alpha = t.Params.Alpha
|
||||
t.polls.m = make(map[uint32]poll)
|
||||
factory := poll.NewEarlyTermNoTraversalFactory(int(config.Params.Alpha))
|
||||
t.polls = poll.NewSet(factory,
|
||||
config.Context.Log,
|
||||
config.Params.Namespace,
|
||||
config.Params.Metrics,
|
||||
)
|
||||
|
||||
return t.bootstrapper.Initialize(config.BootstrapConfig)
|
||||
}
|
||||
|
@ -409,7 +412,7 @@ func (t *Transitive) repoll() {
|
|||
// propagate the most likely branch as quickly as possible
|
||||
prefID := t.Consensus.Preference()
|
||||
|
||||
for i := len(t.polls.m); i < t.Params.ConcurrentRepolls; i++ {
|
||||
for i := t.polls.Len(); i < t.Params.ConcurrentRepolls; i++ {
|
||||
t.pullSample(prefID)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -810,13 +810,13 @@ func TestVoteCanceling(t *testing.T) {
|
|||
|
||||
te.insert(blk)
|
||||
|
||||
if len(te.polls.m) != 1 {
|
||||
if te.polls.Len() != 1 {
|
||||
t.Fatalf("Shouldn't have finished blocking issue")
|
||||
}
|
||||
|
||||
te.QueryFailed(vdr0.ID(), *queryRequestID)
|
||||
|
||||
if len(te.polls.m) != 1 {
|
||||
if te.polls.Len() != 1 {
|
||||
t.Fatalf("Shouldn't have finished blocking issue")
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ func (v *voter) Update() {
|
|||
results := ids.Bag{}
|
||||
finished := false
|
||||
if v.response.IsZero() {
|
||||
results, finished = v.t.polls.CancelVote(v.requestID, v.vdr)
|
||||
results, finished = v.t.polls.Drop(v.requestID, v.vdr)
|
||||
} else {
|
||||
results, finished = v.t.polls.Vote(v.requestID, v.vdr, v.response)
|
||||
}
|
||||
|
|
|
@ -320,7 +320,7 @@ func (sr *ChainRouter) QueryFailed(validatorID ids.ShortID, chainID ids.ID, requ
|
|||
if chain, exists := sr.chains[chainID.Key()]; exists {
|
||||
chain.QueryFailed(validatorID, requestID)
|
||||
} else {
|
||||
sr.log.Error("QueryFailed(%s, %s, %d, %s) dropped due to unknown chain", validatorID, chainID, requestID)
|
||||
sr.log.Error("QueryFailed(%s, %s, %d) dropped due to unknown chain", validatorID, chainID, requestID)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,12 @@ func (h *Handler) Initialize(
|
|||
// Context of this Handler
|
||||
func (h *Handler) Context() *snow.Context { return h.engine.Context() }
|
||||
|
||||
// Engine returns the engine this handler dispatches to
|
||||
func (h *Handler) Engine() common.Engine { return h.engine }
|
||||
|
||||
// SetEngine sets the engine this handler dispatches to
|
||||
func (h *Handler) SetEngine(engine common.Engine) { h.engine = engine }
|
||||
|
||||
// Dispatch waits for incoming messages from the network
|
||||
// and, when they arrive, sends them to the consensus engine
|
||||
func (h *Handler) Dispatch() {
|
||||
|
|
|
@ -13,6 +13,19 @@ import (
|
|||
"github.com/ava-labs/gecko/utils/random"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxExcessCapacityFactor ...
|
||||
// If, when the validator set is reset, cap(set)/len(set) > MaxExcessCapacityFactor,
|
||||
// the underlying arrays' capacities will be reduced by a factor of capacityReductionFactor.
|
||||
// Higher value for maxExcessCapacityFactor --> less aggressive array downsizing --> less memory allocations
|
||||
// but more unnecessary data in the underlying array that can't be garbage collected.
|
||||
// Higher value for capacityReductionFactor --> more aggressive array downsizing --> more memory allocations
|
||||
// but less unnecessary data in the underlying array that can't be garbage collected.
|
||||
maxExcessCapacityFactor = 4
|
||||
// CapacityReductionFactor ...
|
||||
capacityReductionFactor = 2
|
||||
)
|
||||
|
||||
// Set of validators that can be sampled
|
||||
type Set interface {
|
||||
fmt.Stringer
|
||||
|
@ -71,9 +84,21 @@ func (s *set) Set(vdrs []Validator) {
|
|||
}
|
||||
|
||||
func (s *set) set(vdrs []Validator) {
|
||||
s.vdrMap = make(map[[20]byte]int, len(vdrs))
|
||||
lenVdrs := len(vdrs)
|
||||
// If the underlying arrays are much larger than necessary, resize them to
|
||||
// allow garbage collection of unused memory
|
||||
if cap(s.vdrSlice) > len(s.vdrSlice)*maxExcessCapacityFactor {
|
||||
newCap := cap(s.vdrSlice) / capacityReductionFactor
|
||||
if newCap < lenVdrs {
|
||||
newCap = lenVdrs
|
||||
}
|
||||
s.vdrSlice = make([]Validator, 0, newCap)
|
||||
s.sampler.Weights = make([]uint64, 0, newCap)
|
||||
} else {
|
||||
s.vdrSlice = s.vdrSlice[:0]
|
||||
s.sampler.Weights = s.sampler.Weights[:0]
|
||||
}
|
||||
s.vdrMap = make(map[[20]byte]int, lenVdrs)
|
||||
|
||||
for _, vdr := range vdrs {
|
||||
s.add(vdr)
|
||||
|
|
|
@ -40,20 +40,27 @@ func GenerateStakingKeyCert(keyPath, certPath string) error {
|
|||
return fmt.Errorf("couldn't create certificate: %w", err)
|
||||
}
|
||||
|
||||
// Write cert to disk
|
||||
if err := os.MkdirAll(filepath.Dir(certPath), 0755); err != nil {
|
||||
return fmt.Errorf("couldn't create path for key/cert: %w", err)
|
||||
// Ensure directory where key/cert will live exist
|
||||
if err := os.MkdirAll(filepath.Dir(certPath), 0700); err != nil {
|
||||
return fmt.Errorf("couldn't create path for cert: %w", err)
|
||||
} else if err := os.MkdirAll(filepath.Dir(keyPath), 0700); err != nil {
|
||||
return fmt.Errorf("couldn't create path for key: %w", err)
|
||||
}
|
||||
certOut, err := os.Create(certPath)
|
||||
|
||||
// Write cert to disk
|
||||
certFile, err := os.Create(certPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't create cert file: %w", err)
|
||||
}
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil {
|
||||
if err := pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil {
|
||||
return fmt.Errorf("couldn't write cert file: %w", err)
|
||||
}
|
||||
if err := certOut.Close(); err != nil {
|
||||
if err := certFile.Close(); err != nil {
|
||||
return fmt.Errorf("couldn't close cert file: %w", err)
|
||||
}
|
||||
if err := os.Chmod(certPath, 0400); err != nil { // Make cert read-only
|
||||
return fmt.Errorf("couldn't change permissions on cert: %w", err)
|
||||
}
|
||||
|
||||
// Write key to disk
|
||||
keyOut, err := os.Create(keyPath)
|
||||
|
@ -70,5 +77,9 @@ func GenerateStakingKeyCert(keyPath, certPath string) error {
|
|||
if err := keyOut.Close(); err != nil {
|
||||
return fmt.Errorf("couldn't close key file: %w", err)
|
||||
}
|
||||
if err := os.Chmod(keyPath, 0400); err != nil { // Make key read-only
|
||||
return fmt.Errorf("couldn't change permissions on key")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
"github.com/ava-labs/gecko/database"
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
)
|
||||
|
||||
|
@ -46,8 +46,8 @@ func (t *BaseTx) InputUTXOs() []*ava.UTXOID {
|
|||
return utxos
|
||||
}
|
||||
|
||||
// AssetIDs returns the IDs of the assets this transaction depends on
|
||||
func (t *BaseTx) AssetIDs() ids.Set {
|
||||
// ConsumedAssetIDs returns the IDs of the assets this transaction consumes
|
||||
func (t *BaseTx) ConsumedAssetIDs() ids.Set {
|
||||
assets := ids.Set{}
|
||||
for _, in := range t.Ins {
|
||||
assets.Add(in.AssetID())
|
||||
|
@ -55,6 +55,11 @@ func (t *BaseTx) AssetIDs() ids.Set {
|
|||
return assets
|
||||
}
|
||||
|
||||
// AssetIDs returns the IDs of the assets this transaction depends on
|
||||
func (t *BaseTx) AssetIDs() ids.Set {
|
||||
return t.ConsumedAssetIDs()
|
||||
}
|
||||
|
||||
// NumCredentials returns the number of expected credentials
|
||||
func (t *BaseTx) NumCredentials() int { return len(t.Ins) }
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ func TestBaseTxSerialization(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
|
@ -87,7 +87,7 @@ func TestBaseTxSerialization(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -125,7 +125,7 @@ func TestBaseTxGetters(t *testing.T) {
|
|||
tx := &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
|
@ -135,7 +135,7 @@ func TestBaseTxGetters(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -162,6 +162,10 @@ func TestBaseTxGetters(t *testing.T) {
|
|||
t.Fatalf("Wrong number of assets returned")
|
||||
} else if !assets.Contains(asset) {
|
||||
t.Fatalf("Wrong asset returned")
|
||||
} else if assets := tx.ConsumedAssetIDs(); assets.Len() != 1 {
|
||||
t.Fatalf("Wrong number of consumed assets returned")
|
||||
} else if !assets.Contains(asset) {
|
||||
t.Fatalf("Wrong consumed asset returned")
|
||||
} else if utxos := tx.UTXOs(); len(utxos) != 1 {
|
||||
t.Fatalf("Wrong number of utxos returned")
|
||||
} else if utxo := utxos[0]; !utxo.TxID.Equals(txID) {
|
||||
|
@ -179,7 +183,7 @@ func TestBaseTxSyntacticVerify(t *testing.T) {
|
|||
tx := &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
|
@ -189,7 +193,7 @@ func TestBaseTxSyntacticVerify(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -230,7 +234,7 @@ func TestBaseTxSyntacticVerifyWrongNetworkID(t *testing.T) {
|
|||
tx := &BaseTx{
|
||||
NetID: 0,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
|
@ -240,7 +244,7 @@ func TestBaseTxSyntacticVerifyWrongNetworkID(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -272,7 +276,7 @@ func TestBaseTxSyntacticVerifyWrongChainID(t *testing.T) {
|
|||
tx := &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: ids.Empty,
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
|
@ -282,7 +286,7 @@ func TestBaseTxSyntacticVerifyWrongChainID(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -315,7 +319,7 @@ func TestBaseTxSyntacticVerifyInvalidOutput(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{nil},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -348,7 +352,7 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{
|
||||
&ava.TransferableOutput{
|
||||
{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 2,
|
||||
|
@ -358,7 +362,7 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
&ava.TransferableOutput{
|
||||
{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 1,
|
||||
|
@ -370,7 +374,7 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Ins: []*ava.TransferableInput{
|
||||
&ava.TransferableInput{
|
||||
{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -403,7 +407,7 @@ func TestBaseTxSyntacticVerifyInvalidInput(t *testing.T) {
|
|||
tx := &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
|
@ -428,7 +432,7 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) {
|
|||
tx := &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
|
@ -439,7 +443,7 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
Ins: []*ava.TransferableInput{
|
||||
&ava.TransferableInput{
|
||||
{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -457,7 +461,7 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
&ava.TransferableInput{
|
||||
{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -491,7 +495,7 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{
|
||||
&ava.TransferableOutput{
|
||||
{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 2,
|
||||
|
@ -501,7 +505,7 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
&ava.TransferableOutput{
|
||||
{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: math.MaxUint64,
|
||||
|
@ -512,7 +516,7 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -544,7 +548,7 @@ func TestBaseTxSyntacticVerifyInsufficientFunds(t *testing.T) {
|
|||
tx := &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: math.MaxUint64,
|
||||
|
@ -554,7 +558,7 @@ func TestBaseTxSyntacticVerifyInsufficientFunds(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -586,7 +590,7 @@ func TestBaseTxSyntacticVerifyUninitialized(t *testing.T) {
|
|||
tx := &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
|
@ -596,7 +600,7 @@ func TestBaseTxSyntacticVerifyUninitialized(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
|
||||
|
@ -633,7 +637,7 @@ func TestBaseTxSemanticVerify(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -702,7 +706,7 @@ func TestBaseTxSemanticVerifyUnknownFx(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -754,7 +758,7 @@ func TestBaseTxSemanticVerifyWrongAssetID(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -825,11 +829,11 @@ func TestBaseTxSemanticVerifyUnauthorizedFx(t *testing.T) {
|
|||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.Empty,
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
},
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.NewID([32]byte{1}),
|
||||
Fx: &testFx{},
|
||||
},
|
||||
|
@ -863,7 +867,7 @@ func TestBaseTxSemanticVerifyUnauthorizedFx(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -923,7 +927,7 @@ func TestBaseTxSemanticVerifyInvalidSignature(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -941,9 +945,7 @@ func TestBaseTxSemanticVerifyInvalidSignature(t *testing.T) {
|
|||
}}
|
||||
|
||||
tx.Creds = append(tx.Creds, &secp256k1fx.Credential{
|
||||
Sigs: [][crypto.SECP256K1RSigLen]byte{
|
||||
[crypto.SECP256K1RSigLen]byte{},
|
||||
},
|
||||
Sigs: [][crypto.SECP256K1RSigLen]byte{{}},
|
||||
})
|
||||
|
||||
b, err := vm.codec.Marshal(tx)
|
||||
|
@ -977,7 +979,7 @@ func TestBaseTxSemanticVerifyMissingUTXO(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 1,
|
||||
|
@ -1044,7 +1046,7 @@ func TestBaseTxSemanticVerifyInvalidUTXO(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: math.MaxUint32,
|
||||
|
@ -1107,7 +1109,7 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) {
|
|||
pendingTx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -1122,7 +1124,7 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 50000,
|
||||
|
@ -1179,7 +1181,7 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: txID,
|
||||
OutputIndex: 2,
|
||||
|
@ -1241,7 +1243,7 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) {
|
|||
pendingTx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -1256,7 +1258,7 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 50000,
|
||||
|
@ -1313,7 +1315,7 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: txID,
|
||||
OutputIndex: 0,
|
||||
|
@ -1381,11 +1383,11 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) {
|
|||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.NewID([32]byte{1}),
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
},
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.Empty,
|
||||
Fx: &testFx{},
|
||||
},
|
||||
|
@ -1419,7 +1421,7 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) {
|
|||
pendingTx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -1434,7 +1436,7 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 50000,
|
||||
|
@ -1491,7 +1493,7 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: txID,
|
||||
OutputIndex: 0,
|
||||
|
@ -1543,11 +1545,11 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) {
|
|||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.NewID([32]byte{1}),
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
},
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.Empty,
|
||||
Fx: &testFx{},
|
||||
},
|
||||
|
@ -1581,7 +1583,7 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) {
|
|||
pendingTx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -1596,7 +1598,7 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 50000,
|
||||
|
@ -1653,7 +1655,7 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: txID,
|
||||
OutputIndex: 0,
|
||||
|
@ -1671,9 +1673,7 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) {
|
|||
}}
|
||||
|
||||
tx.Creds = append(tx.Creds, &secp256k1fx.Credential{
|
||||
Sigs: [][crypto.SECP256K1RSigLen]byte{
|
||||
[crypto.SECP256K1RSigLen]byte{},
|
||||
},
|
||||
Sigs: [][crypto.SECP256K1RSigLen]byte{{}},
|
||||
})
|
||||
|
||||
b, err = vm.codec.Marshal(tx)
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
"github.com/ava-labs/gecko/vms/secp256k1fx"
|
||||
)
|
||||
|
@ -93,7 +93,7 @@ func TestCreateAssetTxSerialization(t *testing.T) {
|
|||
0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88,
|
||||
}),
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{
|
||||
ID: ids.NewID([32]byte{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
|
@ -122,7 +122,7 @@ func TestCreateAssetTxSerialization(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xf1, 0xe1, 0xd1, 0xc1, 0xb1, 0xa1, 0x91, 0x81,
|
||||
|
@ -152,7 +152,7 @@ func TestCreateAssetTxSerialization(t *testing.T) {
|
|||
Symbol: "VIX",
|
||||
Denomination: 2,
|
||||
States: []*InitialState{
|
||||
&InitialState{
|
||||
{
|
||||
FxID: 0,
|
||||
Outs: []verify.Verifiable{
|
||||
&secp256k1fx.TransferOutput{
|
||||
|
|
|
@ -9,14 +9,15 @@ import (
|
|||
|
||||
"github.com/ava-labs/gecko/chains/atomic"
|
||||
"github.com/ava-labs/gecko/database/memdb"
|
||||
"github.com/ava-labs/gecko/database/prefixdb"
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow"
|
||||
"github.com/ava-labs/gecko/snow/engine/common"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/utils/crypto"
|
||||
"github.com/ava-labs/gecko/utils/hashing"
|
||||
"github.com/ava-labs/gecko/utils/logging"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/vms/secp256k1fx"
|
||||
)
|
||||
|
||||
|
@ -68,7 +69,7 @@ func TestExportTxSerialization(t *testing.T) {
|
|||
0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88,
|
||||
}),
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{TxID: ids.NewID([32]byte{
|
||||
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
|
||||
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
|
||||
|
@ -117,9 +118,10 @@ func TestIssueExportTx(t *testing.T) {
|
|||
genesisBytes := BuildGenesisTest(t)
|
||||
|
||||
issuer := make(chan common.Message, 1)
|
||||
baseDB := memdb.New()
|
||||
|
||||
sm := &atomic.SharedMemory{}
|
||||
sm.Initialize(logging.NoLog{}, memdb.New())
|
||||
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, baseDB))
|
||||
|
||||
ctx := snow.DefaultContextTest()
|
||||
ctx.NetworkID = networkID
|
||||
|
@ -138,10 +140,10 @@ func TestIssueExportTx(t *testing.T) {
|
|||
}
|
||||
err := vm.Initialize(
|
||||
ctx,
|
||||
memdb.New(),
|
||||
prefixdb.New([]byte{1}, baseDB),
|
||||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{&common.Fx{
|
||||
[]*common.Fx{{
|
||||
ID: ids.Empty,
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
}},
|
||||
|
@ -167,7 +169,7 @@ func TestIssueExportTx(t *testing.T) {
|
|||
BaseTx: BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: avaID,
|
||||
OutputIndex: 1,
|
||||
|
@ -179,7 +181,7 @@ func TestIssueExportTx(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
},
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: avaID},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 50000,
|
||||
|
@ -273,9 +275,10 @@ func TestClearForceAcceptedExportTx(t *testing.T) {
|
|||
genesisBytes := BuildGenesisTest(t)
|
||||
|
||||
issuer := make(chan common.Message, 1)
|
||||
baseDB := memdb.New()
|
||||
|
||||
sm := &atomic.SharedMemory{}
|
||||
sm.Initialize(logging.NoLog{}, memdb.New())
|
||||
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, baseDB))
|
||||
|
||||
ctx := snow.DefaultContextTest()
|
||||
ctx.NetworkID = networkID
|
||||
|
@ -294,10 +297,10 @@ func TestClearForceAcceptedExportTx(t *testing.T) {
|
|||
}
|
||||
err := vm.Initialize(
|
||||
ctx,
|
||||
memdb.New(),
|
||||
prefixdb.New([]byte{1}, baseDB),
|
||||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{&common.Fx{
|
||||
[]*common.Fx{{
|
||||
ID: ids.Empty,
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
}},
|
||||
|
@ -323,7 +326,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) {
|
|||
BaseTx: BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: avaID,
|
||||
OutputIndex: 1,
|
||||
|
@ -335,7 +338,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
},
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: avaID},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 50000,
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
"github.com/ava-labs/gecko/database/versiondb"
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
)
|
||||
|
||||
|
@ -33,6 +33,15 @@ func (t *ImportTx) InputUTXOs() []*ava.UTXOID {
|
|||
return utxos
|
||||
}
|
||||
|
||||
// ConsumedAssetIDs returns the IDs of the assets this transaction consumes
|
||||
func (t *ImportTx) ConsumedAssetIDs() ids.Set {
|
||||
assets := t.BaseTx.AssetIDs()
|
||||
for _, in := range t.Ins {
|
||||
assets.Add(in.AssetID())
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
// AssetIDs returns the IDs of the assets this transaction depends on
|
||||
func (t *ImportTx) AssetIDs() ids.Set {
|
||||
assets := t.BaseTx.AssetIDs()
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/ava-labs/gecko/chains/atomic"
|
||||
"github.com/ava-labs/gecko/database/memdb"
|
||||
"github.com/ava-labs/gecko/database/prefixdb"
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow"
|
||||
"github.com/ava-labs/gecko/snow/engine/common"
|
||||
|
@ -68,7 +69,7 @@ func TestImportTxSerialization(t *testing.T) {
|
|||
0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88,
|
||||
}),
|
||||
},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{TxID: ids.NewID([32]byte{
|
||||
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
|
||||
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
|
||||
|
@ -106,9 +107,10 @@ func TestIssueImportTx(t *testing.T) {
|
|||
genesisBytes := BuildGenesisTest(t)
|
||||
|
||||
issuer := make(chan common.Message, 1)
|
||||
baseDB := memdb.New()
|
||||
|
||||
sm := &atomic.SharedMemory{}
|
||||
sm.Initialize(logging.NoLog{}, memdb.New())
|
||||
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, baseDB))
|
||||
|
||||
ctx := snow.DefaultContextTest()
|
||||
ctx.NetworkID = networkID
|
||||
|
@ -127,10 +129,10 @@ func TestIssueImportTx(t *testing.T) {
|
|||
}
|
||||
err := vm.Initialize(
|
||||
ctx,
|
||||
memdb.New(),
|
||||
prefixdb.New([]byte{1}, baseDB),
|
||||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{&common.Fx{
|
||||
[]*common.Fx{{
|
||||
ID: ids.Empty,
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
}},
|
||||
|
@ -166,7 +168,7 @@ func TestIssueImportTx(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: utxoID,
|
||||
Asset: ava.Asset{ID: avaID},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
|
@ -265,9 +267,10 @@ func TestForceAcceptImportTx(t *testing.T) {
|
|||
genesisBytes := BuildGenesisTest(t)
|
||||
|
||||
issuer := make(chan common.Message, 1)
|
||||
baseDB := memdb.New()
|
||||
|
||||
sm := &atomic.SharedMemory{}
|
||||
sm.Initialize(logging.NoLog{}, memdb.New())
|
||||
sm.Initialize(logging.NoLog{}, prefixdb.New([]byte{0}, baseDB))
|
||||
|
||||
ctx := snow.DefaultContextTest()
|
||||
ctx.NetworkID = networkID
|
||||
|
@ -285,10 +288,10 @@ func TestForceAcceptImportTx(t *testing.T) {
|
|||
|
||||
err := vm.Initialize(
|
||||
ctx,
|
||||
memdb.New(),
|
||||
prefixdb.New([]byte{1}, baseDB),
|
||||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{&common.Fx{
|
||||
[]*common.Fx{{
|
||||
ID: ids.Empty,
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
}},
|
||||
|
@ -326,7 +329,7 @@ func TestForceAcceptImportTx(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: utxoID,
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
)
|
||||
|
||||
|
@ -43,11 +43,11 @@ func TestOperationVerifyUTXOIDsNotSorted(t *testing.T) {
|
|||
op := &Operation{
|
||||
Asset: ava.Asset{ID: ids.Empty},
|
||||
UTXOIDs: []*ava.UTXOID{
|
||||
&ava.UTXOID{
|
||||
{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 1,
|
||||
},
|
||||
&ava.UTXOID{
|
||||
{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
},
|
||||
|
@ -64,7 +64,7 @@ func TestOperationVerify(t *testing.T) {
|
|||
op := &Operation{
|
||||
Asset: ava.Asset{ID: ids.Empty},
|
||||
UTXOIDs: []*ava.UTXOID{
|
||||
&ava.UTXOID{
|
||||
{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 1,
|
||||
},
|
||||
|
@ -81,20 +81,20 @@ func TestOperationSorting(t *testing.T) {
|
|||
c.RegisterType(&testOperable{})
|
||||
|
||||
ops := []*Operation{
|
||||
&Operation{
|
||||
{
|
||||
Asset: ava.Asset{ID: ids.Empty},
|
||||
UTXOIDs: []*ava.UTXOID{
|
||||
&ava.UTXOID{
|
||||
{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 1,
|
||||
},
|
||||
},
|
||||
Op: &testOperable{},
|
||||
},
|
||||
&Operation{
|
||||
{
|
||||
Asset: ava.Asset{ID: ids.Empty},
|
||||
UTXOIDs: []*ava.UTXOID{
|
||||
&ava.UTXOID{
|
||||
{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
},
|
||||
|
@ -112,7 +112,7 @@ func TestOperationSorting(t *testing.T) {
|
|||
ops = append(ops, &Operation{
|
||||
Asset: ava.Asset{ID: ids.Empty},
|
||||
UTXOIDs: []*ava.UTXOID{
|
||||
&ava.UTXOID{
|
||||
{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 1,
|
||||
},
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
)
|
||||
|
||||
|
@ -39,6 +39,17 @@ func (t *OperationTx) InputUTXOs() []*ava.UTXOID {
|
|||
return utxos
|
||||
}
|
||||
|
||||
// ConsumedAssetIDs returns the IDs of the assets this transaction consumes
|
||||
func (t *OperationTx) ConsumedAssetIDs() ids.Set {
|
||||
assets := t.BaseTx.AssetIDs()
|
||||
for _, op := range t.Ops {
|
||||
if len(op.UTXOIDs) > 0 {
|
||||
assets.Add(op.AssetID())
|
||||
}
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
// AssetIDs returns the IDs of the assets this transaction depends on
|
||||
func (t *OperationTx) AssetIDs() ids.Set {
|
||||
assets := t.BaseTx.AssetIDs()
|
||||
|
|
|
@ -38,7 +38,7 @@ func TestPrefixedSetsAndGets(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
|
@ -160,9 +160,7 @@ func TestPrefixedFundingAddresses(t *testing.T) {
|
|||
},
|
||||
Asset: ava.Asset{ID: ids.Empty},
|
||||
Out: &ava.TestAddressable{
|
||||
Addrs: [][]byte{
|
||||
[]byte{0},
|
||||
},
|
||||
Addrs: [][]byte{{0}},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -792,7 +792,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
|
|||
|
||||
ava.SortTransferableInputsWithSigners(ins, keys)
|
||||
|
||||
outs := []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
outs := []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: uint64(args.Amount),
|
||||
|
@ -946,7 +946,7 @@ func (service *Service) CreateMintTx(r *http.Request, args *CreateMintTxArgs, re
|
|||
BCID: service.vm.ctx.ChainID,
|
||||
},
|
||||
Ops: []*Operation{
|
||||
&Operation{
|
||||
{
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
UTXOIDs: []*ava.UTXOID{
|
||||
&utxo.UTXOID,
|
||||
|
@ -1197,7 +1197,7 @@ func (service *Service) ImportAVA(_ *http.Request, args *ImportAVAArgs, reply *I
|
|||
|
||||
ava.SortTransferableInputsWithSigners(ins, keys)
|
||||
|
||||
outs := []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
outs := []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: service.vm.ava},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: amount,
|
||||
|
@ -1352,7 +1352,7 @@ func (service *Service) ExportAVA(_ *http.Request, args *ExportAVAArgs, reply *E
|
|||
|
||||
ava.SortTransferableInputsWithSigners(ins, keys)
|
||||
|
||||
exportOuts := []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
exportOuts := []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: service.vm.ava},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: uint64(args.Amount),
|
||||
|
|
|
@ -300,7 +300,7 @@ func TestCreateFixedCapAsset(t *testing.T) {
|
|||
Name: "test asset",
|
||||
Symbol: "test",
|
||||
Denomination: 1,
|
||||
InitialHolders: []*Holder{&Holder{
|
||||
InitialHolders: []*Holder{{
|
||||
Amount: 123456789,
|
||||
Address: vm.Format(keys[0].PublicKey().Address().Bytes()),
|
||||
}},
|
||||
|
@ -326,7 +326,7 @@ func TestCreateVariableCapAsset(t *testing.T) {
|
|||
Name: "test asset",
|
||||
Symbol: "test",
|
||||
MinterSets: []Owners{
|
||||
Owners{
|
||||
{
|
||||
Threshold: 1,
|
||||
Minters: []string{
|
||||
vm.Format(keys[0].PublicKey().Address().Bytes()),
|
||||
|
@ -367,7 +367,7 @@ func TestImportAvmKey(t *testing.T) {
|
|||
factory := crypto.FactorySECP256K1R{}
|
||||
skIntf, err := factory.NewPrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("problem generating private key: %w", err)
|
||||
t.Fatalf("problem generating private key: %s", err)
|
||||
}
|
||||
sk := skIntf.(*crypto.PrivateKeySECP256K1R)
|
||||
|
||||
|
@ -406,7 +406,7 @@ func TestImportAvmKeyNoDuplicates(t *testing.T) {
|
|||
factory := crypto.FactorySECP256K1R{}
|
||||
skIntf, err := factory.NewPrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("problem generating private key: %w", err)
|
||||
t.Fatalf("problem generating private key: %s", err)
|
||||
}
|
||||
sk := skIntf.(*crypto.PrivateKeySECP256K1R)
|
||||
|
||||
|
|
|
@ -288,7 +288,7 @@ func TestStateTXs(t *testing.T) {
|
|||
tx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
|
|
|
@ -11,12 +11,12 @@ func TestBuildGenesis(t *testing.T) {
|
|||
ss := StaticService{}
|
||||
|
||||
args := BuildGenesisArgs{GenesisData: map[string]AssetDefinition{
|
||||
"asset1": AssetDefinition{
|
||||
"asset1": {
|
||||
Name: "myFixedCapAsset",
|
||||
Symbol: "MFCA",
|
||||
Denomination: 8,
|
||||
InitialState: map[string][]interface{}{
|
||||
"fixedCap": []interface{}{
|
||||
"fixedCap": {
|
||||
Holder{
|
||||
Amount: 100000,
|
||||
Address: "A9bTQjfYGBFK3JPRJqF2eh3JYL7cHocvy",
|
||||
|
@ -36,11 +36,11 @@ func TestBuildGenesis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"asset2": AssetDefinition{
|
||||
"asset2": {
|
||||
Name: "myVarCapAsset",
|
||||
Symbol: "MVCA",
|
||||
InitialState: map[string][]interface{}{
|
||||
"variableCap": []interface{}{
|
||||
"variableCap": {
|
||||
Owners{
|
||||
Threshold: 1,
|
||||
Minters: []string{
|
||||
|
@ -58,10 +58,10 @@ func TestBuildGenesis(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"asset3": AssetDefinition{
|
||||
"asset3": {
|
||||
Name: "myOtherVarCapAsset",
|
||||
InitialState: map[string][]interface{}{
|
||||
"variableCap": []interface{}{
|
||||
"variableCap": {
|
||||
Owners{
|
||||
Threshold: 1,
|
||||
Minters: []string{
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
"github.com/ava-labs/gecko/database"
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
)
|
||||
|
||||
|
@ -24,7 +24,9 @@ type UnsignedTx interface {
|
|||
ID() ids.ID
|
||||
Bytes() []byte
|
||||
|
||||
ConsumedAssetIDs() ids.Set
|
||||
AssetIDs() ids.Set
|
||||
|
||||
NumCredentials() int
|
||||
InputUTXOs() []*ava.UTXOID
|
||||
UTXOs() []*ava.UTXO
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/utils/units"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
"github.com/ava-labs/gecko/vms/secp256k1fx"
|
||||
)
|
||||
|
@ -56,7 +56,7 @@ func TestTxInvalidCredential(t *testing.T) {
|
|||
UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
|
@ -95,7 +95,7 @@ func TestTxInvalidUnsignedTx(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{
|
||||
&ava.TransferableInput{
|
||||
{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
|
@ -110,7 +110,7 @@ func TestTxInvalidUnsignedTx(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
&ava.TransferableInput{
|
||||
{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
|
@ -153,7 +153,7 @@ func TestTxInvalidNumberOfCredentials(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{
|
||||
&ava.TransferableInput{
|
||||
{
|
||||
UTXOID: ava.UTXOID{TxID: ids.Empty, OutputIndex: 0},
|
||||
Asset: ava.Asset{ID: asset},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
|
@ -165,7 +165,7 @@ func TestTxInvalidNumberOfCredentials(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
&ava.TransferableInput{
|
||||
{
|
||||
UTXOID: ava.UTXOID{TxID: ids.Empty, OutputIndex: 1},
|
||||
Asset: ava.Asset{ID: asset},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
|
|
|
@ -85,7 +85,11 @@ func (tx *UniqueTx) refresh() {
|
|||
|
||||
// Evict is called when this UniqueTx will no longer be returned from a cache
|
||||
// lookup
|
||||
func (tx *UniqueTx) Evict() { tx.unique = false } // Lock is already held here
|
||||
func (tx *UniqueTx) Evict() {
|
||||
// Lock is already held here
|
||||
tx.unique = false
|
||||
tx.deps = nil
|
||||
}
|
||||
|
||||
func (tx *UniqueTx) setStatus(status choices.Status) error {
|
||||
tx.refresh()
|
||||
|
@ -206,23 +210,26 @@ func (tx *UniqueTx) Dependencies() []snowstorm.Tx {
|
|||
continue
|
||||
}
|
||||
txID, _ := in.InputSource()
|
||||
if !txIDs.Contains(txID) {
|
||||
if txIDs.Contains(txID) {
|
||||
continue
|
||||
}
|
||||
txIDs.Add(txID)
|
||||
tx.deps = append(tx.deps, &UniqueTx{
|
||||
vm: tx.vm,
|
||||
txID: txID,
|
||||
})
|
||||
}
|
||||
}
|
||||
consumedIDs := tx.Tx.ConsumedAssetIDs()
|
||||
for _, assetID := range tx.Tx.AssetIDs().List() {
|
||||
if !txIDs.Contains(assetID) {
|
||||
if consumedIDs.Contains(assetID) || txIDs.Contains(assetID) {
|
||||
continue
|
||||
}
|
||||
txIDs.Add(assetID)
|
||||
tx.deps = append(tx.deps, &UniqueTx{
|
||||
vm: tx.vm,
|
||||
txID: assetID,
|
||||
})
|
||||
}
|
||||
}
|
||||
return tx.deps
|
||||
}
|
||||
|
||||
|
|
|
@ -20,12 +20,12 @@ import (
|
|||
"github.com/ava-labs/gecko/snow/choices"
|
||||
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
|
||||
"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/logging"
|
||||
"github.com/ava-labs/gecko/utils/timer"
|
||||
"github.com/ava-labs/gecko/utils/wrappers"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/utils/codec"
|
||||
|
||||
cjson "github.com/ava-labs/gecko/utils/json"
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ const (
|
|||
batchSize = 30
|
||||
stateCacheSize = 10000
|
||||
idCacheSize = 10000
|
||||
txCacheSize = 100000
|
||||
txCacheSize = 10000
|
||||
addressSep = "-"
|
||||
)
|
||||
|
||||
|
@ -248,8 +248,8 @@ func (vm *VM) CreateHandlers() map[string]*common.HTTPHandler {
|
|||
rpcServer.RegisterService(&Service{vm: vm}, "avm") // name this service "avm"
|
||||
|
||||
return map[string]*common.HTTPHandler{
|
||||
"": &common.HTTPHandler{Handler: rpcServer},
|
||||
"/pubsub": &common.HTTPHandler{LockOptions: common.NoLock, Handler: vm.pubsub},
|
||||
"": {Handler: rpcServer},
|
||||
"/pubsub": {LockOptions: common.NoLock, Handler: vm.pubsub},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ func (vm *VM) CreateStaticHandlers() map[string]*common.HTTPHandler {
|
|||
newServer.RegisterCodec(codec, "application/json;charset=UTF-8")
|
||||
newServer.RegisterService(&StaticService{}, "avm") // name this service "avm"
|
||||
return map[string]*common.HTTPHandler{
|
||||
"": &common.HTTPHandler{LockOptions: common.WriteLock, Handler: newServer},
|
||||
"": {LockOptions: common.WriteLock, Handler: newServer},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,10 +492,10 @@ func (vm *VM) parseTx(b []byte) (*UniqueTx, error) {
|
|||
if err := vm.state.SetTx(tx.ID(), tx.Tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tx.setStatus(choices.Processing); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tx, vm.db.Commit()
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
|
|
|
@ -84,11 +84,11 @@ func BuildGenesisTest(t *testing.T) []byte {
|
|||
addr2 := keys[2].PublicKey().Address()
|
||||
|
||||
args := BuildGenesisArgs{GenesisData: map[string]AssetDefinition{
|
||||
"asset1": AssetDefinition{
|
||||
"asset1": {
|
||||
Name: "myFixedCapAsset",
|
||||
Symbol: "MFCA",
|
||||
InitialState: map[string][]interface{}{
|
||||
"fixedCap": []interface{}{
|
||||
"fixedCap": {
|
||||
Holder{
|
||||
Amount: 100000,
|
||||
Address: addr0.String(),
|
||||
|
@ -108,11 +108,11 @@ func BuildGenesisTest(t *testing.T) []byte {
|
|||
},
|
||||
},
|
||||
},
|
||||
"asset2": AssetDefinition{
|
||||
"asset2": {
|
||||
Name: "myVarCapAsset",
|
||||
Symbol: "MVCA",
|
||||
InitialState: map[string][]interface{}{
|
||||
"variableCap": []interface{}{
|
||||
"variableCap": {
|
||||
Owners{
|
||||
Threshold: 1,
|
||||
Minters: []string{
|
||||
|
@ -131,10 +131,10 @@ func BuildGenesisTest(t *testing.T) []byte {
|
|||
},
|
||||
},
|
||||
},
|
||||
"asset3": AssetDefinition{
|
||||
"asset3": {
|
||||
Name: "myOtherVarCapAsset",
|
||||
InitialState: map[string][]interface{}{
|
||||
"variableCap": []interface{}{
|
||||
"variableCap": {
|
||||
Owners{
|
||||
Threshold: 1,
|
||||
Minters: []string{
|
||||
|
@ -168,7 +168,7 @@ func GenesisVM(t *testing.T) ([]byte, chan common.Message, *VM) {
|
|||
memdb.New(),
|
||||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{&common.Fx{
|
||||
[]*common.Fx{{
|
||||
ID: ids.Empty,
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
}},
|
||||
|
@ -195,7 +195,7 @@ func NewTx(t *testing.T, genesisBytes []byte, vm *VM) *Tx {
|
|||
newTx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -357,7 +357,7 @@ func TestTxSerialization(t *testing.T) {
|
|||
Symbol: "symb",
|
||||
Denomination: 0,
|
||||
States: []*InitialState{
|
||||
&InitialState{
|
||||
{
|
||||
FxID: 0,
|
||||
Outs: []verify.Verifiable{
|
||||
&secp256k1fx.MintOutput{
|
||||
|
@ -456,7 +456,7 @@ func TestFxInitializationFailure(t *testing.T) {
|
|||
/*db=*/ memdb.New(),
|
||||
/*genesisState=*/ genesisBytes,
|
||||
/*engineMessenger=*/ make(chan common.Message, 1),
|
||||
/*fxs=*/ []*common.Fx{&common.Fx{
|
||||
/*fxs=*/ []*common.Fx{{
|
||||
ID: ids.Empty,
|
||||
Fx: &testFx{initialize: errUnknownFx},
|
||||
}},
|
||||
|
@ -537,7 +537,7 @@ func TestIssueDependentTx(t *testing.T) {
|
|||
firstTx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
|
@ -552,7 +552,7 @@ func TestIssueDependentTx(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}},
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Outs: []*ava.TransferableOutput{{
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 50000,
|
||||
|
@ -596,7 +596,7 @@ func TestIssueDependentTx(t *testing.T) {
|
|||
secondTx := &Tx{UnsignedTx: &BaseTx{
|
||||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
Ins: []*ava.TransferableInput{{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: firstTx.ID(),
|
||||
OutputIndex: 0,
|
||||
|
@ -671,11 +671,11 @@ func TestIssueNFT(t *testing.T) {
|
|||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.Empty.Prefix(0),
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
},
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.Empty.Prefix(1),
|
||||
Fx: &nftfx.Fx{},
|
||||
},
|
||||
|
@ -704,7 +704,7 @@ func TestIssueNFT(t *testing.T) {
|
|||
Name: "Team Rocket",
|
||||
Symbol: "TR",
|
||||
Denomination: 0,
|
||||
States: []*InitialState{&InitialState{
|
||||
States: []*InitialState{{
|
||||
FxID: 1,
|
||||
Outs: []verify.Verifiable{
|
||||
&nftfx.MintOutput{
|
||||
|
@ -740,9 +740,9 @@ func TestIssueNFT(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
},
|
||||
Ops: []*Operation{&Operation{
|
||||
Ops: []*Operation{{
|
||||
Asset: ava.Asset{ID: createAssetTx.ID()},
|
||||
UTXOIDs: []*ava.UTXOID{&ava.UTXOID{
|
||||
UTXOIDs: []*ava.UTXOID{{
|
||||
TxID: createAssetTx.ID(),
|
||||
OutputIndex: 0,
|
||||
}},
|
||||
|
@ -752,9 +752,7 @@ func TestIssueNFT(t *testing.T) {
|
|||
},
|
||||
GroupID: 1,
|
||||
Payload: []byte{'h', 'e', 'l', 'l', 'o'},
|
||||
Outputs: []*secp256k1fx.OutputOwners{
|
||||
&secp256k1fx.OutputOwners{},
|
||||
},
|
||||
Outputs: []*secp256k1fx.OutputOwners{{}},
|
||||
},
|
||||
}},
|
||||
}}
|
||||
|
@ -793,9 +791,9 @@ func TestIssueNFT(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
},
|
||||
Ops: []*Operation{&Operation{
|
||||
Ops: []*Operation{{
|
||||
Asset: ava.Asset{ID: createAssetTx.ID()},
|
||||
UTXOIDs: []*ava.UTXOID{&ava.UTXOID{
|
||||
UTXOIDs: []*ava.UTXOID{{
|
||||
TxID: mintNFTTx.ID(),
|
||||
OutputIndex: 0,
|
||||
}},
|
||||
|
@ -840,15 +838,15 @@ func TestIssueProperty(t *testing.T) {
|
|||
genesisBytes,
|
||||
issuer,
|
||||
[]*common.Fx{
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.Empty.Prefix(0),
|
||||
Fx: &secp256k1fx.Fx{},
|
||||
},
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.Empty.Prefix(1),
|
||||
Fx: &nftfx.Fx{},
|
||||
},
|
||||
&common.Fx{
|
||||
{
|
||||
ID: ids.Empty.Prefix(2),
|
||||
Fx: &propertyfx.Fx{},
|
||||
},
|
||||
|
@ -877,7 +875,7 @@ func TestIssueProperty(t *testing.T) {
|
|||
Name: "Team Rocket",
|
||||
Symbol: "TR",
|
||||
Denomination: 0,
|
||||
States: []*InitialState{&InitialState{
|
||||
States: []*InitialState{{
|
||||
FxID: 2,
|
||||
Outs: []verify.Verifiable{
|
||||
&propertyfx.MintOutput{
|
||||
|
@ -905,9 +903,9 @@ func TestIssueProperty(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
},
|
||||
Ops: []*Operation{&Operation{
|
||||
Ops: []*Operation{{
|
||||
Asset: ava.Asset{ID: createAssetTx.ID()},
|
||||
UTXOIDs: []*ava.UTXOID{&ava.UTXOID{
|
||||
UTXOIDs: []*ava.UTXOID{{
|
||||
TxID: createAssetTx.ID(),
|
||||
OutputIndex: 0,
|
||||
}},
|
||||
|
@ -960,9 +958,9 @@ func TestIssueProperty(t *testing.T) {
|
|||
NetID: networkID,
|
||||
BCID: chainID,
|
||||
},
|
||||
Ops: []*Operation{&Operation{
|
||||
Ops: []*Operation{{
|
||||
Asset: ava.Asset{ID: createAssetTx.ID()},
|
||||
UTXOIDs: []*ava.UTXOID{&ava.UTXOID{
|
||||
UTXOIDs: []*ava.UTXOID{{
|
||||
TxID: mintPropertyTx.ID(),
|
||||
OutputIndex: 1,
|
||||
}},
|
||||
|
|
|
@ -128,7 +128,7 @@ func (tx *addDefaultSubnetDelegatorTx) SemanticVerify(db database.Database) (*ve
|
|||
// The account if this block's proposal is committed and the validator is
|
||||
// added to the pending validator set. (Increase the account's nonce;
|
||||
// decrease its balance.)
|
||||
newAccount, err := account.Remove(tx.Wght, tx.Nonce) // Remove also removes the fee
|
||||
newAccount, err := account.Remove(0, tx.Nonce) // Remove also removes the fee
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, permError{err}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue