mirror of https://github.com/poanetwork/gecko.git
commit
60668c3a91
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -ev
|
||||||
|
|
||||||
|
docker run --rm -v "$PWD:$GECKO_HOME" $DOCKERHUB_REPO:$COMMIT bash "$GECKO_HOME/scripts/build_test.sh"
|
||||||
|
docker run --rm -v "$PWD:$GECKO_HOME" $DOCKERHUB_REPO:$COMMIT bash "$GECKO_HOME/scripts/build.sh"
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -ev
|
||||||
|
|
||||||
|
go get -d -t -v github.com/ava-labs/gecko/...
|
||||||
|
|
||||||
|
cd $GOPATH/src/github.com/ava-labs/gecko
|
||||||
|
./scripts/build_test.sh
|
||||||
|
./scripts/build.sh
|
27
.travis.yml
27
.travis.yml
|
@ -1,5 +1,13 @@
|
||||||
dist: bionic
|
|
||||||
language: go
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.13.x
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- os: linux
|
||||||
|
dist: bionic
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode11.4
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
env:
|
env:
|
||||||
|
@ -10,7 +18,16 @@ env:
|
||||||
- DOCKERHUB_REPO=avaplatform/gecko
|
- DOCKERHUB_REPO=avaplatform/gecko
|
||||||
- secure: "L/A9+re0NEKP6EV6H9RcTGiDhX3WMvsiWrkRKDYKqnviqbjY30RK6EM4vvjrM4Lrw2QwsO3YKgnku3+zioE/TxEZFkpkbjNUXru0nYBrWAg1TKVsDXnYaIZkHUejfryST3E8N7F4Hx6zCtGEO0sEdUeKuT+MNUIuHezHooTgGzDjMogm70EWMFjQHc7VucTJu7dWU1RBPjovWQ0q9qflrtCpbrvXFIiihQQ1PQha1Q2C4wLakKuLbhhSafue90Mnyss0blaPHy/tyewcASJu4vsGTKRBn0DzttlkNTwuD6+nKrbmJY0ohunnkVFzYjrZAw1gyN+DCDb/lPbz4ZDItKPwrIUPEtL5xuUOrxUZPUh+0io3Q2d6rjaqkdGjd1KQXzbnW1mn0BxX3d3b2UpIqhBn9umYYjHBKnMuoRiTK33b7U9+LF3K84+tEvVDCPeHs/mw6Inp5jGRSravnM6yPQ6feGzogs4+3EMzZXxnkngKFKCsnd67Oe9xfV9amOU2aQAx4jaAwlPjEpBEkUa8YKx3lPznvmUk1QsNCUbLjdSl5JBaXojLJoiuPbj29hp4S5AXXgn+3Hvwk3ndcFCxi6/l1W9mjYSOtFqg3EAUdF4EgnA/ykQg9ZokkoKY0+qgOzG2bKOAYuCDWeGr7P1apToh00ccsQXL81nVPiq7uDw="
|
- secure: "L/A9+re0NEKP6EV6H9RcTGiDhX3WMvsiWrkRKDYKqnviqbjY30RK6EM4vvjrM4Lrw2QwsO3YKgnku3+zioE/TxEZFkpkbjNUXru0nYBrWAg1TKVsDXnYaIZkHUejfryST3E8N7F4Hx6zCtGEO0sEdUeKuT+MNUIuHezHooTgGzDjMogm70EWMFjQHc7VucTJu7dWU1RBPjovWQ0q9qflrtCpbrvXFIiihQQ1PQha1Q2C4wLakKuLbhhSafue90Mnyss0blaPHy/tyewcASJu4vsGTKRBn0DzttlkNTwuD6+nKrbmJY0ohunnkVFzYjrZAw1gyN+DCDb/lPbz4ZDItKPwrIUPEtL5xuUOrxUZPUh+0io3Q2d6rjaqkdGjd1KQXzbnW1mn0BxX3d3b2UpIqhBn9umYYjHBKnMuoRiTK33b7U9+LF3K84+tEvVDCPeHs/mw6Inp5jGRSravnM6yPQ6feGzogs4+3EMzZXxnkngKFKCsnd67Oe9xfV9amOU2aQAx4jaAwlPjEpBEkUa8YKx3lPznvmUk1QsNCUbLjdSl5JBaXojLJoiuPbj29hp4S5AXXgn+3Hvwk3ndcFCxi6/l1W9mjYSOtFqg3EAUdF4EgnA/ykQg9ZokkoKY0+qgOzG2bKOAYuCDWeGr7P1apToh00ccsQXL81nVPiq7uDw="
|
||||||
- secure: "zfTm7tJBYiPYrli76d4Ep6Lc2TJQ8Xv//+7OoqTA/aIf6YJDHe05f2GFTWAHG2iOIix/yjwHYwnhyIW66eWPb+Ujejnmh4eXlYZFufX9J5jUpDpbFu/+ybOLgE1Tmr0je0ycneSMe/NAaS74nWU1wnP34/cEE4sYL7TJyhwbeEtgz3cbSWwkpdvHFbXCjSOA196jdIYYUwsnqU9yycAG+2WUSk3DHHzzdtMrh/UOH2r1VFyp5US0zmbW90WkWX+o3TIlzZJgTUGQRNnWKq95Mrh1EQotxgL6CJ8NkfY4bVAGAhusPjdjscJsHxfY93WRMH64TzPYYp0zdibatH0ztyhnZPXVKqv+AIIVTEW+xWv5V18kTQAd1uBW103NFacbgXhIGWtbFcN9g1+ws29HROMclYs7ci6+72Qnq0eL55huqSyFx6+InhYwn+LfJmaBcGW4wx1umdp505M0obZ4ghlyn6b0pDYmqsu1XyBC3mjUTFbwlQmWE2Fize4L5o+DdH4ZDc9japF9ntxIMvO+b3nOicr7tplY2AGp61bB89o3dUAFlN5mDaEJotiAuFk5mo244rY1FjSzyGiKkA3M9TkTIbgcbN098hOJoMCYybH7yqiPwNnZiFvUuYjHuC5D1kIYBWuqqO0iVcbIZn0rV2jyzbVFlhFVk2clTZGhkrY="
|
- secure: "zfTm7tJBYiPYrli76d4Ep6Lc2TJQ8Xv//+7OoqTA/aIf6YJDHe05f2GFTWAHG2iOIix/yjwHYwnhyIW66eWPb+Ujejnmh4eXlYZFufX9J5jUpDpbFu/+ybOLgE1Tmr0je0ycneSMe/NAaS74nWU1wnP34/cEE4sYL7TJyhwbeEtgz3cbSWwkpdvHFbXCjSOA196jdIYYUwsnqU9yycAG+2WUSk3DHHzzdtMrh/UOH2r1VFyp5US0zmbW90WkWX+o3TIlzZJgTUGQRNnWKq95Mrh1EQotxgL6CJ8NkfY4bVAGAhusPjdjscJsHxfY93WRMH64TzPYYp0zdibatH0ztyhnZPXVKqv+AIIVTEW+xWv5V18kTQAd1uBW103NFacbgXhIGWtbFcN9g1+ws29HROMclYs7ci6+72Qnq0eL55huqSyFx6+InhYwn+LfJmaBcGW4wx1umdp505M0obZ4ghlyn6b0pDYmqsu1XyBC3mjUTFbwlQmWE2Fize4L5o+DdH4ZDc9japF9ntxIMvO+b3nOicr7tplY2AGp61bB89o3dUAFlN5mDaEJotiAuFk5mo244rY1FjSzyGiKkA3M9TkTIbgcbN098hOJoMCYybH7yqiPwNnZiFvUuYjHuC5D1kIYBWuqqO0iVcbIZn0rV2jyzbVFlhFVk2clTZGhkrY="
|
||||||
before_install: .ci/before_install.sh
|
before_install:
|
||||||
install: DOCKER_BUILDKIT=1 docker build --progress plain -t $DOCKERHUB_REPO:$COMMIT .
|
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then .ci/before_install_linux.sh; fi
|
||||||
script: docker run --rm -v "$PWD:$GECKO_HOME" $DOCKERHUB_REPO:$COMMIT bash "$GECKO_HOME/scripts/build_test.sh"
|
|
||||||
after_success: .ci/after_success.sh
|
install:
|
||||||
|
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then DOCKER_BUILDKIT=1 docker build --progress plain -t $DOCKERHUB_REPO:$COMMIT . ; fi
|
||||||
|
|
||||||
|
script:
|
||||||
|
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then .ci/runscript_osx.sh; fi
|
||||||
|
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then .ci/runscript_linux.sh; fi
|
||||||
|
|
||||||
|
#Need to push to docker hub only from one build
|
||||||
|
after_success:
|
||||||
|
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then .ci/after_success.sh; fi
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
FROM golang:1.13.4-buster
|
FROM golang:1.13.4-buster
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y libssl-dev libuv1-dev curl cmake
|
|
||||||
|
|
||||||
RUN mkdir -p /go/src/github.com/ava-labs
|
RUN mkdir -p /go/src/github.com/ava-labs
|
||||||
|
|
||||||
WORKDIR $GOPATH/src/github.com/ava-labs/
|
WORKDIR $GOPATH/src/github.com/ava-labs/
|
||||||
|
|
34
README.md
34
README.md
|
@ -1,4 +1,4 @@
|
||||||
# gecko
|
# Gecko
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -11,23 +11,6 @@ AVA is an incredibly lightweight protocol, so the minimum computer requirements
|
||||||
|
|
||||||
### Native Install
|
### Native Install
|
||||||
|
|
||||||
Ubuntu users need the following libraries:
|
|
||||||
|
|
||||||
* libssl-dev
|
|
||||||
* libuv1-dev
|
|
||||||
* cmake
|
|
||||||
* make
|
|
||||||
* curl
|
|
||||||
* g++
|
|
||||||
|
|
||||||
Install the libraries:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo apt-get install libssl-dev libuv1-dev cmake make curl g++
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Downloading Gecko Source Code
|
|
||||||
|
|
||||||
Clone the Gecko repository:
|
Clone the Gecko repository:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -72,10 +55,11 @@ You may see a few warnings. These are OK.
|
||||||
You can use `Ctrl + C` to kill the node.
|
You can use `Ctrl + C` to kill the node.
|
||||||
|
|
||||||
If you want to specify your log level. You should set `--log-level` to one of the following values, in decreasing order of logging.
|
If you want to specify your log level. You should set `--log-level` to one of the following values, in decreasing order of logging.
|
||||||
* `--log-level=verbo`
|
|
||||||
* `--log-level=debug`
|
- `--log-level=verbo`
|
||||||
* `--log-level=info`
|
- `--log-level=debug`
|
||||||
* `--log-level=warn`
|
- `--log-level=info`
|
||||||
* `--log-level=error`
|
- `--log-level=warn`
|
||||||
* `--log-level=fatal`
|
- `--log-level=error`
|
||||||
* `--log-level=off`
|
- `--log-level=fatal`
|
||||||
|
- `--log-level=off`
|
||||||
|
|
|
@ -226,3 +226,17 @@ func (service *Admin) AliasChain(_ *http.Request, args *AliasChainArgs, reply *A
|
||||||
reply.Success = true
|
reply.Success = true
|
||||||
return service.httpServer.AddAliasesWithReadLock("bc/"+chainID.String(), "bc/"+args.Alias)
|
return service.httpServer.AddAliasesWithReadLock("bc/"+chainID.String(), "bc/"+args.Alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StacktraceArgs are the arguments for calling Stacktrace
|
||||||
|
type StacktraceArgs struct{}
|
||||||
|
|
||||||
|
// StacktraceReply are the results from calling Stacktrace
|
||||||
|
type StacktraceReply struct {
|
||||||
|
Stacktrace string `json:"stacktrace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stacktrace returns the current global stacktrace
|
||||||
|
func (service *Admin) Stacktrace(_ *http.Request, _ *StacktraceArgs, reply *StacktraceReply) error {
|
||||||
|
reply.Stacktrace = logging.Stacktrace{Global: true}.String()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -51,14 +51,14 @@ func (c gosundheitCheck) Name() string { return c.name }
|
||||||
// returning the results
|
// returning the results
|
||||||
func (c gosundheitCheck) Execute() (interface{}, error) { return c.checkFn() }
|
func (c gosundheitCheck) Execute() (interface{}, error) { return c.checkFn() }
|
||||||
|
|
||||||
// heartbeater provides a getter to the most recently observed heartbeat
|
// Heartbeater provides a getter to the most recently observed heartbeat
|
||||||
type heartbeater interface {
|
type Heartbeater interface {
|
||||||
GetHeartbeat() int64
|
GetHeartbeat() int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeartbeatCheckFn returns a CheckFn that checks the given heartbeater has
|
// HeartbeatCheckFn returns a CheckFn that checks the given heartbeater has
|
||||||
// pulsed within the given duration
|
// pulsed within the given duration
|
||||||
func HeartbeatCheckFn(hb heartbeater, max time.Duration) CheckFn {
|
func HeartbeatCheckFn(hb Heartbeater, max time.Duration) CheckFn {
|
||||||
return func() (data interface{}, err error) {
|
return func() (data interface{}, err error) {
|
||||||
// Get the heartbeat and create a data set to return to the caller
|
// Get the heartbeat and create a data set to return to the caller
|
||||||
hb := hb.GetHeartbeat()
|
hb := hb.GetHeartbeat()
|
||||||
|
|
|
@ -41,7 +41,7 @@ func (h *Health) Handler() *common.HTTPHandler {
|
||||||
|
|
||||||
// RegisterHeartbeat adds a check with default options and a CheckFn that checks
|
// RegisterHeartbeat adds a check with default options and a CheckFn that checks
|
||||||
// the given heartbeater for a recent heartbeat
|
// the given heartbeater for a recent heartbeat
|
||||||
func (h *Health) RegisterHeartbeat(name string, hb heartbeater, max time.Duration) error {
|
func (h *Health) RegisterHeartbeat(name string, hb Heartbeater, max time.Duration) error {
|
||||||
return h.RegisterCheckFunc(name, HeartbeatCheckFn(hb, max))
|
return h.RegisterCheckFunc(name, HeartbeatCheckFn(hb, max))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,8 +73,8 @@ type GetLivenessReply struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLiveness returns a summation of the health of the node
|
// GetLiveness returns a summation of the health of the node
|
||||||
func (service *Health) GetLiveness(_ *http.Request, _ *GetLivenessArgs, reply *GetLivenessReply) error {
|
func (h *Health) GetLiveness(_ *http.Request, _ *GetLivenessArgs, reply *GetLivenessReply) error {
|
||||||
service.log.Debug("Health: GetLiveness called")
|
h.log.Debug("Health: GetLiveness called")
|
||||||
reply.Checks, reply.Healthy = service.health.Results()
|
reply.Checks, reply.Healthy = h.health.Results()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,44 @@
|
||||||
|
|
||||||
package chains
|
package chains
|
||||||
|
|
||||||
import "github.com/ava-labs/gecko/snow/networking"
|
import (
|
||||||
|
"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/snow/validators"
|
||||||
|
"github.com/ava-labs/gecko/utils/math"
|
||||||
|
)
|
||||||
|
|
||||||
// Awaiter can await connections to be connected
|
type awaiter struct {
|
||||||
type Awaiter interface {
|
vdrs validators.Set
|
||||||
AwaitConnections(awaiting *networking.AwaitingConnections)
|
reqWeight uint64
|
||||||
|
weight uint64
|
||||||
|
ctx *snow.Context
|
||||||
|
eng common.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awaiter) Connected(vdrID ids.ShortID) bool {
|
||||||
|
vdr, ok := a.vdrs.Get(vdrID)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
weight, err := math.Add64(vdr.Weight(), a.weight)
|
||||||
|
a.weight = weight
|
||||||
|
if err == nil && a.weight < a.reqWeight {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
a.ctx.Lock.Lock()
|
||||||
|
defer a.ctx.Lock.Unlock()
|
||||||
|
a.eng.Startup()
|
||||||
|
}()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *awaiter) Disconnected(vdrID ids.ShortID) bool {
|
||||||
|
if vdr, ok := a.vdrs.Get(vdrID); ok {
|
||||||
|
a.weight, _ = math.Sub64(vdr.Weight(), a.weight)
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,13 @@ import (
|
||||||
"github.com/ava-labs/gecko/database"
|
"github.com/ava-labs/gecko/database"
|
||||||
"github.com/ava-labs/gecko/database/prefixdb"
|
"github.com/ava-labs/gecko/database/prefixdb"
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/network"
|
||||||
"github.com/ava-labs/gecko/snow"
|
"github.com/ava-labs/gecko/snow"
|
||||||
"github.com/ava-labs/gecko/snow/consensus/snowball"
|
"github.com/ava-labs/gecko/snow/consensus/snowball"
|
||||||
"github.com/ava-labs/gecko/snow/engine/avalanche"
|
"github.com/ava-labs/gecko/snow/engine/avalanche"
|
||||||
"github.com/ava-labs/gecko/snow/engine/avalanche/state"
|
"github.com/ava-labs/gecko/snow/engine/avalanche/state"
|
||||||
"github.com/ava-labs/gecko/snow/engine/common"
|
"github.com/ava-labs/gecko/snow/engine/common"
|
||||||
"github.com/ava-labs/gecko/snow/engine/common/queue"
|
"github.com/ava-labs/gecko/snow/engine/common/queue"
|
||||||
"github.com/ava-labs/gecko/snow/networking"
|
|
||||||
"github.com/ava-labs/gecko/snow/networking/handler"
|
"github.com/ava-labs/gecko/snow/networking/handler"
|
||||||
"github.com/ava-labs/gecko/snow/networking/router"
|
"github.com/ava-labs/gecko/snow/networking/router"
|
||||||
"github.com/ava-labs/gecko/snow/networking/sender"
|
"github.com/ava-labs/gecko/snow/networking/sender"
|
||||||
|
@ -103,14 +103,13 @@ type manager struct {
|
||||||
consensusEvents *triggers.EventDispatcher
|
consensusEvents *triggers.EventDispatcher
|
||||||
db database.Database
|
db database.Database
|
||||||
chainRouter router.Router // Routes incoming messages to the appropriate chain
|
chainRouter router.Router // Routes incoming messages to the appropriate chain
|
||||||
sender sender.ExternalSender // Sends consensus messages to other validators
|
net network.Network // Sends consensus messages to other validators
|
||||||
timeoutManager *timeout.Manager // Manages request timeouts when sending messages to other validators
|
timeoutManager *timeout.Manager // Manages request timeouts when sending messages to other validators
|
||||||
consensusParams avacon.Parameters // The consensus parameters (alpha, beta, etc.) for new chains
|
consensusParams avacon.Parameters // The consensus parameters (alpha, beta, etc.) for new chains
|
||||||
validators validators.Manager // Validators validating on this chain
|
validators validators.Manager // Validators validating on this chain
|
||||||
registrants []Registrant // Those notified when a chain is created
|
registrants []Registrant // Those notified when a chain is created
|
||||||
nodeID ids.ShortID // The ID of this node
|
nodeID ids.ShortID // The ID of this node
|
||||||
networkID uint32 // ID of the network this node is connected to
|
networkID uint32 // ID of the network this node is connected to
|
||||||
awaiter Awaiter // Waits for required connections before running bootstrapping
|
|
||||||
server *api.Server // Handles HTTP API calls
|
server *api.Server // Handles HTTP API calls
|
||||||
keystore *keystore.Keystore
|
keystore *keystore.Keystore
|
||||||
sharedMemory *atomic.SharedMemory
|
sharedMemory *atomic.SharedMemory
|
||||||
|
@ -133,12 +132,11 @@ func New(
|
||||||
consensusEvents *triggers.EventDispatcher,
|
consensusEvents *triggers.EventDispatcher,
|
||||||
db database.Database,
|
db database.Database,
|
||||||
router router.Router,
|
router router.Router,
|
||||||
sender sender.ExternalSender,
|
net network.Network,
|
||||||
consensusParams avacon.Parameters,
|
consensusParams avacon.Parameters,
|
||||||
validators validators.Manager,
|
validators validators.Manager,
|
||||||
nodeID ids.ShortID,
|
nodeID ids.ShortID,
|
||||||
networkID uint32,
|
networkID uint32,
|
||||||
awaiter Awaiter,
|
|
||||||
server *api.Server,
|
server *api.Server,
|
||||||
keystore *keystore.Keystore,
|
keystore *keystore.Keystore,
|
||||||
sharedMemory *atomic.SharedMemory,
|
sharedMemory *atomic.SharedMemory,
|
||||||
|
@ -158,13 +156,12 @@ func New(
|
||||||
consensusEvents: consensusEvents,
|
consensusEvents: consensusEvents,
|
||||||
db: db,
|
db: db,
|
||||||
chainRouter: router,
|
chainRouter: router,
|
||||||
sender: sender,
|
net: net,
|
||||||
timeoutManager: &timeoutManager,
|
timeoutManager: &timeoutManager,
|
||||||
consensusParams: consensusParams,
|
consensusParams: consensusParams,
|
||||||
validators: validators,
|
validators: validators,
|
||||||
nodeID: nodeID,
|
nodeID: nodeID,
|
||||||
networkID: networkID,
|
networkID: networkID,
|
||||||
awaiter: awaiter,
|
|
||||||
server: server,
|
server: server,
|
||||||
keystore: keystore,
|
keystore: keystore,
|
||||||
sharedMemory: sharedMemory,
|
sharedMemory: sharedMemory,
|
||||||
|
@ -390,7 +387,7 @@ func (m *manager) createAvalancheChain(
|
||||||
|
|
||||||
// Passes messages from the consensus engine to the network
|
// Passes messages from the consensus engine to the network
|
||||||
sender := sender.Sender{}
|
sender := sender.Sender{}
|
||||||
sender.Initialize(ctx, m.sender, m.chainRouter, m.timeoutManager)
|
sender.Initialize(ctx, m.net, m.chainRouter, m.timeoutManager)
|
||||||
|
|
||||||
// The engine handles consensus
|
// The engine handles consensus
|
||||||
engine := avaeng.Transitive{
|
engine := avaeng.Transitive{
|
||||||
|
@ -438,17 +435,17 @@ func (m *manager) createAvalancheChain(
|
||||||
m.chainRouter.AddChain(handler)
|
m.chainRouter.AddChain(handler)
|
||||||
go ctx.Log.RecoverAndPanic(handler.Dispatch)
|
go ctx.Log.RecoverAndPanic(handler.Dispatch)
|
||||||
|
|
||||||
awaiting := &networking.AwaitingConnections{
|
reqWeight := (3*bootstrapWeight + 3) / 4
|
||||||
Requested: beacons,
|
if reqWeight == 0 {
|
||||||
WeightRequired: (3*bootstrapWeight + 3) / 4, // 75% must be connected to
|
|
||||||
Finish: func() {
|
|
||||||
ctx.Lock.Lock()
|
|
||||||
defer ctx.Lock.Unlock()
|
|
||||||
|
|
||||||
engine.Startup()
|
engine.Startup()
|
||||||
},
|
} else {
|
||||||
|
go m.net.RegisterHandler(&awaiter{
|
||||||
|
vdrs: beacons,
|
||||||
|
reqWeight: reqWeight, // 75% must be connected to
|
||||||
|
ctx: ctx,
|
||||||
|
eng: &engine,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
m.awaiter.AwaitConnections(awaiting)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -486,7 +483,7 @@ func (m *manager) createSnowmanChain(
|
||||||
|
|
||||||
// Passes messages from the consensus engine to the network
|
// Passes messages from the consensus engine to the network
|
||||||
sender := sender.Sender{}
|
sender := sender.Sender{}
|
||||||
sender.Initialize(ctx, m.sender, m.chainRouter, m.timeoutManager)
|
sender.Initialize(ctx, m.net, m.chainRouter, m.timeoutManager)
|
||||||
|
|
||||||
bootstrapWeight := uint64(0)
|
bootstrapWeight := uint64(0)
|
||||||
for _, beacon := range beacons.List() {
|
for _, beacon := range beacons.List() {
|
||||||
|
@ -524,17 +521,17 @@ func (m *manager) createSnowmanChain(
|
||||||
m.chainRouter.AddChain(handler)
|
m.chainRouter.AddChain(handler)
|
||||||
go ctx.Log.RecoverAndPanic(handler.Dispatch)
|
go ctx.Log.RecoverAndPanic(handler.Dispatch)
|
||||||
|
|
||||||
awaiting := &networking.AwaitingConnections{
|
reqWeight := (3*bootstrapWeight + 3) / 4
|
||||||
Requested: beacons,
|
if reqWeight == 0 {
|
||||||
WeightRequired: (3*bootstrapWeight + 3) / 4, // 75% must be connected to
|
|
||||||
Finish: func() {
|
|
||||||
ctx.Lock.Lock()
|
|
||||||
defer ctx.Lock.Unlock()
|
|
||||||
|
|
||||||
engine.Startup()
|
engine.Startup()
|
||||||
},
|
} else {
|
||||||
|
go m.net.RegisterHandler(&awaiter{
|
||||||
|
vdrs: beacons,
|
||||||
|
reqWeight: reqWeight, // 75% must be connected to
|
||||||
|
ctx: ctx,
|
||||||
|
eng: &engine,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
m.awaiter.AwaitConnections(awaiting)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
main/main.go
15
main/main.go
|
@ -71,21 +71,16 @@ func main() {
|
||||||
mapper.MapPort(Config.StakingIP.Port, Config.StakingIP.Port)
|
mapper.MapPort(Config.StakingIP.Port, Config.StakingIP.Port)
|
||||||
mapper.MapPort(Config.HTTPPort, Config.HTTPPort)
|
mapper.MapPort(Config.HTTPPort, Config.HTTPPort)
|
||||||
|
|
||||||
|
node := node.Node{}
|
||||||
|
|
||||||
log.Debug("initializing node state")
|
log.Debug("initializing node state")
|
||||||
// MainNode is a global variable in the node.go file
|
if err := node.Initialize(&Config, log, factory); err != nil {
|
||||||
if err := node.MainNode.Initialize(&Config, log, factory); err != nil {
|
|
||||||
log.Fatal("error initializing node state: %s", err)
|
log.Fatal("error initializing node state: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Starting servers")
|
defer node.Shutdown()
|
||||||
if err := node.MainNode.StartConsensusServer(); err != nil {
|
|
||||||
log.Fatal("problem starting servers: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer node.MainNode.Shutdown()
|
|
||||||
|
|
||||||
log.Debug("Dispatching node handlers")
|
log.Debug("Dispatching node handlers")
|
||||||
node.MainNode.Dispatch()
|
node.Dispatch()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dbVersion = "v0.2.0"
|
dbVersion = "v0.3.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Results of parsing the CLI
|
// Results of parsing the CLI
|
||||||
|
@ -52,7 +52,7 @@ func GetIPs(networkID uint32) []string {
|
||||||
return []string{
|
return []string{
|
||||||
"3.227.207.132:21001",
|
"3.227.207.132:21001",
|
||||||
"34.207.133.167:21001",
|
"34.207.133.167:21001",
|
||||||
"107.23.241.199:21001",
|
"54.162.71.9:21001",
|
||||||
"54.197.215.186:21001",
|
"54.197.215.186:21001",
|
||||||
"18.234.153.22:21001",
|
"18.234.153.22:21001",
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
// See the file LICENSE for licensing terms.
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
package networking
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
"github.com/ava-labs/gecko/snow/choices"
|
|
||||||
"github.com/ava-labs/gecko/utils"
|
"github.com/ava-labs/gecko/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,9 +15,10 @@ type Builder struct{ Codec }
|
||||||
func (m Builder) GetVersion() (Msg, error) { return m.Pack(GetVersion, nil) }
|
func (m Builder) GetVersion() (Msg, error) { return m.Pack(GetVersion, nil) }
|
||||||
|
|
||||||
// Version message
|
// Version message
|
||||||
func (m Builder) Version(networkID uint32, myTime uint64, ip utils.IPDesc, myVersion string) (Msg, error) {
|
func (m Builder) Version(networkID, nodeID uint32, myTime uint64, ip utils.IPDesc, myVersion string) (Msg, error) {
|
||||||
return m.Pack(Version, map[Field]interface{}{
|
return m.Pack(Version, map[Field]interface{}{
|
||||||
NetworkID: networkID,
|
NetworkID: networkID,
|
||||||
|
NodeID: nodeID,
|
||||||
MyTime: myTime,
|
MyTime: myTime,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
VersionStr: myVersion,
|
VersionStr: myVersion,
|
||||||
|
@ -130,28 +130,3 @@ func (m Builder) Chits(chainID ids.ID, requestID uint32, containerIDs ids.Set) (
|
||||||
ContainerIDs: containerIDBytes,
|
ContainerIDs: containerIDBytes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping message
|
|
||||||
func (m Builder) Ping() (Msg, error) { return m.Pack(Ping, nil) }
|
|
||||||
|
|
||||||
// Pong message
|
|
||||||
func (m Builder) Pong() (Msg, error) { return m.Pack(Pong, nil) }
|
|
||||||
|
|
||||||
// Data message
|
|
||||||
func (m Builder) Data(b []byte) (Msg, error) { return m.Pack(Data, map[Field]interface{}{Bytes: b}) }
|
|
||||||
|
|
||||||
// IssueTx message
|
|
||||||
func (m Builder) IssueTx(chainID ids.ID, tx []byte) (Msg, error) {
|
|
||||||
return m.Pack(IssueTx, map[Field]interface{}{
|
|
||||||
ChainID: chainID.Bytes(),
|
|
||||||
Tx: tx,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecidedTx message
|
|
||||||
func (m Builder) DecidedTx(txID ids.ID, status choices.Status) (Msg, error) {
|
|
||||||
return m.Pack(DecidedTx, map[Field]interface{}{
|
|
||||||
TxID: txID.Bytes(),
|
|
||||||
Status: uint32(status),
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -0,0 +1,316 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TestBuilder Builder
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildGetVersion(t *testing.T) {
|
||||||
|
msg, err := TestBuilder.GetVersion()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, GetVersion, msg.Op())
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, GetVersion, parsedMsg.Op())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildVersion(t *testing.T) {
|
||||||
|
networkID := uint32(1)
|
||||||
|
nodeID := uint32(3)
|
||||||
|
myTime := uint64(2)
|
||||||
|
ip := utils.IPDesc{
|
||||||
|
IP: net.IPv6loopback,
|
||||||
|
Port: 12345,
|
||||||
|
}
|
||||||
|
myVersion := "xD"
|
||||||
|
|
||||||
|
msg, err := TestBuilder.Version(
|
||||||
|
networkID,
|
||||||
|
nodeID,
|
||||||
|
myTime,
|
||||||
|
ip,
|
||||||
|
myVersion,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, Version, msg.Op())
|
||||||
|
assert.Equal(t, networkID, msg.Get(NetworkID))
|
||||||
|
assert.Equal(t, nodeID, msg.Get(NodeID))
|
||||||
|
assert.Equal(t, myTime, msg.Get(MyTime))
|
||||||
|
assert.Equal(t, ip, msg.Get(IP))
|
||||||
|
assert.Equal(t, myVersion, msg.Get(VersionStr))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, Version, parsedMsg.Op())
|
||||||
|
assert.Equal(t, networkID, parsedMsg.Get(NetworkID))
|
||||||
|
assert.Equal(t, nodeID, parsedMsg.Get(NodeID))
|
||||||
|
assert.Equal(t, myTime, parsedMsg.Get(MyTime))
|
||||||
|
assert.Equal(t, ip, parsedMsg.Get(IP))
|
||||||
|
assert.Equal(t, myVersion, parsedMsg.Get(VersionStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildGetPeerList(t *testing.T) {
|
||||||
|
msg, err := TestBuilder.GetPeerList()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, GetPeerList, msg.Op())
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, GetPeerList, parsedMsg.Op())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPeerList(t *testing.T) {
|
||||||
|
ips := []utils.IPDesc{
|
||||||
|
utils.IPDesc{
|
||||||
|
IP: net.IPv6loopback,
|
||||||
|
Port: 12345,
|
||||||
|
},
|
||||||
|
utils.IPDesc{
|
||||||
|
IP: net.IPv6loopback,
|
||||||
|
Port: 54321,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := TestBuilder.PeerList(ips)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, PeerList, msg.Op())
|
||||||
|
assert.Equal(t, ips, msg.Get(Peers))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, PeerList, parsedMsg.Op())
|
||||||
|
assert.Equal(t, ips, parsedMsg.Get(Peers))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildGetAcceptedFrontier(t *testing.T) {
|
||||||
|
chainID := ids.Empty.Prefix(0)
|
||||||
|
requestID := uint32(5)
|
||||||
|
|
||||||
|
msg, err := TestBuilder.GetAcceptedFrontier(chainID, requestID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, GetAcceptedFrontier, msg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), msg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, msg.Get(RequestID))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, GetAcceptedFrontier, parsedMsg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), parsedMsg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, parsedMsg.Get(RequestID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildAcceptedFrontier(t *testing.T) {
|
||||||
|
chainID := ids.Empty.Prefix(0)
|
||||||
|
requestID := uint32(5)
|
||||||
|
containerID := ids.Empty.Prefix(1)
|
||||||
|
containerIDSet := ids.Set{}
|
||||||
|
containerIDSet.Add(containerID)
|
||||||
|
containerIDs := [][]byte{containerID.Bytes()}
|
||||||
|
|
||||||
|
msg, err := TestBuilder.AcceptedFrontier(chainID, requestID, containerIDSet)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, AcceptedFrontier, msg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), msg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, msg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerIDs, msg.Get(ContainerIDs))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, AcceptedFrontier, parsedMsg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), parsedMsg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, parsedMsg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerIDs, parsedMsg.Get(ContainerIDs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildGetAccepted(t *testing.T) {
|
||||||
|
chainID := ids.Empty.Prefix(0)
|
||||||
|
requestID := uint32(5)
|
||||||
|
containerID := ids.Empty.Prefix(1)
|
||||||
|
containerIDSet := ids.Set{}
|
||||||
|
containerIDSet.Add(containerID)
|
||||||
|
containerIDs := [][]byte{containerID.Bytes()}
|
||||||
|
|
||||||
|
msg, err := TestBuilder.GetAccepted(chainID, requestID, containerIDSet)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, GetAccepted, msg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), msg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, msg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerIDs, msg.Get(ContainerIDs))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, GetAccepted, parsedMsg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), parsedMsg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, parsedMsg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerIDs, parsedMsg.Get(ContainerIDs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildAccepted(t *testing.T) {
|
||||||
|
chainID := ids.Empty.Prefix(0)
|
||||||
|
requestID := uint32(5)
|
||||||
|
containerID := ids.Empty.Prefix(1)
|
||||||
|
containerIDSet := ids.Set{}
|
||||||
|
containerIDSet.Add(containerID)
|
||||||
|
containerIDs := [][]byte{containerID.Bytes()}
|
||||||
|
|
||||||
|
msg, err := TestBuilder.Accepted(chainID, requestID, containerIDSet)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, Accepted, msg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), msg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, msg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerIDs, msg.Get(ContainerIDs))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, Accepted, parsedMsg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), parsedMsg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, parsedMsg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerIDs, parsedMsg.Get(ContainerIDs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildGet(t *testing.T) {
|
||||||
|
chainID := ids.Empty.Prefix(0)
|
||||||
|
requestID := uint32(5)
|
||||||
|
containerID := ids.Empty.Prefix(1)
|
||||||
|
|
||||||
|
msg, err := TestBuilder.Get(chainID, requestID, containerID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, Get, msg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), msg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, msg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerID.Bytes(), msg.Get(ContainerID))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, Get, parsedMsg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), parsedMsg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, parsedMsg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerID.Bytes(), parsedMsg.Get(ContainerID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPut(t *testing.T) {
|
||||||
|
chainID := ids.Empty.Prefix(0)
|
||||||
|
requestID := uint32(5)
|
||||||
|
containerID := ids.Empty.Prefix(1)
|
||||||
|
container := []byte{2}
|
||||||
|
|
||||||
|
msg, err := TestBuilder.Put(chainID, requestID, containerID, container)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, Put, msg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), msg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, msg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerID.Bytes(), msg.Get(ContainerID))
|
||||||
|
assert.Equal(t, container, msg.Get(ContainerBytes))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, Put, parsedMsg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), parsedMsg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, parsedMsg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerID.Bytes(), parsedMsg.Get(ContainerID))
|
||||||
|
assert.Equal(t, container, parsedMsg.Get(ContainerBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPushQuery(t *testing.T) {
|
||||||
|
chainID := ids.Empty.Prefix(0)
|
||||||
|
requestID := uint32(5)
|
||||||
|
containerID := ids.Empty.Prefix(1)
|
||||||
|
container := []byte{2}
|
||||||
|
|
||||||
|
msg, err := TestBuilder.PushQuery(chainID, requestID, containerID, container)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, PushQuery, msg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), msg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, msg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerID.Bytes(), msg.Get(ContainerID))
|
||||||
|
assert.Equal(t, container, msg.Get(ContainerBytes))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, PushQuery, parsedMsg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), parsedMsg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, parsedMsg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerID.Bytes(), parsedMsg.Get(ContainerID))
|
||||||
|
assert.Equal(t, container, parsedMsg.Get(ContainerBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPullQuery(t *testing.T) {
|
||||||
|
chainID := ids.Empty.Prefix(0)
|
||||||
|
requestID := uint32(5)
|
||||||
|
containerID := ids.Empty.Prefix(1)
|
||||||
|
|
||||||
|
msg, err := TestBuilder.PullQuery(chainID, requestID, containerID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, PullQuery, msg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), msg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, msg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerID.Bytes(), msg.Get(ContainerID))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, PullQuery, parsedMsg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), parsedMsg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, parsedMsg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerID.Bytes(), parsedMsg.Get(ContainerID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildChits(t *testing.T) {
|
||||||
|
chainID := ids.Empty.Prefix(0)
|
||||||
|
requestID := uint32(5)
|
||||||
|
containerID := ids.Empty.Prefix(1)
|
||||||
|
containerIDSet := ids.Set{}
|
||||||
|
containerIDSet.Add(containerID)
|
||||||
|
containerIDs := [][]byte{containerID.Bytes()}
|
||||||
|
|
||||||
|
msg, err := TestBuilder.Chits(chainID, requestID, containerIDSet)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, msg)
|
||||||
|
assert.Equal(t, Chits, msg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), msg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, msg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerIDs, msg.Get(ContainerIDs))
|
||||||
|
|
||||||
|
parsedMsg, err := TestBuilder.Parse(msg.Bytes())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedMsg)
|
||||||
|
assert.Equal(t, Chits, parsedMsg.Op())
|
||||||
|
assert.Equal(t, chainID.Bytes(), parsedMsg.Get(ChainID))
|
||||||
|
assert.Equal(t, requestID, parsedMsg.Get(RequestID))
|
||||||
|
assert.Equal(t, containerIDs, parsedMsg.Get(ContainerIDs))
|
||||||
|
}
|
|
@ -1,15 +1,13 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
// See the file LICENSE for licensing terms.
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
package networking
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/utils"
|
|
||||||
"github.com/ava-labs/gecko/utils/wrappers"
|
"github.com/ava-labs/gecko/utils/wrappers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,15 +21,15 @@ var (
|
||||||
type Codec struct{}
|
type Codec struct{}
|
||||||
|
|
||||||
// Pack attempts to pack a map of fields into a message.
|
// Pack attempts to pack a map of fields into a message.
|
||||||
//
|
// The first byte of the message is the opcode of the message.
|
||||||
// If a nil error is returned, the message's datastream must be freed manually
|
func (Codec) Pack(op Op, fields map[Field]interface{}) (Msg, error) {
|
||||||
func (Codec) Pack(op salticidae.Opcode, fields map[Field]interface{}) (Msg, error) {
|
|
||||||
message, ok := Messages[op]
|
message, ok := Messages[op]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errBadOp
|
return nil, errBadOp
|
||||||
}
|
}
|
||||||
|
|
||||||
p := wrappers.Packer{MaxSize: math.MaxInt32}
|
p := wrappers.Packer{MaxSize: math.MaxInt32}
|
||||||
|
p.PackByte(byte(op))
|
||||||
for _, field := range message {
|
for _, field := range message {
|
||||||
data, ok := fields[field]
|
data, ok := fields[field]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -40,43 +38,35 @@ func (Codec) Pack(op salticidae.Opcode, fields map[Field]interface{}) (Msg, erro
|
||||||
field.Packer()(&p, data)
|
field.Packer()(&p, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Errored() { // Prevent the datastream from leaking
|
|
||||||
return nil, p.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &msg{
|
return &msg{
|
||||||
op: op,
|
op: op,
|
||||||
ds: salticidae.NewDataStreamFromBytes(p.Bytes, false),
|
|
||||||
fields: fields,
|
fields: fields,
|
||||||
}, nil
|
bytes: p.Bytes,
|
||||||
|
}, p.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse attempts to convert a byte stream into a message.
|
// Parse attempts to convert bytes into a message.
|
||||||
//
|
// The first byte of the message is the opcode of the message.
|
||||||
// The datastream is not freed.
|
func (Codec) Parse(b []byte) (Msg, error) {
|
||||||
func (Codec) Parse(op salticidae.Opcode, ds salticidae.DataStream) (Msg, error) {
|
p := wrappers.Packer{Bytes: b}
|
||||||
|
op := Op(p.UnpackByte())
|
||||||
message, ok := Messages[op]
|
message, ok := Messages[op]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errBadOp
|
return nil, errBadOp
|
||||||
}
|
}
|
||||||
|
|
||||||
size := ds.Size()
|
|
||||||
byteHandle := ds.GetDataInPlace(size)
|
|
||||||
p := wrappers.Packer{Bytes: utils.CopyBytes(byteHandle.Get())}
|
|
||||||
byteHandle.Release()
|
|
||||||
|
|
||||||
fields := make(map[Field]interface{}, len(message))
|
fields := make(map[Field]interface{}, len(message))
|
||||||
for _, field := range message {
|
for _, field := range message {
|
||||||
fields[field] = field.Unpacker()(&p)
|
fields[field] = field.Unpacker()(&p)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Offset != size {
|
if p.Offset != len(b) {
|
||||||
return nil, errBadLength
|
p.Add(fmt.Errorf("expected length %d got %d", len(b), p.Offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &msg{
|
return &msg{
|
||||||
op: op,
|
op: op,
|
||||||
ds: ds,
|
|
||||||
fields: fields,
|
fields: fields,
|
||||||
|
bytes: b,
|
||||||
}, p.Err
|
}, p.Err
|
||||||
}
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TestCodec Codec
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCodecPackInvalidOp(t *testing.T) {
|
||||||
|
_, err := TestCodec.Pack(math.MaxUint8, make(map[Field]interface{}))
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodecPackMissingField(t *testing.T) {
|
||||||
|
_, err := TestCodec.Pack(Get, make(map[Field]interface{}))
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodecParseInvalidOp(t *testing.T) {
|
||||||
|
_, err := TestCodec.Parse([]byte{math.MaxUint8})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodecParseExtraSpace(t *testing.T) {
|
||||||
|
_, err := TestCodec.Parse([]byte{byte(GetVersion), 0x00})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
|
@ -1,11 +1,9 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
// See the file LICENSE for licensing terms.
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
package networking
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/utils/wrappers"
|
"github.com/ava-labs/gecko/utils/wrappers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,6 +14,7 @@ type Field uint32
|
||||||
const (
|
const (
|
||||||
VersionStr Field = iota // Used in handshake
|
VersionStr Field = iota // Used in handshake
|
||||||
NetworkID // Used in handshake
|
NetworkID // Used in handshake
|
||||||
|
NodeID // Used in handshake
|
||||||
MyTime // Used in handshake
|
MyTime // Used in handshake
|
||||||
IP // Used in handshake
|
IP // Used in handshake
|
||||||
Peers // Used in handshake
|
Peers // Used in handshake
|
||||||
|
@ -24,10 +23,6 @@ const (
|
||||||
ContainerID // Used for querying
|
ContainerID // Used for querying
|
||||||
ContainerBytes // Used for gossiping
|
ContainerBytes // Used for gossiping
|
||||||
ContainerIDs // Used for querying
|
ContainerIDs // Used for querying
|
||||||
Bytes // Used as arbitrary data
|
|
||||||
TxID // Used for throughput tests
|
|
||||||
Tx // Used for throughput tests
|
|
||||||
Status // Used for throughput tests
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Packer returns the packer function that can be used to pack this field.
|
// Packer returns the packer function that can be used to pack this field.
|
||||||
|
@ -37,6 +32,8 @@ func (f Field) Packer() func(*wrappers.Packer, interface{}) {
|
||||||
return wrappers.TryPackStr
|
return wrappers.TryPackStr
|
||||||
case NetworkID:
|
case NetworkID:
|
||||||
return wrappers.TryPackInt
|
return wrappers.TryPackInt
|
||||||
|
case NodeID:
|
||||||
|
return wrappers.TryPackInt
|
||||||
case MyTime:
|
case MyTime:
|
||||||
return wrappers.TryPackLong
|
return wrappers.TryPackLong
|
||||||
case IP:
|
case IP:
|
||||||
|
@ -53,14 +50,6 @@ func (f Field) Packer() func(*wrappers.Packer, interface{}) {
|
||||||
return wrappers.TryPackBytes
|
return wrappers.TryPackBytes
|
||||||
case ContainerIDs:
|
case ContainerIDs:
|
||||||
return wrappers.TryPackHashes
|
return wrappers.TryPackHashes
|
||||||
case Bytes:
|
|
||||||
return wrappers.TryPackBytes
|
|
||||||
case TxID:
|
|
||||||
return wrappers.TryPackHash
|
|
||||||
case Tx:
|
|
||||||
return wrappers.TryPackBytes
|
|
||||||
case Status:
|
|
||||||
return wrappers.TryPackInt
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -73,6 +62,8 @@ func (f Field) Unpacker() func(*wrappers.Packer) interface{} {
|
||||||
return wrappers.TryUnpackStr
|
return wrappers.TryUnpackStr
|
||||||
case NetworkID:
|
case NetworkID:
|
||||||
return wrappers.TryUnpackInt
|
return wrappers.TryUnpackInt
|
||||||
|
case NodeID:
|
||||||
|
return wrappers.TryUnpackInt
|
||||||
case MyTime:
|
case MyTime:
|
||||||
return wrappers.TryUnpackLong
|
return wrappers.TryUnpackLong
|
||||||
case IP:
|
case IP:
|
||||||
|
@ -89,14 +80,6 @@ func (f Field) Unpacker() func(*wrappers.Packer) interface{} {
|
||||||
return wrappers.TryUnpackBytes
|
return wrappers.TryUnpackBytes
|
||||||
case ContainerIDs:
|
case ContainerIDs:
|
||||||
return wrappers.TryUnpackHashes
|
return wrappers.TryUnpackHashes
|
||||||
case Bytes:
|
|
||||||
return wrappers.TryUnpackBytes
|
|
||||||
case TxID:
|
|
||||||
return wrappers.TryUnpackHash
|
|
||||||
case Tx:
|
|
||||||
return wrappers.TryUnpackBytes
|
|
||||||
case Status:
|
|
||||||
return wrappers.TryUnpackInt
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -108,6 +91,8 @@ func (f Field) String() string {
|
||||||
return "VersionStr"
|
return "VersionStr"
|
||||||
case NetworkID:
|
case NetworkID:
|
||||||
return "NetworkID"
|
return "NetworkID"
|
||||||
|
case NodeID:
|
||||||
|
return "NodeID"
|
||||||
case MyTime:
|
case MyTime:
|
||||||
return "MyTime"
|
return "MyTime"
|
||||||
case IP:
|
case IP:
|
||||||
|
@ -122,23 +107,51 @@ func (f Field) String() string {
|
||||||
return "Container Bytes"
|
return "Container Bytes"
|
||||||
case ContainerIDs:
|
case ContainerIDs:
|
||||||
return "Container IDs"
|
return "Container IDs"
|
||||||
case Bytes:
|
|
||||||
return "Bytes"
|
|
||||||
case TxID:
|
|
||||||
return "TxID"
|
|
||||||
case Tx:
|
|
||||||
return "Tx"
|
|
||||||
case Status:
|
|
||||||
return "Status"
|
|
||||||
default:
|
default:
|
||||||
return "Unknown Field"
|
return "Unknown Field"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Op is an opcode
|
||||||
|
type Op byte
|
||||||
|
|
||||||
|
func (op Op) String() string {
|
||||||
|
switch op {
|
||||||
|
case GetVersion:
|
||||||
|
return "get_version"
|
||||||
|
case Version:
|
||||||
|
return "version"
|
||||||
|
case GetPeerList:
|
||||||
|
return "get_peerlist"
|
||||||
|
case PeerList:
|
||||||
|
return "peerlist"
|
||||||
|
case GetAcceptedFrontier:
|
||||||
|
return "get_accepted_frontier"
|
||||||
|
case AcceptedFrontier:
|
||||||
|
return "accepted_frontier"
|
||||||
|
case GetAccepted:
|
||||||
|
return "get_accepted"
|
||||||
|
case Accepted:
|
||||||
|
return "accepted"
|
||||||
|
case Get:
|
||||||
|
return "get"
|
||||||
|
case Put:
|
||||||
|
return "put"
|
||||||
|
case PushQuery:
|
||||||
|
return "push_query"
|
||||||
|
case PullQuery:
|
||||||
|
return "pull_query"
|
||||||
|
case Chits:
|
||||||
|
return "chits"
|
||||||
|
default:
|
||||||
|
return "Unknown Op"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Public commands that may be sent between stakers
|
// Public commands that may be sent between stakers
|
||||||
const (
|
const (
|
||||||
// Handshake:
|
// Handshake:
|
||||||
GetVersion salticidae.Opcode = iota
|
GetVersion Op = iota
|
||||||
Version
|
Version
|
||||||
GetPeerList
|
GetPeerList
|
||||||
PeerList
|
PeerList
|
||||||
|
@ -153,22 +166,14 @@ const (
|
||||||
PushQuery
|
PushQuery
|
||||||
PullQuery
|
PullQuery
|
||||||
Chits
|
Chits
|
||||||
// Pinging:
|
|
||||||
Ping
|
|
||||||
Pong
|
|
||||||
// Arbitrary data message:
|
|
||||||
Data
|
|
||||||
// Throughput test:
|
|
||||||
IssueTx
|
|
||||||
DecidedTx
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines the messages that can be sent/received with this network
|
// Defines the messages that can be sent/received with this network
|
||||||
var (
|
var (
|
||||||
Messages = map[salticidae.Opcode][]Field{
|
Messages = map[Op][]Field{
|
||||||
// Handshake:
|
// Handshake:
|
||||||
GetVersion: []Field{},
|
GetVersion: []Field{},
|
||||||
Version: []Field{NetworkID, MyTime, IP, VersionStr},
|
Version: []Field{NetworkID, NodeID, MyTime, IP, VersionStr},
|
||||||
GetPeerList: []Field{},
|
GetPeerList: []Field{},
|
||||||
PeerList: []Field{Peers},
|
PeerList: []Field{Peers},
|
||||||
// Bootstrapping:
|
// Bootstrapping:
|
||||||
|
@ -182,13 +187,5 @@ var (
|
||||||
PushQuery: []Field{ChainID, RequestID, ContainerID, ContainerBytes},
|
PushQuery: []Field{ChainID, RequestID, ContainerID, ContainerBytes},
|
||||||
PullQuery: []Field{ChainID, RequestID, ContainerID},
|
PullQuery: []Field{ChainID, RequestID, ContainerID},
|
||||||
Chits: []Field{ChainID, RequestID, ContainerIDs},
|
Chits: []Field{ChainID, RequestID, ContainerIDs},
|
||||||
// Pinging:
|
|
||||||
Ping: []Field{},
|
|
||||||
Pong: []Field{},
|
|
||||||
// Arbitrary data message:
|
|
||||||
Data: []Field{Bytes},
|
|
||||||
// Throughput test:
|
|
||||||
IssueTx: []Field{ChainID, Tx},
|
|
||||||
DecidedTx: []Field{TxID, Status},
|
|
||||||
}
|
}
|
||||||
)
|
)
|
|
@ -0,0 +1,25 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dialer attempts to create a connection with the provided IP/port pair
|
||||||
|
type Dialer interface {
|
||||||
|
Dial(utils.IPDesc) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dialer struct {
|
||||||
|
network string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDialer returns a new Dialer that calls `net.Dial` with the provided
|
||||||
|
// network.
|
||||||
|
func NewDialer(network string) Dialer { return &dialer{network: network} }
|
||||||
|
|
||||||
|
func (d *dialer) Dial(ip utils.IPDesc) (net.Conn, error) { return net.Dial(d.network, ip.String()) }
|
|
@ -0,0 +1,14 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package network
|
||||||
|
|
||||||
|
import "github.com/ava-labs/gecko/ids"
|
||||||
|
|
||||||
|
// Handler represents a handler that is called when a connection is marked as
|
||||||
|
// connected or disconnected
|
||||||
|
type Handler interface {
|
||||||
|
// returns true if the handler should be removed
|
||||||
|
Connected(id ids.ShortID) bool
|
||||||
|
Disconnected(id ids.ShortID) bool
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/utils/wrappers"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type messageMetrics struct {
|
||||||
|
numSent, numFailed, numReceived prometheus.Counter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mm *messageMetrics) initialize(msgType Op, registerer prometheus.Registerer) error {
|
||||||
|
mm.numSent = prometheus.NewCounter(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Namespace: "gecko",
|
||||||
|
Name: fmt.Sprintf("%s_sent", msgType),
|
||||||
|
Help: fmt.Sprintf("Number of %s messages sent", msgType),
|
||||||
|
})
|
||||||
|
mm.numFailed = prometheus.NewCounter(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Namespace: "gecko",
|
||||||
|
Name: fmt.Sprintf("%s_failed", msgType),
|
||||||
|
Help: fmt.Sprintf("Number of %s messages that failed to be sent", msgType),
|
||||||
|
})
|
||||||
|
mm.numReceived = prometheus.NewCounter(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Namespace: "gecko",
|
||||||
|
Name: fmt.Sprintf("%s_received", msgType),
|
||||||
|
Help: fmt.Sprintf("Number of %s messages received", msgType),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := registerer.Register(mm.numSent); err != nil {
|
||||||
|
return fmt.Errorf("failed to register sent statistics of %s due to %s",
|
||||||
|
msgType, err)
|
||||||
|
}
|
||||||
|
if err := registerer.Register(mm.numFailed); err != nil {
|
||||||
|
return fmt.Errorf("failed to register failed statistics of %s due to %s",
|
||||||
|
msgType, err)
|
||||||
|
}
|
||||||
|
if err := registerer.Register(mm.numReceived); err != nil {
|
||||||
|
return fmt.Errorf("failed to register received statistics of %s due to %s",
|
||||||
|
msgType, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type metrics struct {
|
||||||
|
numPeers prometheus.Gauge
|
||||||
|
|
||||||
|
getVersion, version,
|
||||||
|
getPeerlist, peerlist,
|
||||||
|
getAcceptedFrontier, acceptedFrontier,
|
||||||
|
getAccepted, accepted,
|
||||||
|
get, put,
|
||||||
|
pushQuery, pullQuery, chits messageMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metrics) initialize(registerer prometheus.Registerer) error {
|
||||||
|
m.numPeers = prometheus.NewGauge(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Namespace: "gecko",
|
||||||
|
Name: "peers",
|
||||||
|
Help: "Number of network peers",
|
||||||
|
})
|
||||||
|
|
||||||
|
errs := wrappers.Errs{}
|
||||||
|
if err := registerer.Register(m.numPeers); err != nil {
|
||||||
|
errs.Add(fmt.Errorf("failed to register peers statistics due to %s",
|
||||||
|
err))
|
||||||
|
}
|
||||||
|
|
||||||
|
errs.Add(m.getVersion.initialize(GetVersion, registerer))
|
||||||
|
errs.Add(m.version.initialize(Version, registerer))
|
||||||
|
errs.Add(m.getPeerlist.initialize(GetPeerList, registerer))
|
||||||
|
errs.Add(m.peerlist.initialize(PeerList, registerer))
|
||||||
|
errs.Add(m.getAcceptedFrontier.initialize(GetAcceptedFrontier, registerer))
|
||||||
|
errs.Add(m.acceptedFrontier.initialize(AcceptedFrontier, registerer))
|
||||||
|
errs.Add(m.getAccepted.initialize(GetAccepted, registerer))
|
||||||
|
errs.Add(m.accepted.initialize(Accepted, registerer))
|
||||||
|
errs.Add(m.get.initialize(Get, registerer))
|
||||||
|
errs.Add(m.put.initialize(Put, registerer))
|
||||||
|
errs.Add(m.pushQuery.initialize(PushQuery, registerer))
|
||||||
|
errs.Add(m.pullQuery.initialize(PullQuery, registerer))
|
||||||
|
errs.Add(m.chits.initialize(Chits, registerer))
|
||||||
|
|
||||||
|
return errs.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metrics) message(msgType Op) *messageMetrics {
|
||||||
|
switch msgType {
|
||||||
|
case GetVersion:
|
||||||
|
return &m.getVersion
|
||||||
|
case Version:
|
||||||
|
return &m.version
|
||||||
|
case GetPeerList:
|
||||||
|
return &m.getPeerlist
|
||||||
|
case PeerList:
|
||||||
|
return &m.peerlist
|
||||||
|
case GetAcceptedFrontier:
|
||||||
|
return &m.getAcceptedFrontier
|
||||||
|
case AcceptedFrontier:
|
||||||
|
return &m.acceptedFrontier
|
||||||
|
case GetAccepted:
|
||||||
|
return &m.getAccepted
|
||||||
|
case Accepted:
|
||||||
|
return &m.accepted
|
||||||
|
case Get:
|
||||||
|
return &m.get
|
||||||
|
case Put:
|
||||||
|
return &m.put
|
||||||
|
case PushQuery:
|
||||||
|
return &m.pushQuery
|
||||||
|
case PullQuery:
|
||||||
|
return &m.pullQuery
|
||||||
|
case Chits:
|
||||||
|
return &m.chits
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +1,26 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
// See the file LICENSE for licensing terms.
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
package networking
|
package network
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Msg represents a set of fields that can be serialized into a byte stream
|
// Msg represents a set of fields that can be serialized into a byte stream
|
||||||
type Msg interface {
|
type Msg interface {
|
||||||
Op() salticidae.Opcode
|
Op() Op
|
||||||
Get(Field) interface{}
|
Get(Field) interface{}
|
||||||
DataStream() salticidae.DataStream
|
Bytes() []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type msg struct {
|
type msg struct {
|
||||||
op salticidae.Opcode
|
op Op
|
||||||
ds salticidae.DataStream
|
|
||||||
fields map[Field]interface{}
|
fields map[Field]interface{}
|
||||||
|
bytes []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field returns the value of the specified field in this message
|
// Field returns the value of the specified field in this message
|
||||||
func (msg *msg) Op() salticidae.Opcode { return msg.op }
|
func (msg *msg) Op() Op { return msg.op }
|
||||||
|
|
||||||
// Field returns the value of the specified field in this message
|
// Field returns the value of the specified field in this message
|
||||||
func (msg *msg) Get(field Field) interface{} { return msg.fields[field] }
|
func (msg *msg) Get(field Field) interface{} { return msg.fields[field] }
|
||||||
|
|
||||||
// Bytes returns this message in bytes
|
// Bytes returns this message in bytes
|
||||||
func (msg *msg) DataStream() salticidae.DataStream { return msg.ds }
|
func (msg *msg) Bytes() []byte { return msg.bytes }
|
|
@ -0,0 +1,843 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/api/health"
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/snow/networking/router"
|
||||||
|
"github.com/ava-labs/gecko/snow/networking/sender"
|
||||||
|
"github.com/ava-labs/gecko/snow/triggers"
|
||||||
|
"github.com/ava-labs/gecko/snow/validators"
|
||||||
|
"github.com/ava-labs/gecko/utils"
|
||||||
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
|
"github.com/ava-labs/gecko/utils/random"
|
||||||
|
"github.com/ava-labs/gecko/utils/timer"
|
||||||
|
"github.com/ava-labs/gecko/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultInitialReconnectDelay = time.Second
|
||||||
|
defaultMaxReconnectDelay = time.Hour
|
||||||
|
defaultMaxMessageSize uint32 = 1 << 21
|
||||||
|
defaultSendQueueSize = 1 << 10
|
||||||
|
defaultMaxClockDifference = time.Minute
|
||||||
|
defaultPeerListGossipSpacing = time.Minute
|
||||||
|
defaultPeerListGossipSize = 100
|
||||||
|
defaultPeerListStakerGossipFraction = 2
|
||||||
|
defaultGetVersionTimeout = 2 * time.Second
|
||||||
|
defaultAllowPrivateIPs = true
|
||||||
|
defaultGossipSize = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
// Network defines the functionality of the networking library.
|
||||||
|
type Network interface {
|
||||||
|
// All consensus messages can be sent through this interface. Thread safety
|
||||||
|
// must be managed internally in the network.
|
||||||
|
sender.ExternalSender
|
||||||
|
|
||||||
|
// The network must be able to broadcast accepted decisions to random peers.
|
||||||
|
// Thread safety must be managed internally in the network.
|
||||||
|
triggers.Acceptor
|
||||||
|
|
||||||
|
// The network should be able to report the last time the network interacted
|
||||||
|
// with a peer
|
||||||
|
health.Heartbeater
|
||||||
|
|
||||||
|
// Should only be called once, will run until either a fatal error occurs,
|
||||||
|
// or the network is closed. Returns a non-nil error.
|
||||||
|
Dispatch() error
|
||||||
|
|
||||||
|
// Attempt to connect to this IP. Thread safety must be managed internally
|
||||||
|
// to the network. The network will never stop attempting to connect to this
|
||||||
|
// IP.
|
||||||
|
Track(ip utils.IPDesc)
|
||||||
|
|
||||||
|
// Register a new handler that is called whenever a peer is connected to or
|
||||||
|
// disconnected to. If the handler returns true, then it will never be
|
||||||
|
// called again. Thread safety must be managed internally in the network.
|
||||||
|
// The handler will initially be called with this local node's ID.
|
||||||
|
RegisterHandler(h Handler)
|
||||||
|
|
||||||
|
// Returns the IPs of nodes this network is currently connected to
|
||||||
|
// externally. Thread safety must be managed internally to the network.
|
||||||
|
IPs() []utils.IPDesc
|
||||||
|
|
||||||
|
// Close this network and all existing connections it has. Thread safety
|
||||||
|
// must be managed internally to the network. Calling close multiple times
|
||||||
|
// will return a nil error.
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type network struct {
|
||||||
|
// The metrics that this network tracks
|
||||||
|
metrics
|
||||||
|
|
||||||
|
log logging.Logger
|
||||||
|
id ids.ShortID
|
||||||
|
ip utils.IPDesc
|
||||||
|
networkID uint32
|
||||||
|
version version.Version
|
||||||
|
parser version.Parser
|
||||||
|
listener net.Listener
|
||||||
|
dialer Dialer
|
||||||
|
serverUpgrader Upgrader
|
||||||
|
clientUpgrader Upgrader
|
||||||
|
vdrs validators.Set // set of current validators in the AVAnet
|
||||||
|
router router.Router // router must be thread safe
|
||||||
|
|
||||||
|
nodeID uint32
|
||||||
|
|
||||||
|
clock timer.Clock
|
||||||
|
lastHeartbeat int64
|
||||||
|
|
||||||
|
initialReconnectDelay time.Duration
|
||||||
|
maxReconnectDelay time.Duration
|
||||||
|
maxMessageSize uint32
|
||||||
|
sendQueueSize int
|
||||||
|
maxClockDifference time.Duration
|
||||||
|
peerListGossipSpacing time.Duration
|
||||||
|
peerListGossipSize int
|
||||||
|
peerListStakerGossipFraction int
|
||||||
|
getVersionTimeout time.Duration
|
||||||
|
allowPrivateIPs bool
|
||||||
|
gossipSize int
|
||||||
|
|
||||||
|
executor timer.Executor
|
||||||
|
|
||||||
|
b Builder
|
||||||
|
|
||||||
|
stateLock sync.Mutex
|
||||||
|
closed bool
|
||||||
|
disconnectedIPs map[string]struct{}
|
||||||
|
connectedIPs map[string]struct{}
|
||||||
|
// TODO: bound the size of [myIPs] to avoid DoS. LRU caching would be ideal
|
||||||
|
myIPs map[string]struct{} // set of IPs that resulted in my ID.
|
||||||
|
peers map[[20]byte]*peer
|
||||||
|
handlers []Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultNetwork returns a new Network implementation with the provided
|
||||||
|
// parameters and some reasonable default values.
|
||||||
|
func NewDefaultNetwork(
|
||||||
|
registerer prometheus.Registerer,
|
||||||
|
log logging.Logger,
|
||||||
|
id ids.ShortID,
|
||||||
|
ip utils.IPDesc,
|
||||||
|
networkID uint32,
|
||||||
|
version version.Version,
|
||||||
|
parser version.Parser,
|
||||||
|
listener net.Listener,
|
||||||
|
dialer Dialer,
|
||||||
|
serverUpgrader,
|
||||||
|
clientUpgrader Upgrader,
|
||||||
|
vdrs validators.Set,
|
||||||
|
router router.Router,
|
||||||
|
) Network {
|
||||||
|
return NewNetwork(
|
||||||
|
registerer,
|
||||||
|
log,
|
||||||
|
id,
|
||||||
|
ip,
|
||||||
|
networkID,
|
||||||
|
version,
|
||||||
|
parser,
|
||||||
|
listener,
|
||||||
|
dialer,
|
||||||
|
serverUpgrader,
|
||||||
|
clientUpgrader,
|
||||||
|
vdrs,
|
||||||
|
router,
|
||||||
|
defaultInitialReconnectDelay,
|
||||||
|
defaultMaxReconnectDelay,
|
||||||
|
defaultMaxMessageSize,
|
||||||
|
defaultSendQueueSize,
|
||||||
|
defaultMaxClockDifference,
|
||||||
|
defaultPeerListGossipSpacing,
|
||||||
|
defaultPeerListGossipSize,
|
||||||
|
defaultPeerListStakerGossipFraction,
|
||||||
|
defaultGetVersionTimeout,
|
||||||
|
defaultAllowPrivateIPs,
|
||||||
|
defaultGossipSize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetwork returns a new Network implementation with the provided parameters.
|
||||||
|
func NewNetwork(
|
||||||
|
registerer prometheus.Registerer,
|
||||||
|
log logging.Logger,
|
||||||
|
id ids.ShortID,
|
||||||
|
ip utils.IPDesc,
|
||||||
|
networkID uint32,
|
||||||
|
version version.Version,
|
||||||
|
parser version.Parser,
|
||||||
|
listener net.Listener,
|
||||||
|
dialer Dialer,
|
||||||
|
serverUpgrader,
|
||||||
|
clientUpgrader Upgrader,
|
||||||
|
vdrs validators.Set,
|
||||||
|
router router.Router,
|
||||||
|
initialReconnectDelay,
|
||||||
|
maxReconnectDelay time.Duration,
|
||||||
|
maxMessageSize uint32,
|
||||||
|
sendQueueSize int,
|
||||||
|
maxClockDifference time.Duration,
|
||||||
|
peerListGossipSpacing time.Duration,
|
||||||
|
peerListGossipSize int,
|
||||||
|
peerListStakerGossipFraction int,
|
||||||
|
getVersionTimeout time.Duration,
|
||||||
|
allowPrivateIPs bool,
|
||||||
|
gossipSize int,
|
||||||
|
) Network {
|
||||||
|
net := &network{
|
||||||
|
log: log,
|
||||||
|
id: id,
|
||||||
|
ip: ip,
|
||||||
|
networkID: networkID,
|
||||||
|
version: version,
|
||||||
|
parser: parser,
|
||||||
|
listener: listener,
|
||||||
|
dialer: dialer,
|
||||||
|
serverUpgrader: serverUpgrader,
|
||||||
|
clientUpgrader: clientUpgrader,
|
||||||
|
vdrs: vdrs,
|
||||||
|
router: router,
|
||||||
|
nodeID: rand.Uint32(),
|
||||||
|
initialReconnectDelay: initialReconnectDelay,
|
||||||
|
maxReconnectDelay: maxReconnectDelay,
|
||||||
|
maxMessageSize: maxMessageSize,
|
||||||
|
sendQueueSize: sendQueueSize,
|
||||||
|
maxClockDifference: maxClockDifference,
|
||||||
|
peerListGossipSpacing: peerListGossipSpacing,
|
||||||
|
peerListGossipSize: peerListGossipSize,
|
||||||
|
peerListStakerGossipFraction: peerListStakerGossipFraction,
|
||||||
|
getVersionTimeout: getVersionTimeout,
|
||||||
|
allowPrivateIPs: allowPrivateIPs,
|
||||||
|
gossipSize: gossipSize,
|
||||||
|
|
||||||
|
disconnectedIPs: make(map[string]struct{}),
|
||||||
|
connectedIPs: make(map[string]struct{}),
|
||||||
|
myIPs: map[string]struct{}{ip.String(): struct{}{}},
|
||||||
|
peers: make(map[[20]byte]*peer),
|
||||||
|
}
|
||||||
|
net.initialize(registerer)
|
||||||
|
net.executor.Initialize()
|
||||||
|
net.heartbeat()
|
||||||
|
return net
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAcceptedFrontier implements the Sender interface.
|
||||||
|
func (n *network) GetAcceptedFrontier(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32) {
|
||||||
|
msg, err := n.b.GetAcceptedFrontier(chainID, requestID)
|
||||||
|
n.log.AssertNoError(err)
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
for _, validatorID := range validatorIDs.List() {
|
||||||
|
vID := validatorID
|
||||||
|
peer, sent := n.peers[vID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.executor.Add(func() { n.router.GetAcceptedFrontierFailed(vID, chainID, requestID) })
|
||||||
|
n.getAcceptedFrontier.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.getAcceptedFrontier.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptedFrontier implements the Sender interface.
|
||||||
|
func (n *network) AcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
||||||
|
msg, err := n.b.AcceptedFrontier(chainID, requestID, containerIDs)
|
||||||
|
if err != nil {
|
||||||
|
n.log.Error("attempted to pack too large of an AcceptedFrontier message.\nNumber of containerIDs: %d",
|
||||||
|
containerIDs.Len())
|
||||||
|
return // Packing message failed
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
peer, sent := n.peers[validatorID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.log.Debug("failed to send an AcceptedFrontier message to: %s", validatorID)
|
||||||
|
n.acceptedFrontier.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.acceptedFrontier.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccepted implements the Sender interface.
|
||||||
|
func (n *network) GetAccepted(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
||||||
|
msg, err := n.b.GetAccepted(chainID, requestID, containerIDs)
|
||||||
|
if err != nil {
|
||||||
|
for _, validatorID := range validatorIDs.List() {
|
||||||
|
vID := validatorID
|
||||||
|
n.executor.Add(func() { n.router.GetAcceptedFailed(vID, chainID, requestID) })
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
for _, validatorID := range validatorIDs.List() {
|
||||||
|
vID := validatorID
|
||||||
|
peer, sent := n.peers[vID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.executor.Add(func() { n.router.GetAcceptedFailed(vID, chainID, requestID) })
|
||||||
|
n.getAccepted.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.getAccepted.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accepted implements the Sender interface.
|
||||||
|
func (n *network) Accepted(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
||||||
|
msg, err := n.b.Accepted(chainID, requestID, containerIDs)
|
||||||
|
if err != nil {
|
||||||
|
n.log.Error("attempted to pack too large of an Accepted message.\nNumber of containerIDs: %d",
|
||||||
|
containerIDs.Len())
|
||||||
|
return // Packing message failed
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
peer, sent := n.peers[validatorID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.log.Debug("failed to send an Accepted message to: %s", validatorID)
|
||||||
|
n.accepted.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.accepted.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the Sender interface.
|
||||||
|
func (n *network) Get(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
|
||||||
|
msg, err := n.b.Get(chainID, requestID, containerID)
|
||||||
|
n.log.AssertNoError(err)
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
peer, sent := n.peers[validatorID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.log.Debug("failed to send a Get message to: %s", validatorID)
|
||||||
|
n.get.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.get.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put implements the Sender interface.
|
||||||
|
func (n *network) Put(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
|
||||||
|
msg, err := n.b.Put(chainID, requestID, containerID, container)
|
||||||
|
if err != nil {
|
||||||
|
n.log.Error("failed to build Put message because of container of size %d", len(container))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
peer, sent := n.peers[validatorID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.log.Debug("failed to send a Put message to: %s", validatorID)
|
||||||
|
n.put.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.put.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushQuery implements the Sender interface.
|
||||||
|
func (n *network) PushQuery(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
|
||||||
|
msg, err := n.b.PushQuery(chainID, requestID, containerID, container)
|
||||||
|
if err != nil {
|
||||||
|
for _, validatorID := range validatorIDs.List() {
|
||||||
|
vID := validatorID
|
||||||
|
n.executor.Add(func() { n.router.QueryFailed(vID, chainID, requestID) })
|
||||||
|
}
|
||||||
|
n.log.Error("attempted to pack too large of a PushQuery message.\nContainer length: %d", len(container))
|
||||||
|
return // Packing message failed
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
for _, validatorID := range validatorIDs.List() {
|
||||||
|
vID := validatorID
|
||||||
|
peer, sent := n.peers[vID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.log.Debug("failed sending a PushQuery message to: %s", vID)
|
||||||
|
n.executor.Add(func() { n.router.QueryFailed(vID, chainID, requestID) })
|
||||||
|
n.pushQuery.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.pushQuery.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullQuery implements the Sender interface.
|
||||||
|
func (n *network) PullQuery(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerID ids.ID) {
|
||||||
|
msg, err := n.b.PullQuery(chainID, requestID, containerID)
|
||||||
|
n.log.AssertNoError(err)
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
for _, validatorID := range validatorIDs.List() {
|
||||||
|
vID := validatorID
|
||||||
|
peer, sent := n.peers[vID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.log.Debug("failed sending a PullQuery message to: %s", vID)
|
||||||
|
n.executor.Add(func() { n.router.QueryFailed(vID, chainID, requestID) })
|
||||||
|
n.pullQuery.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.pullQuery.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chits implements the Sender interface.
|
||||||
|
func (n *network) Chits(validatorID ids.ShortID, chainID ids.ID, requestID uint32, votes ids.Set) {
|
||||||
|
msg, err := n.b.Chits(chainID, requestID, votes)
|
||||||
|
if err != nil {
|
||||||
|
n.log.Error("failed to build Chits message because of %d votes", votes.Len())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
peer, sent := n.peers[validatorID.Key()]
|
||||||
|
if sent {
|
||||||
|
sent = peer.send(msg)
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
n.log.Debug("failed to send a Chits message to: %s", validatorID)
|
||||||
|
n.chits.numFailed.Inc()
|
||||||
|
} else {
|
||||||
|
n.chits.numSent.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gossip attempts to gossip the container to the network
|
||||||
|
func (n *network) Gossip(chainID, containerID ids.ID, container []byte) {
|
||||||
|
if err := n.gossipContainer(chainID, containerID, container); err != nil {
|
||||||
|
n.log.Error("error gossiping container %s to %s: %s", containerID, chainID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept is called after every consensus decision
|
||||||
|
func (n *network) Accept(chainID, containerID ids.ID, container []byte) error {
|
||||||
|
return n.gossipContainer(chainID, containerID, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// heartbeat registers a new heartbeat to signal liveness
|
||||||
|
func (n *network) heartbeat() { atomic.StoreInt64(&n.lastHeartbeat, n.clock.Time().Unix()) }
|
||||||
|
|
||||||
|
// GetHeartbeat returns the most recent heartbeat time
|
||||||
|
func (n *network) GetHeartbeat() int64 { return atomic.LoadInt64(&n.lastHeartbeat) }
|
||||||
|
|
||||||
|
// Dispatch starts accepting connections from other nodes attempting to connect
|
||||||
|
// to this node.
|
||||||
|
func (n *network) Dispatch() error {
|
||||||
|
go n.gossip()
|
||||||
|
for {
|
||||||
|
conn, err := n.listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go n.upgrade(&peer{
|
||||||
|
net: n,
|
||||||
|
conn: conn,
|
||||||
|
}, n.serverUpgrader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterHandler implements the Network interface
|
||||||
|
func (n *network) RegisterHandler(h Handler) {
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
if h.Connected(n.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, peer := range n.peers {
|
||||||
|
if peer.connected {
|
||||||
|
if h.Connected(peer.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.handlers = append(n.handlers, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPs implements the Network interface
|
||||||
|
func (n *network) IPs() []utils.IPDesc {
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
ips := []utils.IPDesc(nil)
|
||||||
|
for _, peer := range n.peers {
|
||||||
|
if peer.connected {
|
||||||
|
ips = append(ips, peer.ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the Network interface
|
||||||
|
func (n *network) Close() error {
|
||||||
|
n.stateLock.Lock()
|
||||||
|
if n.closed {
|
||||||
|
n.stateLock.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n.closed = true
|
||||||
|
err := n.listener.Close()
|
||||||
|
|
||||||
|
peersToClose := []*peer(nil)
|
||||||
|
for _, peer := range n.peers {
|
||||||
|
peersToClose = append(peersToClose, peer)
|
||||||
|
}
|
||||||
|
n.stateLock.Unlock()
|
||||||
|
|
||||||
|
for _, peer := range peersToClose {
|
||||||
|
peer.Close() // Grabs the stateLock
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track implements the Network interface
|
||||||
|
func (n *network) Track(ip utils.IPDesc) {
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
n.track(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held.
|
||||||
|
func (n *network) gossipContainer(chainID, containerID ids.ID, container []byte) error {
|
||||||
|
msg, err := n.b.Put(chainID, math.MaxUint32, containerID, container)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("attempted to pack too large of a Put message.\nContainer length: %d", len(container))
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
allPeers := make([]*peer, 0, len(n.peers))
|
||||||
|
for _, peer := range n.peers {
|
||||||
|
allPeers = append(allPeers, peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
numToGossip := n.gossipSize
|
||||||
|
if numToGossip > len(allPeers) {
|
||||||
|
numToGossip = len(allPeers)
|
||||||
|
}
|
||||||
|
|
||||||
|
sampler := random.Uniform{N: len(allPeers)}
|
||||||
|
for i := 0; i < numToGossip; i++ {
|
||||||
|
if allPeers[sampler.Sample()].send(msg) {
|
||||||
|
n.put.numSent.Inc()
|
||||||
|
} else {
|
||||||
|
n.put.numFailed.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is held.
|
||||||
|
func (n *network) track(ip utils.IPDesc) {
|
||||||
|
if n.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
str := ip.String()
|
||||||
|
if _, ok := n.disconnectedIPs[str]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := n.connectedIPs[str]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := n.myIPs[str]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.disconnectedIPs[str] = struct{}{}
|
||||||
|
|
||||||
|
go n.connectTo(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held. Only returns after the network is closed.
|
||||||
|
func (n *network) gossip() {
|
||||||
|
t := time.NewTicker(n.peerListGossipSpacing)
|
||||||
|
defer t.Stop()
|
||||||
|
|
||||||
|
for range t.C {
|
||||||
|
ips := n.validatorIPs()
|
||||||
|
if len(ips) == 0 {
|
||||||
|
n.log.Debug("skipping validator gossiping as no public validators are connected")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg, err := n.b.PeerList(ips)
|
||||||
|
if err != nil {
|
||||||
|
n.log.Warn("failed to gossip PeerList message due to %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
if n.closed {
|
||||||
|
n.stateLock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stakers := []*peer(nil)
|
||||||
|
nonStakers := []*peer(nil)
|
||||||
|
for _, peer := range n.peers {
|
||||||
|
if n.vdrs.Contains(peer.id) {
|
||||||
|
stakers = append(stakers, peer)
|
||||||
|
} else {
|
||||||
|
nonStakers = append(nonStakers, peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
numStakersToSend := (n.peerListGossipSize + n.peerListStakerGossipFraction - 1) / n.peerListStakerGossipFraction
|
||||||
|
if len(stakers) < numStakersToSend {
|
||||||
|
numStakersToSend = len(stakers)
|
||||||
|
}
|
||||||
|
numNonStakersToSend := n.peerListGossipSize - numStakersToSend
|
||||||
|
if len(nonStakers) < numNonStakersToSend {
|
||||||
|
numNonStakersToSend = len(nonStakers)
|
||||||
|
}
|
||||||
|
|
||||||
|
sampler := random.Uniform{N: len(stakers)}
|
||||||
|
for i := 0; i < numStakersToSend; i++ {
|
||||||
|
stakers[sampler.Sample()].send(msg)
|
||||||
|
}
|
||||||
|
sampler.N = len(nonStakers)
|
||||||
|
sampler.Replace()
|
||||||
|
for i := 0; i < numNonStakersToSend; i++ {
|
||||||
|
nonStakers[sampler.Sample()].send(msg)
|
||||||
|
}
|
||||||
|
n.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held. Only returns if the ip is connected to or
|
||||||
|
// the network is closed
|
||||||
|
func (n *network) connectTo(ip utils.IPDesc) {
|
||||||
|
str := ip.String()
|
||||||
|
delay := n.initialReconnectDelay
|
||||||
|
for {
|
||||||
|
n.stateLock.Lock()
|
||||||
|
_, isDisconnected := n.disconnectedIPs[str]
|
||||||
|
_, isConnected := n.connectedIPs[str]
|
||||||
|
_, isMyself := n.myIPs[str]
|
||||||
|
closed := n.closed
|
||||||
|
|
||||||
|
n.stateLock.Unlock()
|
||||||
|
|
||||||
|
if !isDisconnected || isConnected || isMyself || closed {
|
||||||
|
// If the IP was discovered by the peer connecting to us, we don't
|
||||||
|
// need to attempt to connect anymore
|
||||||
|
|
||||||
|
// If the IP was discovered to be our IP address, we don't need to
|
||||||
|
// attempt to connect anymore
|
||||||
|
|
||||||
|
// If the network was closed, we should stop attempting to connect
|
||||||
|
// to the peer
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := n.attemptConnect(ip)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.log.Verbo("error attempting to connect to %s: %s. Reattempting in %s",
|
||||||
|
ip, err, delay)
|
||||||
|
|
||||||
|
time.Sleep(delay)
|
||||||
|
delay *= 2
|
||||||
|
if delay > n.maxReconnectDelay {
|
||||||
|
delay = n.maxReconnectDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held. Returns nil if a connection was able to be
|
||||||
|
// established, or the network is closed.
|
||||||
|
func (n *network) attemptConnect(ip utils.IPDesc) error {
|
||||||
|
n.log.Verbo("attempting to connect to %s", ip)
|
||||||
|
|
||||||
|
conn, err := n.dialer.Dial(ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return n.upgrade(&peer{
|
||||||
|
net: n,
|
||||||
|
ip: ip,
|
||||||
|
conn: conn,
|
||||||
|
}, n.clientUpgrader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held. Returns an error if the peer's connection
|
||||||
|
// wasn't able to be upgraded.
|
||||||
|
func (n *network) upgrade(p *peer, upgrader Upgrader) error {
|
||||||
|
id, conn, err := upgrader.Upgrade(p.conn)
|
||||||
|
if err != nil {
|
||||||
|
n.log.Verbo("failed to upgrade connection with %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.sender = make(chan []byte, n.sendQueueSize)
|
||||||
|
p.id = id
|
||||||
|
p.conn = conn
|
||||||
|
|
||||||
|
key := id.Key()
|
||||||
|
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
if n.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this connection is myself, then I should delete the connection and
|
||||||
|
// mark the IP as one of mine.
|
||||||
|
if id.Equals(n.id) {
|
||||||
|
if !p.ip.IsZero() {
|
||||||
|
// if n.ip is less useful than p.ip set it to this IP
|
||||||
|
if n.ip.IsZero() {
|
||||||
|
n.log.Info("setting my ip to %s because I was able to connect to myself through this channel",
|
||||||
|
p.ip)
|
||||||
|
n.ip = p.ip
|
||||||
|
}
|
||||||
|
str := p.ip.String()
|
||||||
|
delete(n.disconnectedIPs, str)
|
||||||
|
n.myIPs[str] = struct{}{}
|
||||||
|
}
|
||||||
|
p.conn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := n.peers[key]; ok {
|
||||||
|
if !p.ip.IsZero() {
|
||||||
|
delete(n.disconnectedIPs, p.ip.String())
|
||||||
|
}
|
||||||
|
p.conn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n.peers[key] = p
|
||||||
|
n.numPeers.Set(float64(len(n.peers)))
|
||||||
|
p.Start()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held. Returns the ips of connections that have
|
||||||
|
// valid IPs that are marked as validators.
|
||||||
|
func (n *network) validatorIPs() []utils.IPDesc {
|
||||||
|
n.stateLock.Lock()
|
||||||
|
defer n.stateLock.Unlock()
|
||||||
|
|
||||||
|
ips := []utils.IPDesc(nil)
|
||||||
|
for _, peer := range n.peers {
|
||||||
|
if peer.connected &&
|
||||||
|
!peer.ip.IsZero() &&
|
||||||
|
n.vdrs.Contains(peer.id) {
|
||||||
|
ips = append(ips, peer.ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is held when called
|
||||||
|
// should only be called after the peer is marked as connected. Should not be
|
||||||
|
// called after disconnected is called with this peer.
|
||||||
|
func (n *network) connected(p *peer) {
|
||||||
|
n.log.Debug("connected to %s at %s", p.id, p.ip)
|
||||||
|
if !p.ip.IsZero() {
|
||||||
|
str := p.ip.String()
|
||||||
|
|
||||||
|
delete(n.disconnectedIPs, str)
|
||||||
|
n.connectedIPs[str] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(n.handlers); {
|
||||||
|
if n.handlers[i].Connected(p.id) {
|
||||||
|
newLen := len(n.handlers) - 1
|
||||||
|
n.handlers[i] = n.handlers[newLen] // remove the current handler
|
||||||
|
n.handlers = n.handlers[:newLen]
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is held when called
|
||||||
|
// should only be called after the peer is marked as connected.
|
||||||
|
func (n *network) disconnected(p *peer) {
|
||||||
|
n.log.Debug("disconnected from %s at %s", p.id, p.ip)
|
||||||
|
key := p.id.Key()
|
||||||
|
delete(n.peers, key)
|
||||||
|
n.numPeers.Set(float64(len(n.peers)))
|
||||||
|
|
||||||
|
if !p.ip.IsZero() {
|
||||||
|
str := p.ip.String()
|
||||||
|
|
||||||
|
delete(n.disconnectedIPs, str)
|
||||||
|
delete(n.connectedIPs, str)
|
||||||
|
|
||||||
|
n.track(p.ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.connected {
|
||||||
|
for i := 0; i < len(n.handlers); {
|
||||||
|
if n.handlers[i].Disconnected(p.id) {
|
||||||
|
newLen := len(n.handlers) - 1
|
||||||
|
n.handlers[i] = n.handlers[newLen] // remove the current handler
|
||||||
|
n.handlers = n.handlers[:newLen]
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,581 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/utils"
|
||||||
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
|
"github.com/ava-labs/gecko/utils/wrappers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type peer struct {
|
||||||
|
net *network // network this peer is part of
|
||||||
|
|
||||||
|
// if the version message has been received and is valid. is only modified
|
||||||
|
// on the connection's reader routine with the network state lock held.
|
||||||
|
connected bool
|
||||||
|
|
||||||
|
// only close the peer once
|
||||||
|
once sync.Once
|
||||||
|
|
||||||
|
// if the close function has been called, is only modifed when the network
|
||||||
|
// state lock held.
|
||||||
|
closed bool
|
||||||
|
|
||||||
|
// queue of messages this connection is attempting to send the peer. Is
|
||||||
|
// closed when the connection is closed.
|
||||||
|
sender chan []byte
|
||||||
|
|
||||||
|
// ip may or may not be set when the peer is first started. is only modified
|
||||||
|
// on the connection's reader routine with the network state lock held.
|
||||||
|
ip utils.IPDesc
|
||||||
|
|
||||||
|
// id should be set when the peer is first started.
|
||||||
|
id ids.ShortID
|
||||||
|
|
||||||
|
// the connection object that is used to read/write messages from
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// assume the stateLock is held
|
||||||
|
func (p *peer) Start() {
|
||||||
|
go p.ReadMessages()
|
||||||
|
go p.WriteMessages()
|
||||||
|
|
||||||
|
// Initially send the version to the peer
|
||||||
|
go p.Version()
|
||||||
|
go p.requestVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
// request the version from the peer until we get the version from them
|
||||||
|
func (p *peer) requestVersion() {
|
||||||
|
t := time.NewTicker(p.net.getVersionTimeout)
|
||||||
|
defer t.Stop()
|
||||||
|
|
||||||
|
for range t.C {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
connected := p.connected
|
||||||
|
closed := p.closed
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
|
||||||
|
if connected || closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.GetVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to read messages from the peer
|
||||||
|
func (p *peer) ReadMessages() {
|
||||||
|
defer p.Close()
|
||||||
|
|
||||||
|
pendingBuffer := wrappers.Packer{}
|
||||||
|
readBuffer := make([]byte, 1<<10)
|
||||||
|
for {
|
||||||
|
read, err := p.conn.Read(readBuffer)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Verbo("error on connection read to %s %s", p.id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingBuffer.Bytes = append(pendingBuffer.Bytes, readBuffer[:read]...)
|
||||||
|
|
||||||
|
msgBytes := pendingBuffer.UnpackBytes()
|
||||||
|
if pendingBuffer.Errored() {
|
||||||
|
// if reading the bytes errored, then we haven't read the full
|
||||||
|
// message yet
|
||||||
|
pendingBuffer.Offset = 0
|
||||||
|
pendingBuffer.Err = nil
|
||||||
|
|
||||||
|
if uint32(len(pendingBuffer.Bytes)) > p.net.maxMessageSize+wrappers.IntLen {
|
||||||
|
// we have read more bytes than the max message size allows for,
|
||||||
|
// so we should terminate this connection
|
||||||
|
|
||||||
|
p.net.log.Verbo("error reading too many bytes on %s %s", p.id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should try to read more bytes to finish the message
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// we read the full message bytes
|
||||||
|
|
||||||
|
// set the pending bytes to any extra bytes that were read
|
||||||
|
pendingBuffer.Bytes = pendingBuffer.Bytes[pendingBuffer.Offset:]
|
||||||
|
// set the offset back to the start of the next message
|
||||||
|
pendingBuffer.Offset = 0
|
||||||
|
|
||||||
|
if uint32(len(msgBytes)) > p.net.maxMessageSize {
|
||||||
|
// if this message is longer than the max message length, then we
|
||||||
|
// should terminate this connection
|
||||||
|
|
||||||
|
p.net.log.Verbo("error reading too many bytes on %s %s", p.id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.net.log.Verbo("parsing new message from %s:\n%s",
|
||||||
|
p.id,
|
||||||
|
formatting.DumpBytes{Bytes: msgBytes})
|
||||||
|
|
||||||
|
msg, err := p.net.b.Parse(msgBytes)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Debug("failed to parse new message from %s:\n%s\n%s",
|
||||||
|
p.id,
|
||||||
|
formatting.DumpBytes{Bytes: msgBytes},
|
||||||
|
err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.handle(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to write messages to the peer
|
||||||
|
func (p *peer) WriteMessages() {
|
||||||
|
defer p.Close()
|
||||||
|
|
||||||
|
for msg := range p.sender {
|
||||||
|
p.net.log.Verbo("sending new message to %s:\n%s",
|
||||||
|
p.id,
|
||||||
|
formatting.DumpBytes{Bytes: msg})
|
||||||
|
|
||||||
|
packer := wrappers.Packer{Bytes: make([]byte, len(msg)+wrappers.IntLen)}
|
||||||
|
packer.PackBytes(msg)
|
||||||
|
msg = packer.Bytes
|
||||||
|
for len(msg) > 0 {
|
||||||
|
written, err := p.conn.Write(msg)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Verbo("error writing to %s at %s due to: %s", p.id, p.ip, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg = msg[written:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send assumes that the stateLock is not held.
|
||||||
|
func (p *peer) Send(msg Msg) bool {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
defer p.net.stateLock.Unlock()
|
||||||
|
|
||||||
|
return p.send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send assumes that the stateLock is held.
|
||||||
|
func (p *peer) send(msg Msg) bool {
|
||||||
|
if p.closed {
|
||||||
|
p.net.log.Debug("dropping message to %s due to a closed connection", p.id)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case p.sender <- msg.Bytes():
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
p.net.log.Debug("dropping message to %s due to a full send queue", p.id)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) handle(msg Msg) {
|
||||||
|
p.net.heartbeat()
|
||||||
|
|
||||||
|
op := msg.Op()
|
||||||
|
msgMetrics := p.net.message(op)
|
||||||
|
if msgMetrics == nil {
|
||||||
|
p.net.log.Debug("dropping an unknown message from %s with op %d", p.id, op)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msgMetrics.numReceived.Inc()
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case Version:
|
||||||
|
p.version(msg)
|
||||||
|
return
|
||||||
|
case GetVersion:
|
||||||
|
p.getVersion(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !p.connected {
|
||||||
|
p.net.log.Debug("dropping message from %s because the connection hasn't been established yet", p.id)
|
||||||
|
|
||||||
|
// send a get version message so that the peer's future messages are hopefully not dropped
|
||||||
|
p.GetVersion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch op {
|
||||||
|
case GetPeerList:
|
||||||
|
p.getPeerList(msg)
|
||||||
|
case PeerList:
|
||||||
|
p.peerList(msg)
|
||||||
|
case GetAcceptedFrontier:
|
||||||
|
p.getAcceptedFrontier(msg)
|
||||||
|
case AcceptedFrontier:
|
||||||
|
p.acceptedFrontier(msg)
|
||||||
|
case GetAccepted:
|
||||||
|
p.getAccepted(msg)
|
||||||
|
case Accepted:
|
||||||
|
p.accepted(msg)
|
||||||
|
case Get:
|
||||||
|
p.get(msg)
|
||||||
|
case Put:
|
||||||
|
p.put(msg)
|
||||||
|
case PushQuery:
|
||||||
|
p.pushQuery(msg)
|
||||||
|
case PullQuery:
|
||||||
|
p.pullQuery(msg)
|
||||||
|
case Chits:
|
||||||
|
p.chits(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) Close() { p.once.Do(p.close) }
|
||||||
|
|
||||||
|
// assumes only `peer.Close` calls this
|
||||||
|
func (p *peer) close() {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
defer p.net.stateLock.Unlock()
|
||||||
|
|
||||||
|
p.closed = true
|
||||||
|
p.conn.Close()
|
||||||
|
close(p.sender)
|
||||||
|
p.net.disconnected(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) GetVersion() {
|
||||||
|
msg, err := p.net.b.GetVersion()
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
p.Send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) Version() {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
msg, err := p.net.b.Version(
|
||||||
|
p.net.networkID,
|
||||||
|
p.net.nodeID,
|
||||||
|
p.net.clock.Unix(),
|
||||||
|
p.net.ip,
|
||||||
|
p.net.version.String(),
|
||||||
|
)
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
p.Send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) GetPeerList() {
|
||||||
|
msg, err := p.net.b.GetPeerList()
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
p.Send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) PeerList(peers []utils.IPDesc) {
|
||||||
|
msg, err := p.net.b.PeerList(peers)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Warn("failed to send PeerList message due to %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Send(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) getVersion(_ Msg) { p.Version() }
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) version(msg Msg) {
|
||||||
|
if p.connected {
|
||||||
|
p.net.log.Verbo("dropping duplicated version message from %s", p.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if networkID := msg.Get(NetworkID).(uint32); networkID != p.net.networkID {
|
||||||
|
p.net.log.Debug("peer's network ID doesn't match our networkID: Peer's = %d ; Ours = %d",
|
||||||
|
networkID,
|
||||||
|
p.net.networkID)
|
||||||
|
|
||||||
|
// By clearing the IP, we will not attempt to reconnect to this peer
|
||||||
|
if !p.ip.IsZero() {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
delete(p.net.disconnectedIPs, p.ip.String())
|
||||||
|
p.ip = utils.IPDesc{}
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
p.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeID := msg.Get(NodeID).(uint32); nodeID == p.net.nodeID {
|
||||||
|
p.net.log.Debug("peer's node ID matches our nodeID")
|
||||||
|
|
||||||
|
// By clearing the IP, we will not attempt to reconnect to this peer
|
||||||
|
if !p.ip.IsZero() {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
str := p.ip.String()
|
||||||
|
p.net.myIPs[str] = struct{}{}
|
||||||
|
delete(p.net.disconnectedIPs, str)
|
||||||
|
p.ip = utils.IPDesc{}
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
p.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
myTime := float64(p.net.clock.Unix())
|
||||||
|
if peerTime := float64(msg.Get(MyTime).(uint64)); math.Abs(peerTime-myTime) > p.net.maxClockDifference.Seconds() {
|
||||||
|
p.net.log.Debug("peer's clock is too far out of sync with mine. Peer's = %d, Ours = %d (seconds)",
|
||||||
|
uint64(peerTime),
|
||||||
|
uint64(myTime))
|
||||||
|
|
||||||
|
// By clearing the IP, we will not attempt to reconnect to this peer
|
||||||
|
if !p.ip.IsZero() {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
delete(p.net.disconnectedIPs, p.ip.String())
|
||||||
|
p.ip = utils.IPDesc{}
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
p.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerVersionStr := msg.Get(VersionStr).(string)
|
||||||
|
peerVersion, err := p.net.parser.Parse(peerVersionStr)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Debug("peer version could not be parsed due to %s", err)
|
||||||
|
|
||||||
|
// By clearing the IP, we will not attempt to reconnect to this peer
|
||||||
|
if !p.ip.IsZero() {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
delete(p.net.disconnectedIPs, p.ip.String())
|
||||||
|
p.ip = utils.IPDesc{}
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
p.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
peerVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.net.version.Compatible(peerVersion); err != nil {
|
||||||
|
p.net.log.Debug("peer version not compatible due to %s", err)
|
||||||
|
|
||||||
|
// By clearing the IP, we will not attempt to reconnect to this peer
|
||||||
|
if !p.ip.IsZero() {
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
delete(p.net.disconnectedIPs, p.ip.String())
|
||||||
|
p.ip = utils.IPDesc{}
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
p.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ip.IsZero() {
|
||||||
|
// we only care about the claimed IP if we don't know the IP yet
|
||||||
|
peerIP := msg.Get(IP).(utils.IPDesc)
|
||||||
|
|
||||||
|
addr := p.conn.RemoteAddr()
|
||||||
|
localPeerIP, err := utils.ToIPDesc(addr.String())
|
||||||
|
if err == nil {
|
||||||
|
// If we have no clue what the peer's IP is, we can't perform any
|
||||||
|
// verification
|
||||||
|
if bytes.Equal(peerIP.IP, localPeerIP.IP) {
|
||||||
|
// if the IPs match, add this ip:port pair to be tracked
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
p.ip = peerIP
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.SendPeerList()
|
||||||
|
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
defer p.net.stateLock.Unlock()
|
||||||
|
|
||||||
|
// the network connected function can only be called if disconnected wasn't
|
||||||
|
// already called
|
||||||
|
if p.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.connected = true
|
||||||
|
p.net.connected(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) SendPeerList() {
|
||||||
|
ips := p.net.validatorIPs()
|
||||||
|
reply, err := p.net.b.PeerList(ips)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Warn("failed to send PeerList message due to %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Send(reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) getPeerList(_ Msg) { p.SendPeerList() }
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) peerList(msg Msg) {
|
||||||
|
ips := msg.Get(Peers).([]utils.IPDesc)
|
||||||
|
|
||||||
|
p.net.stateLock.Lock()
|
||||||
|
for _, ip := range ips {
|
||||||
|
if !ip.Equal(p.net.ip) &&
|
||||||
|
!ip.IsZero() &&
|
||||||
|
(p.net.allowPrivateIPs || !ip.IsPrivate()) {
|
||||||
|
// TODO: only try to connect once
|
||||||
|
p.net.track(ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.net.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) getAcceptedFrontier(msg Msg) {
|
||||||
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
requestID := msg.Get(RequestID).(uint32)
|
||||||
|
|
||||||
|
p.net.router.GetAcceptedFrontier(p.id, chainID, requestID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) acceptedFrontier(msg Msg) {
|
||||||
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
requestID := msg.Get(RequestID).(uint32)
|
||||||
|
|
||||||
|
containerIDs := ids.Set{}
|
||||||
|
for _, containerIDBytes := range msg.Get(ContainerIDs).([][]byte) {
|
||||||
|
containerID, err := ids.ToID(containerIDBytes)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Debug("error parsing ContainerID 0x%x: %s", containerIDBytes, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
containerIDs.Add(containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.net.router.AcceptedFrontier(p.id, chainID, requestID, containerIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) getAccepted(msg Msg) {
|
||||||
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
requestID := msg.Get(RequestID).(uint32)
|
||||||
|
|
||||||
|
containerIDs := ids.Set{}
|
||||||
|
for _, containerIDBytes := range msg.Get(ContainerIDs).([][]byte) {
|
||||||
|
containerID, err := ids.ToID(containerIDBytes)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Debug("error parsing ContainerID 0x%x: %s", containerIDBytes, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
containerIDs.Add(containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.net.router.GetAccepted(p.id, chainID, requestID, containerIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) accepted(msg Msg) {
|
||||||
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
requestID := msg.Get(RequestID).(uint32)
|
||||||
|
|
||||||
|
containerIDs := ids.Set{}
|
||||||
|
for _, containerIDBytes := range msg.Get(ContainerIDs).([][]byte) {
|
||||||
|
containerID, err := ids.ToID(containerIDBytes)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Debug("error parsing ContainerID 0x%x: %s", containerIDBytes, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
containerIDs.Add(containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.net.router.Accepted(p.id, chainID, requestID, containerIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) get(msg Msg) {
|
||||||
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
requestID := msg.Get(RequestID).(uint32)
|
||||||
|
containerID, err := ids.ToID(msg.Get(ContainerID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
|
||||||
|
p.net.router.Get(p.id, chainID, requestID, containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) put(msg Msg) {
|
||||||
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
requestID := msg.Get(RequestID).(uint32)
|
||||||
|
containerID, err := ids.ToID(msg.Get(ContainerID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
container := msg.Get(ContainerBytes).([]byte)
|
||||||
|
|
||||||
|
p.net.router.Put(p.id, chainID, requestID, containerID, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) pushQuery(msg Msg) {
|
||||||
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
requestID := msg.Get(RequestID).(uint32)
|
||||||
|
containerID, err := ids.ToID(msg.Get(ContainerID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
container := msg.Get(ContainerBytes).([]byte)
|
||||||
|
|
||||||
|
p.net.router.PushQuery(p.id, chainID, requestID, containerID, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) pullQuery(msg Msg) {
|
||||||
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
requestID := msg.Get(RequestID).(uint32)
|
||||||
|
containerID, err := ids.ToID(msg.Get(ContainerID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
|
||||||
|
p.net.router.PullQuery(p.id, chainID, requestID, containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes the stateLock is not held
|
||||||
|
func (p *peer) chits(msg Msg) {
|
||||||
|
chainID, err := ids.ToID(msg.Get(ChainID).([]byte))
|
||||||
|
p.net.log.AssertNoError(err)
|
||||||
|
requestID := msg.Get(RequestID).(uint32)
|
||||||
|
|
||||||
|
containerIDs := ids.Set{}
|
||||||
|
for _, containerIDBytes := range msg.Get(ContainerIDs).([][]byte) {
|
||||||
|
containerID, err := ids.ToID(containerIDBytes)
|
||||||
|
if err != nil {
|
||||||
|
p.net.log.Debug("error parsing ContainerID 0x%x: %s", containerIDBytes, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
containerIDs.Add(containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.net.router.Chits(p.id, chainID, requestID, containerIDs)
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/utils/hashing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoCert = errors.New("tls handshake finished with no peer certificate")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Upgrader ...
|
||||||
|
type Upgrader interface {
|
||||||
|
// Must be thread safe
|
||||||
|
Upgrade(net.Conn) (ids.ShortID, net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipUpgrader struct{}
|
||||||
|
|
||||||
|
// NewIPUpgrader ...
|
||||||
|
func NewIPUpgrader() Upgrader { return ipUpgrader{} }
|
||||||
|
|
||||||
|
func (ipUpgrader) Upgrade(conn net.Conn) (ids.ShortID, net.Conn, error) {
|
||||||
|
addr := conn.RemoteAddr()
|
||||||
|
str := addr.String()
|
||||||
|
id := ids.NewShortID(hashing.ComputeHash160Array([]byte(str)))
|
||||||
|
return id, conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tlsServerUpgrader struct {
|
||||||
|
config *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTLSServerUpgrader ...
|
||||||
|
func NewTLSServerUpgrader(config *tls.Config) Upgrader {
|
||||||
|
return tlsServerUpgrader{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t tlsServerUpgrader) Upgrade(conn net.Conn) (ids.ShortID, net.Conn, error) {
|
||||||
|
encConn := tls.Server(conn, t.config)
|
||||||
|
if err := encConn.Handshake(); err != nil {
|
||||||
|
return ids.ShortID{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connState := encConn.ConnectionState()
|
||||||
|
if len(connState.PeerCertificates) == 0 {
|
||||||
|
return ids.ShortID{}, nil, errNoCert
|
||||||
|
}
|
||||||
|
peerCert := connState.PeerCertificates[0]
|
||||||
|
id := ids.NewShortID(
|
||||||
|
hashing.ComputeHash160Array(
|
||||||
|
hashing.ComputeHash256(peerCert.Raw)))
|
||||||
|
return id, encConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tlsClientUpgrader struct {
|
||||||
|
config *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTLSClientUpgrader ...
|
||||||
|
func NewTLSClientUpgrader(config *tls.Config) Upgrader {
|
||||||
|
return tlsClientUpgrader{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t tlsClientUpgrader) Upgrade(conn net.Conn) (ids.ShortID, net.Conn, error) {
|
||||||
|
encConn := tls.Client(conn, t.config)
|
||||||
|
if err := encConn.Handshake(); err != nil {
|
||||||
|
return ids.ShortID{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connState := encConn.ConnectionState()
|
||||||
|
if len(connState.PeerCertificates) == 0 {
|
||||||
|
return ids.ShortID{}, nil, errNoCert
|
||||||
|
}
|
||||||
|
peerCert := connState.PeerCertificates[0]
|
||||||
|
id := ids.NewShortID(
|
||||||
|
hashing.ComputeHash160Array(
|
||||||
|
hashing.ComputeHash256(peerCert.Raw)))
|
||||||
|
return id, encConn, nil
|
||||||
|
}
|
|
@ -1,281 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package networking
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Connections provides an interface for what a group of connections will
|
|
||||||
// support.
|
|
||||||
type Connections interface {
|
|
||||||
Add(salticidae.PeerID, ids.ShortID, utils.IPDesc)
|
|
||||||
|
|
||||||
GetPeerID(ids.ShortID) (salticidae.PeerID, bool)
|
|
||||||
GetID(salticidae.PeerID) (ids.ShortID, bool)
|
|
||||||
|
|
||||||
ContainsPeerID(salticidae.PeerID) bool
|
|
||||||
ContainsID(ids.ShortID) bool
|
|
||||||
ContainsIP(utils.IPDesc) bool
|
|
||||||
|
|
||||||
Remove(salticidae.PeerID, ids.ShortID)
|
|
||||||
RemovePeerID(salticidae.PeerID)
|
|
||||||
RemoveID(ids.ShortID)
|
|
||||||
|
|
||||||
PeerIDs() []salticidae.PeerID
|
|
||||||
IDs() ids.ShortSet
|
|
||||||
IPs() []utils.IPDesc
|
|
||||||
Conns() ([]salticidae.PeerID, []ids.ShortID, []utils.IPDesc)
|
|
||||||
|
|
||||||
Len() int
|
|
||||||
}
|
|
||||||
|
|
||||||
type connections struct {
|
|
||||||
mux sync.Mutex
|
|
||||||
// peerID -> id
|
|
||||||
peerIDToID map[[32]byte]ids.ShortID
|
|
||||||
// id -> peerID
|
|
||||||
idToPeerID map[[20]byte]salticidae.PeerID
|
|
||||||
// id -> ip
|
|
||||||
idToIP map[[20]byte]utils.IPDesc
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConnections returns a new and empty connections object
|
|
||||||
func NewConnections() Connections {
|
|
||||||
return &connections{
|
|
||||||
peerIDToID: make(map[[32]byte]ids.ShortID),
|
|
||||||
idToPeerID: make(map[[20]byte]salticidae.PeerID),
|
|
||||||
idToIP: make(map[[20]byte]utils.IPDesc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Assumes that peer is garbage collected normally
|
|
||||||
func (c *connections) Add(peer salticidae.PeerID, id ids.ShortID, ip utils.IPDesc) {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
c.add(peer, id, ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerID returns the peer mapped to the id that is provided if one exists.
|
|
||||||
func (c *connections) GetPeerID(id ids.ShortID) (salticidae.PeerID, bool) {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
return c.getPeerID(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetID returns the id mapped to the peer that is provided if one exists.
|
|
||||||
func (c *connections) GetID(peer salticidae.PeerID) (ids.ShortID, bool) {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
return c.getID(peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainsPeerID returns true if the peer is contained in the connection pool
|
|
||||||
func (c *connections) ContainsPeerID(peer salticidae.PeerID) bool {
|
|
||||||
_, exists := c.GetID(peer)
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainsID returns true if the id is contained in the connection pool
|
|
||||||
func (c *connections) ContainsID(id ids.ShortID) bool {
|
|
||||||
_, exists := c.GetPeerID(id)
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainsIP returns true if the ip is contained in the connection pool
|
|
||||||
func (c *connections) ContainsIP(ip utils.IPDesc) bool {
|
|
||||||
for _, otherIP := range c.IPs() {
|
|
||||||
if ip.Equal(otherIP) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove ensures that no connection will have any mapping containing [peer] or
|
|
||||||
// [id].
|
|
||||||
func (c *connections) Remove(peer salticidae.PeerID, id ids.ShortID) {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
c.remove(peer, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemovePeerID ensures that no connection will have a mapping containing [peer]
|
|
||||||
func (c *connections) RemovePeerID(peer salticidae.PeerID) {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
c.removePeerID(peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveID ensures that no connection will have a mapping containing [id]
|
|
||||||
func (c *connections) RemoveID(id ids.ShortID) {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
c.removeID(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeerIDs returns the full list of peers contained in this connection pool.
|
|
||||||
func (c *connections) PeerIDs() []salticidae.PeerID {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
return c.peerIDs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDs return the set of IDs that are mapping in this connection pool.
|
|
||||||
func (c *connections) IDs() ids.ShortSet {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
return c.ids()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPs return the set of IPs that are mapped in this connection pool.
|
|
||||||
func (c *connections) IPs() []utils.IPDesc {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
return c.ips()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conns return the set of connections in this connection pool.
|
|
||||||
func (c *connections) Conns() ([]salticidae.PeerID, []ids.ShortID, []utils.IPDesc) {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
return c.conns()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of elements in the map
|
|
||||||
func (c *connections) Len() int {
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
return c.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) add(peer salticidae.PeerID, id ids.ShortID, ip utils.IPDesc) {
|
|
||||||
c.remove(peer, id)
|
|
||||||
|
|
||||||
key := id.Key()
|
|
||||||
c.peerIDToID[toID(peer)] = id
|
|
||||||
c.idToPeerID[key] = peer
|
|
||||||
c.idToIP[key] = ip
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) getPeerID(id ids.ShortID) (salticidae.PeerID, bool) {
|
|
||||||
peer, exists := c.idToPeerID[id.Key()]
|
|
||||||
return peer, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) getID(peer salticidae.PeerID) (ids.ShortID, bool) {
|
|
||||||
id, exists := c.peerIDToID[toID(peer)]
|
|
||||||
return id, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) remove(peer salticidae.PeerID, id ids.ShortID) {
|
|
||||||
c.removePeerID(peer)
|
|
||||||
c.removeID(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) removePeerID(peer salticidae.PeerID) {
|
|
||||||
peerID := toID(peer)
|
|
||||||
if id, exists := c.peerIDToID[peerID]; exists {
|
|
||||||
idKey := id.Key()
|
|
||||||
|
|
||||||
delete(c.peerIDToID, peerID)
|
|
||||||
delete(c.idToPeerID, idKey)
|
|
||||||
delete(c.idToIP, idKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) removeID(id ids.ShortID) {
|
|
||||||
idKey := id.Key()
|
|
||||||
if peer, exists := c.idToPeerID[idKey]; exists {
|
|
||||||
delete(c.peerIDToID, toID(peer))
|
|
||||||
delete(c.idToPeerID, idKey)
|
|
||||||
delete(c.idToIP, idKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) peerIDs() []salticidae.PeerID {
|
|
||||||
peers := make([]salticidae.PeerID, 0, len(c.idToPeerID))
|
|
||||||
for _, peer := range c.idToPeerID {
|
|
||||||
peers = append(peers, peer)
|
|
||||||
}
|
|
||||||
return peers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) ids() ids.ShortSet {
|
|
||||||
ids := ids.ShortSet{}
|
|
||||||
for _, id := range c.peerIDToID {
|
|
||||||
ids.Add(id)
|
|
||||||
}
|
|
||||||
return ids
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) ips() []utils.IPDesc {
|
|
||||||
ips := make([]utils.IPDesc, 0, len(c.idToIP))
|
|
||||||
for _, ip := range c.idToIP {
|
|
||||||
ips = append(ips, ip)
|
|
||||||
}
|
|
||||||
return ips
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) conns() ([]salticidae.PeerID, []ids.ShortID, []utils.IPDesc) {
|
|
||||||
peers := make([]salticidae.PeerID, 0, len(c.idToPeerID))
|
|
||||||
idList := make([]ids.ShortID, 0, len(c.idToPeerID))
|
|
||||||
ips := make([]utils.IPDesc, 0, len(c.idToPeerID))
|
|
||||||
for id, peer := range c.idToPeerID {
|
|
||||||
idList = append(idList, ids.NewShortID(id))
|
|
||||||
peers = append(peers, peer)
|
|
||||||
ips = append(ips, c.idToIP[id])
|
|
||||||
}
|
|
||||||
return peers, idList, ips
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *connections) len() int { return len(c.idToPeerID) }
|
|
||||||
|
|
||||||
func toID(peer salticidae.PeerID) [32]byte {
|
|
||||||
ds := salticidae.NewDataStream(false)
|
|
||||||
|
|
||||||
peerInt := peer.AsUInt256()
|
|
||||||
peerInt.Serialize(ds)
|
|
||||||
|
|
||||||
size := ds.Size()
|
|
||||||
dsb := ds.GetDataInPlace(size)
|
|
||||||
|
|
||||||
idBytes := dsb.Get()
|
|
||||||
id := [32]byte{}
|
|
||||||
copy(id[:], idBytes)
|
|
||||||
|
|
||||||
dsb.Release()
|
|
||||||
ds.Free()
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func toIPDesc(addr salticidae.NetAddr) utils.IPDesc {
|
|
||||||
ip, err := ToIPDesc(addr)
|
|
||||||
HandshakeNet.log.AssertNoError(err)
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToIPDesc converts an address to an IP
|
|
||||||
func ToIPDesc(addr salticidae.NetAddr) (utils.IPDesc, error) {
|
|
||||||
ip := salticidae.FromBigEndianU32(addr.GetIP())
|
|
||||||
port := salticidae.FromBigEndianU16(addr.GetPort())
|
|
||||||
return utils.ToIPDesc(fmt.Sprintf("%d.%d.%d.%d:%d", byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip), port))
|
|
||||||
}
|
|
|
@ -1,840 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package networking
|
|
||||||
|
|
||||||
// #include "salticidae/network.h"
|
|
||||||
// bool connHandler(msgnetwork_conn_t *, bool, void *);
|
|
||||||
// void unknownPeerHandler(netaddr_t *, x509_t *, void *);
|
|
||||||
// void peerHandler(peernetwork_conn_t *, bool, void *);
|
|
||||||
// void ping(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void pong(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void getVersion(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void version(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void getPeerList(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void peerList(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/snow/networking"
|
|
||||||
"github.com/ava-labs/gecko/snow/validators"
|
|
||||||
"github.com/ava-labs/gecko/utils"
|
|
||||||
"github.com/ava-labs/gecko/utils/hashing"
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
"github.com/ava-labs/gecko/utils/random"
|
|
||||||
"github.com/ava-labs/gecko/utils/timer"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Receive a new connection.
|
|
||||||
- Send version message.
|
|
||||||
Receive version message.
|
|
||||||
- Validate data
|
|
||||||
- Send peer list
|
|
||||||
- Mark this node as being connected
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Periodically gossip peerlists.
|
|
||||||
- Only connected stakers should be gossiped.
|
|
||||||
- Gossip to a caped number of peers.
|
|
||||||
- The peers to gossip to should be at least half full of stakers (or all the
|
|
||||||
stakers should be in the set).
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Attempt reconnections
|
|
||||||
- If a non-staker disconnects, delete the connection
|
|
||||||
- If a staker disconnects, attempt to reconnect to the node for awhile. If the
|
|
||||||
node isn't connected to after awhile delete the connection.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Version this avalanche instance is executing.
|
|
||||||
var (
|
|
||||||
VersionPrefix = "avalanche/"
|
|
||||||
VersionSeparator = "."
|
|
||||||
MajorVersion = 0
|
|
||||||
MinorVersion = 2
|
|
||||||
PatchVersion = 1
|
|
||||||
ClientVersion = fmt.Sprintf("%s%d%s%d%s%d",
|
|
||||||
VersionPrefix,
|
|
||||||
MajorVersion,
|
|
||||||
VersionSeparator,
|
|
||||||
MinorVersion,
|
|
||||||
VersionSeparator,
|
|
||||||
PatchVersion)
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// MaxClockDifference allowed between connected nodes.
|
|
||||||
MaxClockDifference = time.Minute
|
|
||||||
// PeerListGossipSpacing is the amount of time to wait between pushing this
|
|
||||||
// node's peer list to other nodes.
|
|
||||||
PeerListGossipSpacing = time.Minute
|
|
||||||
// PeerListGossipSize is the number of peers to gossip each period.
|
|
||||||
PeerListGossipSize = 100
|
|
||||||
// PeerListStakerGossipFraction calculates the fraction of stakers that are
|
|
||||||
// gossiped to. If set to 1, then only stakers will be gossiped to.
|
|
||||||
PeerListStakerGossipFraction = 2
|
|
||||||
|
|
||||||
// ConnectTimeout is the amount of time to wait before attempt to connect to
|
|
||||||
// an unknown peer
|
|
||||||
ConnectTimeout = 6 * time.Second
|
|
||||||
// GetVersionTimeout is the amount of time to wait before sending a
|
|
||||||
// getVersion message to a partially connected peer
|
|
||||||
GetVersionTimeout = 2 * time.Second
|
|
||||||
// ReconnectTimeout is the amount of time to wait to reconnect to a staker
|
|
||||||
// before giving up
|
|
||||||
ReconnectTimeout = 10 * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
// Manager is the struct that will be accessed on event calls
|
|
||||||
var (
|
|
||||||
HandshakeNet = Handshake{}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errDSValidators = errors.New("couldn't get validator set of default subnet")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handshake handles the authentication of new peers. Only valid stakers
|
|
||||||
// will appear connected.
|
|
||||||
type Handshake struct {
|
|
||||||
handshakeMetrics
|
|
||||||
|
|
||||||
networkID uint32 // ID of the network I'm running, used to prevent connecting to the wrong network
|
|
||||||
|
|
||||||
log logging.Logger
|
|
||||||
vdrs validators.Set // set of current validators in the AVAnet
|
|
||||||
myAddr salticidae.NetAddr // IP I communicate to peers
|
|
||||||
myID ids.ShortID // ID that identifies myself as a staker or not
|
|
||||||
net salticidae.PeerNetwork // C messaging network
|
|
||||||
enableStaking bool // Should only be false for local tests
|
|
||||||
|
|
||||||
clock timer.Clock
|
|
||||||
|
|
||||||
// Connections that I have added by IP, but haven't gotten an ID from
|
|
||||||
requestedLock sync.Mutex
|
|
||||||
requested map[string]struct{}
|
|
||||||
requestedTimeout timer.TimeoutManager // keys are hashes of the ip:port string
|
|
||||||
|
|
||||||
// Connections that I have added as a peer, but haven't gotten a version
|
|
||||||
// message from
|
|
||||||
pending Connections
|
|
||||||
versionTimeout timer.TimeoutManager // keys are the peer IDs
|
|
||||||
|
|
||||||
// Connections that I have gotten a valid version message from
|
|
||||||
connections Connections
|
|
||||||
reconnectTimeout timer.TimeoutManager // keys are the peer IDs
|
|
||||||
|
|
||||||
// IPs of nodes I'm connected to will be repeatedly gossiped throughout the network
|
|
||||||
peerListGossiper *timer.Repeater
|
|
||||||
|
|
||||||
// If any chain is blocked on connecting to peers, track these blockers here
|
|
||||||
awaitingLock sync.Mutex
|
|
||||||
awaiting []*networking.AwaitingConnections
|
|
||||||
|
|
||||||
lastHeartbeat int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize to the c networking library. This should only be done once during
|
|
||||||
// node setup.
|
|
||||||
func (nm *Handshake) Initialize(
|
|
||||||
log logging.Logger,
|
|
||||||
vdrs validators.Set,
|
|
||||||
myAddr salticidae.NetAddr,
|
|
||||||
myID ids.ShortID,
|
|
||||||
peerNet salticidae.PeerNetwork,
|
|
||||||
registerer prometheus.Registerer,
|
|
||||||
enableStaking bool,
|
|
||||||
networkID uint32,
|
|
||||||
) {
|
|
||||||
log.AssertTrue(nm.net == nil, "Should only register network handlers once")
|
|
||||||
|
|
||||||
nm.handshakeMetrics.Initialize(log, registerer)
|
|
||||||
|
|
||||||
nm.networkID = networkID
|
|
||||||
|
|
||||||
nm.log = log
|
|
||||||
nm.vdrs = vdrs
|
|
||||||
nm.myAddr = myAddr
|
|
||||||
nm.myID = myID
|
|
||||||
nm.net = peerNet
|
|
||||||
nm.enableStaking = enableStaking
|
|
||||||
|
|
||||||
nm.requested = make(map[string]struct{})
|
|
||||||
nm.requestedTimeout.Initialize(ConnectTimeout)
|
|
||||||
go nm.log.RecoverAndPanic(nm.requestedTimeout.Dispatch)
|
|
||||||
|
|
||||||
nm.pending = NewConnections()
|
|
||||||
nm.versionTimeout.Initialize(GetVersionTimeout)
|
|
||||||
go nm.log.RecoverAndPanic(nm.versionTimeout.Dispatch)
|
|
||||||
|
|
||||||
nm.connections = NewConnections()
|
|
||||||
nm.reconnectTimeout.Initialize(ReconnectTimeout)
|
|
||||||
go nm.log.RecoverAndPanic(nm.reconnectTimeout.Dispatch)
|
|
||||||
|
|
||||||
nm.peerListGossiper = timer.NewRepeater(nm.gossipPeerList, PeerListGossipSpacing)
|
|
||||||
go nm.log.RecoverAndPanic(nm.peerListGossiper.Dispatch)
|
|
||||||
|
|
||||||
// register c message callbacks
|
|
||||||
net := peerNet.AsMsgNetwork()
|
|
||||||
|
|
||||||
net.RegConnHandler(salticidae.MsgNetworkConnCallback(C.connHandler), nil)
|
|
||||||
peerNet.RegPeerHandler(salticidae.PeerNetworkPeerCallback(C.peerHandler), nil)
|
|
||||||
peerNet.RegUnknownPeerHandler(salticidae.PeerNetworkUnknownPeerCallback(C.unknownPeerHandler), nil)
|
|
||||||
net.RegHandler(Ping, salticidae.MsgNetworkMsgCallback(C.ping), nil)
|
|
||||||
net.RegHandler(Pong, salticidae.MsgNetworkMsgCallback(C.pong), nil)
|
|
||||||
net.RegHandler(GetVersion, salticidae.MsgNetworkMsgCallback(C.getVersion), nil)
|
|
||||||
net.RegHandler(Version, salticidae.MsgNetworkMsgCallback(C.version), nil)
|
|
||||||
net.RegHandler(GetPeerList, salticidae.MsgNetworkMsgCallback(C.getPeerList), nil)
|
|
||||||
net.RegHandler(PeerList, salticidae.MsgNetworkMsgCallback(C.peerList), nil)
|
|
||||||
|
|
||||||
nm.heartbeat()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectTo add the peer as a connection and connects to them.
|
|
||||||
//
|
|
||||||
// assumes the peerID and addr are autofreed
|
|
||||||
func (nm *Handshake) ConnectTo(peer salticidae.PeerID, stakerID ids.ShortID, addr salticidae.NetAddr) {
|
|
||||||
if nm.pending.ContainsPeerID(peer) || nm.connections.ContainsPeerID(peer) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nm.log.Debug("attempting to connect to %s", stakerID)
|
|
||||||
|
|
||||||
nm.net.AddPeer(peer)
|
|
||||||
nm.net.SetPeerAddr(peer, addr)
|
|
||||||
nm.net.ConnPeer(peer, 600, 1)
|
|
||||||
|
|
||||||
ip := toIPDesc(addr)
|
|
||||||
nm.pending.Add(peer, stakerID, ip)
|
|
||||||
|
|
||||||
peerBytes := toID(peer)
|
|
||||||
peerID := ids.NewID(peerBytes)
|
|
||||||
|
|
||||||
nm.reconnectTimeout.Put(peerID, func() {
|
|
||||||
nm.pending.Remove(peer, stakerID)
|
|
||||||
nm.connections.Remove(peer, stakerID)
|
|
||||||
nm.net.DelPeer(peer)
|
|
||||||
|
|
||||||
nm.numPeers.Set(float64(nm.connections.Len()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect attempts to start a connection with this provided address
|
|
||||||
//
|
|
||||||
// assumes addr is autofreed.
|
|
||||||
func (nm *Handshake) Connect(addr salticidae.NetAddr) {
|
|
||||||
ip := toIPDesc(addr)
|
|
||||||
ipStr := ip.String()
|
|
||||||
if nm.pending.ContainsIP(ip) || nm.connections.ContainsIP(ip) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !nm.enableStaking {
|
|
||||||
nm.log.Debug("adding peer %s", ip)
|
|
||||||
|
|
||||||
peer := salticidae.NewPeerIDFromNetAddr(addr, true)
|
|
||||||
nm.ConnectTo(peer, toShortID(ip), addr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nm.requestedLock.Lock()
|
|
||||||
_, exists := nm.requested[ipStr]
|
|
||||||
nm.requestedLock.Unlock()
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nm.log.Debug("adding peer %s", ip)
|
|
||||||
|
|
||||||
count := new(int)
|
|
||||||
*count = 100
|
|
||||||
handler := new(func())
|
|
||||||
*handler = func() {
|
|
||||||
nm.requestedLock.Lock()
|
|
||||||
defer nm.requestedLock.Unlock()
|
|
||||||
|
|
||||||
if *count == 100 {
|
|
||||||
nm.requested[ipStr] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, exists := nm.requested[ipStr]; !exists {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if *count <= 0 {
|
|
||||||
delete(nm.requested, ipStr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*count--
|
|
||||||
|
|
||||||
if nm.pending.ContainsIP(ip) || nm.connections.ContainsIP(ip) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nm.log.Debug("attempting to discover peer at %s", ipStr)
|
|
||||||
|
|
||||||
msgNet := nm.net.AsMsgNetwork()
|
|
||||||
msgNet.Connect(addr)
|
|
||||||
|
|
||||||
ipID := ids.NewID(hashing.ComputeHash256Array([]byte(ipStr)))
|
|
||||||
nm.requestedTimeout.Put(ipID, *handler)
|
|
||||||
}
|
|
||||||
(*handler)()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AwaitConnections ...
|
|
||||||
func (nm *Handshake) AwaitConnections(awaiting *networking.AwaitingConnections) {
|
|
||||||
nm.awaitingLock.Lock()
|
|
||||||
defer nm.awaitingLock.Unlock()
|
|
||||||
|
|
||||||
awaiting.Add(nm.myID)
|
|
||||||
for _, cert := range nm.connections.IDs().List() {
|
|
||||||
awaiting.Add(cert)
|
|
||||||
}
|
|
||||||
if awaiting.Ready() {
|
|
||||||
go awaiting.Finish()
|
|
||||||
} else {
|
|
||||||
nm.awaiting = append(nm.awaiting, awaiting)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *Handshake) gossipPeerList() {
|
|
||||||
stakers := []ids.ShortID{}
|
|
||||||
nonStakers := []ids.ShortID{}
|
|
||||||
for _, id := range nm.connections.IDs().List() {
|
|
||||||
if nm.vdrs.Contains(id) {
|
|
||||||
stakers = append(stakers, id)
|
|
||||||
} else {
|
|
||||||
nonStakers = append(nonStakers, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
numStakersToSend := (PeerListGossipSize + PeerListStakerGossipFraction - 1) / PeerListStakerGossipFraction
|
|
||||||
if len(stakers) < numStakersToSend {
|
|
||||||
numStakersToSend = len(stakers)
|
|
||||||
}
|
|
||||||
numNonStakersToSend := PeerListGossipSize - numStakersToSend
|
|
||||||
if len(nonStakers) < numNonStakersToSend {
|
|
||||||
numNonStakersToSend = len(nonStakers)
|
|
||||||
}
|
|
||||||
|
|
||||||
idsToSend := []ids.ShortID{}
|
|
||||||
sampler := random.Uniform{N: len(stakers)}
|
|
||||||
for i := 0; i < numStakersToSend; i++ {
|
|
||||||
idsToSend = append(idsToSend, stakers[sampler.Sample()])
|
|
||||||
}
|
|
||||||
sampler.N = len(nonStakers)
|
|
||||||
sampler.Replace()
|
|
||||||
for i := 0; i < numNonStakersToSend; i++ {
|
|
||||||
idsToSend = append(idsToSend, nonStakers[sampler.Sample()])
|
|
||||||
}
|
|
||||||
|
|
||||||
peers := []salticidae.PeerID{}
|
|
||||||
for _, id := range idsToSend {
|
|
||||||
if peer, exists := nm.connections.GetPeerID(id); exists {
|
|
||||||
peers = append(peers, peer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nm.SendPeerList(peers...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connections returns the object that tracks the nodes that are currently
|
|
||||||
// connected to this node.
|
|
||||||
func (nm *Handshake) Connections() Connections { return nm.connections }
|
|
||||||
|
|
||||||
// Shutdown the network
|
|
||||||
func (nm *Handshake) Shutdown() {
|
|
||||||
nm.versionTimeout.Stop()
|
|
||||||
nm.peerListGossiper.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGetVersion to the requested peer
|
|
||||||
func (nm *Handshake) SendGetVersion(peer salticidae.PeerID) {
|
|
||||||
build := Builder{}
|
|
||||||
gv, err := build.GetVersion()
|
|
||||||
nm.log.AssertNoError(err)
|
|
||||||
nm.send(gv, peer)
|
|
||||||
|
|
||||||
nm.numGetVersionSent.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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), ClientVersion)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("packing version failed due to: %w", err)
|
|
||||||
}
|
|
||||||
nm.send(v, peer)
|
|
||||||
nm.numVersionSent.Inc()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendPeerList to the requested peer
|
|
||||||
func (nm *Handshake) SendPeerList(peers ...salticidae.PeerID) error {
|
|
||||||
if len(peers) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ids, ips := nm.connections.Conns()
|
|
||||||
ipsToSend := []utils.IPDesc(nil)
|
|
||||||
for i, id := range ids {
|
|
||||||
ip := ips[i]
|
|
||||||
if !ip.IsZero() && nm.vdrs.Contains(id) {
|
|
||||||
ipsToSend = append(ipsToSend, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ipsToSend) == 0 {
|
|
||||||
nm.log.Debug("no IPs to send to %d peer(s)", len(peers))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
nm.log.Verbo("sending %d ips to %d peer(s)", len(ipsToSend), len(peers))
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
pl, err := build.PeerList(ipsToSend)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("packing peer list failed due to: %w", err)
|
|
||||||
}
|
|
||||||
nm.send(pl, peers...)
|
|
||||||
nm.numPeerlistSent.Add(float64(len(peers)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *Handshake) send(msg Msg, peers ...salticidae.PeerID) {
|
|
||||||
ds := msg.DataStream()
|
|
||||||
defer ds.Free()
|
|
||||||
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
|
|
||||||
defer ba.Free()
|
|
||||||
cMsg := salticidae.NewMsgMovedFromByteArray(msg.Op(), ba, false)
|
|
||||||
defer cMsg.Free()
|
|
||||||
|
|
||||||
switch len(peers) {
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
nm.net.SendMsg(cMsg, peers[0])
|
|
||||||
default:
|
|
||||||
nm.net.MulticastMsgByMove(cMsg, peers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// connHandler notifies of a new inbound connection
|
|
||||||
//export connHandler
|
|
||||||
func connHandler(_conn *C.struct_msgnetwork_conn_t, connected C.bool, _ unsafe.Pointer) C.bool {
|
|
||||||
if !HandshakeNet.enableStaking || !bool(connected) {
|
|
||||||
return connected
|
|
||||||
}
|
|
||||||
|
|
||||||
HandshakeNet.requestedLock.Lock()
|
|
||||||
defer HandshakeNet.requestedLock.Unlock()
|
|
||||||
|
|
||||||
conn := salticidae.MsgNetworkConnFromC(salticidae.CMsgNetworkConn(_conn))
|
|
||||||
addr := conn.GetAddr().Copy(true)
|
|
||||||
ip := toIPDesc(addr)
|
|
||||||
ipStr := ip.String()
|
|
||||||
|
|
||||||
ipID := ids.NewID(hashing.ComputeHash256Array([]byte(ipStr)))
|
|
||||||
HandshakeNet.requestedTimeout.Remove(ipID)
|
|
||||||
|
|
||||||
if _, exists := HandshakeNet.requested[ipStr]; !exists {
|
|
||||||
HandshakeNet.log.Debug("connHandler called with ip %s", ip)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
delete(HandshakeNet.requested, ipStr)
|
|
||||||
|
|
||||||
cert := conn.GetPeerCert()
|
|
||||||
peer := salticidae.NewPeerIDFromX509(cert, true)
|
|
||||||
|
|
||||||
HandshakeNet.ConnectTo(peer, getCert(cert), addr)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// assumes peer is autofreed
|
|
||||||
func (nm *Handshake) connectedToPeer(conn *C.struct_peernetwork_conn_t, peer salticidae.PeerID) {
|
|
||||||
peerBytes := toID(peer)
|
|
||||||
peerID := ids.NewID(peerBytes)
|
|
||||||
|
|
||||||
// If we're enforcing staking, use a peer's certificate to uniquely identify them
|
|
||||||
// Otherwise, use a hash of their ip to identify them
|
|
||||||
cert := ids.ShortID{}
|
|
||||||
if nm.enableStaking {
|
|
||||||
cert = getPeerCert(conn)
|
|
||||||
} else {
|
|
||||||
key := [20]byte{}
|
|
||||||
copy(key[:], peerID.Bytes())
|
|
||||||
cert = ids.NewShortID(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
nm.log.Debug("connected to %s", cert)
|
|
||||||
|
|
||||||
nm.reconnectTimeout.Remove(peerID)
|
|
||||||
|
|
||||||
handler := new(func())
|
|
||||||
*handler = func() {
|
|
||||||
if nm.pending.ContainsPeerID(peer) {
|
|
||||||
nm.SendGetVersion(peer)
|
|
||||||
nm.versionTimeout.Put(peerID, *handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(*handler)()
|
|
||||||
}
|
|
||||||
|
|
||||||
// assumes peer is autofreed
|
|
||||||
func (nm *Handshake) disconnectedFromPeer(peer salticidae.PeerID) {
|
|
||||||
cert := ids.ShortID{}
|
|
||||||
if pendingCert, exists := nm.pending.GetID(peer); exists {
|
|
||||||
cert = pendingCert
|
|
||||||
nm.log.Debug("disconnected from pending peer %s", cert)
|
|
||||||
} else if connectedCert, exists := nm.connections.GetID(peer); exists {
|
|
||||||
cert = connectedCert
|
|
||||||
nm.log.Debug("disconnected from peer %s", cert)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
peerBytes := toID(peer)
|
|
||||||
peerID := ids.NewID(peerBytes)
|
|
||||||
|
|
||||||
nm.versionTimeout.Remove(peerID)
|
|
||||||
nm.connections.Remove(peer, cert)
|
|
||||||
nm.numPeers.Set(float64(nm.connections.Len()))
|
|
||||||
|
|
||||||
if !nm.enableStaking || nm.vdrs.Contains(cert) {
|
|
||||||
nm.reconnectTimeout.Put(peerID, func() {
|
|
||||||
nm.pending.Remove(peer, cert)
|
|
||||||
nm.connections.Remove(peer, cert)
|
|
||||||
nm.net.DelPeer(peer)
|
|
||||||
|
|
||||||
nm.numPeers.Set(float64(nm.connections.Len()))
|
|
||||||
})
|
|
||||||
nm.pending.Add(peer, cert, utils.IPDesc{})
|
|
||||||
} else {
|
|
||||||
nm.pending.Remove(peer, cert)
|
|
||||||
nm.net.DelPeer(peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !nm.enableStaking {
|
|
||||||
nm.vdrs.Remove(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
nm.awaitingLock.Lock()
|
|
||||||
defer nm.awaitingLock.Unlock()
|
|
||||||
for _, awaiting := range HandshakeNet.awaiting {
|
|
||||||
awaiting.Remove(cert)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.Debug("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.Debug("peer attempted to connect with an invalid number of subversions")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
major, err := strconv.Atoi(splitPeerVersion[0])
|
|
||||||
if err != nil {
|
|
||||||
nm.log.Debug("peer attempted to connect with an invalid major version")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
minor, err := strconv.Atoi(splitPeerVersion[1])
|
|
||||||
if err != nil {
|
|
||||||
nm.log.Debug("peer attempted to connect with an invalid minor version")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
patch, err := strconv.Atoi(splitPeerVersion[2])
|
|
||||||
if err != nil {
|
|
||||||
nm.log.Debug("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
|
|
||||||
}
|
|
||||||
|
|
||||||
// heartbeat registers a new heartbeat to signal liveness
|
|
||||||
func (nm *Handshake) heartbeat() {
|
|
||||||
atomic.StoreInt64(&nm.lastHeartbeat, nm.clock.Time().Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeartbeat returns the most recent heartbeat time
|
|
||||||
func (nm *Handshake) GetHeartbeat() int64 {
|
|
||||||
return atomic.LoadInt64(&nm.lastHeartbeat)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
//export peerHandler
|
|
||||||
func peerHandler(_conn *C.struct_peernetwork_conn_t, connected C.bool, _ unsafe.Pointer) {
|
|
||||||
HandshakeNet.log.Debug("peerHandler called")
|
|
||||||
|
|
||||||
pConn := salticidae.PeerNetworkConnFromC(salticidae.CPeerNetworkConn(_conn))
|
|
||||||
peer := pConn.GetPeerID(true)
|
|
||||||
|
|
||||||
if connected {
|
|
||||||
HandshakeNet.connectedToPeer(_conn, peer)
|
|
||||||
} else {
|
|
||||||
HandshakeNet.disconnectedFromPeer(peer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unknownPeerHandler notifies of an unknown peer connection attempt
|
|
||||||
//export unknownPeerHandler
|
|
||||||
func unknownPeerHandler(_addr *C.netaddr_t, _cert *C.x509_t, _ unsafe.Pointer) {
|
|
||||||
HandshakeNet.log.Debug("unknownPeerHandler called")
|
|
||||||
|
|
||||||
addr := salticidae.NetAddrFromC(salticidae.CNetAddr(_addr)).Copy(true)
|
|
||||||
ip := toIPDesc(addr)
|
|
||||||
|
|
||||||
HandshakeNet.log.Debug("adding peer at %s", ip)
|
|
||||||
|
|
||||||
var peer salticidae.PeerID
|
|
||||||
var id ids.ShortID
|
|
||||||
if HandshakeNet.enableStaking {
|
|
||||||
cert := salticidae.X509FromC(salticidae.CX509(_cert))
|
|
||||||
peer = salticidae.NewPeerIDFromX509(cert, true)
|
|
||||||
id = getCert(cert)
|
|
||||||
} else {
|
|
||||||
peer = salticidae.NewPeerIDFromNetAddr(addr, true)
|
|
||||||
id = toShortID(ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
peerBytes := toID(peer)
|
|
||||||
peerID := ids.NewID(peerBytes)
|
|
||||||
|
|
||||||
HandshakeNet.reconnectTimeout.Put(peerID, func() {
|
|
||||||
HandshakeNet.pending.Remove(peer, id)
|
|
||||||
HandshakeNet.connections.Remove(peer, id)
|
|
||||||
HandshakeNet.net.DelPeer(peer)
|
|
||||||
|
|
||||||
HandshakeNet.numPeers.Set(float64(HandshakeNet.connections.Len()))
|
|
||||||
})
|
|
||||||
HandshakeNet.pending.Add(peer, id, utils.IPDesc{})
|
|
||||||
HandshakeNet.net.AddPeer(peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ping handles the recept of a ping message
|
|
||||||
//export ping
|
|
||||||
func ping(_ *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
conn := salticidae.PeerNetworkConnFromC(salticidae.CPeerNetworkConn(_conn))
|
|
||||||
peer := conn.GetPeerID(false)
|
|
||||||
defer peer.Free()
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
pong, err := build.Pong()
|
|
||||||
HandshakeNet.log.AssertNoError(err)
|
|
||||||
|
|
||||||
HandshakeNet.send(pong, peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pong handles the recept of a pong message
|
|
||||||
//export pong
|
|
||||||
func pong(*C.struct_msg_t, *C.struct_msgnetwork_conn_t, unsafe.Pointer) {}
|
|
||||||
|
|
||||||
// getVersion handles the recept of a getVersion message
|
|
||||||
//export getVersion
|
|
||||||
func getVersion(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
HandshakeNet.numGetVersionReceived.Inc()
|
|
||||||
HandshakeNet.heartbeat()
|
|
||||||
|
|
||||||
conn := salticidae.PeerNetworkConnFromC(salticidae.CPeerNetworkConn(_conn))
|
|
||||||
peer := conn.GetPeerID(false)
|
|
||||||
defer peer.Free()
|
|
||||||
|
|
||||||
HandshakeNet.SendVersion(peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// version handles the recept of a version message
|
|
||||||
//export version
|
|
||||||
func version(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
HandshakeNet.numVersionReceived.Inc()
|
|
||||||
HandshakeNet.heartbeat()
|
|
||||||
|
|
||||||
msg := salticidae.MsgFromC(salticidae.CMsg(_msg))
|
|
||||||
conn := salticidae.PeerNetworkConnFromC(salticidae.CPeerNetworkConn(_conn))
|
|
||||||
peer := conn.GetPeerID(true)
|
|
||||||
|
|
||||||
peerBytes := toID(peer)
|
|
||||||
peerID := ids.NewID(peerBytes)
|
|
||||||
|
|
||||||
HandshakeNet.versionTimeout.Remove(peerID)
|
|
||||||
|
|
||||||
id, exists := HandshakeNet.pending.GetID(peer)
|
|
||||||
if !exists {
|
|
||||||
HandshakeNet.log.Debug("dropping Version message because the peer isn't pending")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
HandshakeNet.pending.Remove(peer, id)
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
pMsg, err := build.Parse(Version, msg.GetPayloadByMove())
|
|
||||||
if err != nil {
|
|
||||||
HandshakeNet.log.Debug("failed to parse Version message")
|
|
||||||
|
|
||||||
HandshakeNet.net.DelPeer(peer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if networkID := pMsg.Get(NetworkID).(uint32); networkID != HandshakeNet.networkID {
|
|
||||||
HandshakeNet.log.Debug("peer's network ID doesn't match our networkID: Peer's = %d ; Ours = %d", networkID, HandshakeNet.networkID)
|
|
||||||
|
|
||||||
HandshakeNet.net.DelPeer(peer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
myTime := float64(HandshakeNet.clock.Unix())
|
|
||||||
if peerTime := float64(pMsg.Get(MyTime).(uint64)); math.Abs(peerTime-myTime) > MaxClockDifference.Seconds() {
|
|
||||||
HandshakeNet.log.Debug("peer's clock is too far out of sync with mine. Peer's = %d, Ours = %d (seconds)", uint64(peerTime), uint64(myTime))
|
|
||||||
|
|
||||||
HandshakeNet.net.DelPeer(peer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if peerVersion := pMsg.Get(VersionStr).(string); !HandshakeNet.checkCompatibility(peerVersion) {
|
|
||||||
HandshakeNet.log.Debug("peer version, %s, is not compatible. dropping connection.", peerVersion)
|
|
||||||
|
|
||||||
HandshakeNet.net.DelPeer(peer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := pMsg.Get(IP).(utils.IPDesc)
|
|
||||||
|
|
||||||
HandshakeNet.log.Debug("Finishing handshake with %s", ip)
|
|
||||||
|
|
||||||
HandshakeNet.SendPeerList(peer)
|
|
||||||
HandshakeNet.connections.Add(peer, id, ip)
|
|
||||||
HandshakeNet.numPeers.Set(float64(HandshakeNet.connections.Len()))
|
|
||||||
|
|
||||||
if !HandshakeNet.enableStaking {
|
|
||||||
HandshakeNet.vdrs.Add(validators.NewValidator(id, 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
HandshakeNet.awaitingLock.Lock()
|
|
||||||
defer HandshakeNet.awaitingLock.Unlock()
|
|
||||||
|
|
||||||
for i := 0; i < len(HandshakeNet.awaiting); i++ {
|
|
||||||
awaiting := HandshakeNet.awaiting[i]
|
|
||||||
awaiting.Add(id)
|
|
||||||
if !awaiting.Ready() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
newLen := len(HandshakeNet.awaiting) - 1
|
|
||||||
HandshakeNet.awaiting[i] = HandshakeNet.awaiting[newLen]
|
|
||||||
HandshakeNet.awaiting = HandshakeNet.awaiting[:newLen]
|
|
||||||
|
|
||||||
i--
|
|
||||||
|
|
||||||
go awaiting.Finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPeerList handles the recept of a getPeerList message
|
|
||||||
//export getPeerList
|
|
||||||
func getPeerList(_ *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
HandshakeNet.numGetPeerlistReceived.Inc()
|
|
||||||
HandshakeNet.heartbeat()
|
|
||||||
|
|
||||||
conn := salticidae.PeerNetworkConnFromC(salticidae.CPeerNetworkConn(_conn))
|
|
||||||
peer := conn.GetPeerID(false)
|
|
||||||
defer peer.Free()
|
|
||||||
|
|
||||||
HandshakeNet.SendPeerList(peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// peerList handles the recept of a peerList message
|
|
||||||
//export peerList
|
|
||||||
func peerList(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
HandshakeNet.numPeerlistReceived.Inc()
|
|
||||||
HandshakeNet.heartbeat()
|
|
||||||
|
|
||||||
msg := salticidae.MsgFromC(salticidae.CMsg(_msg))
|
|
||||||
build := Builder{}
|
|
||||||
pMsg, err := build.Parse(PeerList, msg.GetPayloadByMove())
|
|
||||||
if err != nil {
|
|
||||||
HandshakeNet.log.Debug("failed to parse PeerList message due to %s", err)
|
|
||||||
// TODO: What should we do here?
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ips := pMsg.Get(Peers).([]utils.IPDesc)
|
|
||||||
cErr := salticidae.NewError()
|
|
||||||
for _, ip := range ips {
|
|
||||||
addr := salticidae.NewNetAddrFromIPPortString(ip.String(), true, &cErr)
|
|
||||||
|
|
||||||
if cErr.GetCode() != 0 || HandshakeNet.myAddr.IsEq(addr) {
|
|
||||||
// Make sure not to connect to myself
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
HandshakeNet.Connect(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPeerCert(_conn *C.struct_peernetwork_conn_t) ids.ShortID {
|
|
||||||
conn := salticidae.MsgNetworkConnFromC(salticidae.CMsgNetworkConn(_conn))
|
|
||||||
return getCert(conn.GetPeerCert())
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCert(cert salticidae.X509) ids.ShortID {
|
|
||||||
der := cert.GetDer(false)
|
|
||||||
certDS := salticidae.NewDataStreamMovedFromByteArray(der, false)
|
|
||||||
certBytes := certDS.GetDataInPlace(certDS.Size()).Get()
|
|
||||||
certID, err := ids.ToShortID(hashing.PubkeyBytesToAddress(certBytes))
|
|
||||||
|
|
||||||
certDS.Free()
|
|
||||||
der.Free()
|
|
||||||
HandshakeNet.log.AssertNoError(err)
|
|
||||||
return certID
|
|
||||||
}
|
|
||||||
|
|
||||||
func toShortID(ip utils.IPDesc) ids.ShortID {
|
|
||||||
return ids.NewShortID(hashing.ComputeHash160Array([]byte(ip.String())))
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package networking
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
type handshakeMetrics struct {
|
|
||||||
numPeers prometheus.Gauge
|
|
||||||
|
|
||||||
numGetVersionSent, numGetVersionReceived,
|
|
||||||
numVersionSent, numVersionReceived,
|
|
||||||
numGetPeerlistSent, numGetPeerlistReceived,
|
|
||||||
numPeerlistSent, numPeerlistReceived prometheus.Counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hm *handshakeMetrics) Initialize(log logging.Logger, registerer prometheus.Registerer) {
|
|
||||||
hm.numPeers = prometheus.NewGauge(
|
|
||||||
prometheus.GaugeOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "peers",
|
|
||||||
Help: "Number of network peers",
|
|
||||||
})
|
|
||||||
hm.numGetVersionSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_version_sent",
|
|
||||||
Help: "Number of get_version messages sent",
|
|
||||||
})
|
|
||||||
hm.numGetVersionReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_version_received",
|
|
||||||
Help: "Number of get_version messages received",
|
|
||||||
})
|
|
||||||
hm.numVersionSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "version_sent",
|
|
||||||
Help: "Number of version messages sent",
|
|
||||||
})
|
|
||||||
hm.numVersionReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "version_received",
|
|
||||||
Help: "Number of version messages received",
|
|
||||||
})
|
|
||||||
hm.numGetPeerlistSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_peerlist_sent",
|
|
||||||
Help: "Number of get_peerlist messages sent",
|
|
||||||
})
|
|
||||||
hm.numGetPeerlistReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_peerlist_received",
|
|
||||||
Help: "Number of get_peerlist messages received",
|
|
||||||
})
|
|
||||||
hm.numPeerlistSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "peerlist_sent",
|
|
||||||
Help: "Number of peerlist messages sent",
|
|
||||||
})
|
|
||||||
hm.numPeerlistReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "peerlist_received",
|
|
||||||
Help: "Number of peerlist messages received",
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := registerer.Register(hm.numPeers); err != nil {
|
|
||||||
log.Error("Failed to register peers statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(hm.numGetVersionSent); err != nil {
|
|
||||||
log.Error("Failed to register get_version_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(hm.numGetVersionReceived); err != nil {
|
|
||||||
log.Error("Failed to register get_version_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(hm.numVersionSent); err != nil {
|
|
||||||
log.Error("Failed to register version_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(hm.numVersionReceived); err != nil {
|
|
||||||
log.Error("Failed to register version_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(hm.numGetPeerlistSent); err != nil {
|
|
||||||
log.Error("Failed to register get_peerlist_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(hm.numGetPeerlistReceived); err != nil {
|
|
||||||
log.Error("Failed to register get_peerlist_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(hm.numPeerlistSent); err != nil {
|
|
||||||
log.Error("Failed to register peerlist_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(hm.numPeerlistReceived); err != nil {
|
|
||||||
log.Error("Failed to register peerlist_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,658 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package networking
|
|
||||||
|
|
||||||
// #include "salticidae/network.h"
|
|
||||||
// void getAcceptedFrontier(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void acceptedFrontier(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void getAccepted(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void accepted(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void get(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void put(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void pushQuery(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void pullQuery(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
// void chits(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/snow/networking/router"
|
|
||||||
"github.com/ava-labs/gecko/snow/validators"
|
|
||||||
"github.com/ava-labs/gecko/utils/formatting"
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
"github.com/ava-labs/gecko/utils/random"
|
|
||||||
"github.com/ava-labs/gecko/utils/timer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GossipSize is the maximum number of peers to gossip a container to
|
|
||||||
const (
|
|
||||||
GossipSize = 50
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// VotingNet implements the SenderExternal interface.
|
|
||||||
VotingNet = Voting{}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errConnectionDropped = errors.New("connection dropped before receiving message")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Voting implements the SenderExternal interface with a c++ library.
|
|
||||||
type Voting struct {
|
|
||||||
votingMetrics
|
|
||||||
|
|
||||||
log logging.Logger
|
|
||||||
vdrs validators.Set
|
|
||||||
net salticidae.PeerNetwork
|
|
||||||
conns Connections
|
|
||||||
|
|
||||||
router router.Router
|
|
||||||
executor timer.Executor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize to the c networking library. Should only be called once ever.
|
|
||||||
func (s *Voting) Initialize(log logging.Logger, vdrs validators.Set, peerNet salticidae.PeerNetwork, conns Connections, router router.Router, registerer prometheus.Registerer) {
|
|
||||||
log.AssertTrue(s.net == nil, "Should only register network handlers once")
|
|
||||||
log.AssertTrue(s.conns == nil, "Should only set connections once")
|
|
||||||
log.AssertTrue(s.router == nil, "Should only set the router once")
|
|
||||||
|
|
||||||
s.log = log
|
|
||||||
s.vdrs = vdrs
|
|
||||||
s.net = peerNet
|
|
||||||
s.conns = conns
|
|
||||||
s.router = router
|
|
||||||
|
|
||||||
s.votingMetrics.Initialize(log, registerer)
|
|
||||||
|
|
||||||
net := peerNet.AsMsgNetwork()
|
|
||||||
|
|
||||||
net.RegHandler(GetAcceptedFrontier, salticidae.MsgNetworkMsgCallback(C.getAcceptedFrontier), nil)
|
|
||||||
net.RegHandler(AcceptedFrontier, salticidae.MsgNetworkMsgCallback(C.acceptedFrontier), nil)
|
|
||||||
net.RegHandler(GetAccepted, salticidae.MsgNetworkMsgCallback(C.getAccepted), nil)
|
|
||||||
net.RegHandler(Accepted, salticidae.MsgNetworkMsgCallback(C.accepted), nil)
|
|
||||||
net.RegHandler(Get, salticidae.MsgNetworkMsgCallback(C.get), nil)
|
|
||||||
net.RegHandler(Put, salticidae.MsgNetworkMsgCallback(C.put), nil)
|
|
||||||
net.RegHandler(PushQuery, salticidae.MsgNetworkMsgCallback(C.pushQuery), nil)
|
|
||||||
net.RegHandler(PullQuery, salticidae.MsgNetworkMsgCallback(C.pullQuery), nil)
|
|
||||||
net.RegHandler(Chits, salticidae.MsgNetworkMsgCallback(C.chits), nil)
|
|
||||||
|
|
||||||
s.executor.Initialize()
|
|
||||||
go log.RecoverAndPanic(s.executor.Dispatch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown threads
|
|
||||||
func (s *Voting) Shutdown() { s.executor.Stop() }
|
|
||||||
|
|
||||||
// Accept is called after every consensus decision
|
|
||||||
func (s *Voting) Accept(chainID, containerID ids.ID, container []byte) error {
|
|
||||||
return s.gossip(chainID, containerID, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAcceptedFrontier implements the Sender interface.
|
|
||||||
func (s *Voting) GetAcceptedFrontier(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32) {
|
|
||||||
peers := []salticidae.PeerID(nil)
|
|
||||||
validatorIDList := validatorIDs.List()
|
|
||||||
for _, validatorID := range validatorIDList {
|
|
||||||
vID := validatorID
|
|
||||||
if peer, exists := s.conns.GetPeerID(vID); exists {
|
|
||||||
peers = append(peers, peer)
|
|
||||||
s.log.Verbo("Sending a GetAcceptedFrontier to %s", vID)
|
|
||||||
} else {
|
|
||||||
s.log.Debug("attempted to send a GetAcceptedFrontier message to a disconnected validator: %s", vID)
|
|
||||||
s.executor.Add(func() { s.router.GetAcceptedFrontierFailed(vID, chainID, requestID) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.GetAcceptedFrontier(chainID, requestID)
|
|
||||||
s.log.AssertNoError(err)
|
|
||||||
|
|
||||||
s.log.Verbo("Sending a GetAcceptedFrontier message."+
|
|
||||||
"\nNumber of Validators: %d"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nRequest ID: %d",
|
|
||||||
len(peers),
|
|
||||||
chainID,
|
|
||||||
requestID,
|
|
||||||
)
|
|
||||||
s.send(msg, peers...)
|
|
||||||
s.numGetAcceptedFrontierSent.Add(float64(len(peers)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptedFrontier implements the Sender interface.
|
|
||||||
func (s *Voting) AcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
|
||||||
peer, exists := s.conns.GetPeerID(validatorID)
|
|
||||||
if !exists {
|
|
||||||
s.log.Debug("attempted to send an AcceptedFrontier message to disconnected validator: %s", validatorID)
|
|
||||||
return // Validator is not connected
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.AcceptedFrontier(chainID, requestID, containerIDs)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("attempted to pack too large of an AcceptedFrontier message.\nNumber of containerIDs: %d", containerIDs.Len())
|
|
||||||
return // Packing message failed
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Verbo("Sending an AcceptedFrontier message."+
|
|
||||||
"\nValidator: %s"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nRequest ID: %d"+
|
|
||||||
"\nContainer IDs: %s",
|
|
||||||
validatorID,
|
|
||||||
chainID,
|
|
||||||
requestID,
|
|
||||||
containerIDs,
|
|
||||||
)
|
|
||||||
s.send(msg, peer)
|
|
||||||
s.numAcceptedFrontierSent.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccepted implements the Sender interface.
|
|
||||||
func (s *Voting) GetAccepted(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
|
||||||
peers := []salticidae.PeerID(nil)
|
|
||||||
validatorIDList := validatorIDs.List()
|
|
||||||
for _, validatorID := range validatorIDList {
|
|
||||||
vID := validatorID
|
|
||||||
if peer, exists := s.conns.GetPeerID(validatorID); exists {
|
|
||||||
peers = append(peers, peer)
|
|
||||||
s.log.Verbo("sending a GetAccepted to %s", vID)
|
|
||||||
} else {
|
|
||||||
s.log.Debug("attempted to send a GetAccepted message to a disconnected validator: %s", vID)
|
|
||||||
s.executor.Add(func() { s.router.GetAcceptedFailed(vID, chainID, requestID) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.GetAccepted(chainID, requestID, containerIDs)
|
|
||||||
if err != nil {
|
|
||||||
for _, peer := range peers {
|
|
||||||
if validatorID, exists := s.conns.GetID(peer); exists {
|
|
||||||
s.executor.Add(func() { s.router.GetAcceptedFailed(validatorID, chainID, requestID) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.log.Debug("attempted to pack too large of a GetAccepted message.\nNumber of containerIDs: %d", containerIDs.Len())
|
|
||||||
return // Packing message failed
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Verbo("Sending a GetAccepted message."+
|
|
||||||
"\nNumber of Validators: %d"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nRequest ID: %d"+
|
|
||||||
"\nContainer IDs:%s",
|
|
||||||
len(peers),
|
|
||||||
chainID,
|
|
||||||
requestID,
|
|
||||||
containerIDs,
|
|
||||||
)
|
|
||||||
s.send(msg, peers...)
|
|
||||||
s.numGetAcceptedSent.Add(float64(len(peers)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accepted implements the Sender interface.
|
|
||||||
func (s *Voting) Accepted(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
|
|
||||||
peer, exists := s.conns.GetPeerID(validatorID)
|
|
||||||
if !exists {
|
|
||||||
s.log.Debug("attempted to send an Accepted message to a disconnected validator: %s", validatorID)
|
|
||||||
return // Validator is not connected
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.Accepted(chainID, requestID, containerIDs)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("attempted to pack too large of an Accepted message.\nNumber of containerIDs: %d", containerIDs.Len())
|
|
||||||
return // Packing message failed
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Verbo("Sending an Accepted message."+
|
|
||||||
"\nValidator: %s"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nRequest ID: %d"+
|
|
||||||
"\nContainer IDs: %s",
|
|
||||||
validatorID,
|
|
||||||
chainID,
|
|
||||||
requestID,
|
|
||||||
containerIDs,
|
|
||||||
)
|
|
||||||
s.send(msg, peer)
|
|
||||||
s.numAcceptedSent.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get implements the Sender interface.
|
|
||||||
func (s *Voting) Get(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
|
|
||||||
peer, exists := s.conns.GetPeerID(validatorID)
|
|
||||||
if !exists {
|
|
||||||
s.log.Debug("attempted to send a Get message to a disconnected validator: %s", validatorID)
|
|
||||||
s.executor.Add(func() { s.router.GetFailed(validatorID, chainID, requestID) })
|
|
||||||
return // Validator is not connected
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.Get(chainID, requestID, containerID)
|
|
||||||
s.log.AssertNoError(err)
|
|
||||||
|
|
||||||
s.log.Verbo("Sending a Get message."+
|
|
||||||
"\nValidator: %s"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nRequest ID: %d"+
|
|
||||||
"\nContainer ID: %s",
|
|
||||||
validatorID,
|
|
||||||
chainID,
|
|
||||||
requestID,
|
|
||||||
containerID,
|
|
||||||
)
|
|
||||||
s.send(msg, peer)
|
|
||||||
s.numGetSent.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put implements the Sender interface.
|
|
||||||
func (s *Voting) Put(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
|
|
||||||
peer, exists := s.conns.GetPeerID(validatorID)
|
|
||||||
if !exists {
|
|
||||||
s.log.Debug("attempted to send a Container message to a disconnected validator: %s", validatorID)
|
|
||||||
return // Validator is not connected
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.Put(chainID, requestID, containerID, container)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("attempted to pack too large of a Put message.\nContainer length: %d", len(container))
|
|
||||||
return // Packing message failed
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Verbo("Sending a Container message."+
|
|
||||||
"\nValidator: %s"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nRequest ID: %d"+
|
|
||||||
"\nContainer ID: %s"+
|
|
||||||
"\nContainer:\n%s",
|
|
||||||
validatorID,
|
|
||||||
chainID,
|
|
||||||
requestID,
|
|
||||||
containerID,
|
|
||||||
formatting.DumpBytes{Bytes: container},
|
|
||||||
)
|
|
||||||
s.send(msg, peer)
|
|
||||||
s.numPutSent.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushQuery implements the Sender interface.
|
|
||||||
func (s *Voting) PushQuery(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
|
|
||||||
peers := []salticidae.PeerID(nil)
|
|
||||||
validatorIDList := validatorIDs.List()
|
|
||||||
for _, validatorID := range validatorIDList {
|
|
||||||
vID := validatorID
|
|
||||||
if peer, exists := s.conns.GetPeerID(vID); exists {
|
|
||||||
peers = append(peers, peer)
|
|
||||||
s.log.Verbo("Sending a PushQuery to %s", vID)
|
|
||||||
} else {
|
|
||||||
s.log.Debug("attempted to send a PushQuery message to a disconnected validator: %s", vID)
|
|
||||||
s.executor.Add(func() { s.router.QueryFailed(vID, chainID, requestID) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.PushQuery(chainID, requestID, containerID, container)
|
|
||||||
if err != nil {
|
|
||||||
for _, peer := range peers {
|
|
||||||
if validatorID, exists := s.conns.GetID(peer); exists {
|
|
||||||
s.executor.Add(func() { s.router.QueryFailed(validatorID, chainID, requestID) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.log.Error("attempted to pack too large of a PushQuery message.\nContainer length: %d", len(container))
|
|
||||||
return // Packing message failed
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Verbo("Sending a PushQuery message."+
|
|
||||||
"\nNumber of Validators: %d"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nRequest ID: %d"+
|
|
||||||
"\nContainer ID: %s"+
|
|
||||||
"\nContainer:\n%s",
|
|
||||||
len(peers),
|
|
||||||
chainID,
|
|
||||||
requestID,
|
|
||||||
containerID,
|
|
||||||
formatting.DumpBytes{Bytes: container},
|
|
||||||
)
|
|
||||||
s.send(msg, peers...)
|
|
||||||
s.numPushQuerySent.Add(float64(len(peers)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PullQuery implements the Sender interface.
|
|
||||||
func (s *Voting) PullQuery(validatorIDs ids.ShortSet, chainID ids.ID, requestID uint32, containerID ids.ID) {
|
|
||||||
peers := []salticidae.PeerID(nil)
|
|
||||||
validatorIDList := validatorIDs.List()
|
|
||||||
for _, validatorID := range validatorIDList {
|
|
||||||
vID := validatorID
|
|
||||||
if peer, exists := s.conns.GetPeerID(vID); exists {
|
|
||||||
peers = append(peers, peer)
|
|
||||||
s.log.Verbo("Sending a PullQuery to %s", vID)
|
|
||||||
} else {
|
|
||||||
s.log.Debug("attempted to send a PullQuery message to a disconnected validator: %s", vID)
|
|
||||||
s.executor.Add(func() { s.router.QueryFailed(vID, chainID, requestID) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.PullQuery(chainID, requestID, containerID)
|
|
||||||
s.log.AssertNoError(err)
|
|
||||||
|
|
||||||
s.log.Verbo("Sending a PullQuery message."+
|
|
||||||
"\nNumber of Validators: %d"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nRequest ID: %d"+
|
|
||||||
"\nContainer ID: %s",
|
|
||||||
len(peers),
|
|
||||||
chainID,
|
|
||||||
requestID,
|
|
||||||
containerID,
|
|
||||||
)
|
|
||||||
s.send(msg, peers...)
|
|
||||||
s.numPullQuerySent.Add(float64(len(peers)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chits implements the Sender interface.
|
|
||||||
func (s *Voting) Chits(validatorID ids.ShortID, chainID ids.ID, requestID uint32, votes ids.Set) {
|
|
||||||
peer, exists := s.conns.GetPeerID(validatorID)
|
|
||||||
if !exists {
|
|
||||||
s.log.Debug("attempted to send a Chits message to a disconnected validator: %s", validatorID)
|
|
||||||
return // Validator is not connected
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.Chits(chainID, requestID, votes)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("attempted to pack too large of a Chits message.\nChits length: %d", votes.Len())
|
|
||||||
return // Packing message failed
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Verbo("Sending a Chits message."+
|
|
||||||
"\nValidator: %s"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nRequest ID: %d"+
|
|
||||||
"\nNumber of Chits: %d",
|
|
||||||
validatorID,
|
|
||||||
chainID,
|
|
||||||
requestID,
|
|
||||||
votes.Len(),
|
|
||||||
)
|
|
||||||
s.send(msg, peer)
|
|
||||||
s.numChitsSent.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gossip attempts to gossip the container to the network
|
|
||||||
func (s *Voting) Gossip(chainID, containerID ids.ID, container []byte) {
|
|
||||||
if err := s.gossip(chainID, containerID, container); err != nil {
|
|
||||||
s.log.Error("error gossiping container %s to %s: %s", containerID, chainID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Voting) send(msg Msg, peers ...salticidae.PeerID) {
|
|
||||||
ds := msg.DataStream()
|
|
||||||
defer ds.Free()
|
|
||||||
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
|
|
||||||
defer ba.Free()
|
|
||||||
cMsg := salticidae.NewMsgMovedFromByteArray(msg.Op(), ba, false)
|
|
||||||
defer cMsg.Free()
|
|
||||||
|
|
||||||
switch len(peers) {
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
s.net.SendMsg(cMsg, peers[0])
|
|
||||||
default:
|
|
||||||
s.net.MulticastMsgByMove(cMsg, peers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Voting) gossip(chainID, containerID ids.ID, container []byte) error {
|
|
||||||
allPeers := s.conns.PeerIDs()
|
|
||||||
|
|
||||||
numToGossip := GossipSize
|
|
||||||
if numToGossip > len(allPeers) {
|
|
||||||
numToGossip = len(allPeers)
|
|
||||||
}
|
|
||||||
peers := make([]salticidae.PeerID, numToGossip)
|
|
||||||
|
|
||||||
sampler := random.Uniform{N: len(allPeers)}
|
|
||||||
for i := range peers {
|
|
||||||
peers[i] = allPeers[sampler.Sample()]
|
|
||||||
}
|
|
||||||
|
|
||||||
build := Builder{}
|
|
||||||
msg, err := build.Put(chainID, math.MaxUint32, containerID, container)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("attempted to pack too large of a Put message.\nContainer length: %d", len(container))
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Verbo("Sending a Put message to peers."+
|
|
||||||
"\nNumber of Peers: %d"+
|
|
||||||
"\nChain: %s"+
|
|
||||||
"\nContainer ID: %s"+
|
|
||||||
"\nContainer:\n%s",
|
|
||||||
len(peers),
|
|
||||||
chainID,
|
|
||||||
containerID,
|
|
||||||
formatting.DumpBytes{Bytes: container},
|
|
||||||
)
|
|
||||||
s.send(msg, peers...)
|
|
||||||
s.numPutSent.Add(float64(len(peers)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAcceptedFrontier handles the recept of a getAcceptedFrontier container
|
|
||||||
// message for a chain
|
|
||||||
//export getAcceptedFrontier
|
|
||||||
func getAcceptedFrontier(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
VotingNet.numGetAcceptedFrontierReceived.Inc()
|
|
||||||
|
|
||||||
validatorID, chainID, requestID, _, err := VotingNet.sanitize(_msg, _conn, GetAcceptedFrontier)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("failed to sanitize getAcceptedFrontier message due to: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
VotingNet.router.GetAcceptedFrontier(validatorID, chainID, requestID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// acceptedFrontier handles the recept of an acceptedFrontier message
|
|
||||||
//export acceptedFrontier
|
|
||||||
func acceptedFrontier(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
VotingNet.numAcceptedFrontierReceived.Inc()
|
|
||||||
|
|
||||||
validatorID, chainID, requestID, msg, err := VotingNet.sanitize(_msg, _conn, AcceptedFrontier)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("failed to sanitize acceptedFrontier message due to: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
containerIDs := ids.Set{}
|
|
||||||
for _, containerIDBytes := range msg.Get(ContainerIDs).([][]byte) {
|
|
||||||
containerID, err := ids.ToID(containerIDBytes)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("error parsing ContainerID %v: %s", containerIDBytes, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
containerIDs.Add(containerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
VotingNet.router.AcceptedFrontier(validatorID, chainID, requestID, containerIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAccepted handles the recept of a getAccepted message
|
|
||||||
//export getAccepted
|
|
||||||
func getAccepted(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
VotingNet.numGetAcceptedReceived.Inc()
|
|
||||||
|
|
||||||
validatorID, chainID, requestID, msg, err := VotingNet.sanitize(_msg, _conn, GetAccepted)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("failed to sanitize getAccepted message due to: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
containerIDs := ids.Set{}
|
|
||||||
for _, containerIDBytes := range msg.Get(ContainerIDs).([][]byte) {
|
|
||||||
containerID, err := ids.ToID(containerIDBytes)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("error parsing ContainerID %v: %s", containerIDBytes, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
containerIDs.Add(containerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
VotingNet.router.GetAccepted(validatorID, chainID, requestID, containerIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// accepted handles the recept of an accepted message
|
|
||||||
//export accepted
|
|
||||||
func accepted(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
VotingNet.numAcceptedReceived.Inc()
|
|
||||||
|
|
||||||
validatorID, chainID, requestID, msg, err := VotingNet.sanitize(_msg, _conn, Accepted)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("failed to sanitize accepted message due to: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
containerIDs := ids.Set{}
|
|
||||||
for _, containerIDBytes := range msg.Get(ContainerIDs).([][]byte) {
|
|
||||||
containerID, err := ids.ToID(containerIDBytes)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("error parsing ContainerID %v: %s", containerIDBytes, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
containerIDs.Add(containerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
VotingNet.router.Accepted(validatorID, chainID, requestID, containerIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get handles the recept of a get container message for a chain
|
|
||||||
//export get
|
|
||||||
func get(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
VotingNet.numGetReceived.Inc()
|
|
||||||
|
|
||||||
validatorID, chainID, requestID, msg, err := VotingNet.sanitize(_msg, _conn, Get)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("failed to sanitize get message due to: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
containerID, _ := ids.ToID(msg.Get(ContainerID).([]byte))
|
|
||||||
|
|
||||||
VotingNet.router.Get(validatorID, chainID, requestID, containerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// put handles the receipt of a container message
|
|
||||||
//export put
|
|
||||||
func put(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
VotingNet.numPutReceived.Inc()
|
|
||||||
|
|
||||||
validatorID, chainID, requestID, msg, err := VotingNet.sanitize(_msg, _conn, Put)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("failed to sanitize put message due to: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
containerID, _ := ids.ToID(msg.Get(ContainerID).([]byte))
|
|
||||||
|
|
||||||
containerBytes := msg.Get(ContainerBytes).([]byte)
|
|
||||||
|
|
||||||
VotingNet.router.Put(validatorID, chainID, requestID, containerID, containerBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pushQuery handles the recept of a pull query message
|
|
||||||
//export pushQuery
|
|
||||||
func pushQuery(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
VotingNet.numPushQueryReceived.Inc()
|
|
||||||
|
|
||||||
validatorID, chainID, requestID, msg, err := VotingNet.sanitize(_msg, _conn, PushQuery)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("failed to sanitize pushQuery message due to: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
containerID, _ := ids.ToID(msg.Get(ContainerID).([]byte))
|
|
||||||
|
|
||||||
containerBytes := msg.Get(ContainerBytes).([]byte)
|
|
||||||
|
|
||||||
VotingNet.router.PushQuery(validatorID, chainID, requestID, containerID, containerBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pullQuery handles the recept of a query message
|
|
||||||
//export pullQuery
|
|
||||||
func pullQuery(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
VotingNet.numPullQueryReceived.Inc()
|
|
||||||
|
|
||||||
validatorID, chainID, requestID, msg, err := VotingNet.sanitize(_msg, _conn, PullQuery)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("failed to sanitize pullQuery message due to: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
containerID, _ := ids.ToID(msg.Get(ContainerID).([]byte))
|
|
||||||
|
|
||||||
VotingNet.router.PullQuery(validatorID, chainID, requestID, containerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// chits handles the recept of a chits message
|
|
||||||
//export chits
|
|
||||||
func chits(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
VotingNet.numChitsReceived.Inc()
|
|
||||||
|
|
||||||
validatorID, chainID, requestID, msg, err := VotingNet.sanitize(_msg, _conn, Chits)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("failed to sanitize chits message due to: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
votes := ids.Set{}
|
|
||||||
for _, voteBytes := range msg.Get(ContainerIDs).([][]byte) {
|
|
||||||
vote, err := ids.ToID(voteBytes)
|
|
||||||
if err != nil {
|
|
||||||
VotingNet.log.Debug("error parsing chit %v: %s", voteBytes, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
votes.Add(vote)
|
|
||||||
}
|
|
||||||
|
|
||||||
VotingNet.router.Chits(validatorID, chainID, requestID, votes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Voting) sanitize(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, op salticidae.Opcode) (ids.ShortID, ids.ID, uint32, Msg, error) {
|
|
||||||
conn := salticidae.PeerNetworkConnFromC(salticidae.CPeerNetworkConn((*C.peernetwork_conn_t)(_conn)))
|
|
||||||
peer := conn.GetPeerID(false)
|
|
||||||
defer peer.Free()
|
|
||||||
|
|
||||||
validatorID, exists := s.conns.GetID(peer)
|
|
||||||
if !exists {
|
|
||||||
return ids.ShortID{}, ids.ID{}, 0, nil, fmt.Errorf("received message from un-registered peer %s", validatorID)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Verbo("received message from %s", validatorID)
|
|
||||||
|
|
||||||
msg := salticidae.MsgFromC(salticidae.CMsg(_msg))
|
|
||||||
codec := Codec{}
|
|
||||||
pMsg, err := codec.Parse(op, msg.GetPayloadByMove())
|
|
||||||
if err != nil {
|
|
||||||
return ids.ShortID{}, ids.ID{}, 0, nil, fmt.Errorf("couldn't parse payload: %w", err) // The message couldn't be parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
chainID, err := ids.ToID(pMsg.Get(ChainID).([]byte))
|
|
||||||
s.log.AssertNoError(err)
|
|
||||||
|
|
||||||
requestID := pMsg.Get(RequestID).(uint32)
|
|
||||||
|
|
||||||
return validatorID, chainID, requestID, pMsg, nil
|
|
||||||
}
|
|
|
@ -1,188 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package networking
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
type votingMetrics struct {
|
|
||||||
numGetAcceptedFrontierSent, numGetAcceptedFrontierReceived,
|
|
||||||
numAcceptedFrontierSent, numAcceptedFrontierReceived,
|
|
||||||
numGetAcceptedSent, numGetAcceptedReceived,
|
|
||||||
numAcceptedSent, numAcceptedReceived,
|
|
||||||
numGetSent, numGetReceived,
|
|
||||||
numPutSent, numPutReceived,
|
|
||||||
numPushQuerySent, numPushQueryReceived,
|
|
||||||
numPullQuerySent, numPullQueryReceived,
|
|
||||||
numChitsSent, numChitsReceived prometheus.Counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vm *votingMetrics) Initialize(log logging.Logger, registerer prometheus.Registerer) {
|
|
||||||
vm.numGetAcceptedFrontierSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_accepted_frontier_sent",
|
|
||||||
Help: "Number of get accepted frontier messages sent",
|
|
||||||
})
|
|
||||||
vm.numGetAcceptedFrontierReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_accepted_frontier_received",
|
|
||||||
Help: "Number of get accepted frontier messages received",
|
|
||||||
})
|
|
||||||
vm.numAcceptedFrontierSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "accepted_frontier_sent",
|
|
||||||
Help: "Number of accepted frontier messages sent",
|
|
||||||
})
|
|
||||||
vm.numAcceptedFrontierReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "accepted_frontier_received",
|
|
||||||
Help: "Number of accepted frontier messages received",
|
|
||||||
})
|
|
||||||
vm.numGetAcceptedSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_accepted_sent",
|
|
||||||
Help: "Number of get accepted messages sent",
|
|
||||||
})
|
|
||||||
vm.numGetAcceptedReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_accepted_received",
|
|
||||||
Help: "Number of get accepted messages received",
|
|
||||||
})
|
|
||||||
vm.numAcceptedSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "accepted_sent",
|
|
||||||
Help: "Number of accepted messages sent",
|
|
||||||
})
|
|
||||||
vm.numAcceptedReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "accepted_received",
|
|
||||||
Help: "Number of accepted messages received",
|
|
||||||
})
|
|
||||||
vm.numGetSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_sent",
|
|
||||||
Help: "Number of get messages sent",
|
|
||||||
})
|
|
||||||
vm.numGetReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "get_received",
|
|
||||||
Help: "Number of get messages received",
|
|
||||||
})
|
|
||||||
vm.numPutSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "put_sent",
|
|
||||||
Help: "Number of put messages sent",
|
|
||||||
})
|
|
||||||
vm.numPutReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "put_received",
|
|
||||||
Help: "Number of put messages received",
|
|
||||||
})
|
|
||||||
vm.numPushQuerySent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "push_query_sent",
|
|
||||||
Help: "Number of push query messages sent",
|
|
||||||
})
|
|
||||||
vm.numPushQueryReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "push_query_received",
|
|
||||||
Help: "Number of push query messages received",
|
|
||||||
})
|
|
||||||
vm.numPullQuerySent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "pull_query_sent",
|
|
||||||
Help: "Number of pull query messages sent",
|
|
||||||
})
|
|
||||||
vm.numPullQueryReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "pull_query_received",
|
|
||||||
Help: "Number of pull query messages received",
|
|
||||||
})
|
|
||||||
vm.numChitsSent = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "chits_sent",
|
|
||||||
Help: "Number of chits messages sent",
|
|
||||||
})
|
|
||||||
vm.numChitsReceived = prometheus.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: "gecko",
|
|
||||||
Name: "chits_received",
|
|
||||||
Help: "Number of chits messages received",
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := registerer.Register(vm.numGetAcceptedFrontierSent); err != nil {
|
|
||||||
log.Error("Failed to register get_accepted_frontier_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numGetAcceptedFrontierReceived); err != nil {
|
|
||||||
log.Error("Failed to register get_accepted_frontier_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numAcceptedFrontierSent); err != nil {
|
|
||||||
log.Error("Failed to register accepted_frontier_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numAcceptedFrontierReceived); err != nil {
|
|
||||||
log.Error("Failed to register accepted_frontier_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numGetAcceptedSent); err != nil {
|
|
||||||
log.Error("Failed to register get_accepted_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numGetAcceptedReceived); err != nil {
|
|
||||||
log.Error("Failed to register get_accepted_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numAcceptedSent); err != nil {
|
|
||||||
log.Error("Failed to register accepted_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numAcceptedReceived); err != nil {
|
|
||||||
log.Error("Failed to register accepted_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numGetSent); err != nil {
|
|
||||||
log.Error("Failed to register get_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numGetReceived); err != nil {
|
|
||||||
log.Error("Failed to register get_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numPutSent); err != nil {
|
|
||||||
log.Error("Failed to register put_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numPutReceived); err != nil {
|
|
||||||
log.Error("Failed to register put_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numPushQuerySent); err != nil {
|
|
||||||
log.Error("Failed to register push_query_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numPushQueryReceived); err != nil {
|
|
||||||
log.Error("Failed to register push_query_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numPullQuerySent); err != nil {
|
|
||||||
log.Error("Failed to register pull_query_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numPullQueryReceived); err != nil {
|
|
||||||
log.Error("Failed to register pull_query_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numChitsSent); err != nil {
|
|
||||||
log.Error("Failed to register chits_sent statistics due to %s", err)
|
|
||||||
}
|
|
||||||
if err := registerer.Register(vm.numChitsReceived); err != nil {
|
|
||||||
log.Error("Failed to register chits_received statistics due to %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package xputtest
|
|
||||||
|
|
||||||
// #include "salticidae/network.h"
|
|
||||||
// void issueTx(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/networking"
|
|
||||||
"github.com/ava-labs/gecko/snow/choices"
|
|
||||||
"github.com/ava-labs/gecko/utils/hashing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CClientHandler is the struct that will be accessed on event calls
|
|
||||||
var CClientHandler CClient
|
|
||||||
|
|
||||||
// CClient manages a client network using the c networking library
|
|
||||||
type CClient struct {
|
|
||||||
issuer *Issuer
|
|
||||||
net salticidae.MsgNetwork
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize to the c networking library. This should only be called once
|
|
||||||
// during setup of the node.
|
|
||||||
func (h *CClient) Initialize(net salticidae.MsgNetwork, issuer *Issuer) {
|
|
||||||
h.issuer = issuer
|
|
||||||
h.net = net
|
|
||||||
|
|
||||||
net.RegHandler(networking.IssueTx, salticidae.MsgNetworkMsgCallback(C.issueTx), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *CClient) send(msg networking.Msg, conn salticidae.MsgNetworkConn) {
|
|
||||||
ds := msg.DataStream()
|
|
||||||
defer ds.Free()
|
|
||||||
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
|
|
||||||
defer ba.Free()
|
|
||||||
cMsg := salticidae.NewMsgMovedFromByteArray(msg.Op(), ba, false)
|
|
||||||
defer cMsg.Free()
|
|
||||||
|
|
||||||
h.net.SendMsg(cMsg, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// issueTx handles the recept of an IssueTx message
|
|
||||||
//export issueTx
|
|
||||||
func issueTx(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
msg := salticidae.MsgFromC(salticidae.CMsg(_msg))
|
|
||||||
|
|
||||||
build := networking.Builder{}
|
|
||||||
pMsg, err := build.Parse(networking.IssueTx, msg.GetPayloadByMove())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chainID, _ := ids.ToID(pMsg.Get(networking.ChainID).([]byte))
|
|
||||||
|
|
||||||
txBytes := pMsg.Get(networking.Tx).([]byte)
|
|
||||||
|
|
||||||
txID := ids.NewID(hashing.ComputeHash256Array(txBytes))
|
|
||||||
|
|
||||||
conn := salticidae.MsgNetworkConnFromC(salticidae.CMsgNetworkConn(_conn)).Copy(false)
|
|
||||||
CClientHandler.issuer.IssueTx(chainID, txBytes, func(status choices.Status) {
|
|
||||||
build := networking.Builder{}
|
|
||||||
msg, _ := build.DecidedTx(txID, status)
|
|
||||||
|
|
||||||
CClientHandler.send(msg, conn)
|
|
||||||
conn.Free()
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package xputtest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/snow"
|
|
||||||
"github.com/ava-labs/gecko/snow/choices"
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
type issuableVM interface {
|
|
||||||
IssueTx([]byte, func(choices.Status)) (ids.ID, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issuer manages all the chain transaction flushing.
|
|
||||||
type Issuer struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
log logging.Logger
|
|
||||||
vms map[[32]byte]issuableVM
|
|
||||||
locks map[[32]byte]sync.Locker
|
|
||||||
callbacks chan func()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize this flusher
|
|
||||||
func (i *Issuer) Initialize(log logging.Logger) {
|
|
||||||
i.lock.Lock()
|
|
||||||
defer i.lock.Unlock()
|
|
||||||
i.log = log
|
|
||||||
i.vms = make(map[[32]byte]issuableVM)
|
|
||||||
i.locks = make(map[[32]byte]sync.Locker)
|
|
||||||
i.callbacks = make(chan func(), 1000)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for callback := range i.callbacks {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterChain implements the registrant
|
|
||||||
func (i *Issuer) RegisterChain(ctx *snow.Context, vm interface{}) {
|
|
||||||
i.lock.Lock()
|
|
||||||
defer i.lock.Unlock()
|
|
||||||
|
|
||||||
key := ctx.ChainID.Key()
|
|
||||||
|
|
||||||
switch vm := vm.(type) {
|
|
||||||
case issuableVM:
|
|
||||||
i.vms[key] = vm
|
|
||||||
i.locks[key] = &ctx.Lock
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssueTx issue the transaction to the chain and register the timeout.
|
|
||||||
func (i *Issuer) IssueTx(chainID ids.ID, tx []byte, finalized func(choices.Status)) {
|
|
||||||
i.lock.Lock()
|
|
||||||
defer i.lock.Unlock()
|
|
||||||
|
|
||||||
key := chainID.Key()
|
|
||||||
if lock, exists := i.locks[key]; exists {
|
|
||||||
i.callbacks <- func() {
|
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
if vm, exists := i.vms[key]; exists {
|
|
||||||
if _, err := vm.IssueTx(tx, finalized); err != nil {
|
|
||||||
i.log.Error("Issuing the tx returned with %s unexpectedly", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i.log.Warn("Attempted to issue a Tx to an unsupported chain %s", chainID)
|
|
||||||
}
|
|
||||||
}
|
|
277
node/node.go
277
node/node.go
|
@ -3,24 +3,17 @@
|
||||||
|
|
||||||
package node
|
package node
|
||||||
|
|
||||||
// #include "salticidae/network.h"
|
|
||||||
// void onTerm(threadcall_handle_t *, void *);
|
|
||||||
// void errorHandler(SalticidaeCError *, bool, int32_t, void *);
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/api"
|
"github.com/ava-labs/gecko/api"
|
||||||
"github.com/ava-labs/gecko/api/admin"
|
"github.com/ava-labs/gecko/api/admin"
|
||||||
|
@ -34,14 +27,14 @@ import (
|
||||||
"github.com/ava-labs/gecko/database/prefixdb"
|
"github.com/ava-labs/gecko/database/prefixdb"
|
||||||
"github.com/ava-labs/gecko/genesis"
|
"github.com/ava-labs/gecko/genesis"
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
"github.com/ava-labs/gecko/networking"
|
"github.com/ava-labs/gecko/network"
|
||||||
"github.com/ava-labs/gecko/networking/xputtest"
|
|
||||||
"github.com/ava-labs/gecko/snow/triggers"
|
"github.com/ava-labs/gecko/snow/triggers"
|
||||||
"github.com/ava-labs/gecko/snow/validators"
|
"github.com/ava-labs/gecko/snow/validators"
|
||||||
"github.com/ava-labs/gecko/utils"
|
"github.com/ava-labs/gecko/utils"
|
||||||
"github.com/ava-labs/gecko/utils/hashing"
|
"github.com/ava-labs/gecko/utils/hashing"
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
"github.com/ava-labs/gecko/utils/logging"
|
||||||
"github.com/ava-labs/gecko/utils/wrappers"
|
"github.com/ava-labs/gecko/utils/wrappers"
|
||||||
|
"github.com/ava-labs/gecko/version"
|
||||||
"github.com/ava-labs/gecko/vms"
|
"github.com/ava-labs/gecko/vms"
|
||||||
"github.com/ava-labs/gecko/vms/avm"
|
"github.com/ava-labs/gecko/vms/avm"
|
||||||
"github.com/ava-labs/gecko/vms/nftfx"
|
"github.com/ava-labs/gecko/vms/nftfx"
|
||||||
|
@ -54,16 +47,17 @@ import (
|
||||||
"github.com/ava-labs/gecko/vms/timestampvm"
|
"github.com/ava-labs/gecko/vms/timestampvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Networking constants
|
||||||
const (
|
const (
|
||||||
maxMessageSize = 1 << 25 // maximum size of a message sent with salticidae
|
TCP = "tcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
genesisHashKey = []byte("genesisID")
|
genesisHashKey = []byte("genesisID")
|
||||||
)
|
|
||||||
|
|
||||||
// MainNode is the reference for node callbacks
|
nodeVersion = version.NewDefaultVersion("avalanche", 0, 3, 0)
|
||||||
var MainNode = Node{}
|
versionParser = version.NewDefaultParser()
|
||||||
|
)
|
||||||
|
|
||||||
// Node is an instance of an Ava node.
|
// Node is an instance of an Ava node.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
|
@ -94,30 +88,12 @@ type Node struct {
|
||||||
DecisionDispatcher *triggers.EventDispatcher
|
DecisionDispatcher *triggers.EventDispatcher
|
||||||
ConsensusDispatcher *triggers.EventDispatcher
|
ConsensusDispatcher *triggers.EventDispatcher
|
||||||
|
|
||||||
// Event loop manager
|
// Net runs the networking stack
|
||||||
EC salticidae.EventContext
|
Net network.Network
|
||||||
|
|
||||||
// Caller to the event context
|
|
||||||
TCall salticidae.ThreadCall
|
|
||||||
|
|
||||||
// Network that manages validator peers
|
|
||||||
PeerNet salticidae.PeerNetwork
|
|
||||||
// Network that manages clients
|
|
||||||
ClientNet salticidae.MsgNetwork // TODO: Remove
|
|
||||||
|
|
||||||
// API that handles new connections
|
|
||||||
ValidatorAPI *networking.Handshake
|
|
||||||
// API that handles voting messages
|
|
||||||
ConsensusAPI *networking.Voting
|
|
||||||
|
|
||||||
// current validators of the network
|
// current validators of the network
|
||||||
vdrs validators.Manager
|
vdrs validators.Manager
|
||||||
|
|
||||||
// APIs that handle client messages
|
|
||||||
// TODO: Remove
|
|
||||||
Issuer *xputtest.Issuer
|
|
||||||
CClientAPI *xputtest.CClient
|
|
||||||
|
|
||||||
// Handles HTTP API calls
|
// Handles HTTP API calls
|
||||||
APIServer api.Server
|
APIServer api.Server
|
||||||
|
|
||||||
|
@ -134,71 +110,38 @@ type Node struct {
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//export onTerm
|
func (n *Node) initNetworking() error {
|
||||||
func onTerm(*C.threadcall_handle_t, unsafe.Pointer) {
|
listener, err := net.Listen(TCP, n.Config.StakingIP.PortString())
|
||||||
MainNode.Log.Debug("Terminate signal received")
|
if err != nil {
|
||||||
MainNode.EC.Stop()
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
//export errorHandler
|
|
||||||
func errorHandler(_err *C.struct_SalticidaeCError, fatal C.bool, asyncID C.int32_t, _ unsafe.Pointer) {
|
|
||||||
err := (*salticidae.Error)(unsafe.Pointer(_err))
|
|
||||||
if fatal {
|
|
||||||
MainNode.Log.Fatal("Error during async call: %s", salticidae.StrError(err.GetCode()))
|
|
||||||
MainNode.EC.Stop()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
MainNode.Log.Debug("Error during async with ID %d call: %s", asyncID, salticidae.StrError(err.GetCode()))
|
dialer := network.NewDialer(TCP)
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) initNetlib() error {
|
|
||||||
// Create main event context
|
|
||||||
n.EC = salticidae.NewEventContext()
|
|
||||||
n.TCall = salticidae.NewThreadCall(n.EC)
|
|
||||||
|
|
||||||
n.nodeCloser = utils.HandleSignals(func(os.Signal) {
|
|
||||||
n.TCall.AsyncCall(salticidae.ThreadCallCallback(C.onTerm), nil)
|
|
||||||
}, os.Interrupt, os.Kill)
|
|
||||||
|
|
||||||
// Create peer network config, may have tls enabled
|
|
||||||
peerConfig := salticidae.NewPeerNetworkConfig()
|
|
||||||
peerConfig.ConnTimeout(60)
|
|
||||||
|
|
||||||
msgConfig := peerConfig.AsMsgNetworkConfig()
|
|
||||||
msgConfig.MaxMsgSize(maxMessageSize)
|
|
||||||
|
|
||||||
|
var serverUpgrader, clientUpgrader network.Upgrader
|
||||||
if n.Config.EnableStaking {
|
if n.Config.EnableStaking {
|
||||||
msgConfig.EnableTLS(true)
|
cert, err := tls.LoadX509KeyPair(n.Config.StakingCertFile, n.Config.StakingKeyFile)
|
||||||
msgConfig.TLSKeyFile(n.Config.StakingKeyFile)
|
if err != nil {
|
||||||
msgConfig.TLSCertFile(n.Config.StakingCertFile)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the peer network
|
tlsConfig := &tls.Config{
|
||||||
err := salticidae.NewError()
|
Certificates: []tls.Certificate{cert},
|
||||||
n.PeerNet = salticidae.NewPeerNetwork(n.EC, peerConfig, &err)
|
ClientAuth: tls.RequireAnyClientCert,
|
||||||
if code := err.GetCode(); code != 0 {
|
// We do not use TLS's CA functionality, we just require an
|
||||||
return errors.New(salticidae.StrError(code))
|
// authenticated channel. Therefore, we can safely skip verification
|
||||||
}
|
// here.
|
||||||
// Add peer network error handling
|
//
|
||||||
net := n.PeerNet.AsMsgNetwork()
|
// TODO: Security audit required
|
||||||
net.RegErrorHandler(salticidae.MsgNetworkErrorCallback(C.errorHandler), nil)
|
InsecureSkipVerify: true,
|
||||||
|
|
||||||
if n.Config.ThroughputServerEnabled {
|
|
||||||
// Create the client network
|
|
||||||
msgConfig := salticidae.NewMsgNetworkConfig()
|
|
||||||
msgConfig.MaxMsgSize(maxMessageSize)
|
|
||||||
n.ClientNet = salticidae.NewMsgNetwork(n.EC, msgConfig, &err)
|
|
||||||
if code := err.GetCode(); code != 0 {
|
|
||||||
return errors.New(salticidae.StrError(code))
|
|
||||||
}
|
|
||||||
// Add client network error handling
|
|
||||||
n.ClientNet.RegErrorHandler(salticidae.MsgNetworkErrorCallback(C.errorHandler), nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
serverUpgrader = network.NewTLSServerUpgrader(tlsConfig)
|
||||||
}
|
clientUpgrader = network.NewTLSClientUpgrader(tlsConfig)
|
||||||
|
} else {
|
||||||
|
serverUpgrader = network.NewIPUpgrader()
|
||||||
|
clientUpgrader = network.NewIPUpgrader()
|
||||||
|
}
|
||||||
|
|
||||||
func (n *Node) initValidatorNet() error {
|
|
||||||
// Initialize validator manager and default subnet's validator set
|
// Initialize validator manager and default subnet's validator set
|
||||||
defaultSubnetValidators := validators.NewSet()
|
defaultSubnetValidators := validators.NewSet()
|
||||||
if !n.Config.EnableStaking {
|
if !n.Config.EnableStaking {
|
||||||
|
@ -207,104 +150,64 @@ func (n *Node) initValidatorNet() error {
|
||||||
n.vdrs = validators.NewManager()
|
n.vdrs = validators.NewManager()
|
||||||
n.vdrs.PutValidatorSet(platformvm.DefaultSubnetID, defaultSubnetValidators)
|
n.vdrs.PutValidatorSet(platformvm.DefaultSubnetID, defaultSubnetValidators)
|
||||||
|
|
||||||
cErr := salticidae.NewError()
|
n.Net = network.NewDefaultNetwork(
|
||||||
serverIP := salticidae.NewNetAddrFromIPPortString(n.Config.StakingIP.String(), true, &cErr)
|
n.Config.ConsensusParams.Metrics,
|
||||||
if code := cErr.GetCode(); code != 0 {
|
n.Log,
|
||||||
return errors.New(salticidae.StrError(code))
|
n.ID,
|
||||||
|
n.Config.StakingIP,
|
||||||
|
n.Config.NetworkID,
|
||||||
|
nodeVersion,
|
||||||
|
versionParser,
|
||||||
|
listener,
|
||||||
|
dialer,
|
||||||
|
serverUpgrader,
|
||||||
|
clientUpgrader,
|
||||||
|
defaultSubnetValidators,
|
||||||
|
n.Config.ConsensusRouter,
|
||||||
|
)
|
||||||
|
|
||||||
|
if !n.Config.EnableStaking {
|
||||||
|
n.Net.RegisterHandler(&insecureValidatorManager{
|
||||||
|
vdrs: defaultSubnetValidators,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
n.ValidatorAPI = &networking.HandshakeNet
|
n.nodeCloser = utils.HandleSignals(func(os.Signal) {
|
||||||
n.ValidatorAPI.Initialize(
|
n.Net.Close()
|
||||||
/*log=*/ n.Log,
|
}, os.Interrupt, os.Kill)
|
||||||
/*validators=*/ defaultSubnetValidators,
|
|
||||||
/*myIP=*/ serverIP,
|
|
||||||
/*myID=*/ n.ID,
|
|
||||||
/*network=*/ n.PeerNet,
|
|
||||||
/*metrics=*/ n.Config.ConsensusParams.Metrics,
|
|
||||||
/*enableStaking=*/ n.Config.EnableStaking,
|
|
||||||
/*networkID=*/ n.Config.NetworkID,
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) initConsensusNet() {
|
type insecureValidatorManager struct {
|
||||||
vdrs, ok := n.vdrs.GetValidatorSet(platformvm.DefaultSubnetID)
|
vdrs validators.Set
|
||||||
n.Log.AssertTrue(ok, "should have initialize the validator set already")
|
|
||||||
|
|
||||||
n.ConsensusAPI = &networking.VotingNet
|
|
||||||
n.ConsensusAPI.Initialize(n.Log, vdrs, n.PeerNet, n.ValidatorAPI.Connections(), n.chainManager.Router(), n.Config.ConsensusParams.Metrics)
|
|
||||||
|
|
||||||
n.Log.AssertNoError(n.ConsensusDispatcher.Register("gossip", n.ConsensusAPI))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) initClients() {
|
func (i *insecureValidatorManager) Connected(vdrID ids.ShortID) bool {
|
||||||
n.Issuer = &xputtest.Issuer{}
|
i.vdrs.Add(validators.NewValidator(vdrID, 1))
|
||||||
n.Issuer.Initialize(n.Log)
|
return false
|
||||||
|
|
||||||
n.CClientAPI = &xputtest.CClientHandler
|
|
||||||
n.CClientAPI.Initialize(n.ClientNet, n.Issuer)
|
|
||||||
|
|
||||||
n.chainManager.AddRegistrant(n.Issuer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartConsensusServer starts the P2P server this node uses to communicate
|
func (i *insecureValidatorManager) Disconnected(vdrID ids.ShortID) bool {
|
||||||
// with other nodes
|
i.vdrs.Remove(vdrID)
|
||||||
func (n *Node) StartConsensusServer() error {
|
return false
|
||||||
n.Log.Verbo("starting the consensus server")
|
}
|
||||||
|
|
||||||
n.PeerNet.AsMsgNetwork().Start()
|
|
||||||
|
|
||||||
err := salticidae.NewError()
|
|
||||||
|
|
||||||
// The IP this node listens on for P2P messaging
|
|
||||||
serverIP := salticidae.NewNetAddrFromIPPortString(n.Config.StakingIP.String(), true, &err)
|
|
||||||
if code := err.GetCode(); code != 0 {
|
|
||||||
return fmt.Errorf("failed to create ip addr: %s", salticidae.StrError(code))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen for P2P messages
|
|
||||||
n.PeerNet.Listen(serverIP, &err)
|
|
||||||
if code := err.GetCode(); code != 0 {
|
|
||||||
return fmt.Errorf("failed to listen on consensus server at %s: %s", n.Config.StakingIP, salticidae.StrError(code))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a server to handle throughput tests if configuration says to. Disabled by default.
|
|
||||||
if n.Config.ThroughputServerEnabled {
|
|
||||||
n.ClientNet.Start()
|
|
||||||
|
|
||||||
clientIP := salticidae.NewNetAddrFromIPPortString(fmt.Sprintf("127.0.0.1:%d", n.Config.ThroughputPort), true, &err)
|
|
||||||
if code := err.GetCode(); code != 0 {
|
|
||||||
return fmt.Errorf("failed to start xput server: %s", salticidae.StrError(code))
|
|
||||||
}
|
|
||||||
|
|
||||||
n.ClientNet.Listen(clientIP, &err)
|
|
||||||
if code := err.GetCode(); code != 0 {
|
|
||||||
return fmt.Errorf("failed to listen on xput server at 127.0.0.1:%d: %s", n.Config.ThroughputPort, salticidae.StrError(code))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Dispatch starts the node's servers.
|
||||||
|
// Returns when the node exits.
|
||||||
|
func (n *Node) Dispatch() {
|
||||||
// Add bootstrap nodes to the peer network
|
// Add bootstrap nodes to the peer network
|
||||||
for _, peer := range n.Config.BootstrapPeers {
|
for _, peer := range n.Config.BootstrapPeers {
|
||||||
if !peer.IP.Equal(n.Config.StakingIP) {
|
if !peer.IP.Equal(n.Config.StakingIP) {
|
||||||
bootstrapAddr := salticidae.NewNetAddrFromIPPortString(peer.IP.String(), true, &err)
|
n.Net.Track(peer.IP)
|
||||||
if code := err.GetCode(); code != 0 {
|
|
||||||
return fmt.Errorf("failed to create bootstrap ip addr: %s", salticidae.StrError(code))
|
|
||||||
}
|
|
||||||
|
|
||||||
n.ValidatorAPI.Connect(bootstrapAddr)
|
|
||||||
} else {
|
} else {
|
||||||
n.Log.Error("can't add self as a bootstrapper")
|
n.Log.Error("can't add self as a bootstrapper")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
n.Net.Dispatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch starts the node's servers.
|
|
||||||
// Returns when the node exits.
|
|
||||||
func (n *Node) Dispatch() { n.EC.Dispatch() }
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*********************** End P2P Networking Section ***************************
|
*********************** End P2P Networking Section ***************************
|
||||||
|
@ -410,6 +313,8 @@ func (n *Node) initEventDispatcher() {
|
||||||
|
|
||||||
n.ConsensusDispatcher = &triggers.EventDispatcher{}
|
n.ConsensusDispatcher = &triggers.EventDispatcher{}
|
||||||
n.ConsensusDispatcher.Initialize(n.Log)
|
n.ConsensusDispatcher.Initialize(n.Log)
|
||||||
|
|
||||||
|
n.Log.AssertNoError(n.ConsensusDispatcher.Register("gossip", n.Net))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializes the Platform chain.
|
// Initializes the Platform chain.
|
||||||
|
@ -492,7 +397,7 @@ func (n *Node) initAPIServer() {
|
||||||
err := n.APIServer.Dispatch()
|
err := n.APIServer.Dispatch()
|
||||||
|
|
||||||
n.Log.Fatal("API server initialization failed with %s", err)
|
n.Log.Fatal("API server initialization failed with %s", err)
|
||||||
n.TCall.AsyncCall(salticidae.ThreadCallCallback(C.onTerm), nil)
|
n.Net.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,12 +412,11 @@ func (n *Node) initChainManager() {
|
||||||
n.ConsensusDispatcher,
|
n.ConsensusDispatcher,
|
||||||
n.DB,
|
n.DB,
|
||||||
n.Config.ConsensusRouter,
|
n.Config.ConsensusRouter,
|
||||||
&networking.VotingNet,
|
n.Net,
|
||||||
n.Config.ConsensusParams,
|
n.Config.ConsensusParams,
|
||||||
n.vdrs,
|
n.vdrs,
|
||||||
n.ID,
|
n.ID,
|
||||||
n.Config.NetworkID,
|
n.Config.NetworkID,
|
||||||
n.ValidatorAPI,
|
|
||||||
&n.APIServer,
|
&n.APIServer,
|
||||||
&n.keystoreServer,
|
&n.keystoreServer,
|
||||||
&n.sharedMemory,
|
&n.sharedMemory,
|
||||||
|
@ -556,7 +460,7 @@ func (n *Node) initMetricsAPI() {
|
||||||
func (n *Node) initAdminAPI() {
|
func (n *Node) initAdminAPI() {
|
||||||
if n.Config.AdminAPIEnabled {
|
if n.Config.AdminAPIEnabled {
|
||||||
n.Log.Info("initializing Admin API")
|
n.Log.Info("initializing Admin API")
|
||||||
service := admin.NewService(n.ID, n.Config.NetworkID, n.Log, n.chainManager, n.ValidatorAPI.Connections(), &n.APIServer)
|
service := admin.NewService(n.ID, n.Config.NetworkID, n.Log, n.chainManager, n.Net, &n.APIServer)
|
||||||
n.APIServer.AddRoute(service, &sync.RWMutex{}, "admin", "", n.HTTPLog)
|
n.APIServer.AddRoute(service, &sync.RWMutex{}, "admin", "", n.HTTPLog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -570,7 +474,7 @@ func (n *Node) initHealthAPI() {
|
||||||
|
|
||||||
n.Log.Info("initializing Health API")
|
n.Log.Info("initializing Health API")
|
||||||
service := health.NewService(n.Log)
|
service := health.NewService(n.Log)
|
||||||
service.RegisterHeartbeat("network.validators.heartbeat", n.ValidatorAPI, 5*time.Minute)
|
service.RegisterHeartbeat("network.validators.heartbeat", n.Net, 5*time.Minute)
|
||||||
n.APIServer.AddRoute(service.Handler(), &sync.RWMutex{}, "health", "", n.HTTPLog)
|
n.APIServer.AddRoute(service.Handler(), &sync.RWMutex{}, "health", "", n.HTTPLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,34 +540,24 @@ func (n *Node) Initialize(Config *Config, logger logging.Logger, logFactory logg
|
||||||
return fmt.Errorf("problem initializing staker ID: %w", err)
|
return fmt.Errorf("problem initializing staker ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize shared memory
|
|
||||||
n.initSharedMemory()
|
|
||||||
|
|
||||||
if err = n.initNetlib(); err != nil { // Set up all networking
|
|
||||||
return fmt.Errorf("problem initializing networking: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start HTTP APIs
|
// Start HTTP APIs
|
||||||
n.initAPIServer() // Start the API Server
|
n.initAPIServer() // Start the API Server
|
||||||
n.initKeystoreAPI() // Start the Keystore API
|
n.initKeystoreAPI() // Start the Keystore API
|
||||||
n.initMetricsAPI() // Start the Metrics API
|
n.initMetricsAPI() // Start the Metrics API
|
||||||
|
|
||||||
// Start node-to-node consensus server
|
// initialize shared memory
|
||||||
if err := n.initValidatorNet(); err != nil { // Set up the validator handshake + authentication
|
n.initSharedMemory()
|
||||||
return fmt.Errorf("problem initializing validator network: %w", err)
|
|
||||||
|
if err = n.initNetworking(); err != nil { // Set up all networking
|
||||||
|
return fmt.Errorf("problem initializing networking: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := n.initVMManager(); err != nil { // Set up the vm manager
|
if err := n.initVMManager(); err != nil { // Set up the vm manager
|
||||||
return fmt.Errorf("problem initializing the VM manager: %w", err)
|
return fmt.Errorf("problem initializing the VM manager: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
n.initEventDispatcher() // Set up the event dipatcher
|
n.initEventDispatcher() // Set up the event dipatcher
|
||||||
n.initChainManager() // Set up the chain manager
|
n.initChainManager() // Set up the chain manager
|
||||||
n.initConsensusNet() // Set up the main consensus network
|
|
||||||
|
|
||||||
// TODO: Remove once API is fully featured for throughput tests
|
|
||||||
if n.Config.ThroughputServerEnabled {
|
|
||||||
n.initClients() // Set up the client servers
|
|
||||||
}
|
|
||||||
|
|
||||||
n.initAdminAPI() // Start the Admin API
|
n.initAdminAPI() // Start the Admin API
|
||||||
n.initHealthAPI() // Start the Health API
|
n.initHealthAPI() // Start the Health API
|
||||||
|
@ -678,8 +572,7 @@ func (n *Node) Initialize(Config *Config, logger logging.Logger, logFactory logg
|
||||||
// Shutdown this node
|
// Shutdown this node
|
||||||
func (n *Node) Shutdown() {
|
func (n *Node) Shutdown() {
|
||||||
n.Log.Info("shutting down the node")
|
n.Log.Info("shutting down the node")
|
||||||
n.ValidatorAPI.Shutdown()
|
n.Net.Close()
|
||||||
n.ConsensusAPI.Shutdown()
|
|
||||||
n.chainManager.Shutdown()
|
n.chainManager.Shutdown()
|
||||||
utils.ClearSignals(n.nodeCloser)
|
utils.ClearSignals(n.nodeCloser)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ borealis_node:
|
||||||
node1:
|
node1:
|
||||||
ansible_host: 34.207.133.167
|
ansible_host: 34.207.133.167
|
||||||
node2:
|
node2:
|
||||||
ansible_host: 107.23.241.199
|
ansible_host: 54.162.71.9
|
||||||
node3:
|
node3:
|
||||||
ansible_host: 54.197.215.186
|
ansible_host: 54.197.215.186
|
||||||
node4:
|
node4:
|
||||||
|
|
|
@ -10,7 +10,7 @@ PREFIX="${PREFIX:-$(pwd)/build}"
|
||||||
PLUGIN_PREFIX="$PREFIX/plugins"
|
PLUGIN_PREFIX="$PREFIX/plugins"
|
||||||
|
|
||||||
SRC_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
SRC_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
||||||
source "$SRC_DIR/env.sh"
|
GOPATH="$(go env GOPATH)"
|
||||||
|
|
||||||
CORETH_PKG=github.com/ava-labs/coreth
|
CORETH_PKG=github.com/ava-labs/coreth
|
||||||
CORETH_PATH="$GOPATH/src/$CORETH_PKG"
|
CORETH_PATH="$GOPATH/src/$CORETH_PKG"
|
||||||
|
@ -36,9 +36,8 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
go build -o "$PREFIX/ava" "$GECKO_PATH/main/"*.go
|
go build -o "$PREFIX/ava" "$GECKO_PATH/main/"*.go
|
||||||
go build -o "$PREFIX/xputtest" "$GECKO_PATH/xputtest/"*.go
|
|
||||||
go build -o "$PLUGIN_PREFIX/evm" "$CORETH_PATH/plugin/"*.go
|
go build -o "$PLUGIN_PREFIX/evm" "$CORETH_PATH/plugin/"*.go
|
||||||
if [[ -f "$PREFIX/ava" && -f "$PREFIX/xputtest" && -f "$PLUGIN_PREFIX/evm" ]]; then
|
if [[ -f "$PREFIX/ava" && -f "$PLUGIN_PREFIX/evm" ]]; then
|
||||||
echo "Build Successful"
|
echo "Build Successful"
|
||||||
else
|
else
|
||||||
echo "Build failure"
|
echo "Build failure"
|
||||||
|
|
|
@ -6,7 +6,4 @@ set -o pipefail
|
||||||
|
|
||||||
# Ted: contact me when you make any changes
|
# Ted: contact me when you make any changes
|
||||||
|
|
||||||
SRC_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
go test -race -timeout="60s" -coverprofile="coverage.out" -covermode="atomic" ./...
|
||||||
source "$SRC_DIR/env.sh"
|
|
||||||
|
|
||||||
go test -race -timeout="30s" -coverprofile="coverage.out" -covermode="atomic" ./...
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Ted: contact me when you make any changes
|
|
||||||
|
|
||||||
# resolve the required env for salticidae-go
|
|
||||||
GOPATH="$(go env GOPATH)"
|
|
||||||
SALTICIDAE_GO_HOME="$GOPATH/src/github.com/ava-labs/salticidae-go/"
|
|
||||||
|
|
||||||
if [[ -f "$SALTICIDAE_GO_HOME/salticidae/libsalticidae.a" ]]; then
|
|
||||||
source "$SALTICIDAE_GO_HOME/scripts/env.sh"
|
|
||||||
else
|
|
||||||
source /dev/stdin <<<"$(curl -sS https://raw.githubusercontent.com/ava-labs/salticidae-go/v0.1.0/setup.sh)"
|
|
||||||
fi
|
|
|
@ -30,9 +30,16 @@ type bootstrapper struct {
|
||||||
metrics
|
metrics
|
||||||
common.Bootstrapper
|
common.Bootstrapper
|
||||||
|
|
||||||
|
// IDs of vertices that we're already in the process of getting
|
||||||
|
// TODO: Find a better way to track; this keeps every single vertex's ID in memory when bootstrapping from nothing
|
||||||
|
seen ids.Set
|
||||||
|
|
||||||
|
numFetched uint64 // number of vertices that have been fetched from validators
|
||||||
|
|
||||||
// vtxReqs prevents asking validators for the same vertex
|
// vtxReqs prevents asking validators for the same vertex
|
||||||
vtxReqs common.Requests
|
vtxReqs common.Requests
|
||||||
|
|
||||||
|
// IDs of vertices that we have requested from other validators but haven't received
|
||||||
pending ids.Set
|
pending ids.Set
|
||||||
finished bool
|
finished bool
|
||||||
onFinished func()
|
onFinished func()
|
||||||
|
@ -91,8 +98,6 @@ func (b *bootstrapper) ForceAccepted(acceptedContainerIDs ids.Set) {
|
||||||
|
|
||||||
// Put ...
|
// Put ...
|
||||||
func (b *bootstrapper) Put(vdr ids.ShortID, requestID uint32, vtxID ids.ID, vtxBytes []byte) {
|
func (b *bootstrapper) Put(vdr ids.ShortID, requestID uint32, vtxID ids.ID, vtxBytes []byte) {
|
||||||
b.BootstrapConfig.Context.Log.Verbo("Put called for vertexID %s", vtxID)
|
|
||||||
|
|
||||||
vtx, err := b.State.ParseVertex(vtxBytes)
|
vtx, err := b.State.ParseVertex(vtxBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.BootstrapConfig.Context.Log.Debug("ParseVertex failed due to %s for block:\n%s",
|
b.BootstrapConfig.Context.Log.Debug("ParseVertex failed due to %s for block:\n%s",
|
||||||
|
@ -168,6 +173,10 @@ func (b *bootstrapper) addVertex(vtx avalanche.Vertex) {
|
||||||
|
|
||||||
func (b *bootstrapper) storeVertex(vtx avalanche.Vertex) {
|
func (b *bootstrapper) storeVertex(vtx avalanche.Vertex) {
|
||||||
vts := []avalanche.Vertex{vtx}
|
vts := []avalanche.Vertex{vtx}
|
||||||
|
b.numFetched++
|
||||||
|
if b.numFetched%2500 == 0 { // perioidcally inform user of progress
|
||||||
|
b.BootstrapConfig.Context.Log.Info("bootstrapping has fetched %d vertices", b.numFetched)
|
||||||
|
}
|
||||||
|
|
||||||
for len(vts) > 0 {
|
for len(vts) > 0 {
|
||||||
newLen := len(vts) - 1
|
newLen := len(vts) - 1
|
||||||
|
@ -187,6 +196,8 @@ func (b *bootstrapper) storeVertex(vtx avalanche.Vertex) {
|
||||||
vtx: vtx,
|
vtx: vtx,
|
||||||
}); err == nil {
|
}); err == nil {
|
||||||
b.numBlockedVtx.Inc()
|
b.numBlockedVtx.Inc()
|
||||||
|
} else {
|
||||||
|
b.BootstrapConfig.Context.Log.Verbo("couldn't push to vtxBlocked")
|
||||||
}
|
}
|
||||||
for _, tx := range vtx.Txs() {
|
for _, tx := range vtx.Txs() {
|
||||||
if err := b.TxBlocked.Push(&txJob{
|
if err := b.TxBlocked.Push(&txJob{
|
||||||
|
@ -195,10 +206,16 @@ func (b *bootstrapper) storeVertex(vtx avalanche.Vertex) {
|
||||||
tx: tx,
|
tx: tx,
|
||||||
}); err == nil {
|
}); err == nil {
|
||||||
b.numBlockedTx.Inc()
|
b.numBlockedTx.Inc()
|
||||||
|
} else {
|
||||||
|
b.BootstrapConfig.Context.Log.Verbo("couldn't push to txBlocked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, parent := range vtx.Parents() {
|
||||||
|
if parentID := parent.ID(); !b.seen.Contains(parentID) {
|
||||||
|
b.seen.Add(parentID)
|
||||||
|
vts = append(vts, parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vts = append(vts, vtx.Parents()...)
|
|
||||||
case choices.Accepted:
|
case choices.Accepted:
|
||||||
b.BootstrapConfig.Context.Log.Verbo("Bootstrapping confirmed %s", vtxID)
|
b.BootstrapConfig.Context.Log.Verbo("Bootstrapping confirmed %s", vtxID)
|
||||||
case choices.Rejected:
|
case choices.Rejected:
|
||||||
|
@ -214,12 +231,14 @@ func (b *bootstrapper) finish() {
|
||||||
if b.finished {
|
if b.finished {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
b.BootstrapConfig.Context.Log.Info("bootstrapping finished fetching vertices. executing state transitions...")
|
||||||
|
|
||||||
b.executeAll(b.TxBlocked, b.numBlockedTx)
|
b.executeAll(b.TxBlocked, b.numBlockedTx)
|
||||||
b.executeAll(b.VtxBlocked, b.numBlockedVtx)
|
b.executeAll(b.VtxBlocked, b.numBlockedVtx)
|
||||||
|
|
||||||
// Start consensus
|
// Start consensus
|
||||||
b.onFinished()
|
b.onFinished()
|
||||||
|
b.seen = ids.Set{}
|
||||||
b.finished = true
|
b.finished = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
@ -50,7 +49,7 @@ func ByteArraysToHash256Array(byteArray ...[]byte) [32]byte {
|
||||||
for _, b := range byteArray {
|
for _, b := range byteArray {
|
||||||
err := binary.Write(buffer, binary.LittleEndian, b)
|
err := binary.Write(buffer, binary.LittleEndian, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ComputeHash256Array(buffer.Bytes())
|
return ComputeHash256Array(buffer.Bytes())
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parser defines the interface of a version parser
|
||||||
|
type Parser interface {
|
||||||
|
Parse(string) (Version, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
appSeparator string
|
||||||
|
versionSeparator string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultParser returns a new parser with the default separators
|
||||||
|
func NewDefaultParser() Parser { return NewParser(defaultAppSeparator, defaultVersionSeparator) }
|
||||||
|
|
||||||
|
// NewParser returns a new parser
|
||||||
|
func NewParser(appSeparator string, versionSeparator string) Parser {
|
||||||
|
return &parser{
|
||||||
|
appSeparator: appSeparator,
|
||||||
|
versionSeparator: versionSeparator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) Parse(s string) (Version, error) {
|
||||||
|
splitApp := strings.SplitN(s, p.appSeparator, 2)
|
||||||
|
if len(splitApp) != 2 {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s as a version", s)
|
||||||
|
}
|
||||||
|
splitVersion := strings.SplitN(splitApp[1], p.versionSeparator, 3)
|
||||||
|
if len(splitVersion) != 3 {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s as a version", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
major, err := strconv.Atoi(splitVersion[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s as a version due to %w", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
minor, err := strconv.Atoi(splitVersion[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s as a version due to %w", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patch, err := strconv.Atoi(splitVersion[2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s as a version due to %w", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewVersion(
|
||||||
|
splitApp[0],
|
||||||
|
p.appSeparator,
|
||||||
|
p.versionSeparator,
|
||||||
|
major,
|
||||||
|
minor,
|
||||||
|
patch,
|
||||||
|
), nil
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewDefaultParser(t *testing.T) {
|
||||||
|
p := NewDefaultParser()
|
||||||
|
|
||||||
|
v, err := p.Parse("ava/1.2.3")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
assert.Equal(t, "ava/1.2.3", v.String())
|
||||||
|
assert.Equal(t, "ava", v.App())
|
||||||
|
assert.Equal(t, 1, v.Major())
|
||||||
|
assert.Equal(t, 2, v.Minor())
|
||||||
|
assert.Equal(t, 3, v.Patch())
|
||||||
|
assert.NoError(t, v.Compatible(v))
|
||||||
|
assert.False(t, v.Before(v))
|
||||||
|
|
||||||
|
badVersions := []string{
|
||||||
|
"",
|
||||||
|
"ava/",
|
||||||
|
"ava/z.0.0",
|
||||||
|
"ava/0.z.0",
|
||||||
|
"ava/0.0.z",
|
||||||
|
}
|
||||||
|
for _, badVersion := range badVersions {
|
||||||
|
_, err := p.Parse(badVersion)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewParser(t *testing.T) {
|
||||||
|
p := NewParser(":", ",")
|
||||||
|
|
||||||
|
v, err := p.Parse("ava:1,2,3")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
assert.Equal(t, "ava:1,2,3", v.String())
|
||||||
|
assert.Equal(t, "ava", v.App())
|
||||||
|
assert.Equal(t, 1, v.Major())
|
||||||
|
assert.Equal(t, 2, v.Minor())
|
||||||
|
assert.Equal(t, 3, v.Patch())
|
||||||
|
assert.NoError(t, v.Compatible(v))
|
||||||
|
assert.False(t, v.Before(v))
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultAppSeparator = "/"
|
||||||
|
defaultVersionSeparator = "."
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errDifferentApps = errors.New("different applications")
|
||||||
|
errDifferentMajor = errors.New("different major version")
|
||||||
|
errDifferentMinor = errors.New("different minor version")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version defines what is needed to describe a version
|
||||||
|
type Version interface {
|
||||||
|
fmt.Stringer
|
||||||
|
|
||||||
|
App() string
|
||||||
|
Major() int
|
||||||
|
Minor() int
|
||||||
|
Patch() int
|
||||||
|
|
||||||
|
Compatible(Version) error
|
||||||
|
Before(Version) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type version struct {
|
||||||
|
app string
|
||||||
|
major int
|
||||||
|
minor int
|
||||||
|
patch int
|
||||||
|
str string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultVersion returns a new version with default separators
|
||||||
|
func NewDefaultVersion(
|
||||||
|
app string,
|
||||||
|
major int,
|
||||||
|
minor int,
|
||||||
|
patch int,
|
||||||
|
) Version {
|
||||||
|
return NewVersion(
|
||||||
|
app,
|
||||||
|
defaultAppSeparator,
|
||||||
|
defaultVersionSeparator,
|
||||||
|
major,
|
||||||
|
minor,
|
||||||
|
patch,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVersion returns a new version
|
||||||
|
func NewVersion(
|
||||||
|
app string,
|
||||||
|
appSeparator string,
|
||||||
|
versionSeparator string,
|
||||||
|
major int,
|
||||||
|
minor int,
|
||||||
|
patch int,
|
||||||
|
) Version {
|
||||||
|
return &version{
|
||||||
|
app: app,
|
||||||
|
major: major,
|
||||||
|
minor: minor,
|
||||||
|
patch: patch,
|
||||||
|
str: fmt.Sprintf("%s%s%d%s%d%s%d",
|
||||||
|
app,
|
||||||
|
appSeparator,
|
||||||
|
major,
|
||||||
|
versionSeparator,
|
||||||
|
minor,
|
||||||
|
versionSeparator,
|
||||||
|
patch,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *version) App() string { return v.app }
|
||||||
|
func (v *version) Major() int { return v.major }
|
||||||
|
func (v *version) Minor() int { return v.minor }
|
||||||
|
func (v *version) Patch() int { return v.patch }
|
||||||
|
func (v *version) String() string { return v.str }
|
||||||
|
|
||||||
|
func (v *version) Compatible(o Version) error {
|
||||||
|
switch {
|
||||||
|
case v.App() != o.App():
|
||||||
|
return errDifferentApps
|
||||||
|
case v.Major() != o.Major():
|
||||||
|
return errDifferentMajor
|
||||||
|
case v.Minor() != o.Minor():
|
||||||
|
return errDifferentMinor
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *version) Before(o Version) bool {
|
||||||
|
if v.App() != o.App() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
v := v.Major()
|
||||||
|
o := o.Major()
|
||||||
|
switch {
|
||||||
|
case v < o:
|
||||||
|
return true
|
||||||
|
case v > o:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
v := v.Minor()
|
||||||
|
o := o.Minor()
|
||||||
|
switch {
|
||||||
|
case v < o:
|
||||||
|
return true
|
||||||
|
case v > o:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
v := v.Patch()
|
||||||
|
o := o.Patch()
|
||||||
|
switch {
|
||||||
|
case v < o:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
||||||
|
// See the file LICENSE for licensing terms.
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewDefaultVersion(t *testing.T) {
|
||||||
|
v := NewDefaultVersion("ava", 1, 2, 3)
|
||||||
|
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
assert.Equal(t, "ava/1.2.3", v.String())
|
||||||
|
assert.Equal(t, "ava", v.App())
|
||||||
|
assert.Equal(t, 1, v.Major())
|
||||||
|
assert.Equal(t, 2, v.Minor())
|
||||||
|
assert.Equal(t, 3, v.Patch())
|
||||||
|
assert.NoError(t, v.Compatible(v))
|
||||||
|
assert.False(t, v.Before(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewVersion(t *testing.T) {
|
||||||
|
v := NewVersion("ava", ":", ",", 1, 2, 3)
|
||||||
|
|
||||||
|
assert.NotNil(t, v)
|
||||||
|
assert.Equal(t, "ava:1,2,3", v.String())
|
||||||
|
assert.Equal(t, "ava", v.App())
|
||||||
|
assert.Equal(t, 1, v.Major())
|
||||||
|
assert.Equal(t, 2, v.Minor())
|
||||||
|
assert.Equal(t, 3, v.Patch())
|
||||||
|
assert.NoError(t, v.Compatible(v))
|
||||||
|
assert.False(t, v.Before(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncompatibleApps(t *testing.T) {
|
||||||
|
v0 := NewDefaultVersion("ava", 1, 2, 3)
|
||||||
|
v1 := NewDefaultVersion("notava", 1, 2, 3)
|
||||||
|
|
||||||
|
assert.NotNil(t, v0)
|
||||||
|
assert.NotNil(t, v1)
|
||||||
|
assert.Error(t, v0.Compatible(v1))
|
||||||
|
assert.Error(t, v1.Compatible(v0))
|
||||||
|
|
||||||
|
assert.False(t, v0.Before(v1))
|
||||||
|
assert.False(t, v1.Before(v0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncompatibleMajor(t *testing.T) {
|
||||||
|
v0 := NewDefaultVersion("ava", 1, 2, 3)
|
||||||
|
v1 := NewDefaultVersion("ava", 2, 2, 3)
|
||||||
|
|
||||||
|
assert.NotNil(t, v0)
|
||||||
|
assert.NotNil(t, v1)
|
||||||
|
assert.Error(t, v0.Compatible(v1))
|
||||||
|
assert.Error(t, v1.Compatible(v0))
|
||||||
|
|
||||||
|
assert.True(t, v0.Before(v1))
|
||||||
|
assert.False(t, v1.Before(v0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncompatibleMinor(t *testing.T) {
|
||||||
|
v0 := NewDefaultVersion("ava", 1, 2, 3)
|
||||||
|
v1 := NewDefaultVersion("ava", 1, 3, 3)
|
||||||
|
|
||||||
|
assert.NotNil(t, v0)
|
||||||
|
assert.NotNil(t, v1)
|
||||||
|
assert.Error(t, v0.Compatible(v1))
|
||||||
|
assert.Error(t, v1.Compatible(v0))
|
||||||
|
|
||||||
|
assert.True(t, v0.Before(v1))
|
||||||
|
assert.False(t, v1.Before(v0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompatiblePatch(t *testing.T) {
|
||||||
|
v0 := NewDefaultVersion("ava", 1, 2, 3)
|
||||||
|
v1 := NewDefaultVersion("ava", 1, 2, 4)
|
||||||
|
|
||||||
|
assert.NotNil(t, v0)
|
||||||
|
assert.NotNil(t, v1)
|
||||||
|
assert.NoError(t, v0.Compatible(v1))
|
||||||
|
assert.NoError(t, v1.Compatible(v0))
|
||||||
|
|
||||||
|
assert.True(t, v0.Before(v1))
|
||||||
|
assert.False(t, v1.Before(v0))
|
||||||
|
}
|
|
@ -16,10 +16,11 @@ import (
|
||||||
"github.com/ava-labs/gecko/utils/formatting"
|
"github.com/ava-labs/gecko/utils/formatting"
|
||||||
"github.com/ava-labs/gecko/utils/hashing"
|
"github.com/ava-labs/gecko/utils/hashing"
|
||||||
"github.com/ava-labs/gecko/utils/json"
|
"github.com/ava-labs/gecko/utils/json"
|
||||||
safemath "github.com/ava-labs/gecko/utils/math"
|
|
||||||
"github.com/ava-labs/gecko/vms/components/ava"
|
"github.com/ava-labs/gecko/vms/components/ava"
|
||||||
"github.com/ava-labs/gecko/vms/components/verify"
|
"github.com/ava-labs/gecko/vms/components/verify"
|
||||||
"github.com/ava-labs/gecko/vms/secp256k1fx"
|
"github.com/ava-labs/gecko/vms/secp256k1fx"
|
||||||
|
|
||||||
|
safemath "github.com/ava-labs/gecko/utils/math"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -37,6 +38,7 @@ var (
|
||||||
errUnknownOutputType = errors.New("unknown output type")
|
errUnknownOutputType = errors.New("unknown output type")
|
||||||
errUnneededAddress = errors.New("address not required to sign")
|
errUnneededAddress = errors.New("address not required to sign")
|
||||||
errUnknownCredentialType = errors.New("unknown credential type")
|
errUnknownCredentialType = errors.New("unknown credential type")
|
||||||
|
errNilTxID = errors.New("nil transaction ID")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service defines the base service for the asset vm
|
// Service defines the base service for the asset vm
|
||||||
|
@ -75,10 +77,6 @@ type GetTxStatusReply struct {
|
||||||
Status choices.Status `json:"status"`
|
Status choices.Status `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
errNilTxID = errors.New("nil transaction ID")
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetTxStatus returns the status of the specified transaction
|
// GetTxStatus returns the status of the specified transaction
|
||||||
func (service *Service) GetTxStatus(r *http.Request, args *GetTxStatusArgs, reply *GetTxStatusReply) error {
|
func (service *Service) GetTxStatus(r *http.Request, args *GetTxStatusArgs, reply *GetTxStatusReply) error {
|
||||||
service.vm.ctx.Log.Verbo("GetTxStatus called with %s", args.TxID)
|
service.vm.ctx.Log.Verbo("GetTxStatus called with %s", args.TxID)
|
||||||
|
@ -247,6 +245,7 @@ func (service *Service) GetBalance(r *http.Request, args *GetBalanceArgs, reply
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reply.UTXOIDs = make([]ava.UTXOID, 0, len(utxos))
|
||||||
for _, utxo := range utxos {
|
for _, utxo := range utxos {
|
||||||
if !utxo.AssetID().Equals(assetID) {
|
if !utxo.AssetID().Equals(assetID) {
|
||||||
continue
|
continue
|
||||||
|
@ -545,7 +544,7 @@ func (service *Service) CreateAddress(r *http.Request, args *CreateAddressArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
addresses, _ := user.Addresses(db)
|
addresses, _ := user.Addresses(db)
|
||||||
addresses = append(addresses, ids.NewID(hashing.ComputeHash256Array(sk.PublicKey().Address().Bytes())))
|
addresses = append(addresses, sk.PublicKey().Address())
|
||||||
|
|
||||||
if err := user.SetAddresses(db, addresses); err != nil {
|
if err := user.SetAddresses(db, addresses); err != nil {
|
||||||
return fmt.Errorf("problem saving address: %w", err)
|
return fmt.Errorf("problem saving address: %w", err)
|
||||||
|
@ -555,6 +554,40 @@ func (service *Service) CreateAddress(r *http.Request, args *CreateAddressArgs,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListAddressesArgs ...
|
||||||
|
type ListAddressesArgs struct {
|
||||||
|
// User that we're listing the addresses of
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAddressesResponse ...
|
||||||
|
type ListAddressesResponse struct {
|
||||||
|
// Each element is an address controlled by specified account
|
||||||
|
Addresses []string `json:"addresses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAddresses returns all of the addresses controlled by user [args.Username]
|
||||||
|
func (service *Service) ListAddresses(_ *http.Request, args *ListAddressesArgs, response *ListAddressesResponse) error {
|
||||||
|
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("problem retrieving user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Addresses = []string{}
|
||||||
|
|
||||||
|
user := userState{vm: service.vm}
|
||||||
|
addresses, err := user.Addresses(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, address := range addresses {
|
||||||
|
response.Addresses = append(response.Addresses, service.vm.Format(address.Bytes()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ExportKeyArgs are arguments for ExportKey
|
// ExportKeyArgs are arguments for ExportKey
|
||||||
type ExportKeyArgs struct {
|
type ExportKeyArgs struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
@ -576,6 +609,10 @@ func (service *Service) ExportKey(r *http.Request, args *ExportKeyArgs, reply *E
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("problem parsing address: %w", err)
|
return fmt.Errorf("problem parsing address: %w", err)
|
||||||
}
|
}
|
||||||
|
addr, err := ids.ToShortID(address)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("problem parsing address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
|
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -584,7 +621,7 @@ func (service *Service) ExportKey(r *http.Request, args *ExportKeyArgs, reply *E
|
||||||
|
|
||||||
user := userState{vm: service.vm}
|
user := userState{vm: service.vm}
|
||||||
|
|
||||||
sk, err := user.Key(db, ids.NewID(hashing.ComputeHash256Array(address)))
|
sk, err := user.Key(db, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("problem retrieving private key: %w", err)
|
return fmt.Errorf("problem retrieving private key: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -629,7 +666,7 @@ func (service *Service) ImportKey(r *http.Request, args *ImportKeyArgs, reply *I
|
||||||
}
|
}
|
||||||
|
|
||||||
addresses, _ := user.Addresses(db)
|
addresses, _ := user.Addresses(db)
|
||||||
addresses = append(addresses, ids.NewID(hashing.ComputeHash256Array(sk.PublicKey().Address().Bytes())))
|
addresses = append(addresses, sk.PublicKey().Address())
|
||||||
|
|
||||||
if err := user.SetAddresses(db, addresses); err != nil {
|
if err := user.SetAddresses(db, addresses); err != nil {
|
||||||
return fmt.Errorf("problem saving addresses: %w", err)
|
return fmt.Errorf("problem saving addresses: %w", err)
|
||||||
|
@ -688,7 +725,9 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
|
||||||
addresses, _ := user.Addresses(db)
|
addresses, _ := user.Addresses(db)
|
||||||
|
|
||||||
addrs := ids.Set{}
|
addrs := ids.Set{}
|
||||||
addrs.Add(addresses...)
|
for _, addr := range addresses {
|
||||||
|
addrs.Add(ids.NewID(hashing.ComputeHash256Array(addr.Bytes())))
|
||||||
|
}
|
||||||
utxos, err := service.vm.GetUTXOs(addrs)
|
utxos, err := service.vm.GetUTXOs(addrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("problem retrieving user's UTXOs: %w", err)
|
return fmt.Errorf("problem retrieving user's UTXOs: %w", err)
|
||||||
|
@ -957,6 +996,10 @@ func (service *Service) SignMintTx(r *http.Request, args *SignMintTxArgs, reply
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("problem parsing address '%s': %w", args.Minter, err)
|
return fmt.Errorf("problem parsing address '%s': %w", args.Minter, err)
|
||||||
}
|
}
|
||||||
|
addr, err := ids.ToShortID(minter)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("problem parsing address '%s': %w", args.Minter, err)
|
||||||
|
}
|
||||||
|
|
||||||
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
|
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -965,7 +1008,6 @@ func (service *Service) SignMintTx(r *http.Request, args *SignMintTxArgs, reply
|
||||||
|
|
||||||
user := userState{vm: service.vm}
|
user := userState{vm: service.vm}
|
||||||
|
|
||||||
addr := ids.NewID(hashing.ComputeHash256Array(minter))
|
|
||||||
sk, err := user.Key(db, addr)
|
sk, err := user.Key(db, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("problem retriving private key: %w", err)
|
return fmt.Errorf("problem retriving private key: %w", err)
|
||||||
|
@ -1095,7 +1137,10 @@ func (service *Service) ImportAVA(_ *http.Request, args *ImportAVAArgs, reply *I
|
||||||
addresses, _ := user.Addresses(db)
|
addresses, _ := user.Addresses(db)
|
||||||
|
|
||||||
addrs := ids.Set{}
|
addrs := ids.Set{}
|
||||||
addrs.Add(addresses...)
|
for _, addr := range addresses {
|
||||||
|
addrs.Add(ids.NewID(hashing.ComputeHash256Array(addr.Bytes())))
|
||||||
|
}
|
||||||
|
|
||||||
utxos, err := service.vm.GetAtomicUTXOs(addrs)
|
utxos, err := service.vm.GetAtomicUTXOs(addrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err)
|
return fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err)
|
||||||
|
@ -1239,7 +1284,10 @@ func (service *Service) ExportAVA(_ *http.Request, args *ExportAVAArgs, reply *E
|
||||||
addresses, _ := user.Addresses(db)
|
addresses, _ := user.Addresses(db)
|
||||||
|
|
||||||
addrs := ids.Set{}
|
addrs := ids.Set{}
|
||||||
addrs.Add(addresses...)
|
for _, addr := range addresses {
|
||||||
|
addrs.Add(ids.NewID(hashing.ComputeHash256Array(addr.Bytes())))
|
||||||
|
}
|
||||||
|
|
||||||
utxos, err := service.vm.GetUTXOs(addrs)
|
utxos, err := service.vm.GetUTXOs(addrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("problem retrieving user's UTXOs: %w", err)
|
return fmt.Errorf("problem retrieving user's UTXOs: %w", err)
|
||||||
|
|
|
@ -7,14 +7,13 @@ import (
|
||||||
"github.com/ava-labs/gecko/database"
|
"github.com/ava-labs/gecko/database"
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
"github.com/ava-labs/gecko/utils/crypto"
|
"github.com/ava-labs/gecko/utils/crypto"
|
||||||
"github.com/ava-labs/gecko/utils/hashing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var addresses = ids.Empty
|
var addresses = ids.Empty
|
||||||
|
|
||||||
type userState struct{ vm *VM }
|
type userState struct{ vm *VM }
|
||||||
|
|
||||||
func (s *userState) SetAddresses(db database.Database, addrs []ids.ID) error {
|
func (s *userState) SetAddresses(db database.Database, addrs []ids.ShortID) error {
|
||||||
bytes, err := s.vm.codec.Marshal(addrs)
|
bytes, err := s.vm.codec.Marshal(addrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -22,12 +21,12 @@ func (s *userState) SetAddresses(db database.Database, addrs []ids.ID) error {
|
||||||
return db.Put(addresses.Bytes(), bytes)
|
return db.Put(addresses.Bytes(), bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *userState) Addresses(db database.Database) ([]ids.ID, error) {
|
func (s *userState) Addresses(db database.Database) ([]ids.ShortID, error) {
|
||||||
bytes, err := db.Get(addresses.Bytes())
|
bytes, err := db.Get(addresses.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
addresses := []ids.ID{}
|
addresses := []ids.ShortID{}
|
||||||
if err := s.vm.codec.Unmarshal(bytes, &addresses); err != nil {
|
if err := s.vm.codec.Unmarshal(bytes, &addresses); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -35,10 +34,10 @@ func (s *userState) Addresses(db database.Database) ([]ids.ID, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *userState) SetKey(db database.Database, sk *crypto.PrivateKeySECP256K1R) error {
|
func (s *userState) SetKey(db database.Database, sk *crypto.PrivateKeySECP256K1R) error {
|
||||||
return db.Put(hashing.ComputeHash256(sk.PublicKey().Address().Bytes()), sk.Bytes())
|
return db.Put(sk.PublicKey().Address().Bytes(), sk.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *userState) Key(db database.Database, address ids.ID) (*crypto.PrivateKeySECP256K1R, error) {
|
func (s *userState) Key(db database.Database, address ids.ShortID) (*crypto.PrivateKeySECP256K1R, error) {
|
||||||
factory := crypto.FactorySECP256K1R{}
|
factory := crypto.FactorySECP256K1R{}
|
||||||
|
|
||||||
bytes, err := db.Get(address.Bytes())
|
bytes, err := db.Get(address.Bytes())
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Throughput testing
|
|
||||||
|
|
||||||
A throughput test is run in two parts. First a network must be running with at least one of the nodes running a throughput server. To start a throughput server when running a node the `--xput-server-enabled=true` flag should be passed.
|
|
||||||
|
|
||||||
An example single node network can be started with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./build/ava --public-ip=127.0.0.1 --xput-server-port=9652 --xput-server-enabled=true --db-enabled=false --staking-tls-enabled=false --snow-sample-size=1 --snow-quorum-size=1
|
|
||||||
```
|
|
||||||
|
|
||||||
The thoughput node can be started with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./build/xputtest --ip=127.0.0.1 --port=9652 --sp-chain
|
|
||||||
```
|
|
||||||
|
|
||||||
The above example with run a throughput test on the simple payment chain. Tests can be run with `--sp-dag` to run throughput tests on the simple payment dag. Tests can be run with `--avm` to run throughput tests on the AVA virtual machine.
|
|
108
xputtest/avm.go
108
xputtest/avm.go
|
@ -1,108 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/networking"
|
|
||||||
"github.com/ava-labs/gecko/utils/crypto"
|
|
||||||
"github.com/ava-labs/gecko/utils/timer"
|
|
||||||
"github.com/ava-labs/gecko/vms/avm"
|
|
||||||
"github.com/ava-labs/gecko/vms/platformvm"
|
|
||||||
"github.com/ava-labs/gecko/xputtest/avmwallet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// benchmark an instance of the avm
|
|
||||||
func (n *network) benchmarkAVM(chain *platformvm.CreateChainTx) {
|
|
||||||
genesisBytes := chain.GenesisData
|
|
||||||
wallet, err := avmwallet.NewWallet(n.log, n.networkID, chain.ID(), config.AvaTxFee)
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
|
|
||||||
factory := crypto.FactorySECP256K1R{}
|
|
||||||
sk, err := factory.ToPrivateKey(config.Key)
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
wallet.ImportKey(sk.(*crypto.PrivateKeySECP256K1R))
|
|
||||||
|
|
||||||
codec := wallet.Codec()
|
|
||||||
|
|
||||||
genesis := avm.Genesis{}
|
|
||||||
n.log.AssertNoError(codec.Unmarshal(genesisBytes, &genesis))
|
|
||||||
|
|
||||||
genesisTx := genesis.Txs[0]
|
|
||||||
tx := avm.Tx{
|
|
||||||
UnsignedTx: &genesisTx.CreateAssetTx,
|
|
||||||
}
|
|
||||||
txBytes, err := codec.Marshal(&tx)
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
tx.Initialize(txBytes)
|
|
||||||
|
|
||||||
for _, utxo := range tx.UTXOs() {
|
|
||||||
wallet.AddUTXO(utxo)
|
|
||||||
}
|
|
||||||
|
|
||||||
assetID := genesisTx.ID()
|
|
||||||
|
|
||||||
n.log.AssertNoError(wallet.GenerateTxs(config.NumTxs, assetID))
|
|
||||||
|
|
||||||
go n.log.RecoverAndPanic(func() { n.IssueAVM(chain.ID(), assetID, wallet) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// issue transactions to the instance of the avm funded by the provided wallet
|
|
||||||
func (n *network) IssueAVM(chainID ids.ID, assetID ids.ID, wallet *avmwallet.Wallet) {
|
|
||||||
n.log.Debug("Issuing with %d", wallet.Balance(assetID))
|
|
||||||
numAccepted := 0
|
|
||||||
numPending := 0
|
|
||||||
|
|
||||||
n.decided <- ids.ID{}
|
|
||||||
|
|
||||||
// track the last second of transactions
|
|
||||||
meter := timer.TimedMeter{Duration: time.Second}
|
|
||||||
for d := range n.decided {
|
|
||||||
// display the TPS every 1000 txs
|
|
||||||
if numAccepted%1000 == 0 {
|
|
||||||
n.log.Info("TPS: %d", meter.Ticks())
|
|
||||||
}
|
|
||||||
|
|
||||||
// d is the ID of the tx that was accepted
|
|
||||||
if !d.IsZero() {
|
|
||||||
meter.Tick()
|
|
||||||
n.log.Debug("Finalized %s", d)
|
|
||||||
numAccepted++
|
|
||||||
numPending--
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue all the txs that we can right now
|
|
||||||
for numPending < config.MaxOutstandingTxs && wallet.Balance(assetID) > 0 && numAccepted+numPending < config.NumTxs {
|
|
||||||
tx := wallet.NextTx()
|
|
||||||
n.log.AssertTrue(tx != nil, "Tx creation failed")
|
|
||||||
|
|
||||||
// send the IssueTx message
|
|
||||||
it, err := n.build.IssueTx(chainID, tx.Bytes())
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
ds := it.DataStream()
|
|
||||||
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
|
|
||||||
newMsg := salticidae.NewMsgMovedFromByteArray(networking.IssueTx, ba, false)
|
|
||||||
|
|
||||||
n.conn.GetNet().SendMsg(newMsg, n.conn)
|
|
||||||
|
|
||||||
ds.Free()
|
|
||||||
ba.Free()
|
|
||||||
newMsg.Free()
|
|
||||||
|
|
||||||
numPending++
|
|
||||||
n.log.Debug("Sent tx, pending = %d, accepted = %d", numPending, numAccepted)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are done issuing txs, return from the function
|
|
||||||
if numAccepted+numPending >= config.NumTxs {
|
|
||||||
n.log.Info("done with test")
|
|
||||||
net.ec.Stop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
// ChainType ...
|
|
||||||
type ChainType int
|
|
||||||
|
|
||||||
// Chain types
|
|
||||||
const (
|
|
||||||
unknown ChainType = iota
|
|
||||||
spChain
|
|
||||||
spDAG
|
|
||||||
avmDAG
|
|
||||||
)
|
|
|
@ -1,32 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ava-labs/gecko/utils"
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config contains all of the configurations of an Ava client.
|
|
||||||
type Config struct {
|
|
||||||
// Networking configurations
|
|
||||||
RemoteIP utils.IPDesc // Which Ava node to connect to
|
|
||||||
|
|
||||||
// ID of the network that this client will be issuing transactions to
|
|
||||||
NetworkID uint32
|
|
||||||
|
|
||||||
// Transaction fee
|
|
||||||
AvaTxFee uint64
|
|
||||||
|
|
||||||
EnableCrypto bool
|
|
||||||
LoggingConfig logging.Config
|
|
||||||
|
|
||||||
// Key describes which key to use to issue transactions
|
|
||||||
Key []byte
|
|
||||||
|
|
||||||
// NumTxs describes the number of transactions to issue
|
|
||||||
// MaxOutstandingTxs describes how many txs to pipeline
|
|
||||||
NumTxs, MaxOutstandingTxs int
|
|
||||||
Chain ChainType
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/genesis"
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/utils/crypto"
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
"github.com/ava-labs/gecko/vms/avm"
|
|
||||||
"github.com/ava-labs/gecko/vms/spchainvm"
|
|
||||||
"github.com/ava-labs/gecko/vms/spdagvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to parse arguments: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up logging
|
|
||||||
config.LoggingConfig.Directory = path.Join(config.LoggingConfig.Directory, "client")
|
|
||||||
log, err := logging.New(config.LoggingConfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to start the logger: %s\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer log.Stop()
|
|
||||||
|
|
||||||
// initialize state based on CLI args
|
|
||||||
net.log = log
|
|
||||||
crypto.EnableCrypto = config.EnableCrypto
|
|
||||||
net.decided = make(chan ids.ID, config.MaxOutstandingTxs)
|
|
||||||
|
|
||||||
// Init the network
|
|
||||||
log.AssertNoError(net.Initialize())
|
|
||||||
|
|
||||||
net.net.Start()
|
|
||||||
defer net.net.Stop()
|
|
||||||
|
|
||||||
// connect to the node
|
|
||||||
serr := salticidae.NewError()
|
|
||||||
remoteIP := salticidae.NewNetAddrFromIPPortString(config.RemoteIP.String(), true, &serr)
|
|
||||||
if code := serr.GetCode(); code != 0 {
|
|
||||||
log.Fatal("Sync error %s", salticidae.StrError(serr.GetCode()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
net.conn = net.net.ConnectSync(remoteIP, true, &serr)
|
|
||||||
if serr.GetCode() != 0 {
|
|
||||||
log.Fatal("Sync error %s", salticidae.StrError(serr.GetCode()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// start a cpu profile
|
|
||||||
file, gErr := os.Create("cpu_client.profile")
|
|
||||||
log.AssertNoError(gErr)
|
|
||||||
gErr = pprof.StartCPUProfile(file)
|
|
||||||
log.AssertNoError(gErr)
|
|
||||||
runtime.SetMutexProfileFraction(1)
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
defer pprof.StopCPUProfile()
|
|
||||||
|
|
||||||
net.networkID = config.NetworkID
|
|
||||||
|
|
||||||
// start the benchmark we want to run
|
|
||||||
switch config.Chain {
|
|
||||||
case spChain:
|
|
||||||
tx, err := genesis.VMGenesis(config.NetworkID, spchainvm.ID)
|
|
||||||
log.AssertNoError(err)
|
|
||||||
net.benchmarkSPChain(tx)
|
|
||||||
case spDAG:
|
|
||||||
tx, err := genesis.VMGenesis(config.NetworkID, spdagvm.ID)
|
|
||||||
log.AssertNoError(err)
|
|
||||||
net.benchmarkSPChain(tx)
|
|
||||||
case avmDAG:
|
|
||||||
tx, err := genesis.VMGenesis(config.NetworkID, avm.ID)
|
|
||||||
log.AssertNoError(err)
|
|
||||||
net.benchmarkSPChain(tx)
|
|
||||||
default:
|
|
||||||
log.Fatal("did not specify whether to test dag or chain. Exiting")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// start processing network messages
|
|
||||||
net.ec.Dispatch()
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
// #include "salticidae/network.h"
|
|
||||||
// void onTerm(int sig, void *);
|
|
||||||
// void decidedTx(msg_t *, msgnetwork_conn_t *, void *);
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/networking"
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
// network stores the persistent data needed when running the test.
|
|
||||||
type network struct {
|
|
||||||
ec salticidae.EventContext
|
|
||||||
build networking.Builder
|
|
||||||
|
|
||||||
net salticidae.MsgNetwork
|
|
||||||
conn salticidae.MsgNetworkConn
|
|
||||||
|
|
||||||
log logging.Logger
|
|
||||||
decided chan ids.ID
|
|
||||||
|
|
||||||
networkID uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
var net = network{}
|
|
||||||
|
|
||||||
func (n *network) Initialize() error {
|
|
||||||
n.ec = salticidae.NewEventContext()
|
|
||||||
evInt := salticidae.NewSigEvent(n.ec, salticidae.SigEventCallback(C.onTerm), nil)
|
|
||||||
evInt.Add(salticidae.SIGINT)
|
|
||||||
evTerm := salticidae.NewSigEvent(n.ec, salticidae.SigEventCallback(C.onTerm), nil)
|
|
||||||
evTerm.Add(salticidae.SIGTERM)
|
|
||||||
|
|
||||||
serr := salticidae.NewError()
|
|
||||||
netconfig := salticidae.NewMsgNetworkConfig()
|
|
||||||
n.net = salticidae.NewMsgNetwork(n.ec, netconfig, &serr)
|
|
||||||
if serr.GetCode() != 0 {
|
|
||||||
return fmt.Errorf("sync error %s", salticidae.StrError(serr.GetCode()))
|
|
||||||
}
|
|
||||||
|
|
||||||
n.net.RegHandler(networking.DecidedTx, salticidae.MsgNetworkMsgCallback(C.decidedTx), nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export onTerm
|
|
||||||
func onTerm(C.int, unsafe.Pointer) {
|
|
||||||
net.log.Info("Terminate signal received")
|
|
||||||
net.ec.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// decidedTx handles the recept of a decidedTx message
|
|
||||||
//export decidedTx
|
|
||||||
func decidedTx(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
|
|
||||||
msg := salticidae.MsgFromC(salticidae.CMsg(_msg))
|
|
||||||
|
|
||||||
pMsg, err := net.build.Parse(networking.DecidedTx, msg.GetPayloadByMove())
|
|
||||||
if err != nil {
|
|
||||||
net.log.Warn("Failed to parse DecidedTx message")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
txID, err := ids.ToID(pMsg.Get(networking.TxID).([]byte))
|
|
||||||
net.log.AssertNoError(err) // Length is checked in message parsing
|
|
||||||
|
|
||||||
net.log.Debug("Decided %s", txID)
|
|
||||||
net.decided <- txID
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
stdnet "net"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/genesis"
|
|
||||||
"github.com/ava-labs/gecko/utils"
|
|
||||||
"github.com/ava-labs/gecko/utils/formatting"
|
|
||||||
"github.com/ava-labs/gecko/utils/logging"
|
|
||||||
"github.com/ava-labs/gecko/utils/wrappers"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
config Config
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parse the CLI arguments
|
|
||||||
func init() {
|
|
||||||
errs := &wrappers.Errs{}
|
|
||||||
defer func() { err = errs.Err }()
|
|
||||||
|
|
||||||
loggingConfig, err := logging.DefaultConfig()
|
|
||||||
errs.Add(err)
|
|
||||||
|
|
||||||
fs := flag.NewFlagSet("xputtest", flag.ContinueOnError)
|
|
||||||
|
|
||||||
// NetworkID:
|
|
||||||
networkName := fs.String("network-id", genesis.LocalName, "Network ID this node will connect to")
|
|
||||||
|
|
||||||
// Ava fees:
|
|
||||||
fs.Uint64Var(&config.AvaTxFee, "ava-tx-fee", 0, "Ava transaction fee, in $nAva")
|
|
||||||
|
|
||||||
// Assertions:
|
|
||||||
fs.BoolVar(&loggingConfig.Assertions, "assertions-enabled", true, "Turn on assertion execution")
|
|
||||||
|
|
||||||
// Crypto:
|
|
||||||
fs.BoolVar(&config.EnableCrypto, "signature-verification-enabled", true, "Turn on signature verification")
|
|
||||||
|
|
||||||
// Remote Server:
|
|
||||||
ip := fs.String("ip", "127.0.0.1", "IP address of the remote server socket")
|
|
||||||
port := fs.Uint("port", 9652, "Port of the remote server socket")
|
|
||||||
|
|
||||||
// Logging:
|
|
||||||
logsDir := fs.String("log-dir", "", "Logging directory for Ava")
|
|
||||||
logLevel := fs.String("log-level", "info", "The log level. Should be one of {all, debug, info, warn, error, fatal, off}")
|
|
||||||
|
|
||||||
// Test Variables:
|
|
||||||
spchain := fs.Bool("sp-chain", false, "Execute simple payment chain transactions")
|
|
||||||
spdag := fs.Bool("sp-dag", false, "Execute simple payment dag transactions")
|
|
||||||
avm := fs.Bool("avm", false, "Execute avm transactions")
|
|
||||||
key := fs.String("key", "", "Funded key in the genesis key to use to issue transactions")
|
|
||||||
fs.IntVar(&config.NumTxs, "num-txs", 25000, "Total number of transaction to issue")
|
|
||||||
fs.IntVar(&config.MaxOutstandingTxs, "max-outstanding", 1000, "Maximum number of transactions to leave outstanding")
|
|
||||||
|
|
||||||
ferr := fs.Parse(os.Args[1:])
|
|
||||||
|
|
||||||
if ferr == flag.ErrHelp {
|
|
||||||
// display usage/help text and exit successfully
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ferr != nil {
|
|
||||||
// other type of error occurred when parsing args
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
networkID, err := genesis.NetworkID(*networkName)
|
|
||||||
errs.Add(err)
|
|
||||||
|
|
||||||
config.NetworkID = networkID
|
|
||||||
|
|
||||||
// Remote:
|
|
||||||
parsedIP := stdnet.ParseIP(*ip)
|
|
||||||
if parsedIP == nil {
|
|
||||||
errs.Add(fmt.Errorf("invalid IP Address %s", *ip))
|
|
||||||
}
|
|
||||||
config.RemoteIP = utils.IPDesc{
|
|
||||||
IP: parsedIP,
|
|
||||||
Port: uint16(*port),
|
|
||||||
}
|
|
||||||
|
|
||||||
cb58 := formatting.CB58{}
|
|
||||||
errs.Add(cb58.FromString(*key))
|
|
||||||
config.Key = cb58.Bytes
|
|
||||||
|
|
||||||
// Logging:
|
|
||||||
if *logsDir != "" {
|
|
||||||
loggingConfig.Directory = *logsDir
|
|
||||||
}
|
|
||||||
level, err := logging.ToLevel(*logLevel)
|
|
||||||
errs.Add(err)
|
|
||||||
loggingConfig.LogLevel = level
|
|
||||||
loggingConfig.DisplayLevel = level
|
|
||||||
config.LoggingConfig = loggingConfig
|
|
||||||
|
|
||||||
// Test Variables:
|
|
||||||
switch {
|
|
||||||
case *spchain:
|
|
||||||
config.Chain = spChain
|
|
||||||
case *spdag:
|
|
||||||
config.Chain = spDAG
|
|
||||||
case *avm:
|
|
||||||
config.Chain = avmDAG
|
|
||||||
default:
|
|
||||||
config.Chain = unknown
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/networking"
|
|
||||||
"github.com/ava-labs/gecko/utils/crypto"
|
|
||||||
"github.com/ava-labs/gecko/utils/timer"
|
|
||||||
"github.com/ava-labs/gecko/vms/platformvm"
|
|
||||||
"github.com/ava-labs/gecko/vms/spchainvm"
|
|
||||||
"github.com/ava-labs/gecko/xputtest/chainwallet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// benchmark an instance of the sp chain
|
|
||||||
func (n *network) benchmarkSPChain(chain *platformvm.CreateChainTx) {
|
|
||||||
genesisBytes := chain.GenesisData
|
|
||||||
wallet := chainwallet.NewWallet(n.log, n.networkID, chain.ID())
|
|
||||||
|
|
||||||
codec := spchainvm.Codec{}
|
|
||||||
accounts, err := codec.UnmarshalGenesis(genesisBytes)
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
|
|
||||||
factory := crypto.FactorySECP256K1R{}
|
|
||||||
skGen, err := factory.ToPrivateKey(config.Key)
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
sk := skGen.(*crypto.PrivateKeySECP256K1R)
|
|
||||||
wallet.ImportKey(sk)
|
|
||||||
|
|
||||||
for _, account := range accounts {
|
|
||||||
wallet.AddAccount(account)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
n.log.AssertNoError(wallet.GenerateTxs(config.NumTxs))
|
|
||||||
|
|
||||||
go n.log.RecoverAndPanic(func() { n.IssueSPChain(chain.ID(), wallet) })
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *network) IssueSPChain(chainID ids.ID, wallet *chainwallet.Wallet) {
|
|
||||||
n.log.Debug("Issuing with %d", wallet.Balance())
|
|
||||||
numAccepted := 0
|
|
||||||
numPending := 0
|
|
||||||
|
|
||||||
n.decided <- ids.ID{}
|
|
||||||
|
|
||||||
meter := timer.TimedMeter{Duration: time.Second}
|
|
||||||
for d := range n.decided {
|
|
||||||
if numAccepted%1000 == 0 {
|
|
||||||
n.log.Info("TPS: %d", meter.Ticks())
|
|
||||||
}
|
|
||||||
if !d.IsZero() {
|
|
||||||
meter.Tick()
|
|
||||||
n.log.Debug("Finalized %s", d)
|
|
||||||
numAccepted++
|
|
||||||
numPending--
|
|
||||||
}
|
|
||||||
|
|
||||||
for numPending < config.MaxOutstandingTxs && wallet.Balance() > 0 && numAccepted+numPending < config.NumTxs {
|
|
||||||
tx := wallet.NextTx()
|
|
||||||
n.log.AssertTrue(tx != nil, "Tx creation failed")
|
|
||||||
|
|
||||||
it, err := n.build.IssueTx(chainID, tx.Bytes())
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
ds := it.DataStream()
|
|
||||||
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
|
|
||||||
newMsg := salticidae.NewMsgMovedFromByteArray(networking.IssueTx, ba, false)
|
|
||||||
|
|
||||||
n.conn.GetNet().SendMsg(newMsg, n.conn)
|
|
||||||
|
|
||||||
ds.Free()
|
|
||||||
ba.Free()
|
|
||||||
newMsg.Free()
|
|
||||||
|
|
||||||
numPending++
|
|
||||||
n.log.Debug("Sent tx, pending = %d, accepted = %d", numPending, numAccepted)
|
|
||||||
}
|
|
||||||
if numAccepted+numPending >= config.NumTxs {
|
|
||||||
n.log.Info("done with test")
|
|
||||||
net.ec.Stop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
||||||
// See the file LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ava-labs/salticidae-go"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
|
||||||
"github.com/ava-labs/gecko/networking"
|
|
||||||
"github.com/ava-labs/gecko/utils/crypto"
|
|
||||||
"github.com/ava-labs/gecko/utils/timer"
|
|
||||||
"github.com/ava-labs/gecko/vms/platformvm"
|
|
||||||
"github.com/ava-labs/gecko/vms/spdagvm"
|
|
||||||
"github.com/ava-labs/gecko/xputtest/dagwallet"
|
|
||||||
)
|
|
||||||
|
|
||||||
// benchmark an instance of the sp dag
|
|
||||||
func (n *network) benchmarkSPDAG(chain *platformvm.CreateChainTx) {
|
|
||||||
genesisBytes := chain.GenesisData
|
|
||||||
wallet := dagwallet.NewWallet(n.networkID, chain.ID(), config.AvaTxFee)
|
|
||||||
|
|
||||||
codec := spdagvm.Codec{}
|
|
||||||
tx, err := codec.UnmarshalTx(genesisBytes)
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
|
|
||||||
factory := crypto.FactorySECP256K1R{}
|
|
||||||
skGen, err := factory.ToPrivateKey(config.Key)
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
sk := skGen.(*crypto.PrivateKeySECP256K1R)
|
|
||||||
wallet.ImportKey(sk)
|
|
||||||
|
|
||||||
for _, utxo := range tx.UTXOs() {
|
|
||||||
wallet.AddUTXO(utxo)
|
|
||||||
}
|
|
||||||
|
|
||||||
go n.log.RecoverAndPanic(func() { n.IssueSPDAG(chain.ID(), wallet) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// issue transactions to the instance of the spdag funded by the provided wallet
|
|
||||||
func (n *network) IssueSPDAG(chainID ids.ID, wallet *dagwallet.Wallet) {
|
|
||||||
n.log.Info("starting avalanche benchmark")
|
|
||||||
pending := make(map[[32]byte]*spdagvm.Tx)
|
|
||||||
canAdd := []*spdagvm.Tx{}
|
|
||||||
numAccepted := 0
|
|
||||||
|
|
||||||
n.decided <- ids.ID{}
|
|
||||||
meter := timer.TimedMeter{Duration: time.Second}
|
|
||||||
for d := range n.decided {
|
|
||||||
if numAccepted%1000 == 0 {
|
|
||||||
n.log.Info("TPS: %d", meter.Ticks())
|
|
||||||
}
|
|
||||||
if !d.IsZero() {
|
|
||||||
meter.Tick()
|
|
||||||
key := d.Key()
|
|
||||||
if tx := pending[key]; tx != nil {
|
|
||||||
canAdd = append(canAdd, tx)
|
|
||||||
|
|
||||||
n.log.Debug("Finalized %s", d)
|
|
||||||
delete(pending, key)
|
|
||||||
numAccepted++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(pending) < config.MaxOutstandingTxs && (wallet.Balance() > 0 || len(canAdd) > 0) {
|
|
||||||
if wallet.Balance() == 0 {
|
|
||||||
tx := canAdd[0]
|
|
||||||
canAdd = canAdd[1:]
|
|
||||||
|
|
||||||
for _, utxo := range tx.UTXOs() {
|
|
||||||
wallet.AddUTXO(utxo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tx := wallet.Send(1, 0, wallet.GetAddress())
|
|
||||||
n.log.AssertTrue(tx != nil, "Tx creation failed")
|
|
||||||
|
|
||||||
it, err := n.build.IssueTx(chainID, tx.Bytes())
|
|
||||||
n.log.AssertNoError(err)
|
|
||||||
ds := it.DataStream()
|
|
||||||
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
|
|
||||||
newMsg := salticidae.NewMsgMovedFromByteArray(networking.IssueTx, ba, false)
|
|
||||||
|
|
||||||
n.conn.GetNet().SendMsg(newMsg, n.conn)
|
|
||||||
|
|
||||||
ds.Free()
|
|
||||||
ba.Free()
|
|
||||||
newMsg.Free()
|
|
||||||
|
|
||||||
pending[tx.ID().Key()] = tx
|
|
||||||
n.log.Debug("Sent tx, pending = %d, accepted = %d", len(pending), numAccepted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue