diff --git a/main/params.go b/main/params.go index f5155a0..68b63c4 100644 --- a/main/params.go +++ b/main/params.go @@ -26,6 +26,10 @@ import ( "github.com/ava-labs/gecko/utils/wrappers" ) +const ( + dbVersion = "v0.1.0" +) + // Results of parsing the CLI var ( Config = node.Config{} @@ -143,7 +147,7 @@ func init() { // DB: if *db && err == nil { // TODO: Add better params here - dbPath := path.Join(*dbDir, genesis.NetworkName(Config.NetworkID)) + dbPath := path.Join(*dbDir, genesis.NetworkName(Config.NetworkID), dbVersion) db, err := leveldb.New(dbPath, 0, 0, 0) Config.DB = db errs.Add(err) diff --git a/networking/handshake_handlers.go b/networking/handshake_handlers.go index 18476f9..9e0c9cf 100644 --- a/networking/handshake_handlers.go +++ b/networking/handshake_handlers.go @@ -19,6 +19,8 @@ import ( "errors" "fmt" "math" + "strconv" + "strings" "sync" "time" "unsafe" @@ -61,9 +63,23 @@ Attempt reconnections node isn't connected to after awhile delete the connection. */ +// Version this avalanche instance is executing. +var ( + VersionPrefix = "avalanche/" + VersionSeparator = "." + MajorVersion = 0 + MinorVersion = 1 + PatchVersion = 0 + ClientVersion = fmt.Sprintf("%s%d%s%d%s%d", + VersionPrefix, + MajorVersion, + VersionSeparator, + MinorVersion, + VersionSeparator, + PatchVersion) +) + const ( - // CurrentVersion this avalanche instance is executing. - CurrentVersion = "avalanche/0.0.1" // MaxClockDifference allowed between connected nodes. MaxClockDifference = time.Minute // PeerListGossipSpacing is the amount of time to wait between pushing this @@ -356,7 +372,7 @@ func (nm *Handshake) SendGetVersion(peer salticidae.PeerID) { // SendVersion to the requested peer func (nm *Handshake) SendVersion(peer salticidae.PeerID) error { build := Builder{} - v, err := build.Version(nm.networkID, nm.clock.Unix(), toIPDesc(nm.myAddr), CurrentVersion) + v, err := build.Version(nm.networkID, nm.clock.Unix(), toIPDesc(nm.myAddr), ClientVersion) if err != nil { return fmt.Errorf("packing Version failed due to %s", err) } @@ -518,6 +534,59 @@ func (nm *Handshake) disconnectedFromPeer(peer salticidae.PeerID) { } } +// checkCompatibility Check to make sure that the peer and I speak the same language. +func (nm *Handshake) checkCompatibility(peerVersion string) bool { + if !strings.HasPrefix(peerVersion, VersionPrefix) { + nm.log.Warn("Peer attempted to connect with an invalid version prefix") + return false + } + peerVersion = peerVersion[len(VersionPrefix):] + splitPeerVersion := strings.SplitN(peerVersion, VersionSeparator, 3) + if len(splitPeerVersion) != 3 { + nm.log.Warn("Peer attempted to connect with an invalid number of subversions") + return false + } + + major, err := strconv.Atoi(splitPeerVersion[0]) + if err != nil { + nm.log.Warn("Peer attempted to connect with an invalid major version") + return false + } + minor, err := strconv.Atoi(splitPeerVersion[1]) + if err != nil { + nm.log.Warn("Peer attempted to connect with an invalid minor version") + return false + } + patch, err := strconv.Atoi(splitPeerVersion[2]) + if err != nil { + nm.log.Warn("Peer attempted to connect with an invalid patch version") + return false + } + + switch { + case major < MajorVersion: + // peers major version is too low + return false + case major > MajorVersion: + nm.log.Warn("Peer attempted to connect with a higher major version, this client may need to be updated") + return false + } + + switch { + case minor < MinorVersion: + // peers minor version is too low + return false + case minor > MinorVersion: + nm.log.Warn("Peer attempted to connect with a higher minor version, this client may need to be updated") + return false + } + + if patch > PatchVersion { + nm.log.Warn("Peer is connecting with a higher patch version, this client may need to be updated") + } + return true +} + // peerHandler notifies a change to the set of connected peers // connected is true if a new peer is connected // connected is false if a formerly connected peer has disconnected @@ -645,8 +714,8 @@ func version(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.P return } - if peerVersion := pMsg.Get(VersionStr).(string); !checkCompatibility(CurrentVersion, peerVersion) { - HandshakeNet.log.Warn("Bad version") + if peerVersion := pMsg.Get(VersionStr).(string); !HandshakeNet.checkCompatibility(peerVersion) { + HandshakeNet.log.Debug("Dropping connection due to an incompatible version from peer") HandshakeNet.net.DelPeer(peer) return @@ -741,12 +810,6 @@ func getCert(cert salticidae.X509) ids.ShortID { return certID } -// checkCompatibility Check to make sure that the peer and I speak the same language. -func checkCompatibility(myVersion string, peerVersion string) bool { - // At the moment, we are all compatible. - return true -} - func toShortID(ip utils.IPDesc) ids.ShortID { return ids.NewShortID(hashing.ComputeHash160Array([]byte(ip.String()))) } diff --git a/snow/consensus/snowball/parameters.go b/snow/consensus/snowball/parameters.go index 7d77405..230f2d7 100644 --- a/snow/consensus/snowball/parameters.go +++ b/snow/consensus/snowball/parameters.go @@ -10,20 +10,21 @@ import ( ) const ( - errMsg = "__________ .___\n" + - "\\______ \\____________ __| _/__.__.\n" + - " | | _/\\_ __ \\__ \\ / __ < | |\n" + - " | | \\ | | \\// __ \\_/ /_/ |\\___ |\n" + - " |______ / |__| (____ /\\____ |/ ____|\n" + - " \\/ \\/ \\/\\/\n" + + errMsg = "" + + `__________ .___` + "\n" + + `\______ \____________ __| _/__.__.` + "\n" + + ` | | _/\_ __ \__ \ / __ < | |` + "\n" + + ` | | \ | | \// __ \_/ /_/ |\___ |` + "\n" + + ` |______ / |__| (____ /\____ |/ ____|` + "\n" + + ` \/ \/ \/\/` + "\n" + "\n" + - "🏆 🏆 🏆 🏆 🏆 🏆\n" + - " ________ ________ ________________\n" + - " / _____/ \\_____ \\ / _ \\__ ___/\n" + - "/ \\ ___ / | \\ / /_\\ \\| |\n" + - "\\ \\_\\ \\/ | \\/ | \\ |\n" + - " \\______ /\\_______ /\\____|__ /____|\n" + - " \\/ \\/ \\/\n" + `🏆 🏆 🏆 🏆 🏆 🏆` + "\n" + + ` ________ ________ ________________` + "\n" + + ` / _____/ \_____ \ / _ \__ ___/` + "\n" + + `/ \ ___ / | \ / /_\ \| |` + "\n" + + `\ \_\ \/ | \/ | \ |` + "\n" + + ` \______ /\_______ /\____|__ /____|` + "\n" + + ` \/ \/ \/` + "\n" ) // Parameters required for snowball consensus diff --git a/snow/engine/avalanche/state/serializer.go b/snow/engine/avalanche/state/serializer.go index f076548..0034ad1 100644 --- a/snow/engine/avalanche/state/serializer.go +++ b/snow/engine/avalanche/state/serializer.go @@ -82,6 +82,10 @@ func (s *Serializer) ParseVertex(b []byte) (avacon.Vertex, error) { // BuildVertex implements the avalanche.State interface func (s *Serializer) BuildVertex(parentSet ids.Set, txs []snowstorm.Tx) (avacon.Vertex, error) { + if len(txs) == 0 { + return nil, errNoTxs + } + parentIDs := parentSet.List() ids.SortIDs(parentIDs) sortTxs(txs) diff --git a/snow/engine/avalanche/state/vertex.go b/snow/engine/avalanche/state/vertex.go index 928c11c..327a865 100644 --- a/snow/engine/avalanche/state/vertex.go +++ b/snow/engine/avalanche/state/vertex.go @@ -24,6 +24,7 @@ var ( errExtraSpace = errors.New("trailing buffer space") errInvalidParents = errors.New("vertex contains non-sorted or duplicated parentIDs") errInvalidTxs = errors.New("vertex contains non-sorted or duplicated transactions") + errNoTxs = errors.New("vertex contains no transactions") ) type vertex struct { @@ -45,6 +46,8 @@ func (vtx *vertex) Verify() error { switch { case !ids.IsSortedAndUniqueIDs(vtx.parentIDs): return errInvalidParents + case len(vtx.txs) == 0: + return errNoTxs case !isSortedAndUniqueTxs(vtx.txs): return errInvalidTxs default: @@ -55,7 +58,7 @@ func (vtx *vertex) Verify() error { /* * Vertex: * Codec | 04 Bytes - * Chain | 32 Bytes + * Chain | 32 Bytes * Height | 08 Bytes * NumParents | 04 Bytes * Repeated (NumParents): diff --git a/snow/engine/avalanche/transitive.go b/snow/engine/avalanche/transitive.go index 4de2aa5..fc55250 100644 --- a/snow/engine/avalanche/transitive.go +++ b/snow/engine/avalanche/transitive.go @@ -316,8 +316,37 @@ func (t *Transitive) batch(txs []snowstorm.Tx, force, empty bool) { } } - if len(batch) > 0 || (empty && !issued) { + if len(batch) > 0 { t.issueBatch(batch) + } else if empty && !issued { + t.issueRepoll() + } +} + +func (t *Transitive) issueRepoll() { + preferredIDs := t.Consensus.Preferences().List() + numPreferredIDs := len(preferredIDs) + if numPreferredIDs == 0 { + t.Config.Context.Log.Error("Re-query attempt was dropped due to no pending vertices") + return + } + + sampler := random.Uniform{N: len(preferredIDs)} + vtxID := preferredIDs[sampler.Sample()] + + p := t.Consensus.Parameters() + vdrs := t.Config.Validators.Sample(p.K) // Validators to sample + + vdrSet := ids.ShortSet{} // Validators to sample repr. as a set + for _, vdr := range vdrs { + vdrSet.Add(vdr.ID()) + } + + t.RequestID++ + if numVdrs := len(vdrs); numVdrs == p.K && t.polls.Add(t.RequestID, vdrSet.Len()) { + t.Config.Sender.PullQuery(vdrSet, t.RequestID, vtxID) + } else if numVdrs < p.K { + t.Config.Context.Log.Error("Re-query for %s was dropped due to an insufficient number of validators", vtxID) } } diff --git a/snow/engine/avalanche/transitive_test.go b/snow/engine/avalanche/transitive_test.go index defd2df..604afaf 100644 --- a/snow/engine/avalanche/transitive_test.go +++ b/snow/engine/avalanche/transitive_test.go @@ -698,23 +698,12 @@ func TestEngineScheduleRepoll(t *testing.T) { sender.PushQueryF = nil - st.buildVertex = func(_ ids.Set, txs []snowstorm.Tx) (avalanche.Vertex, error) { - consumers := []snowstorm.Tx{} - for _, tx := range txs { - consumers = append(consumers, tx) - } - return &Vtx{ - parents: []avalanche.Vertex{gVtx, mVtx}, - id: GenerateID(), - txs: consumers, - status: choices.Processing, - bytes: []byte{1}, - }, nil - } - repolled := new(bool) - sender.PushQueryF = func(_ ids.ShortSet, _ uint32, _ ids.ID, _ []byte) { + sender.PullQueryF = func(_ ids.ShortSet, _ uint32, vtxID ids.ID) { *repolled = true + if !vtxID.Equals(vtx.ID()) { + t.Fatalf("Wrong vertex queried") + } } te.QueryFailed(vdr.ID(), *requestID) @@ -979,31 +968,14 @@ func TestEngineIssueRepoll(t *testing.T) { te.Initialize(config) te.finishBootstrapping() - newVtxID := new(ids.ID) - - st.buildVertex = func(s ids.Set, txs []snowstorm.Tx) (avalanche.Vertex, error) { - if len(txs) != 0 { - t.Fatalf("Wrong vertex issued") - } - if s.Len() != 2 || !s.Contains(gVtx.ID()) || !s.Contains(mVtx.ID()) { - t.Fatalf("Wrong vertex issued") - } - - vtx := &Vtx{ - parents: []avalanche.Vertex{gVtx, mVtx}, - id: GenerateID(), - status: choices.Processing, - bytes: []byte{1}, - } - *newVtxID = vtx.ID() - return vtx, nil - } - - sender.PushQueryF = func(vdrs ids.ShortSet, _ uint32, vtxID ids.ID, vtx []byte) { + sender.PullQueryF = func(vdrs ids.ShortSet, _ uint32, vtxID ids.ID) { vdrSet := ids.ShortSet{} vdrSet.Add(vdr.ID()) - if !vdrs.Equals(vdrSet) || !vtxID.Equals(*newVtxID) { - t.Fatalf("Wrong query message") + if !vdrs.Equals(vdrSet) { + t.Fatalf("Wrong query recipients") + } + if !vtxID.Equals(gVtx.ID()) && !vtxID.Equals(mVtx.ID()) { + t.Fatalf("Unknown re-query") } } diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go index 6efb8d6..051c2c0 100644 --- a/vms/platformvm/service_test.go +++ b/vms/platformvm/service_test.go @@ -9,7 +9,7 @@ import ( ) func TestAddDefaultSubnetValidator(t *testing.T) { - expectedJSONString := `{"startTime":"0","endtime":"0","id":null,"destination":null,"delegationFeeRate":"0","payerNonce":"0"}` + expectedJSONString := `{"startTime":"0","endTime":"0","id":null,"destination":null,"delegationFeeRate":"0","payerNonce":"0"}` args := AddDefaultSubnetValidatorArgs{} bytes, err := json.Marshal(&args) if err != nil { diff --git a/vms/platformvm/static_service.go b/vms/platformvm/static_service.go index 80b66d2..86af005 100644 --- a/vms/platformvm/static_service.go +++ b/vms/platformvm/static_service.go @@ -44,7 +44,7 @@ type APIAccount struct { // is sent when this staker is done staking. type APIValidator struct { StartTime json.Uint64 `json:"startTime"` - EndTime json.Uint64 `json:"endtime"` + EndTime json.Uint64 `json:"endTime"` Weight *json.Uint64 `json:"weight,omitempty"` StakeAmount *json.Uint64 `json:"stakeAmount,omitempty"` ID ids.ShortID `json:"id"`