diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..954315f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.idea
+log
+wallet.dat
+*.dht-tool
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..34c9c16
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,34 @@
+GOFMT=gofmt
+GC=go build
+
+VERSION := $(shell git describe --always --tags --long)
+BUILD_NODE_PAR = -ldflags "-X main.Version=1.0.0"
+
+ARCH=$(shell uname -m)
+SRC_FILES = $(shell git ls-files | grep -e .go$ | grep -v _test.go)
+
+ont-tool: $(SRC_FILES)
+ $(GC) $(BUILD_NODE_PAR) -o dht-tool main.go
+
+dht-tool-cross: dht-tool-windows dht-tool-linux dht-tool-darwin
+
+dht-tool-windows:
+ CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GC) $(BUILD_NODE_PAR) -o dht-tool-windows-amd64.exe main.go
+
+dht-tool-linux:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GC) $(BUILD_NODE_PAR) -o dht-tool-linux-amd64 main.go
+
+dht-tool-darwin:
+ CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GC) $(BUILD_NODE_PAR) -o dht-tool-darwin-amd64 main.go
+
+tools-cross: tools-windows tools-linux tools-darwin
+
+format:
+ $(GOFMT) -w main.go
+
+clean:
+ rm -rf *.8 *.o *.out *.6 *exe
+ rm -rf dht-tool dht-tool-*
+
+restart:
+ make clean && make dht-tool && ./dht-tool
diff --git a/common/common.go b/common/common.go
new file mode 100644
index 0000000..20bb5c6
--- /dev/null
+++ b/common/common.go
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package common
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "github.com/ontio/ontology-tool/config"
+ "time"
+
+ log4 "github.com/alecthomas/log4go"
+ "github.com/ontio/ontology-crypto/keypair"
+ gosdk "github.com/ontio/ontology-go-sdk"
+ scommon "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/password"
+ "github.com/ontio/ontology/consensus/vbft"
+ "github.com/ontio/ontology/consensus/vbft/config"
+ "github.com/ontio/ontology/core/types"
+)
+
+var sdk = gosdk.NewOntologySdk()
+
+func Initialize() {
+ sdk.NewRpcClient().SetAddress(config.DefConfig.JsonRpcAddress)
+}
+
+func GetAccountByPassword(path string) (*gosdk.Account, bool) {
+ wallet, err := sdk.OpenWallet(path)
+ if err != nil {
+ log4.Error("open wallet error:", err)
+ return nil, false
+ }
+ pwd, err := password.GetPassword()
+ if err != nil {
+ log4.Error("getPassword error:", err)
+ return nil, false
+ }
+ user, err := wallet.GetDefaultAccount(pwd)
+ if err != nil {
+ log4.Error("getDefaultAccount error:", err)
+ return nil, false
+ }
+ return user, true
+}
+
+func InvokeNativeContractWithMultiSign(
+ gasPrice,
+ gasLimit uint64,
+ pubKeys []keypair.PublicKey,
+ singers []*gosdk.Account,
+ cversion byte,
+ contractAddress scommon.Address,
+ method string,
+ params []interface{},
+) (scommon.Uint256, error) {
+ tx, err := sdk.Native.NewNativeInvokeTransaction(gasPrice, gasLimit, cversion, contractAddress, method, params)
+ if err != nil {
+ return scommon.UINT256_EMPTY, err
+ }
+ for _, singer := range singers {
+ err = sdk.MultiSignToTransaction(tx, uint16((5*len(pubKeys)+6)/7), pubKeys, singer)
+ if err != nil {
+ return scommon.UINT256_EMPTY, err
+ }
+ }
+ return sdk.SendTransaction(tx)
+}
+
+func WaitForBlock(sdk *gosdk.OntologySdk) bool {
+ _, err := sdk.WaitForGenerateBlock(30*time.Second, 1)
+ if err != nil {
+ log4.Error("WaitForGenerateBlock error:", err)
+ return false
+ }
+ return true
+}
+
+func ConcatKey(args ...[]byte) []byte {
+ temp := []byte{}
+ for _, arg := range args {
+ temp = append(temp, arg...)
+ }
+ return temp
+}
+
+func InitVbftBlock(block *types.Block) (*vbft.Block, error) {
+ if block == nil {
+ return nil, fmt.Errorf("nil block in initVbftBlock")
+ }
+
+ blkInfo := &vconfig.VbftBlockInfo{}
+ if err := json.Unmarshal(block.Header.ConsensusPayload, blkInfo); err != nil {
+ return nil, fmt.Errorf("unmarshal blockInfo: %s", err)
+ }
+
+ return &vbft.Block{
+ Block: block,
+ Info: blkInfo,
+ }, nil
+}
+
+func GetAddressByHexString(hexString string) (scommon.Address, error) {
+ contractByte, err := hex.DecodeString(hexString)
+ if err != nil {
+ return scommon.Address{}, fmt.Errorf("hex.DecodeString failed %v", err)
+ }
+ contractAddress, err := scommon.AddressParseFromBytes(scommon.ToArrayReverse(contractByte))
+ if err != nil {
+ return scommon.Address{}, fmt.Errorf("common.AddressParseFromBytes failed %v", err)
+ }
+ return contractAddress, nil
+}
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..28b0e83
--- /dev/null
+++ b/config.json
@@ -0,0 +1,43 @@
+{
+ "Net":{
+ "ReservedPeersOnly":false,
+ "ReservedCfg":{
+ "reserved":[
+ "1.2.3.4",
+ "1.2.3.5"
+ ],
+ "mask":[
+ "2.3.4.5"
+ ]
+ },
+ "NetworkMagic":0,
+ "NetworkId":0,
+ "NetworkName":"",
+ "NodePort":0,
+ "IsTLS":false
+ },
+ "Sdk":{
+ "JsonRpcAddress":"http://localhost:20336",
+ "GasPrice":0,
+ "GasLimit":20000
+ },
+ "Sync":[
+ "172.168.3.151:20338",
+ "172.168.3.152:20338",
+ "172.168.3.153:20338",
+ "172.168.3.154:20338",
+ "172.168.3.155:20338"
+ ],
+ "Seed":[
+ "172.168.3.156:20338",
+ "172.168.3.157:20338",
+ "172.168.3.158:20338",
+ "172.168.3.159:20338",
+ "172.168.3.160:20338",
+ "172.168.3.161:20338",
+ "172.168.3.162:20338",
+ "172.168.3.163:20338",
+ "172.168.3.164:20338",
+ "172.168.3.165:20338"
+ ]
+}
\ No newline at end of file
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..a89da91
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package config
+
+import (
+ "encoding/json"
+ "fmt"
+ log4 "github.com/alecthomas/log4go"
+ "github.com/ontio/ontology/common/config"
+ "io/ioutil"
+ "os"
+)
+
+var DefConfig = NewDHTConfig()
+
+type DHTConfig struct {
+ Seed []string
+ Sync []string
+ Net *config.P2PNodeConfig
+ Sdk *SDKConfig
+}
+
+type SDKConfig struct {
+ JsonRpcAddress string
+ RestfulAddress string
+ WebSocketAddress string
+
+ //Gas Price of transaction
+ GasPrice uint64
+ //Gas Limit of invoke transaction
+ GasLimit uint64
+ //Gas Limit of deploy transaction
+ GasDeployLimit uint64
+}
+
+func NewDHTConfig() *DHTConfig {
+ return &DHTConfig{}
+}
+
+func (c *DHTConfig) Init(fileName string) error {
+ err := c.loadConfig(fileName)
+ if err != nil {
+ return fmt.Errorf("loadConfig error:%s", err)
+ }
+ return nil
+}
+
+func (this *DHTConfig) loadConfig(fileName string) error {
+ data, err := this.readFile(fileName)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(data, this)
+ if err != nil {
+ return fmt.Errorf("json.Unmarshal TestConfig:%s error:%s", data, err)
+ }
+ return nil
+}
+
+func (this *DHTConfig) readFile(fileName string) ([]byte, error) {
+ file, err := os.OpenFile(fileName, os.O_RDONLY, 0666)
+ if err != nil {
+ return nil, fmt.Errorf("OpenFile %s error %s", fileName, err)
+ }
+ defer func() {
+ err := file.Close()
+ if err != nil {
+ log4.Error("File %s close error %s", fileName, err)
+ }
+ }()
+ data, err := ioutil.ReadAll(file)
+ if err != nil {
+ return nil, fmt.Errorf("ioutil.ReadAll %s error %s", fileName, err)
+ }
+ return data, nil
+}
diff --git a/config/config_test.go b/config/config_test.go
new file mode 100644
index 0000000..4f6d140
--- /dev/null
+++ b/config/config_test.go
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package config
+
+import "testing"
+
+// go test -count=1 -v github.com/ontio/ontology-tool/config -run TestLoadDHTConfig
+func TestLoadDHTConfig(t *testing.T) {
+ if err := DefConfig.Init("../dht_config.json"); err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log(DefConfig.Seed)
+ t.Log(DefConfig.Sync)
+ t.Log(DefConfig.Net)
+}
diff --git a/core/core.go b/core/core.go
new file mode 100644
index 0000000..2eda01e
--- /dev/null
+++ b/core/core.go
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package core
+
+import (
+ log4 "github.com/alecthomas/log4go"
+)
+
+var OntTool = NewOntologyTool()
+
+type Method func() bool
+
+type OntologyTool struct {
+ //Map name to method
+ methodsMap map[string]Method
+ //Map method result
+ methodsRes map[string]bool
+}
+
+func NewOntologyTool() *OntologyTool {
+ return &OntologyTool{
+ methodsMap: make(map[string]Method, 0),
+ methodsRes: make(map[string]bool, 0),
+ }
+}
+
+func (this *OntologyTool) RegMethod(name string, method Method) {
+ this.methodsMap[name] = method
+}
+
+//Start run
+func (this *OntologyTool) Start(methodsList []string) {
+ if len(methodsList) > 0 {
+ this.runMethodList(methodsList)
+ return
+ }
+ log4.Info("No method to run")
+ return
+}
+
+func (this *OntologyTool) runMethodList(methodsList []string) {
+ this.onStart()
+ defer this.onFinish(methodsList)
+ for i, method := range methodsList {
+ this.runMethod(i+1, method)
+ }
+}
+
+func (this *OntologyTool) runMethod(index int, methodName string) {
+ this.onBeforeMethodStart(index, methodName)
+ method := this.getMethodByName(methodName)
+ if method != nil {
+ ok := method()
+ this.onAfterMethodFinish(index, methodName, ok)
+ this.methodsRes[methodName] = ok
+ }
+}
+
+func (this *OntologyTool) onStart() {
+ log4.Info("===============================================================")
+ log4.Info("-------Ontology Tool Start-------")
+ log4.Info("===============================================================")
+ log4.Info("")
+}
+
+func (this *OntologyTool) onFinish(methodsList []string) {
+ failedList := make([]string, 0)
+ successList := make([]string, 0)
+ for methodName, ok := range this.methodsRes {
+ if ok {
+ successList = append(successList, methodName)
+ } else {
+ failedList = append(failedList, methodName)
+ }
+ }
+
+ skipList := make([]string, 0)
+ for _, method := range methodsList {
+ _, ok := this.methodsRes[method]
+ if !ok {
+ skipList = append(skipList, method)
+ }
+ }
+
+ succCount := len(successList)
+ failedCount := len(failedList)
+
+ log4.Info("===============================================================")
+ log4.Info("Ontology Tool Finish Total:%v Success:%v Failed:%v Skip:%v",
+ len(methodsList),
+ succCount,
+ failedCount,
+ len(methodsList)-succCount-failedCount)
+ if succCount > 0 {
+ log4.Info("---------------------------------------------------------------")
+ log4.Info("Success list:")
+ for i, succ := range successList {
+ log4.Info("%d.\t%s", i+1, succ)
+ }
+ }
+ if failedCount > 0 {
+ log4.Info("---------------------------------------------------------------")
+ log4.Info("Fail list:")
+ for i, fail := range failedList {
+ log4.Info("%d.\t%s", i+1, fail)
+ }
+ }
+ if len(skipList) > 0 {
+ log4.Info("---------------------------------------------------------------")
+ log4.Info("Skip list:")
+ for i, skip := range skipList {
+ log4.Info("%d.\t%s", i+1, skip)
+ }
+ }
+ log4.Info("===============================================================")
+}
+
+func (this *OntologyTool) onBeforeMethodStart(index int, methodName string) {
+ log4.Info("===============================================================")
+ log4.Info("%d. Start Method:%s", index, methodName)
+ log4.Info("---------------------------------------------------------------")
+}
+
+func (this *OntologyTool) onAfterMethodFinish(index int, methodName string, res bool) {
+ if res {
+ log4.Info("Run Method:%s success.", methodName)
+ } else {
+ log4.Info("Run Method:%s failed.", methodName)
+ }
+ log4.Info("---------------------------------------------------------------")
+ log4.Info("")
+}
+
+func (this *OntologyTool) getMethodByName(name string) Method {
+ return this.methodsMap[name]
+}
diff --git a/dht-tool b/dht-tool
new file mode 100755
index 0000000..1433b60
Binary files /dev/null and b/dht-tool differ
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..cdca460
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,17 @@
+module github.com/ontio/ontology-tool
+
+go 1.12
+
+require (
+ github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect
+ github.com/alecthomas/log4go v0.0.0-20180109082532-d146e6b86faa
+ github.com/blang/semver v3.5.1+incompatible
+ github.com/hashicorp/golang-lru v0.5.3
+ github.com/ontio/ontology v1.9.0
+ github.com/ontio/ontology-crypto v1.0.8
+ github.com/ontio/ontology-eventbus v0.9.1
+ github.com/ontio/ontology-go-sdk v1.11.1
+ github.com/scylladb/go-set v1.0.2
+)
+
+replace github.com/ontio/ontology v1.9.0 => ../ontology
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..5f5ea6c
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,235 @@
+github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
+github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
+github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4=
+github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
+github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
+github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
+github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
+github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
+github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
+github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
+github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc=
+github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw=
+github.com/JohnCGriffin/overflow v0.0.0-20170615021017-4d914c927216 h1:2ZboyJ8vl75fGesnG9NpMTD2DyQI3FzMXy4x752rGF0=
+github.com/JohnCGriffin/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
+github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
+github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo=
+github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA=
+github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
+github.com/alecthomas/log4go v0.0.0-20180109082532-d146e6b86faa h1:0zdYOLyuQ3TWIgWNgEH+LnmZNMmkO1ze3wriQt093Mk=
+github.com/alecthomas/log4go v0.0.0-20180109082532-d146e6b86faa/go.mod h1:iCVmQ9g4TfaRX5m5jq5sXY7RXYWPv9/PynM/GocbG3w=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
+github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ=
+github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
+github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
+github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
+github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
+github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
+github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
+github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
+github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
+github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
+github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
+github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P3vAIAh+Y2GAxg0PrPN1P8WkepXGpjbUPDHJqqKM=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
+github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/ethereum/go-ethereum v1.9.6 h1:EacwxMGKZezZi+m3in0Tlyk0veDQgnfZ9BjQqHAaQLM=
+github.com/ethereum/go-ethereum v1.9.6/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY=
+github.com/ethereum/go-ethereum v1.9.11 h1:Z0jugPDfuI5qsPY1XgBGVwikpdFK/ANqP7MrYvkmk+A=
+github.com/ethereum/go-ethereum v1.9.11/go.mod h1:7oC0Ni6dosMv5pxMigm6s0hN8g4haJMBnqmmo0D9YfQ=
+github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
+github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
+github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+github.com/golang/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+github.com/golang/text v0.3.0/go.mod h1:GUiq9pdJKRKKAZXiVgWFEvocYuREvC14NhI4OPgEjeE=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gosuri/uilive v0.0.3/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=
+github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
+github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
+github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
+github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c h1:aY2hhxLhjEAbfXOx2nRJxCXezC6CO2V/yN+OCr1srtk=
+github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag=
+github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
+github.com/itchyny/base58-go v0.0.5/go.mod h1:SrMWPE3DFuJJp1M/RUhu4fccp/y9AlB8AL3o3duPToU=
+github.com/itchyny/base58-go v0.1.0 h1:zF5spLDo956exUAD17o+7GamZTRkXOZlqJjRciZwd1I=
+github.com/itchyny/base58-go v0.1.0/go.mod h1:SrMWPE3DFuJJp1M/RUhu4fccp/y9AlB8AL3o3duPToU=
+github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
+github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
+github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
+github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
+github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
+github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/ontio/go-bip32 v0.0.0-20190520025953-d3cea6894a2b h1:UQDN12BzdWhXQL0t2QcRixHqAIG+JKNvQ20DhrIODtU=
+github.com/ontio/go-bip32 v0.0.0-20190520025953-d3cea6894a2b/go.mod h1:J0eVc7BEMmVVXbGv9PHoxjRSEwOwLr0qfzPk8Rdl5iw=
+github.com/ontio/ontology v1.8.2/go.mod h1:byQJEyJE7TY0Rfmi1rQNp4YZOydD7T84lyl8ZwpQs0c=
+github.com/ontio/ontology v1.9.0 h1:BcJ3WE9CoSyQno+Urvlvot/PWqQZ67yqMNqeeDchFH8=
+github.com/ontio/ontology v1.9.0/go.mod h1:SZxX++4lKT1VY3WFJkHNUbQ96+5ojuXtYEC1dVDQm9E=
+github.com/ontio/ontology-crypto v1.0.5/go.mod h1:ebrQJ4/VS2F6pwHGktHDYtY/7Y2ca/ogfnlYABrQI2c=
+github.com/ontio/ontology-crypto v1.0.7/go.mod h1:ebrQJ4/VS2F6pwHGktHDYtY/7Y2ca/ogfnlYABrQI2c=
+github.com/ontio/ontology-crypto v1.0.8 h1:xft6K8I43vkl60kywT/9GZlUjdacaL7OF6MFFb32kE4=
+github.com/ontio/ontology-crypto v1.0.8/go.mod h1:RW/HSgBTd6Qcuhr/C4luOftN+LNl5oZTQzAywHTsmtY=
+github.com/ontio/ontology-eventbus v0.9.1 h1:nt3AXWx3gOyqtLiU4EwI92Yc4ik/pWHu9xRK15uHSOs=
+github.com/ontio/ontology-eventbus v0.9.1/go.mod h1:hCQIlbdPckcfykMeVUdWrqHZ8d30TBdmLfXCVWGkYhM=
+github.com/ontio/ontology-go-sdk v1.11.1 h1:tgeZ9IHtR7jiGzsFdgLVEtg4Za9OxLB+S1xz2nr5id4=
+github.com/ontio/ontology-go-sdk v1.11.1/go.mod h1:L6W59mkdmShcr8YCu1BZBcDqDTnmee45u/h956UgPtg=
+github.com/ontio/wagon v0.4.1 h1:3A8BxTMVGrQnyWxD1h8w5PLvN9GZMWjC75Jw+5Vgpe0=
+github.com/ontio/wagon v0.4.1/go.mod h1:oTPdgWT7WfPlEyzVaHSn1vQPMSbOpQPv+WphxibWlhg=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
+github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
+github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ=
+github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/scylladb/go-set v1.0.2 h1:SkvlMCKhP0wyyct6j+0IHJkBkSZL+TDzZ4E7f7BCcRE=
+github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs=
+github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
+github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw=
+github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs=
+github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
+github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
+github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8=
+github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
+github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=
+golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20191219195013-becbf705a915 h1:aJ0ex187qoXrJHPo8ZasVTASQB7llQP6YeNzgDALPRk=
+golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
+gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
+gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
diff --git a/log4go.xml b/log4go.xml
new file mode 100644
index 0000000..c5e9051
--- /dev/null
+++ b/log4go.xml
@@ -0,0 +1,31 @@
+
+
+ stdout
+ console
+
+ DEBUG
+ [%D %T] [%L] %M
+
+
+ file
+ file
+ DEBUG
+ ./log/test.log
+
+ [%D %T] [%L] %M
+ false
+ 100M
+ 0K
+ true
+
+
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..aa42fe2
--- /dev/null
+++ b/main.go
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package main
+
+import (
+ "flag"
+ "math/rand"
+ "strings"
+ "time"
+
+ log4 "github.com/alecthomas/log4go"
+ "github.com/ontio/ontology-tool/config"
+ "github.com/ontio/ontology-tool/core"
+ _ "github.com/ontio/ontology-tool/methods"
+)
+
+var (
+ Version string
+ Config string //config file
+ LogConfig string //Log config file
+ Methods string //Methods list in cmdline
+)
+
+func init() {
+ flag.StringVar(&Config, "cfg", "./config.json", "Config of ontology-tool")
+ flag.StringVar(&LogConfig, "lfg", "./log4go.xml", "Log config of ontology-tool")
+ flag.StringVar(&Methods, "t", "", "methods to run. use ',' to split methods")
+ flag.Parse()
+}
+
+func main() {
+ rand.Seed(time.Now().UnixNano())
+ log4.LoadConfiguration(LogConfig)
+ defer time.Sleep(time.Second)
+
+ err := config.DefConfig.Init(Config)
+ if err != nil {
+ log4.Error("DefConfig.Init error:%s", err)
+ return
+ }
+
+ methods := make([]string, 0)
+ if Methods != "" {
+ methods = strings.Split(Methods, ",")
+ }
+
+ core.OntTool.Start(methods)
+}
diff --git a/methods/common.go b/methods/common.go
new file mode 100644
index 0000000..9f6c74c
--- /dev/null
+++ b/methods/common.go
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package methods
diff --git a/methods/endpoint.go b/methods/endpoint.go
new file mode 100644
index 0000000..5a2e0c1
--- /dev/null
+++ b/methods/endpoint.go
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package methods
+
+import (
+ "github.com/ontio/ontology-tool/core"
+)
+
+func init() {
+ core.OntTool.RegMethod("demo", Demo)
+}
diff --git a/methods/methods.go b/methods/methods.go
new file mode 100644
index 0000000..fc26714
--- /dev/null
+++ b/methods/methods.go
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package methods
+
+import (
+ log4 "github.com/alecthomas/log4go"
+)
+
+func Demo() bool {
+ log4.Info("hello, dht demo")
+
+ return true
+}
diff --git a/methods/net_server.go b/methods/net_server.go
new file mode 100644
index 0000000..091a1a5
--- /dev/null
+++ b/methods/net_server.go
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package methods
+
+import (
+ "fmt"
+ log4 "github.com/alecthomas/log4go"
+ "github.com/ontio/ontology-tool/config"
+ commCfg "github.com/ontio/ontology/common/config"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/connect_controller"
+ "github.com/ontio/ontology/p2pserver/message/types"
+ netsvr "github.com/ontio/ontology/p2pserver/net/netserver"
+ // "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/peer"
+ "net"
+ "time"
+)
+
+const Version = ""
+
+type NetServer struct {
+ base *peer.PeerInfo
+ listener net.Listener
+ NetChan chan *types.MsgPayload
+ Np *netsvr.NbrPeers
+
+ connCtrl *connect_controller.ConnectController
+
+ stopRecvCh chan bool // To stop sync channel
+}
+
+func NewNetServer() (*NetServer, error) {
+ n := &NetServer{
+ NetChan: make(chan *types.MsgPayload, common.CHAN_CAPABILITY),
+ base: &peer.PeerInfo{},
+ Np: netsvr.NewNbrPeers(),
+ stopRecvCh: make(chan bool),
+ }
+
+ err := n.init(config.DefConfig.Net, Version)
+ if err != nil {
+ return nil, err
+ }
+ return n, nil
+}
+
+func (s *NetServer) init(conf *commCfg.P2PNodeConfig, version string) error {
+ keyId := common.RandPeerKeyId()
+
+ httpInfo := conf.HttpInfoPort
+ nodePort := conf.NodePort
+ if nodePort == 0 {
+ return fmt.Errorf("[p2p]invalid link port")
+ }
+
+ s.base = peer.NewPeerInfo(keyId.Id, common.PROTOCOL_VERSION, common.SERVICE_NODE, true, httpInfo,
+ nodePort, 0, version, "")
+
+ option, err := connect_controller.ConnCtrlOptionFromConfig(conf)
+ if err != nil {
+ return err
+ }
+ s.connCtrl = connect_controller.NewConnectController(s.base, keyId, option)
+
+ syncPort := s.base.Port
+ if syncPort == 0 {
+ return fmt.Errorf("[p2p]sync port invalid")
+ }
+ s.listener, err = connect_controller.NewListener(syncPort, conf)
+ if err != nil {
+ return fmt.Errorf("[p2p]failed to create sync listener")
+ }
+
+ log4.Info("[p2p]init peer ID to ", s.base.Id.ToHexString())
+
+ return nil
+}
+
+func (this *NetServer) handleClientConnection(conn net.Conn) error {
+ peerInfo, conn, err := this.connCtrl.AcceptConnect(conn)
+ if err != nil {
+ return err
+ }
+ remotePeer := createPeer(peerInfo, conn)
+ remotePeer.AttachChan(this.NetChan)
+ this.ReplacePeer(remotePeer)
+
+ go remotePeer.Link.Rx()
+
+ // todo
+ // this.protocol.HandleSystemMessage(this, p2p.PeerConnected{Info: remotePeer.Info})
+ return nil
+}
+
+func createPeer(info *peer.PeerInfo, conn net.Conn) *peer.Peer {
+ remotePeer := peer.NewPeer()
+ remotePeer.SetInfo(info)
+ remotePeer.Link.UpdateRXTime(time.Now())
+ remotePeer.Link.SetAddr(conn.RemoteAddr().String())
+ remotePeer.Link.SetConn(conn)
+ remotePeer.Link.SetID(info.Id)
+
+ return remotePeer
+}
+
+func (this *NetServer) SendTo(p common.PeerId, msg types.Message) {
+ // todo
+ //peer := this.GetPeer(p)
+ //if peer != nil {
+ // this.Send(peer, msg)
+ //}
+}
+
+func (this *NetServer) GetPeer(id common.PeerId) *peer.Peer {
+ return this.Np.GetPeer(id)
+}
+
+func (this *NetServer) ReplacePeer(remotePeer *peer.Peer) {
+ // todo
+ //old := this.Np.ReplacePeer(remotePeer, this)
+ //if old != nil {
+ // old.Close()
+ //}
+}
diff --git a/methods/utils.go b/methods/utils.go
new file mode 100644
index 0000000..9f6c74c
--- /dev/null
+++ b/methods/utils.go
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package methods
diff --git a/p2pserver/actor/req/consensus.go b/p2pserver/actor/req/consensus.go
new file mode 100644
index 0000000..e7dca12
--- /dev/null
+++ b/p2pserver/actor/req/consensus.go
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package req
+
+import (
+ "github.com/ontio/ontology-eventbus/actor"
+)
+
+var ConsensusPid *actor.PID
+
+func SetConsensusPid(conPid *actor.PID) {
+ ConsensusPid = conPid
+}
diff --git a/p2pserver/actor/req/txnpool.go b/p2pserver/actor/req/txnpool.go
new file mode 100644
index 0000000..9844bec
--- /dev/null
+++ b/p2pserver/actor/req/txnpool.go
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package req
+
+import (
+ log4 "github.com/alecthomas/log4go"
+ "github.com/ontio/ontology-eventbus/actor"
+ "github.com/ontio/ontology/core/types"
+ tc "github.com/ontio/ontology/txnpool/common"
+)
+
+var txnPoolPid *actor.PID
+
+func SetTxnPoolPid(txnPid *actor.PID) {
+ txnPoolPid = txnPid
+}
+
+//add txn to txnpool
+func AddTransaction(transaction *types.Transaction) {
+ if txnPoolPid == nil {
+ _ = log4.Error("[p2p]net_server AddTransaction(): txnpool pid is nil")
+ return
+ }
+ txReq := &tc.TxReq{
+ Tx: transaction,
+ Sender: tc.NetSender,
+ TxResultCh: nil,
+ }
+ txnPoolPid.Tell(txReq)
+}
diff --git a/p2pserver/common/checksum.go b/p2pserver/common/checksum.go
new file mode 100644
index 0000000..307403b
--- /dev/null
+++ b/p2pserver/common/checksum.go
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package common
+
+import (
+ "crypto/sha256"
+ "hash"
+)
+
+// checksum implement hash.Hash interface and io.Writer
+type checksum struct {
+ hash.Hash
+}
+
+func (self *checksum) Size() int {
+ return CHECKSUM_LEN
+}
+
+func (self *checksum) Sum(b []byte) []byte {
+ temp := self.Hash.Sum(nil)
+ h := sha256.Sum256(temp)
+
+ return append(b, h[:CHECKSUM_LEN]...)
+}
+
+func NewChecksum() hash.Hash {
+ return &checksum{sha256.New()}
+}
+
+func Checksum(data []byte) [CHECKSUM_LEN]byte {
+ var checksum [CHECKSUM_LEN]byte
+ t := sha256.Sum256(data)
+ s := sha256.Sum256(t[:])
+
+ copy(checksum[:], s[:])
+
+ return checksum
+}
diff --git a/p2pserver/common/checksum_test.go b/p2pserver/common/checksum_test.go
new file mode 100644
index 0000000..c95097b
--- /dev/null
+++ b/p2pserver/common/checksum_test.go
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package common
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestChecksum(t *testing.T) {
+ data := []byte{1, 2, 3}
+ cs := Checksum(data)
+
+ writer := NewChecksum()
+ writer.Write(data)
+ checksum2 := writer.Sum(nil)
+ assert.Equal(t, cs[:], checksum2)
+
+}
diff --git a/p2pserver/common/id.go b/p2pserver/common/id.go
new file mode 100644
index 0000000..a9e6801
--- /dev/null
+++ b/p2pserver/common/id.go
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package common
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/binary"
+ "errors"
+ "io"
+ "math"
+ "math/big"
+ "math/bits"
+
+ "github.com/ontio/ontology-crypto/keypair"
+ "github.com/ontio/ontology/account"
+ "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/core/types"
+)
+
+var Difficulty = 18 //bit
+
+type PeerId struct {
+ val common.Address
+}
+
+type PeerIDAddressPair struct {
+ ID PeerId
+ Address string
+}
+
+func (self PeerId) IsEmpty() bool {
+ return self.val == common.ADDRESS_EMPTY
+}
+
+func (self PeerId) Serialization(sink *common.ZeroCopySink) {
+ sink.WriteAddress(self.val)
+}
+
+func (self *PeerId) Deserialization(source *common.ZeroCopySource) error {
+ val, eof := source.NextAddress()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ self.val = val
+ return nil
+}
+
+func (self *PeerId) ToHexString() string {
+ return self.val.ToHexString()
+}
+
+type PeerKeyId struct {
+ PublicKey keypair.PublicKey
+
+ Id PeerId
+}
+
+func (self PeerId) GenRandPeerId(prefix uint) PeerId {
+ var ret PeerId
+ rand.Read(ret.val[:])
+ if prefix == 0 {
+ ret.val[0] &= 0x7f
+ // make first bit different
+ if (0x80 & self.val[0]) == 0 {
+ ret.val[0] |= 0x80
+ }
+ return ret
+ }
+
+ num := prefix / 8
+ left := prefix % 8
+ if num > uint(len(self.val[:])) {
+ num = uint(len(self.val[:]))
+ }
+
+ if num > 0 {
+ copy(ret.val[:num], self.val[:num])
+ }
+ // make all prefix bits same
+ mask := (uint8(0xff) >> (8 - left)) << (8 - left)
+ ret.val[num] &= (^mask)
+ ret.val[num] |= (self.val[num] & mask)
+
+ // and prefix + 1 different
+ // clear prefix + 1
+ ret.val[num] &= ^(1 << (8 - left - 1))
+ // if prefix + 1 bit is 1 then we already satisfied
+ // if prefix + 1 bit is 0, then we should set this bit
+ if (self.val[num]>>(8-left-1))&1 == 0 {
+ ret.val[num] |= (1 << (8 - left - 1))
+ }
+
+ return ret
+}
+
+func (self PeerId) ToUint64() uint64 {
+ if self.IsPseudoPeerId() {
+ nonce := binary.LittleEndian.Uint64(self.val[:8])
+ return nonce
+ }
+ kid := new(big.Int).SetBytes(self.val[:])
+ uint64Max := new(big.Int).SetUint64(math.MaxUint64)
+ res := kid.Mod(kid, uint64Max)
+ return res.Uint64()
+}
+
+func (self PeerId) IsPseudoPeerId() bool {
+ for i := 8; i < len(self.val); i++ {
+ if self.val[i] != 0 {
+ return false
+ }
+ }
+ return true
+}
+
+func PseudoPeerIdFromUint64(data uint64) PeerId {
+ id := common.ADDRESS_EMPTY
+ binary.LittleEndian.PutUint64(id[:], data)
+ return PeerId{
+ val: id,
+ }
+}
+
+func (this *PeerKeyId) Serialization(sink *common.ZeroCopySink) {
+ sink.WriteVarBytes(keypair.SerializePublicKey(this.PublicKey))
+}
+
+func (this *PeerKeyId) Deserialization(source *common.ZeroCopySource) error {
+ data, _, irregular, eof := source.NextVarBytes()
+ if irregular {
+ return common.ErrIrregularData
+ }
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ pub, err := keypair.DeserializePublicKey(data)
+ if err != nil {
+ return err
+ }
+ if !validatePublicKey(pub) {
+ return errors.New("invalid kad public key")
+ }
+ this.PublicKey = pub
+ this.Id = peerIdFromPubkey(pub)
+ return nil
+}
+
+func peerIdFromPubkey(pubKey keypair.PublicKey) PeerId {
+ return PeerId{val: types.AddressFromPubKey(pubKey)}
+}
+
+func RandPeerKeyId() *PeerKeyId {
+ var acc *account.Account
+ for {
+ acc = account.NewAccount("")
+ if validatePublicKey(acc.PublicKey) {
+ break
+ }
+ }
+ kid := peerIdFromPubkey(acc.PublicKey)
+ return &PeerKeyId{
+ PublicKey: acc.PublicKey,
+ Id: kid,
+ }
+}
+
+func validatePublicKey(pubKey keypair.PublicKey) bool {
+ pub := keypair.SerializePublicKey(pubKey)
+ res := sha256.Sum256(pub)
+ hash := sha256.Sum256(res[:])
+ limit := Difficulty >> 3
+ for i := 0; i < limit; i++ {
+ if hash[i] != 0 {
+ return false
+ }
+ }
+ diff := Difficulty % 8
+ if diff != 0 {
+ x := hash[limit] >> uint8(8-diff)
+ return x == 0
+ }
+ return true
+}
+
+func (self PeerId) Distance(b PeerId) [20]byte {
+ var c PeerId
+ for i := 0; i < len(self.val); i++ {
+ c.val[i] = self.val[i] ^ b.val[i]
+ }
+
+ return c.val
+}
+
+// Closer returns true if a is closer to self than b is
+func (self PeerId) Closer(a, b PeerId) bool {
+ adist := self.Distance(a)
+ bdist := self.Distance(b)
+
+ return bytes.Compare(adist[:], bdist[:]) < 0
+}
+
+// CommonPrefixLen(cpl) calculate two ID's xor prefix 0
+func CommonPrefixLen(a, b PeerId) int {
+ dis := a.Distance(b)
+ return zeroPrefixLen(dis[:])
+}
+
+// ZeroPrefixLen returns the number of consecutive zeroes in a byte slice.
+func zeroPrefixLen(id []byte) int {
+ for i, b := range id {
+ if b != 0 {
+ return i*8 + bits.LeadingZeros8(uint8(b))
+ }
+ }
+
+ return len(id) * 8
+}
diff --git a/p2pserver/common/id_test.go b/p2pserver/common/id_test.go
new file mode 100644
index 0000000..b68780d
--- /dev/null
+++ b/p2pserver/common/id_test.go
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package common
+
+import (
+ "fmt"
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestConvertPeerID(t *testing.T) {
+ start := time.Now().Unix()
+ fmt.Println("start:", start)
+ RandPeerKeyId()
+
+ end := time.Now().Unix()
+ fmt.Println("end:", end)
+ fmt.Println(end - start)
+}
+
+func TestKIdToUint64(t *testing.T) {
+ for i := 0; i < 100; i++ {
+ data := rand.Uint64()
+ id := PseudoPeerIdFromUint64(data)
+ data2 := id.ToUint64()
+ assert.Equal(t, data, data2)
+ }
+}
+
+func TestKadId_IsEmpty(t *testing.T) {
+ id := PeerId{}
+ assert.True(t, id.IsEmpty())
+ kid := RandPeerKeyId()
+ assert.False(t, kid.Id.IsEmpty())
+}
diff --git a/p2pserver/common/p2p_common.go b/p2pserver/common/p2p_common.go
new file mode 100644
index 0000000..a8cabf6
--- /dev/null
+++ b/p2pserver/common/p2p_common.go
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package common
+
+import (
+ "errors"
+ "strconv"
+ "strings"
+)
+
+//peer capability
+const (
+ VERIFY_NODE = 1 //peer involved in consensus
+ SERVICE_NODE = 2 //peer only sync with consensus peer
+)
+
+const MIN_VERSION_FOR_DHT = "1.9.1-beta"
+
+//link and concurrent const
+const (
+ PER_SEND_LEN = 1024 * 256 //byte len per conn write
+ MAX_BUF_LEN = 1024 * 256 //the maximum buffer to receive message
+ WRITE_DEADLINE = 5 //deadline of conn write
+ REQ_INTERVAL = 3 //single request max interval in second
+ MAX_REQ_RECORD_SIZE = 1000 //the maximum request record size
+ MAX_RESP_CACHE_SIZE = 50 //the maximum response cache
+ MAX_TX_CACHE_SIZE = 100000 //the maximum txHash cache size
+)
+
+//msg cmd const
+const (
+ MSG_CMD_LEN = 12 //msg type length in byte
+ CHECKSUM_LEN = 4 //checksum length in byte
+ MSG_HDR_LEN = 24 //msg hdr length in byte
+ MAX_BLK_HDR_CNT = 500 //hdr count once when sync header
+ MAX_MSG_LEN = 30 * 1024 * 1024 //the maximum message length
+ MAX_PAYLOAD_LEN = MAX_MSG_LEN - MSG_HDR_LEN
+)
+
+//msg type const
+const (
+ MAX_ADDR_NODE_CNT = 64 //the maximum peer address from msg
+ MAX_INV_BLK_CNT = 64 //the maximum blk hash cnt of inv msg
+)
+
+//info update const
+const (
+ PROTOCOL_VERSION = 0 //protocol version
+ UPDATE_RATE_PER_BLOCK = 2 //info update rate in one generate block period
+ KEEPALIVE_TIMEOUT = 15 //contact timeout in sec
+ DIAL_TIMEOUT = 6 //connect timeout in sec
+ CONN_MONITOR = 6 //time to retry connect in sec
+ CONN_MAX_BACK = 4000 //max backoff time in micro sec
+ MAX_RETRY_COUNT = 3 //max reconnect time of remote peer
+ CHAN_CAPABILITY = 10000 //channel capability of recv link
+ SYNC_BLK_WAIT = 2 //timespan for blk sync check
+)
+
+const (
+ RecentPeerElapseLimit = 60
+)
+
+//cap flag
+const HTTP_INFO_FLAG = 0 //peer`s http info bit in cap field
+
+//recent contact const
+const (
+ RECENT_TIMEOUT = 60
+ RECENT_FILE_NAME = "peers.recent"
+)
+
+//PeerAddr represent peer`s net information
+type PeerAddr struct {
+ Time int64 //latest timestamp
+ Services uint64 //service type
+ IpAddr [16]byte //ip address
+ Port uint16 //sync port
+ //todo remove this legecy field
+ ConsensusPort uint16 //consensus port
+ ID PeerId //Unique ID
+}
+
+//const channel msg id and type
+const (
+ VERSION_TYPE = "version" //peer`s information
+ VERACK_TYPE = "verack" //ack msg after version recv
+ GetADDR_TYPE = "getaddr" //req nbr address from peer
+ ADDR_TYPE = "addr" //nbr address
+ PING_TYPE = "ping" //ping sync height
+ PONG_TYPE = "pong" //pong recv nbr height
+ GET_HEADERS_TYPE = "getheaders" //req blk hdr
+ HEADERS_TYPE = "headers" //blk hdr
+ INV_TYPE = "inv" //inv payload
+ GET_DATA_TYPE = "getdata" //req data from peer
+ BLOCK_TYPE = "block" //blk payload
+ TX_TYPE = "tx" //transaction
+ CONSENSUS_TYPE = "consensus" //consensus payload
+ GET_BLOCKS_TYPE = "getblocks" //req blks from peer
+ NOT_FOUND_TYPE = "notfound" //peer can`t find blk according to the hash
+ FINDNODE_TYPE = "findnode" // find node using dht
+ FINDNODE_RESP_TYPE = "findnodeack" // find node using dht
+ UPDATE_KADID_TYPE = "updatekadid" //update node kadid
+)
+
+//ParseIPAddr return ip address
+func ParseIPAddr(s string) (string, error) {
+ i := strings.Index(s, ":")
+ if i < 0 {
+ return "", errors.New("[p2p]split ip address error")
+ }
+ return s[:i], nil
+}
+
+//ParseIPPort return ip port
+func ParseIPPort(s string) (string, error) {
+ i := strings.LastIndex(s, ":")
+ if i < 0 || i == len(s)-1 {
+ return "", errors.New("[p2p]split ip port error")
+ }
+ port, err := strconv.Atoi(s[i+1:])
+ if err != nil {
+ return "", errors.New("[p2p]parse port error")
+ }
+ if port <= 0 || port >= 65535 {
+ return "", errors.New("[p2p]port out of bound")
+ }
+ return s[i:], nil
+}
diff --git a/p2pserver/connect_controller/connect_controller.go b/p2pserver/connect_controller/connect_controller.go
new file mode 100644
index 0000000..bd8e78b
--- /dev/null
+++ b/p2pserver/connect_controller/connect_controller.go
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package connect_controller
+
+import (
+ "fmt"
+ "net"
+ "sort"
+ "strconv"
+ "sync"
+ "sync/atomic"
+
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/handshake"
+ "github.com/ontio/ontology/p2pserver/peer"
+ "github.com/scylladb/go-set/strset"
+)
+
+const INBOUND_INDEX = 0
+const OUTBOUND_INDEX = 1
+
+type connectedPeer struct {
+ connectId uint64
+ addr string
+ peer *peer.PeerInfo
+}
+
+type ConnectController struct {
+ ConnCtrlOption
+
+ selfId *common.PeerKeyId
+ peerInfo *peer.PeerInfo
+
+ mutex sync.Mutex
+ inoutbounds [2]*strset.Set // in/outbounds address list
+ connecting *strset.Set
+ peers map[common.PeerId]*connectedPeer // all connected peers
+
+ ownAddr string
+ nextConnectId uint64
+}
+
+func NewConnectController(peerInfo *peer.PeerInfo, keyid *common.PeerKeyId,
+ option ConnCtrlOption) *ConnectController {
+ control := &ConnectController{
+ ConnCtrlOption: option,
+ selfId: keyid,
+ peerInfo: peerInfo,
+ inoutbounds: [2]*strset.Set{strset.New(), strset.New()},
+ connecting: strset.New(),
+ peers: make(map[common.PeerId]*connectedPeer),
+ }
+ // put domain to the end
+ sort.Slice(control.ReservedPeers, func(i, j int) bool {
+ return net.ParseIP(control.ReservedPeers[i]) != nil
+ })
+
+ return control
+}
+
+func (self *ConnectController) OwnAddress() string {
+ return self.ownAddr
+}
+
+func (self *ConnectController) getConnectId() uint64 {
+ return atomic.AddUint64(&self.nextConnectId, 1)
+}
+
+func (self *ConnectController) hasInbound(addr string) bool {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+ return self.inoutbounds[INBOUND_INDEX].Has(addr)
+}
+
+func (self *ConnectController) OutboundsCount() uint {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+ return uint(self.inoutbounds[OUTBOUND_INDEX].Size())
+}
+
+func (self *ConnectController) InboundsCount() uint {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+ return uint(self.inoutbounds[INBOUND_INDEX].Size())
+}
+
+func (self *ConnectController) isBoundFull(index int) bool {
+ count := self.boundsCount(index)
+ if index == INBOUND_INDEX {
+ return count >= self.MaxConnInBound
+ }
+ return count >= self.MaxConnOutBound
+}
+
+func (self *ConnectController) boundsCount(index int) uint {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+ return uint(self.inoutbounds[index].Size())
+}
+
+func (self *ConnectController) hasBoundAddr(addr string, index int) bool {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+ return self.inoutbounds[index].Has(addr)
+}
+
+func (self *ConnectController) tryAddConnecting(addr string) bool {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+
+ if self.connecting.Has(addr) {
+ return false
+ }
+ self.connecting.Add(addr)
+
+ return true
+}
+
+func (self *ConnectController) removeConnecting(addr string) {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+
+ self.connecting.Remove(addr)
+}
+
+func (self *ConnectController) reserveEnabled() bool {
+ return len(self.ReservedPeers) > 0
+}
+
+// remoteAddr format 192.168.1.1:61234
+func (self *ConnectController) inReserveList(remoteIPPort string) bool {
+ // 192.168.1.1 in reserve list, 192.168.1.111:61234 and 192.168.1.11:61234 can connect in if we are using prefix matching
+ // so get this IP to do fully match
+ remoteAddr, _, err := net.SplitHostPort(remoteIPPort)
+ if err != nil {
+ return false
+ }
+ // we don't load domain in start because we consider domain's A/AAAA record may change sometimes
+ for _, curIPOrName := range self.ReservedPeers {
+ curIPs, err := net.LookupHost(curIPOrName)
+ if err != nil {
+ continue
+ }
+ for _, digIP := range curIPs {
+ if digIP == remoteAddr {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+func (self *ConnectController) checkReservedPeers(remoteAddr string) error {
+ if !self.reserveEnabled() || self.inReserveList(remoteAddr) {
+ return nil
+ }
+ return fmt.Errorf("the remote addr: %s not in reserved list", remoteAddr)
+}
+
+func (self *ConnectController) getInboundCountWithIp(ip string) uint {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+ var count uint
+ self.inoutbounds[INBOUND_INDEX].Each(func(addr string) bool {
+ ipRecord, _ := common.ParseIPAddr(addr)
+ if ipRecord == ip {
+ count += 1
+ }
+
+ return true
+ })
+
+ return count
+}
+
+func (self *ConnectController) AcceptConnect(conn net.Conn) (*peer.PeerInfo, net.Conn, error) {
+ addr := conn.RemoteAddr().String()
+ err := self.beforeHandshakeCheck(addr, INBOUND_INDEX)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ peerInfo, err := handshake.HandshakeServer(self.peerInfo, self.selfId, conn)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ err = self.afterHandshakeCheck(peerInfo, addr)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ wrapped := self.savePeer(conn, peerInfo, INBOUND_INDEX)
+
+ log.Infof("inbound peer %s connected, %s", conn.RemoteAddr().String(), peerInfo)
+ return peerInfo, wrapped, nil
+}
+
+//Connect used to connect net address under sync or cons mode
+// need call Peer.Close to clean up resource.
+func (self *ConnectController) Connect(addr string) (*peer.PeerInfo, net.Conn, error) {
+ err := self.beforeHandshakeCheck(addr, OUTBOUND_INDEX)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if !self.tryAddConnecting(addr) {
+ return nil, nil, fmt.Errorf("node exist in connecting list: %s", addr)
+ }
+ defer self.removeConnecting(addr)
+
+ conn, err := self.dialer.Dial(addr)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ peerInfo, err := handshake.HandshakeClient(self.peerInfo, self.selfId, conn)
+ if err != nil {
+ _ = conn.Close()
+ return nil, nil, err
+ }
+
+ err = self.afterHandshakeCheck(peerInfo, conn.RemoteAddr().String())
+ if err != nil {
+ _ = conn.Close()
+ return nil, nil, err
+ }
+
+ wrapped := self.savePeer(conn, peerInfo, OUTBOUND_INDEX)
+
+ log.Infof("outbound peer %s connected. %s", conn.RemoteAddr().String(), peerInfo)
+ return peerInfo, wrapped, nil
+}
+
+func (self *ConnectController) afterHandshakeCheck(remotePeer *peer.PeerInfo, remoteAddr string) error {
+ if err := self.isHandWithSelf(remotePeer, remoteAddr); err != nil {
+ return err
+ }
+
+ return self.checkPeerIdAndIP(remotePeer, remoteAddr)
+}
+
+func (self *ConnectController) beforeHandshakeCheck(addr string, index int) error {
+ err := self.checkReservedPeers(addr)
+ if err != nil {
+ return err
+ }
+
+ if self.hasBoundAddr(addr, index) {
+ return fmt.Errorf("peer %s already in connection records", addr)
+ }
+
+ if self.ownAddr == addr {
+ return fmt.Errorf("connecting with self address %s", addr)
+ }
+
+ if self.isBoundFull(index) {
+ return fmt.Errorf("[p2p] bound %d connections reach max limit", index)
+ }
+ if index == INBOUND_INDEX {
+ remoteIp, err := common.ParseIPAddr(addr)
+ if err != nil {
+ return fmt.Errorf("[p2p]parse ip error %v", err.Error())
+ }
+ connNum := self.getInboundCountWithIp(remoteIp)
+ if connNum >= self.MaxConnInBoundPerIP {
+ return fmt.Errorf("connections(%d) with ip(%s) has reach max limit(%d), "+
+ "conn closed", connNum, remoteIp, self.MaxConnInBoundPerIP)
+ }
+ }
+
+ return nil
+}
+
+func (self *ConnectController) isHandWithSelf(remotePeer *peer.PeerInfo, remoteAddr string) error {
+ addrIp, err := common.ParseIPAddr(remoteAddr)
+ if err != nil {
+ log.Warn(err)
+ return err
+ }
+ nodeAddr := addrIp + ":" + strconv.Itoa(int(remotePeer.Port))
+ if remotePeer.Id.ToUint64() == self.selfId.Id.ToUint64() {
+ self.ownAddr = nodeAddr
+ return fmt.Errorf("the node handshake with itself: %s", remoteAddr)
+ }
+
+ return nil
+}
+
+func (self *ConnectController) getPeer(kid common.PeerId) *connectedPeer {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+ p := self.peers[kid]
+ return p
+}
+
+func (self *ConnectController) savePeer(conn net.Conn, p *peer.PeerInfo, index int) net.Conn {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+
+ addr := conn.RemoteAddr().String()
+ self.inoutbounds[index].Add(addr)
+
+ cid := self.getConnectId()
+ self.peers[p.Id] = &connectedPeer{
+ connectId: cid,
+ addr: addr,
+ peer: p,
+ }
+
+ return &Conn{
+ Conn: conn,
+ connectId: cid,
+ kid: p.Id,
+ addr: addr,
+ boundIndex: index,
+ controller: self,
+ }
+}
+
+func (self *ConnectController) removePeer(conn *Conn) {
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+
+ self.inoutbounds[conn.boundIndex].Remove(conn.addr)
+
+ p := self.peers[conn.kid]
+ if p == nil || p.peer == nil {
+ log.Fatalf("connection %s not in controller", conn.kid.ToHexString())
+ } else if p.connectId == conn.connectId { // connection not replaced
+ delete(self.peers, conn.kid)
+ }
+}
+
+// if connection with peer.Kid exist, but has different IP, return error
+func (self *ConnectController) checkPeerIdAndIP(peer *peer.PeerInfo, addr string) error {
+ oldPeer := self.getPeer(peer.Id)
+ if oldPeer == nil {
+ return nil
+ }
+
+ ipOld, err := common.ParseIPAddr(oldPeer.addr)
+ if err != nil {
+ err := fmt.Errorf("[createPeer]exist peer ip format is wrong %s", oldPeer.addr)
+ log.Fatal(err)
+ return err
+ }
+ ipNew, err := common.ParseIPAddr(addr)
+ if err != nil {
+ err := fmt.Errorf("[createPeer]connecting peer ip format is wrong %s, close", addr)
+ log.Fatal(err)
+ return err
+ }
+
+ if ipNew != ipOld {
+ err := fmt.Errorf("[createPeer]same peer id from different addr: %s, %s close latest one", ipOld, ipNew)
+ log.Warn(err)
+ return err
+ }
+
+ return nil
+}
diff --git a/p2pserver/connect_controller/connect_controller_test.go b/p2pserver/connect_controller/connect_controller_test.go
new file mode 100644
index 0000000..fa881bd
--- /dev/null
+++ b/p2pserver/connect_controller/connect_controller_test.go
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package connect_controller
+
+import (
+ "fmt"
+ "net"
+ "sort"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/handshake"
+ "github.com/ontio/ontology/p2pserver/peer"
+ "github.com/stretchr/testify/assert"
+)
+
+func init() {
+ common.Difficulty = 1
+ handshake.HANDSHAKE_DURATION = 10 * time.Second
+}
+
+type Transport struct {
+ dialer Dialer
+ listener net.Listener
+ listenAddr string
+ t *testing.T
+}
+
+func NewTransport(t *testing.T) *Transport {
+ listener, err := net.Listen("tcp", "127.0.0.1:")
+ assert.Nil(t, err)
+ assert.NotNil(t, listener)
+
+ return &Transport{
+ t: t,
+ listenAddr: listener.Addr().String(),
+ listener: listener,
+ dialer: &noTlsDialer{},
+ }
+}
+
+func (self *Transport) Accept() net.Conn {
+ conn, err := self.listener.Accept()
+ assert.Nil(self.t, err)
+ return conn
+}
+
+func (self *Transport) Pipe() (net.Conn, net.Conn) {
+ c := make(chan net.Conn)
+ go func() {
+ conn, err := self.listener.Accept()
+ assert.Nil(self.t, err)
+ c <- conn
+ }()
+ client, err := self.dialer.Dial(self.listenAddr)
+ assert.Nil(self.t, err)
+
+ server := <-c
+
+ return client, server
+}
+
+type Node struct {
+ *ConnectController
+ Info *peer.PeerInfo
+ Key *common.PeerKeyId
+}
+
+func NewNode(option ConnCtrlOption) *Node {
+ key := common.RandPeerKeyId()
+ info := &peer.PeerInfo{
+ Id: key.Id,
+ Port: 20338,
+ SoftVersion: common.MIN_VERSION_FOR_DHT,
+ }
+
+ return &Node{
+ ConnectController: NewConnectController(info, key, option),
+ Info: info,
+ Key: key,
+ }
+}
+
+func TestConnectController_CanDetectSelfAddress(t *testing.T) {
+ versions := []string{"v1.8.0", "v1.7.0", "v1.9.0", "v1.9.0-beta", "v1.20"}
+ for _, version := range versions {
+ trans := NewTransport(t)
+ server := NewNode(NewConnCtrlOption())
+ server.Info.SoftVersion = version
+ assert.Equal(t, server.OwnAddress(), "")
+
+ c, s := trans.Pipe()
+ go func() {
+ _, _ = handshake.HandshakeClient(server.peerInfo, server.Key, c)
+ }()
+
+ _, _, err := server.AcceptConnect(s)
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "handshake with itself")
+
+ assert.Equal(t, server.OwnAddress(), "127.0.0.1:20338")
+ }
+}
+
+func TestConnectController_AcceptConnect_MaxInBound(t *testing.T) {
+ trans := NewTransport(t)
+ maxInboud := 5
+ server := NewNode(NewConnCtrlOption().MaxInBound(uint(maxInboud)))
+ client := NewNode(NewConnCtrlOption().MaxOutBound(uint(maxInboud * 2)))
+
+ clientConns := make(chan net.Conn, maxInboud)
+ for i := 0; i < maxInboud*2; i++ {
+ conn1, conn2 := trans.Pipe()
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ _, err := handshake.HandshakeClient(client.peerInfo, client.Key, conn1)
+ if i < int(maxInboud) {
+ assert.Nil(t, err)
+ } else {
+ assert.NotNil(t, err)
+ }
+ }(i)
+ checkServer(t, client, server, clientConns, i, conn2, maxInboud, false)
+ wg.Wait()
+ }
+
+ close(clientConns)
+ for conn := range clientConns {
+ _ = conn.Close()
+ }
+
+ assert.Equal(t, server.inoutbounds[INBOUND_INDEX].Size(), 0)
+}
+
+func TestConnectController_OutboundsCount(t *testing.T) {
+ maxOutboud := 5
+ server := NewNode(NewConnCtrlOption().MaxInBound(uint(maxOutboud * 2)))
+ client := NewNode(NewConnCtrlOption().MaxOutBound(uint(maxOutboud)))
+
+ clientConns := make(chan net.Conn, maxOutboud*2)
+ for i := 0; i < maxOutboud*2; i++ {
+ trans := NewTransport(t)
+ go func(i int, trans *Transport) {
+ con := trans.Accept()
+ checkServer(t, client, server, clientConns, i, con, maxOutboud, true)
+ }(i, trans)
+ _, _, err := client.Connect(trans.listenAddr)
+ if i < maxOutboud {
+ assert.Nil(t, err)
+ assert.Equal(t, client.boundsCount(OUTBOUND_INDEX), uint(i+1))
+ } else {
+ assert.NotNil(t, err)
+ }
+ }
+
+ assert.Equal(t, client.boundsCount(OUTBOUND_INDEX), uint(maxOutboud))
+ for i := 0; i < maxOutboud; i++ {
+ conn := <-clientConns
+ _ = conn.Close()
+ }
+
+ assert.Equal(t, server.boundsCount(INBOUND_INDEX), uint(0))
+}
+
+func TestConnCtrlOption_MaxInBoundPerIp(t *testing.T) {
+ trans := NewTransport(t)
+ maxInBoundPerIp := 5
+ server := NewNode(NewConnCtrlOption().MaxInBoundPerIp(uint(maxInBoundPerIp)))
+ client := NewNode(NewConnCtrlOption().MaxInBoundPerIp(uint(maxInBoundPerIp)))
+
+ clientConns := make(chan net.Conn, maxInBoundPerIp)
+ for i := 0; i < maxInBoundPerIp*2; i++ {
+ conn1, conn2 := trans.Pipe()
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ _, err := handshake.HandshakeClient(client.peerInfo, client.Key, conn1)
+ if i < int(maxInBoundPerIp) {
+ assert.Nil(t, err)
+ } else {
+ assert.NotNil(t, err)
+ }
+ }(i)
+
+ checkServer(t, client, server, clientConns, i, conn2, maxInBoundPerIp, false)
+ wg.Wait()
+ }
+
+ close(clientConns)
+ for conn := range clientConns {
+ _ = conn.Close()
+ }
+
+ assert.Equal(t, server.inoutbounds[INBOUND_INDEX].Size(), 0)
+}
+
+func checkServer(t *testing.T, client, server *Node, clientConns chan<- net.Conn, i int, conn2 net.Conn, maxLimit int, isCheck bool) {
+ info, conn, err := server.AcceptConnect(conn2)
+ if i >= maxLimit && isCheck == false {
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "reach max limit")
+ _ = conn2.Close()
+ return
+ }
+ assert.Nil(t, err)
+ info.Addr = "" // client.Info is not set
+ assert.Equal(t, info, client.Info)
+
+ clientConns <- conn
+}
+
+func TestCheckReserveWithDomain(t *testing.T) {
+ a := assert.New(t)
+ // this domain only have one A record, so we can assure two lookup below return the same IP
+ // other domain may fail the test sometimes
+ dname := "www.onchain.com"
+
+ gips, err := net.LookupHost(dname)
+ a.Nil(err, "fail to get domain record")
+
+ cc := &ConnectController{}
+ cc.ReservedPeers = []string{dname}
+ for _, ip := range gips {
+ err := cc.checkReservedPeers(fmt.Sprintf("%s:1234", ip))
+ a.Nil(err, "fail")
+ }
+
+ cc.ReservedPeers = []string{"192.168.1.111"}
+ cret := cc.inReserveList("192.168.1.1:1234")
+ a.False(cret, "fail")
+ cret = cc.inReserveList("192.168.1.11:1234")
+ a.False(cret, "fail")
+ cret = cc.inReserveList("192.168.1.111:1234")
+ a.True(cret, "fail")
+
+ cc.ReservedPeers = []string{"192.168.1.2", "www.baidu.com", "192.168.1.1"}
+ sort.Slice(cc.ReservedPeers, func(i, j int) bool {
+ return net.ParseIP(cc.ReservedPeers[i]) != nil
+ })
+ a.Equal(cc.ReservedPeers[len(cc.ReservedPeers)-1], "www.baidu.com", "fail")
+}
diff --git a/p2pserver/connect_controller/connection.go b/p2pserver/connect_controller/connection.go
new file mode 100644
index 0000000..98aec2f
--- /dev/null
+++ b/p2pserver/connect_controller/connection.go
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package connect_controller
+
+import (
+ "net"
+
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+// Conn is a net.Conn wrapper to do some clean up when Close.
+type Conn struct {
+ net.Conn
+ addr string
+ kid common.PeerId
+ boundIndex int
+ connectId uint64
+ controller *ConnectController
+}
+
+// Close overwrite net.Conn
+// warning: this method will try to lock the controller, be carefull to avoid deadlock
+func (self *Conn) Close() error {
+ log.Infof("closing connection: peer %s, address: %s", self.kid.ToHexString(), self.addr)
+
+ self.controller.removePeer(self)
+
+ return self.Conn.Close()
+}
diff --git a/p2pserver/connect_controller/control_option.go b/p2pserver/connect_controller/control_option.go
new file mode 100644
index 0000000..02963a2
--- /dev/null
+++ b/p2pserver/connect_controller/control_option.go
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package connect_controller
+
+import "github.com/ontio/ontology/common/config"
+
+type ConnCtrlOption struct {
+ MaxConnOutBound uint
+ MaxConnInBound uint
+ MaxConnInBoundPerIP uint
+ ReservedPeers []string // enabled if not empty
+ dialer Dialer
+}
+
+func NewConnCtrlOption() ConnCtrlOption {
+ return ConnCtrlOption{
+ MaxConnInBound: config.DEFAULT_MAX_CONN_IN_BOUND,
+ MaxConnOutBound: config.DEFAULT_MAX_CONN_OUT_BOUND,
+ MaxConnInBoundPerIP: config.DEFAULT_MAX_CONN_IN_BOUND_FOR_SINGLE_IP,
+ dialer: &noTlsDialer{},
+ }
+}
+
+func (self ConnCtrlOption) MaxOutBound(n uint) ConnCtrlOption {
+ self.MaxConnOutBound = n
+ return self
+}
+
+func (self ConnCtrlOption) MaxInBound(n uint) ConnCtrlOption {
+ self.MaxConnInBound = n
+ return self
+}
+
+func (self ConnCtrlOption) MaxInBoundPerIp(n uint) ConnCtrlOption {
+ self.MaxConnInBoundPerIP = n
+ return self
+}
+
+func (self ConnCtrlOption) ReservedOnly(peers []string) ConnCtrlOption {
+ self.ReservedPeers = peers
+ return self
+}
+
+func (self ConnCtrlOption) WithDialer(dialer Dialer) ConnCtrlOption {
+ self.dialer = dialer
+ return self
+}
+
+func ConnCtrlOptionFromConfig(config *config.P2PNodeConfig) (option ConnCtrlOption, err error) {
+ var rsv []string
+ if config.ReservedPeersOnly && config.ReservedCfg != nil {
+ rsv = config.ReservedCfg.ReservedPeers
+ }
+ dialer, e := NewDialer(config)
+ if e != nil {
+ err = e
+ return
+ }
+ return ConnCtrlOption{
+ MaxConnOutBound: config.MaxConnOutBound,
+ MaxConnInBound: config.MaxConnInBound,
+ MaxConnInBoundPerIP: config.MaxConnInBoundForSingleIP,
+ ReservedPeers: rsv,
+
+ dialer: dialer,
+ }, nil
+}
diff --git a/p2pserver/connect_controller/transport.go b/p2pserver/connect_controller/transport.go
new file mode 100644
index 0000000..bf5d8dc
--- /dev/null
+++ b/p2pserver/connect_controller/transport.go
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package connect_controller
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "errors"
+ "io/ioutil"
+ "net"
+ "strconv"
+ "time"
+
+ "github.com/ontio/ontology/common/config"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type Dialer interface {
+ Dial(nodeAddr string) (net.Conn, error)
+}
+
+func NewDialer(config *config.P2PNodeConfig) (Dialer, error) {
+ if config.IsTLS {
+ return newTlsDialer(config)
+ }
+
+ return &noTlsDialer{}, nil
+}
+
+type tlsDialer struct {
+ config *tls.Config
+}
+
+func newTlsDialer(config *config.P2PNodeConfig) (*tlsDialer, error) {
+ clientCertPool := x509.NewCertPool()
+ cacert, err := ioutil.ReadFile(config.CAPath)
+ if err != nil {
+ return nil, err
+ }
+ cert, err := tls.LoadX509KeyPair(config.CertPath, config.KeyPath)
+ if err != nil {
+ return nil, err
+ }
+
+ ret := clientCertPool.AppendCertsFromPEM(cacert)
+ if !ret {
+ return nil, errors.New("[p2p]failed to parse root certificate")
+ }
+
+ conf := &tls.Config{
+ RootCAs: clientCertPool,
+ Certificates: []tls.Certificate{cert},
+ }
+
+ return &tlsDialer{config: conf}, nil
+}
+
+func (self *tlsDialer) Dial(nodeAddr string) (net.Conn, error) {
+ var dialer net.Dialer
+ dialer.Timeout = time.Second * common.DIAL_TIMEOUT
+ return tls.DialWithDialer(&dialer, "tcp", nodeAddr, self.config)
+}
+
+type noTlsDialer struct{}
+
+func (self *noTlsDialer) Dial(nodeAddr string) (net.Conn, error) {
+ return net.DialTimeout("tcp", nodeAddr, time.Second*common.DIAL_TIMEOUT)
+}
+
+func NewListener(port uint16, config *config.P2PNodeConfig) (net.Listener, error) {
+ if config == nil || !config.IsTLS {
+ return net.Listen("tcp", ":"+strconv.Itoa(int(port)))
+ }
+
+ // load cert
+ cert, err := tls.LoadX509KeyPair(config.CertPath, config.KeyPath)
+ if err != nil {
+ return nil, err
+ }
+ // load root ca
+ caData, err := ioutil.ReadFile(config.CAPath)
+ if err != nil {
+ return nil, err
+ }
+ pool := x509.NewCertPool()
+ ret := pool.AppendCertsFromPEM(caData)
+ if !ret {
+ return nil, errors.New("[p2p]failed to parse root certificate")
+ }
+
+ tlsConfig := &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ RootCAs: pool,
+ ClientAuth: tls.RequireAndVerifyClientCert,
+ ClientCAs: pool,
+ }
+
+ return tls.Listen("tcp", ":"+strconv.Itoa(int(port)), tlsConfig)
+}
diff --git a/p2pserver/dht/dht.go b/p2pserver/dht/dht.go
new file mode 100644
index 0000000..248f36e
--- /dev/null
+++ b/p2pserver/dht/dht.go
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package dht
+
+import (
+ "time"
+
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ kb "github.com/ontio/ontology/p2pserver/dht/kbucket"
+)
+
+// Pool size is the number of nodes used for group find/set RPC calls
+var PoolSize = 6
+
+// K is the maximum number of requests to perform before returning failure.
+var KValue = 20
+
+// Alpha is the concurrency factor for asynchronous requests.
+var AlphaValue = 3
+
+type DHT struct {
+ localId common.PeerId
+ bucketSize int
+ routeTable *kb.RouteTable // Array of routing tables for differently distanced nodes
+
+ AutoRefresh bool
+ RtRefreshPeriod time.Duration
+}
+
+// RouteTable return dht's routeTable
+func (dht *DHT) RouteTable() *kb.RouteTable {
+ return dht.routeTable
+}
+
+// NewDHT creates a new DHT with the specified host and options.
+func NewDHT(id common.PeerId) *DHT {
+ bucketSize := KValue
+ rt := kb.NewRoutingTable(bucketSize, id)
+
+ rt.PeerAdded = func(p common.PeerId) {
+ log.Debugf("dht: peer: %d added to dht", p)
+ }
+
+ rt.PeerRemoved = func(p common.PeerId) {
+ log.Debugf("dht: peer: %d removed from dht", p)
+ }
+
+ return &DHT{
+ localId: id,
+ routeTable: rt,
+ bucketSize: bucketSize,
+ AutoRefresh: true,
+ RtRefreshPeriod: 10 * time.Second,
+ }
+}
+
+// Update signals the routeTable to Update its last-seen status
+// on the given peer.
+func (dht *DHT) Update(peer common.PeerId, addr string) bool {
+ err := dht.routeTable.Update(peer, addr)
+ return err == nil
+}
+
+func (dht *DHT) Contains(peer common.PeerId) bool {
+ _, ok := dht.routeTable.Find(peer)
+ return ok
+}
+
+func (dht *DHT) Remove(peer common.PeerId) {
+ dht.routeTable.Remove(peer)
+}
+
+func (dht *DHT) BetterPeers(id common.PeerId, count int) []common.PeerIDAddressPair {
+ closer := dht.routeTable.NearestPeers(id, count)
+ filtered := make([]common.PeerIDAddressPair, 0, len(closer))
+ // don't include self and target id
+ for _, curPair := range closer {
+ if curPair.ID == dht.localId {
+ continue
+ }
+ if curPair.ID == id {
+ continue
+ }
+ filtered = append(filtered, curPair)
+ }
+
+ return filtered
+}
diff --git a/p2pserver/dht/dht_test.go b/p2pserver/dht/dht_test.go
new file mode 100644
index 0000000..0e827fb
--- /dev/null
+++ b/p2pserver/dht/dht_test.go
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package dht
+
+import (
+ "fmt"
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNewDHT(t *testing.T) {
+ id := common.RandPeerKeyId()
+ dht := NewDHT(id.Id)
+ assert.NotNil(t, dht)
+ assert.Equal(t, dht.AutoRefresh, true)
+}
+
+func init() {
+ rand.Seed(time.Now().Unix())
+ common.Difficulty = 1
+}
+func TestDHT_Update(t *testing.T) {
+ for i := 0; i < 10; i++ {
+ id := common.RandPeerKeyId()
+ dht := NewDHT(id.Id)
+ local := dht.localId
+ prefix := rand.Int31n(15)
+ kid := local.GenRandPeerId(uint(prefix))
+ boo := dht.Update(kid, "127.0.0.1")
+ assert.True(t, boo)
+ if prefix == 0 {
+ continue
+ }
+ kids := dht.BetterPeers(dht.localId, int(prefix))
+ assert.Equal(t, len(kids), 1)
+ assert.Equal(t, kids[0].ID, kid)
+ }
+}
+
+func TestDHT_Remove(t *testing.T) {
+ for i := 0; i < 100; i++ {
+ id := common.RandPeerKeyId()
+ dht := NewDHT(id.Id)
+ local := dht.localId
+ prefix := rand.Int31n(15)
+ kid := local.GenRandPeerId(uint(prefix))
+ boo := dht.Update(kid, "127.0.0.1")
+ assert.True(t, boo)
+ kids := dht.BetterPeers(dht.localId, 1)
+ assert.Equal(t, len(kids), 1)
+ assert.Equal(t, kids[0].ID, kid)
+ dht.Remove(kid)
+ kids = dht.BetterPeers(dht.localId, int(prefix))
+ assert.Equal(t, len(kids), 0)
+ }
+
+}
+
+func TestDHT_BetterPeers(t *testing.T) {
+ id := common.RandPeerKeyId()
+ dht := NewDHT(id.Id)
+ local := dht.localId
+ rand.Seed(time.Now().Unix())
+ prefix := rand.Int31n(15)
+ for i := 0; i < 15; i++ {
+ kid := local.GenRandPeerId(uint(prefix))
+ boo := dht.Update(kid, "127.0.0.1")
+ if !boo {
+ fmt.Println(boo, prefix)
+ }
+ assert.True(t, boo)
+ }
+ kids := dht.BetterPeers(dht.localId, 3)
+ assert.Equal(t, len(kids), 3)
+}
diff --git a/p2pserver/dht/kbucket/bucket.go b/p2pserver/dht/kbucket/bucket.go
new file mode 100644
index 0000000..66661c5
--- /dev/null
+++ b/p2pserver/dht/kbucket/bucket.go
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package kbucket
+
+import (
+ "container/list"
+ "sync"
+
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+// Bucket holds a list of peers.
+type Bucket struct {
+ lk sync.RWMutex
+ list *list.List
+}
+
+func newBucket() *Bucket {
+ b := new(Bucket)
+ b.list = list.New()
+ return b
+}
+
+func (b *Bucket) Peers() []common.PeerIDAddressPair {
+ b.lk.RLock()
+ defer b.lk.RUnlock()
+ ps := make([]common.PeerIDAddressPair, 0, b.list.Len())
+ for e := b.list.Front(); e != nil; e = e.Next() {
+ id := e.Value.(common.PeerIDAddressPair)
+ ps = append(ps, id)
+ }
+ return ps
+}
+
+func (b *Bucket) Has(id common.PeerId) bool {
+ b.lk.RLock()
+ defer b.lk.RUnlock()
+ for e := b.list.Front(); e != nil; e = e.Next() {
+ curr := e.Value.(common.PeerIDAddressPair)
+ if curr.ID == id {
+ return true
+ }
+ }
+ return false
+}
+
+func (b *Bucket) Remove(id common.PeerId) bool {
+ b.lk.Lock()
+ defer b.lk.Unlock()
+ for e := b.list.Front(); e != nil; e = e.Next() {
+ curr := e.Value.(common.PeerIDAddressPair)
+ if curr.ID == id {
+ b.list.Remove(e)
+ return true
+ }
+ }
+ return false
+}
+
+func (b *Bucket) MoveToFront(id common.PeerId) {
+ b.lk.Lock()
+ defer b.lk.Unlock()
+ for e := b.list.Front(); e != nil; e = e.Next() {
+ curr := e.Value.(common.PeerIDAddressPair)
+ if curr.ID == id {
+ b.list.MoveToFront(e)
+ }
+ }
+}
+
+func (b *Bucket) PushFront(p common.PeerIDAddressPair) {
+ b.lk.Lock()
+ b.list.PushFront(p)
+ b.lk.Unlock()
+}
+
+func (b *Bucket) PopBack() common.PeerIDAddressPair {
+ b.lk.Lock()
+ defer b.lk.Unlock()
+ last := b.list.Back()
+ b.list.Remove(last)
+ return last.Value.(common.PeerIDAddressPair)
+}
+
+func (b *Bucket) Len() int {
+ b.lk.RLock()
+ defer b.lk.RUnlock()
+ return b.list.Len()
+}
+
+// Split splits a buckets peers into two buckets, the methods receiver will have
+// peers with CPL equal to cpl, the returned bucket will have peers with CPL
+// greater than cpl (returned bucket has closer peers)
+// CPL ==> CommonPrefixLen
+func (b *Bucket) Split(cpl int, target common.PeerId) *Bucket {
+ b.lk.Lock()
+ defer b.lk.Unlock()
+
+ out := list.New()
+ newbuck := newBucket()
+ newbuck.list = out
+ e := b.list.Front()
+ for e != nil {
+ pair := e.Value.(common.PeerIDAddressPair)
+ peerCPL := common.CommonPrefixLen(pair.ID, target)
+ if peerCPL > cpl {
+ cur := e
+ out.PushBack(e.Value)
+ e = e.Next()
+ b.list.Remove(cur)
+ continue
+ }
+ e = e.Next()
+ }
+ return newbuck
+}
diff --git a/p2pserver/dht/kbucket/sorting.go b/p2pserver/dht/kbucket/sorting.go
new file mode 100644
index 0000000..1a17cb8
--- /dev/null
+++ b/p2pserver/dht/kbucket/sorting.go
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package kbucket
+
+import (
+ "bytes"
+ "container/list"
+ "sort"
+
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+// A helper struct to sort peers by their distance to the local node
+type peerDistance struct {
+ p common.PeerIDAddressPair
+ distance [20]byte
+}
+
+// peerDistanceSorter implements sort.Interface to sort peers by xor distance
+type peerDistanceSorter struct {
+ peers []peerDistance
+ target common.PeerId
+}
+
+func (pds *peerDistanceSorter) Len() int { return len(pds.peers) }
+func (pds *peerDistanceSorter) Swap(a, b int) { pds.peers[a], pds.peers[b] = pds.peers[b], pds.peers[a] }
+func (pds *peerDistanceSorter) Less(a, b int) bool {
+ return bytes.Compare(pds.peers[a].distance[:], pds.peers[b].distance[:]) < 0
+}
+
+// Append the peer.ID to the sorter's slice. It may no longer be sorted.
+func (pds *peerDistanceSorter) appendPeer(p common.PeerIDAddressPair) {
+ pds.peers = append(pds.peers, peerDistance{
+ p: p,
+ distance: pds.target.Distance(p.ID),
+ })
+}
+
+// Append the peer.ID values in the list to the sorter's slice. It may no longer be sorted.
+func (pds *peerDistanceSorter) appendPeersFromList(l *list.List) {
+ for e := l.Front(); e != nil; e = e.Next() {
+ pds.appendPeer(e.Value.(common.PeerIDAddressPair))
+ }
+}
+
+func (pds *peerDistanceSorter) sort() {
+ sort.Sort(pds)
+}
diff --git a/p2pserver/dht/kbucket/table.go b/p2pserver/dht/kbucket/table.go
new file mode 100644
index 0000000..3c2654f
--- /dev/null
+++ b/p2pserver/dht/kbucket/table.go
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+// package kbucket implements a kademlia 'k-bucket' routing table.
+package kbucket
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+var ErrPeerRejectedHighLatency = errors.New("peer rejected; latency too high")
+var ErrPeerRejectedNoCapacity = errors.New("peer rejected; insufficient capacity")
+
+// maxCplForRefresh is the maximum cpl we support for refresh.
+// This limit exists because we can only generate 'maxCplForRefresh' bit prefixes for now.
+const maxCplForRefresh uint = 15
+
+// CplRefresh contains a CPL(common prefix length) with the host & the last time
+// we refreshed that cpl/searched for an ID which has that cpl with the host.
+type CplRefresh struct {
+ Cpl uint
+ LastRefreshAt time.Time
+}
+
+// RouteTable defines the routing table.
+type RouteTable struct {
+ // ID of the local peer
+ local common.PeerId
+
+ tabLock sync.RWMutex
+
+ // kBuckets define all the fingers to other nodes.
+ Buckets []*Bucket
+ bucketsize int
+
+ cplRefreshLk sync.RWMutex
+ cplRefreshedAt map[uint]time.Time
+
+ // notification callback functions
+ PeerRemoved func(id common.PeerId)
+ PeerAdded func(id common.PeerId)
+}
+
+func noop(_ common.PeerId) {}
+
+// NewRoutingTable creates a new routing table with a given bucketsize, local ID, and latency tolerance.
+func NewRoutingTable(bucketsize int, localID common.PeerId) *RouteTable {
+ rt := &RouteTable{
+ Buckets: []*Bucket{newBucket()},
+ bucketsize: bucketsize,
+ local: localID,
+ cplRefreshedAt: make(map[uint]time.Time),
+ PeerRemoved: noop,
+ PeerAdded: noop,
+ }
+
+ return rt
+}
+
+// GetTrackedCplsForRefresh returns the Cpl's we are tracking for refresh.
+// Caller is free to modify the returned slice as it is a defensive copy.
+func (rt *RouteTable) GetTrackedCplsForRefresh() []CplRefresh {
+ rt.cplRefreshLk.RLock()
+ defer rt.cplRefreshLk.RUnlock()
+
+ cpls := make([]CplRefresh, 0, len(rt.cplRefreshedAt))
+
+ for c, t := range rt.cplRefreshedAt {
+ cpls = append(cpls, CplRefresh{c, t})
+ }
+
+ return cpls
+}
+
+// GenRandPeerID generates a random peerID for a given Cpl
+func (rt *RouteTable) GenRandKadId(targetCpl uint) common.PeerId {
+ if targetCpl > maxCplForRefresh {
+ targetCpl = maxCplForRefresh
+ }
+
+ return rt.local.GenRandPeerId(targetCpl)
+}
+
+// ResetCplRefreshedAtForID resets the refresh time for the Cpl of the given ID.
+func (rt *RouteTable) ResetCplRefreshedAtForID(id common.PeerId, newTime time.Time) {
+ cpl := common.CommonPrefixLen(id, rt.local)
+ if uint(cpl) > maxCplForRefresh {
+ return
+ }
+
+ rt.cplRefreshLk.Lock()
+ defer rt.cplRefreshLk.Unlock()
+
+ rt.cplRefreshedAt[uint(cpl)] = newTime
+}
+
+// Update adds or moves the given peer to the front of its respective bucket
+func (rt *RouteTable) Update(peerID common.PeerId, addr string) error {
+ pair := common.PeerIDAddressPair{
+ ID: peerID,
+ Address: addr,
+ }
+ cpl := common.CommonPrefixLen(peerID, rt.local)
+ rt.tabLock.Lock()
+ defer rt.tabLock.Unlock()
+ bucketID := cpl
+ if bucketID >= len(rt.Buckets) {
+ bucketID = len(rt.Buckets) - 1
+ }
+
+ bucket := rt.Buckets[bucketID]
+ if bucket.Has(peerID) {
+ // If the peer is already in the table, move it to the front.
+ // This signifies that it it "more active" and the less active nodes
+ // Will as a result tend towards the back of the list
+
+ // the paper put more active node at tail, we put more active node at head
+ bucket.MoveToFront(peerID)
+ return nil
+ }
+
+ // We have enough space in the bucket (whether spawned or grouped).
+ if bucket.Len() < rt.bucketsize {
+ bucket.PushFront(pair)
+ // call back notifier
+ rt.PeerAdded(peerID)
+ return nil
+ }
+
+ if bucketID == len(rt.Buckets)-1 {
+ // if the bucket is too large and this is the last bucket (i.e. wildcard), unfold it.
+ rt.nextBucket()
+ // the structure of the table has changed, so let's recheck if the peer now has a dedicated bucket.
+ bucketID = cpl
+ if bucketID >= len(rt.Buckets) {
+ bucketID = len(rt.Buckets) - 1
+ }
+ bucket = rt.Buckets[bucketID]
+ if bucket.Len() >= rt.bucketsize {
+ // if after all the unfolding, we're unable to find room for this peer, scrap it.
+ return ErrPeerRejectedNoCapacity
+ }
+ bucket.PushFront(pair)
+ rt.PeerAdded(peerID)
+ return nil
+ }
+
+ return ErrPeerRejectedNoCapacity
+}
+
+// Remove deletes a peer from the routing table. This is to be used
+// when we are sure a node has disconnected completely.
+func (rt *RouteTable) Remove(p common.PeerId) {
+ peerID := p
+ cpl := common.CommonPrefixLen(peerID, rt.local)
+
+ rt.tabLock.Lock()
+ defer rt.tabLock.Unlock()
+
+ bucketID := cpl
+ if bucketID >= len(rt.Buckets) {
+ bucketID = len(rt.Buckets) - 1
+ }
+
+ bucket := rt.Buckets[bucketID]
+ if bucket.Remove(p) {
+ rt.PeerRemoved(p)
+ }
+}
+
+func (rt *RouteTable) nextBucket() {
+ // This is the last bucket, which allegedly is a mixed bag containing peers not belonging in dedicated (unfolded) buckets.
+ // _allegedly_ is used here to denote that *all* peers in the last bucket might feasibly belong to another bucket.
+ // This could happen if e.g. we've unfolded 4 buckets, and all peers in folded bucket 5 really belong in bucket 8.
+ bucket := rt.Buckets[len(rt.Buckets)-1]
+ newBucket := bucket.Split(len(rt.Buckets)-1, rt.local)
+ rt.Buckets = append(rt.Buckets, newBucket)
+
+ // The newly formed bucket still contains too many peers. We probably just unfolded a empty bucket.
+ if newBucket.Len() >= rt.bucketsize {
+ // Keep unfolding the table until the last bucket is not overflowing.
+ rt.nextBucket()
+ }
+}
+
+// Find a specific peer by ID or return nil
+func (rt *RouteTable) Find(id common.PeerId) (common.PeerIDAddressPair, bool) {
+ srch := rt.NearestPeers(id, 1)
+ if len(srch) == 0 || srch[0].ID != id {
+ return common.PeerIDAddressPair{}, false
+ }
+
+ return srch[0], true
+}
+
+func (rt *RouteTable) NearestPeers(id common.PeerId, count int) []common.PeerIDAddressPair {
+ // This is the number of bits _we_ share with the key. All peers in this
+ // bucket share cpl bits with us and will therefore share at least cpl+1
+ // bits with the given key. +1 because both the target and all peers in
+ // this bucket differ from us in the cpl bit.
+ cpl := common.CommonPrefixLen(id, rt.local)
+
+ // It's assumed that this also protects the buckets.
+ rt.tabLock.RLock()
+
+ // Get bucket index or last bucket
+ if cpl >= len(rt.Buckets) {
+ cpl = len(rt.Buckets) - 1
+ }
+
+ pds := peerDistanceSorter{
+ peers: make([]peerDistance, 0, count+rt.bucketsize),
+ target: id,
+ }
+
+ // Add peers from the target bucket (cpl+1 shared bits).
+ pds.appendPeersFromList(rt.Buckets[cpl].list)
+
+ // If we're short, add peers from buckets to the right until we have
+ // enough. All buckets to the right share exactly cpl bits (as opposed
+ // to the cpl+1 bits shared by the peers in the cpl bucket).
+ //
+ // Unfortunately, to be completely correct, we can't just take from
+ // buckets until we have enough peers because peers because _all_ of
+ // these peers will be ~2**(256-cpl) from us.
+ //
+ // However, we're going to do that anyways as it's "good enough"
+
+ for i := cpl + 1; i < len(rt.Buckets) && pds.Len() < count; i++ {
+ pds.appendPeersFromList(rt.Buckets[i].list)
+ }
+
+ // If we're still short, add in buckets that share _fewer_ bits. We can
+ // do this bucket by bucket because each bucket will share 1 fewer bit
+ // than the last.
+ //
+ // * bucket cpl-1: cpl-1 shared bits.
+ // * bucket cpl-2: cpl-2 shared bits.
+ // ...
+ for i := cpl - 1; i >= 0 && pds.Len() < count; i-- {
+ pds.appendPeersFromList(rt.Buckets[i].list)
+ }
+ rt.tabLock.RUnlock()
+
+ // Sort by distance to local peer
+ pds.sort()
+
+ if count < pds.Len() {
+ pds.peers = pds.peers[:count]
+ }
+
+ out := make([]common.PeerIDAddressPair, 0, pds.Len())
+ for _, p := range pds.peers {
+ out = append(out, p.p)
+ }
+
+ return out
+}
+
+// Size returns the total number of peers in the routing table
+func (rt *RouteTable) Size() int {
+ var tot int
+ rt.tabLock.RLock()
+ for _, buck := range rt.Buckets {
+ tot += buck.Len()
+ }
+ rt.tabLock.RUnlock()
+ return tot
+}
+
+// ListPeers takes a RoutingTable and returns a list of all peers from all buckets in the table.
+func (rt *RouteTable) ListPeers() []common.PeerIDAddressPair {
+ var peers []common.PeerIDAddressPair
+ rt.tabLock.RLock()
+ for _, buck := range rt.Buckets {
+ peers = append(peers, buck.Peers()...)
+ }
+ rt.tabLock.RUnlock()
+ return peers
+}
+
+// Print prints a descriptive statement about the provided RouteTable
+func (rt *RouteTable) Print() {
+ fmt.Printf("Routing Table, bs = %d\n", rt.bucketsize)
+ rt.tabLock.RLock()
+
+ for i, b := range rt.Buckets {
+ fmt.Printf("\tbucket: %d\n", i)
+
+ b.lk.RLock()
+ for e := b.list.Front(); e != nil; e = e.Next() {
+ p := e.Value.(common.PeerIDAddressPair)
+ fmt.Printf("\t\t- %s\n", p.ID.ToHexString())
+ }
+ b.lk.RUnlock()
+ }
+ rt.tabLock.RUnlock()
+}
diff --git a/p2pserver/dht/kbucket/table_test.go b/p2pserver/dht/kbucket/table_test.go
new file mode 100644
index 0000000..e7aeb4c
--- /dev/null
+++ b/p2pserver/dht/kbucket/table_test.go
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package kbucket
+
+import (
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/scylladb/go-set/strset"
+ "github.com/stretchr/testify/require"
+)
+
+func init() {
+ // lower the difficulty
+ common.Difficulty = 1
+ rand.Seed(time.Now().UnixNano())
+}
+
+func genpeerID() *common.PeerKeyId {
+ id := common.RandPeerKeyId()
+ return id
+}
+
+// Test basic features of the bucket struct
+func TestBucket(t *testing.T) {
+ t.Parallel()
+
+ b := newBucket()
+
+ peers := make([]*common.PeerKeyId, 100)
+ for i := 0; i < 100; i++ {
+ peers[i] = genpeerID()
+ pair := common.PeerIDAddressPair{
+ ID: peers[i].Id,
+ }
+ b.PushFront(pair)
+ }
+
+ local := genpeerID()
+ localID := local
+
+ i := rand.Intn(len(peers))
+ if !b.Has(peers[i].Id) {
+ t.Errorf("Failed to find peer: %v", peers[i])
+ }
+
+ spl := b.Split(0, local.Id)
+ llist := b.list
+ for e := llist.Front(); e != nil; e = e.Next() {
+ p := e.Value.(common.PeerIDAddressPair)
+ cpl := common.CommonPrefixLen(p.ID, localID.Id)
+ if cpl > 0 {
+ t.Fatalf("Split failed. found id with cpl > 0 in 0 bucket")
+ }
+ }
+
+ rlist := spl.list
+ for e := rlist.Front(); e != nil; e = e.Next() {
+ p := e.Value.(common.PeerIDAddressPair)
+ cpl := common.CommonPrefixLen(p.ID, localID.Id)
+ if cpl == 0 {
+ t.Fatalf("Split failed. found id with cpl == 0 in non 0 bucket")
+ }
+ }
+}
+
+func TestRefreshAndGetTrackedCpls(t *testing.T) {
+ t.Parallel()
+
+ local := genpeerID()
+ rt := NewRoutingTable(1, local.Id)
+
+ // add cpl's for tracking
+ for cpl := uint(0); cpl < maxCplForRefresh; cpl++ {
+ peerID := rt.GenRandKadId(cpl)
+ rt.ResetCplRefreshedAtForID(peerID, time.Now())
+ }
+
+ // fetch cpl's
+ trackedCpls := rt.GetTrackedCplsForRefresh()
+ require.Len(t, trackedCpls, int(maxCplForRefresh))
+ actualCpls := make(map[uint]struct{})
+ for i := 0; i < len(trackedCpls); i++ {
+ actualCpls[trackedCpls[i].Cpl] = struct{}{}
+ }
+
+ for i := uint(0); i < maxCplForRefresh; i++ {
+ _, ok := actualCpls[i]
+ require.True(t, ok, "tracked cpl's should have cpl %d", i)
+ }
+}
+
+func TestGenRandKadID(t *testing.T) {
+ local := genpeerID()
+ ranid := local.Id.GenRandPeerId(1)
+ cpl := common.CommonPrefixLen(local.Id, ranid)
+ t.Log(cpl)
+}
+
+func TestGenRandPeerID(t *testing.T) {
+ t.Parallel()
+
+ local := genpeerID()
+ rt := NewRoutingTable(1, local.Id)
+
+ // test generate rand peer ID
+ for cpl := uint(0); cpl <= maxCplForRefresh; cpl++ {
+ for i := 0; i < 10000; i++ {
+ peerID := rt.GenRandKadId(cpl)
+
+ require.True(t, uint(common.CommonPrefixLen(peerID, rt.local)) == cpl, "failed for cpl=%d", cpl)
+ }
+ }
+}
+
+func TestTableCallbacks(t *testing.T) {
+ t.Parallel()
+
+ local := genpeerID()
+ rt := NewRoutingTable(10, local.Id)
+
+ peers := make([]*common.PeerKeyId, 100)
+ for i := 0; i < 100; i++ {
+ peers[i] = genpeerID()
+ }
+
+ pset := make(map[common.PeerId]struct{})
+ rt.PeerAdded = func(p common.PeerId) {
+ pset[p] = struct{}{}
+ }
+ rt.PeerRemoved = func(p common.PeerId) {
+ delete(pset, p)
+ }
+
+ rt.Update(peers[0].Id, "0.0.0.0")
+ if _, ok := pset[peers[0].Id]; !ok {
+ t.Fatal("should have this peer")
+ }
+
+ rt.Remove(peers[0].Id)
+ if _, ok := pset[peers[0].Id]; ok {
+ t.Fatal("should not have this peer")
+ }
+
+ for _, p := range peers {
+ rt.Update(p.Id, "0.0.0.0")
+ }
+
+ out := rt.ListPeers()
+ for _, outp := range out {
+ if _, ok := pset[outp.ID]; !ok {
+ t.Fatal("should have peer in the peerset")
+ }
+ delete(pset, outp.ID)
+ }
+
+ if len(pset) > 0 {
+ t.Fatal("have peers in peerset that were not in the table", len(pset))
+ }
+}
+
+// Right now, this just makes sure that it doesnt hang or crash
+func TestTableUpdate(t *testing.T) {
+ t.Parallel()
+
+ local := genpeerID()
+ rt := NewRoutingTable(10, local.Id)
+
+ peers := make([]*common.PeerKeyId, 100)
+ for i := 0; i < 100; i++ {
+ peers[i] = genpeerID()
+ }
+
+ // Testing Update
+ for i := 0; i < 10000; i++ {
+ rt.Update(peers[rand.Intn(len(peers))].Id, "127.0.0.1")
+ }
+
+ for i := 0; i < 100; i++ {
+ id := genpeerID()
+ ret := rt.NearestPeers(id.Id, 5)
+ if len(ret) == 0 {
+ t.Fatal("Failed to find node near ID.")
+ }
+ }
+}
+
+func TestTableFind(t *testing.T) {
+ t.Parallel()
+
+ local := genpeerID()
+ rt := NewRoutingTable(10, local.Id)
+
+ peers := make([]*common.PeerKeyId, 100)
+ for i := 0; i < 5; i++ {
+ peers[i] = genpeerID()
+ rt.Update(peers[i].Id, "127.0.0.1")
+ }
+
+ t.Logf("Searching for peer: '%s'", peers[2].Id.ToHexString())
+ found := rt.NearestPeers(peers[2].Id, 3)
+ if !(found[0].ID == peers[2].Id) {
+ t.Fatalf("Failed to lookup known node...")
+ }
+}
+
+func TestTableEldestPreferred(t *testing.T) {
+ t.Parallel()
+
+ local := genpeerID()
+ rt := NewRoutingTable(10, local.Id)
+
+ // generate size + 1 peers to saturate a bucket
+ peers := make([]*common.PeerKeyId, 15)
+ for i := 0; i < 15; {
+ if p := genpeerID(); common.CommonPrefixLen(local.Id, p.Id) == 0 {
+ peers[i] = p
+ i++
+ }
+ }
+
+ // test 10 first peers are accepted.
+ for _, p := range peers[:10] {
+ if err := rt.Update(p.Id, "127.0.0.1"); err != nil {
+ t.Errorf("expected all 10 peers to be accepted; instead got: %v", err)
+ }
+ }
+
+ // test next 5 peers are rejected.
+ for _, p := range peers[10:] {
+ err := rt.Update(p.Id, "127.0.0.1")
+ if err != ErrPeerRejectedNoCapacity {
+ t.Errorf("expected extra 5 peers to be rejected; instead got: %v", err)
+ }
+ if len(rt.Buckets) != 2 {
+ t.Fatalf("buket size not OK")
+ }
+ }
+}
+
+func TestTableFindMultiple(t *testing.T) {
+ t.Parallel()
+
+ local := genpeerID()
+ rt := NewRoutingTable(20, local.Id)
+
+ peers := make([]*common.PeerKeyId, 100)
+ for i := 0; i < 18; i++ {
+ peers[i] = genpeerID()
+ rt.Update(peers[i].Id, "127.0.0.1")
+ }
+
+ // put in one bucket
+ t.Logf("Searching for peer: '%s'", peers[2].Id.ToHexString())
+ found := rt.NearestPeers(peers[2].Id, 15)
+ if len(found) != 15 {
+ t.Fatalf("Got back different number of peers than we expected.")
+ }
+}
+
+func assertSortedPeerIdsEqual(t *testing.T, a, b []common.PeerIDAddressPair) {
+ t.Helper()
+ if len(a) != len(b) {
+ t.Fatal("slices aren't the same length")
+ }
+ for i, p := range a {
+ if p != b[i] {
+ t.Fatalf("unexpected peer %d", i)
+ }
+ }
+}
+
+// Sort the given peers by their ascending distance from the target. A new slice is returned.
+func SortClosestPeers(peers []common.PeerIDAddressPair, target common.PeerId) []common.PeerIDAddressPair {
+ sorter := peerDistanceSorter{
+ peers: make([]peerDistance, 0, len(peers)),
+ target: target,
+ }
+ for _, p := range peers {
+ sorter.appendPeer(p)
+ }
+ sorter.sort()
+ out := make([]common.PeerIDAddressPair, 0, sorter.Len())
+ for _, p := range sorter.peers {
+ out = append(out, p.p)
+ }
+ return out
+}
+
+func TestTableFindMultipleBuckets(t *testing.T) {
+ t.Parallel()
+
+ local := genpeerID()
+ localID := local
+
+ rt := NewRoutingTable(5, local.Id)
+
+ peers := make([]*common.PeerKeyId, 100)
+ for i := 0; i < 100; i++ {
+ peers[i] = genpeerID()
+ rt.Update(peers[i].Id, "127.0.0.1")
+ }
+
+ targetID := peers[2]
+
+ closest := SortClosestPeers(rt.ListPeers(), targetID.Id)
+ require.Equal(t, closest[0].ID, peers[2].Id, "first one should be the 0 distance with local")
+
+ targetCpl := common.CommonPrefixLen(localID.Id, targetID.Id)
+
+ // Split the peers into closer, same, and further from the key (than us).
+ var (
+ closer, same, further []common.PeerIDAddressPair
+ )
+ var i int
+ for i = 0; i < len(closest); i++ {
+ cpl := common.CommonPrefixLen(closest[i].ID, targetID.Id)
+ if targetCpl >= cpl {
+ break
+ }
+ }
+ closer = closest[:i]
+
+ var j int
+ for j = len(closer); j < len(closest); j++ {
+ cpl := common.CommonPrefixLen(closest[j].ID, targetID.Id)
+ if targetCpl > cpl {
+ break
+ }
+ }
+ same = closest[i:j]
+ further = closest[j:]
+
+ // should be able to find at least 30
+ // ~31 (logtwo(100) * 5)
+ found := rt.NearestPeers(targetID.Id, 20)
+ if len(found) != 20 {
+ t.Fatalf("asked for 20 peers, got %d", len(found))
+ }
+
+ // We expect this list to include:
+ // * All peers with a common prefix length > than the CPL between our key
+ // and the target (peers in the target bucket).
+ // * Then a subset of the peers with the _same_ CPL (peers in buckets to the right).
+ // * Finally, other peers to the left of the target bucket.
+
+ // First, handle the peers that are _closer_ than us.
+ if len(found) <= len(closer) {
+ // All of our peers are "closer".
+ assertSortedPeerIdsEqual(t, found, closer[:len(found)])
+ return
+ }
+ assertSortedPeerIdsEqual(t, found[:len(closer)], closer)
+
+ // We've worked through closer peers, let's work through peers at the
+ // "same" distance.
+ found = found[len(closer):]
+
+ // Next, handle peers that are at the same distance as us.
+ if len(found) <= len(same) {
+ // Ok, all the remaining peers are at the same distance.
+ // unfortunately, that means we may be missing peers that are
+ // technically closer.
+
+ // Make sure all remaining peers are _somewhere_ in the "closer" set.
+ pset := strset.New()
+ for _, p := range same {
+ pset.Add(p.ID.ToHexString())
+ }
+ for _, p := range found {
+ if !pset.Has(p.ID.ToHexString()) {
+ t.Fatalf("unexpected peer %s", p.ID.ToHexString())
+ }
+ }
+ return
+ }
+ assertSortedPeerIdsEqual(t, found[:len(same)], same)
+
+ // We've worked through closer peers, let's work through the further
+ // peers.
+ found = found[len(same):]
+
+ // All _further_ peers will be correctly sorted.
+ assertSortedPeerIdsEqual(t, found, further[:len(found)])
+
+ // Finally, test getting _all_ peers. These should be in the correct
+ // order.
+
+ // Ok, now let's try finding all of them.
+ found = rt.NearestPeers(peers[2].Id, 100)
+ if len(found) != rt.Size() {
+ t.Fatalf("asked for %d peers, got %d", rt.Size(), len(found))
+ }
+
+ // We should get _all_ peers in sorted order.
+ for i, p := range found {
+ if p != closest[i] {
+ t.Fatalf("unexpected peer %d", i)
+ }
+ }
+}
+
+// Looks for race conditions in table operations. For a more 'certain'
+// test, increase the loop counter from 1000 to a much higher number
+// and set GOMAXPROCS above 1
+func TestTableMultithreaded(t *testing.T) {
+ t.Parallel()
+
+ local := genpeerID()
+ tab := NewRoutingTable(20, local.Id)
+ var peers []*common.PeerKeyId
+ for i := 0; i < 500; i++ {
+ peers = append(peers, genpeerID())
+ }
+
+ done := make(chan struct{})
+ go func() {
+ for i := 0; i < 1000; i++ {
+ n := rand.Intn(len(peers))
+ tab.Update(peers[n].Id, "127.0.0.1")
+ }
+ done <- struct{}{}
+ }()
+
+ go func() {
+ for i := 0; i < 1000; i++ {
+ n := rand.Intn(len(peers))
+ tab.Update(peers[n].Id, "127.0.0.1")
+ }
+ done <- struct{}{}
+ }()
+
+ go func() {
+ for i := 0; i < 1000; i++ {
+ n := rand.Intn(len(peers))
+ tab.Find(peers[n].Id)
+ }
+ done <- struct{}{}
+ }()
+ <-done
+ <-done
+ <-done
+}
+
+func BenchmarkUpdates(b *testing.B) {
+ b.StopTimer()
+ local := genpeerID()
+ tab := NewRoutingTable(20, local.Id)
+
+ var peers []*common.PeerKeyId
+ for i := 0; i < b.N; i++ {
+ peers = append(peers, genpeerID())
+ }
+
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ tab.Update(peers[i].Id, "127.0.0.1")
+ }
+}
+
+func BenchmarkFinds(b *testing.B) {
+ b.StopTimer()
+ local := genpeerID()
+ tab := NewRoutingTable(20, local.Id)
+
+ var peers []*common.PeerKeyId
+ for i := 0; i < b.N; i++ {
+ peers = append(peers, genpeerID())
+ tab.Update(peers[i].Id, "127.0.0.1")
+ }
+
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ tab.Find(peers[i].Id)
+ }
+}
diff --git a/p2pserver/handshake/handshake.go b/p2pserver/handshake/handshake.go
new file mode 100644
index 0000000..84966ae
--- /dev/null
+++ b/p2pserver/handshake/handshake.go
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package handshake
+
+import (
+ "fmt"
+ "net"
+ "time"
+
+ "github.com/blang/semver"
+ common2 "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/message/types"
+ "github.com/ontio/ontology/p2pserver/peer"
+)
+
+var HANDSHAKE_DURATION = 10 * time.Second // handshake time can not exceed this duration, or will treat as attack.
+
+func HandshakeClient(info *peer.PeerInfo, selfId *common.PeerKeyId, conn net.Conn) (*peer.PeerInfo, error) {
+ version := newVersion(info)
+ if err := conn.SetDeadline(time.Now().Add(HANDSHAKE_DURATION)); err != nil {
+ return nil, err
+ }
+ defer func() {
+ _ = conn.SetDeadline(time.Time{}) //reset back
+ }()
+
+ // 1. sendMsg version
+ err := sendMsg(conn, version)
+ if err != nil {
+ return nil, err
+ }
+
+ // 2. read version
+ msg, _, err := types.ReadMessage(conn)
+ if err != nil {
+ return nil, err
+ }
+ receivedVersion, ok := msg.(*types.Version)
+ if !ok {
+ return nil, fmt.Errorf("expected version message, but got message type: %s", msg.CmdType())
+ }
+
+ // 3. update kadId
+ kid := common.PseudoPeerIdFromUint64(receivedVersion.P.Nonce)
+ if useDHT(receivedVersion.P.SoftVersion, info.SoftVersion) {
+ err = sendMsg(conn, &types.UpdatePeerKeyId{KadKeyId: selfId})
+ if err != nil {
+ return nil, err
+ }
+ // 4. read kadkeyid
+ msg, _, err = types.ReadMessage(conn)
+ if err != nil {
+ return nil, err
+ }
+ kadKeyId, ok := msg.(*types.UpdatePeerKeyId)
+ if !ok {
+ return nil, fmt.Errorf("handshake failed, expect kad id message, got %s", msg.CmdType())
+ }
+
+ kid = kadKeyId.KadKeyId.Id
+ }
+
+ // 5. sendMsg ack
+ err = sendMsg(conn, &types.VerACK{})
+ if err != nil {
+ return nil, err
+ }
+
+ msg, _, err = types.ReadMessage(conn)
+ if err != nil {
+ return nil, err
+ }
+
+ // 6. receive verack
+ if _, ok := msg.(*types.VerACK); !ok {
+ return nil, fmt.Errorf("handshake failed, expect verack message, got %s", msg.CmdType())
+ }
+
+ return createPeerInfo(receivedVersion, kid, conn.RemoteAddr().String()), nil
+}
+
+func HandshakeServer(info *peer.PeerInfo, selfId *common.PeerKeyId, conn net.Conn) (*peer.PeerInfo, error) {
+ ver := newVersion(info)
+ if err := conn.SetDeadline(time.Now().Add(HANDSHAKE_DURATION)); err != nil {
+ return nil, err
+ }
+ defer func() {
+ _ = conn.SetDeadline(time.Time{}) //reset back
+ }()
+
+ // 1. read version
+ msg, _, err := types.ReadMessage(conn)
+ if err != nil {
+ return nil, fmt.Errorf("[HandshakeServer] ReadMessage failed, error: %s", err)
+ }
+ if msg.CmdType() != common.VERSION_TYPE {
+ return nil, fmt.Errorf("[HandshakeServer] expected version message")
+ }
+ version := msg.(*types.Version)
+
+ // 2. sendMsg version
+ err = sendMsg(conn, ver)
+ if err != nil {
+ return nil, err
+ }
+
+ // 3. read update kadkey id
+ kid := common.PseudoPeerIdFromUint64(version.P.Nonce)
+ if useDHT(version.P.SoftVersion, info.SoftVersion) {
+ msg, _, err := types.ReadMessage(conn)
+ if err != nil {
+ return nil, fmt.Errorf("[HandshakeServer] ReadMessage failed, error: %s", err)
+ }
+ kadkeyId, ok := msg.(*types.UpdatePeerKeyId)
+ if !ok {
+ return nil, fmt.Errorf("[HandshakeServer] expected update kadkeyid message")
+ }
+ kid = kadkeyId.KadKeyId.Id
+ // 4. sendMsg update kadkey id
+ err = sendMsg(conn, &types.UpdatePeerKeyId{KadKeyId: selfId})
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // 5. read version ack
+ msg, _, err = types.ReadMessage(conn)
+ if err != nil {
+ return nil, fmt.Errorf("[HandshakeServer] ReadMessage failed, error: %s", err)
+ }
+ if msg.CmdType() != common.VERACK_TYPE {
+ return nil, fmt.Errorf("[HandshakeServer] expected version ack message")
+ }
+
+ // 6. sendMsg ack
+ err = sendMsg(conn, &types.VerACK{})
+ if err != nil {
+ return nil, err
+ }
+
+ return createPeerInfo(version, kid, conn.RemoteAddr().String()), nil
+}
+
+func sendMsg(conn net.Conn, msg types.Message) error {
+ sink := common2.NewZeroCopySink(nil)
+ types.WriteMessage(sink, msg)
+ _, err := conn.Write(sink.Bytes())
+ if err != nil {
+ return fmt.Errorf("[handshake]error sending messge to %s :%s", conn.RemoteAddr().String(), err.Error())
+ }
+
+ return nil
+}
+
+func createPeerInfo(version *types.Version, kid common.PeerId, addr string) *peer.PeerInfo {
+ return peer.NewPeerInfo(kid, version.P.Version, version.P.Services, version.P.Relay != 0, version.P.HttpInfoPort,
+ version.P.SyncPort, version.P.StartHeight, version.P.SoftVersion, addr)
+}
+
+func newVersion(peerInfo *peer.PeerInfo) *types.Version {
+ var version types.Version
+ version.P = types.VersionPayload{
+ Version: peerInfo.Version,
+ Services: peerInfo.Services,
+ SyncPort: peerInfo.Port,
+ Nonce: peerInfo.Id.ToUint64(),
+ IsConsensus: false,
+ HttpInfoPort: peerInfo.HttpInfoPort,
+ StartHeight: peerInfo.Height,
+ TimeStamp: time.Now().UnixNano(),
+ SoftVersion: peerInfo.SoftVersion,
+ }
+
+ if peerInfo.Relay {
+ version.P.Relay = 1
+ } else {
+ version.P.Relay = 0
+ }
+ if peerInfo.HttpInfoPort > 0 {
+ version.P.Cap[common.HTTP_INFO_FLAG] = 0x01
+ } else {
+ version.P.Cap[common.HTTP_INFO_FLAG] = 0x00
+ }
+
+ return &version
+}
+
+func useDHT(client, server string) bool {
+ // we make this symmetric, because config.Version is depend on compile option, so to avoid the case:
+ // remote version is 1.9.0 and we support DHT, but the config.Version is not valid.
+ // remote will decide to not use DHT, but we will decide to use DHT, lead to handshake failure.
+ return supportDHT(client) && supportDHT(server)
+}
+
+func supportDHT(version string) bool {
+ if version == "" {
+ return false
+ }
+ v1, err := semver.ParseTolerant(version)
+ if err != nil {
+ return false
+ }
+ min, err := semver.ParseTolerant(common.MIN_VERSION_FOR_DHT)
+ if err != nil {
+ panic(err) // enforced by testcase
+ }
+
+ return v1.GTE(min)
+}
diff --git a/p2pserver/handshake/handshake_test.go b/p2pserver/handshake/handshake_test.go
new file mode 100644
index 0000000..d24b26d
--- /dev/null
+++ b/p2pserver/handshake/handshake_test.go
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package handshake
+
+import (
+ "math/rand"
+ "net"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/message/types"
+ "github.com/ontio/ontology/p2pserver/peer"
+ "github.com/stretchr/testify/assert"
+)
+
+func init() {
+ common.Difficulty = 1
+ HANDSHAKE_DURATION = 1 * time.Second
+}
+
+type Node struct {
+ Id *common.PeerKeyId
+ Info *peer.PeerInfo
+ Conn net.Conn
+}
+
+func NewNode(conn net.Conn) Node {
+ node := Node{
+ Id: common.RandPeerKeyId(),
+ Info: &peer.PeerInfo{},
+ Conn: conn,
+ }
+ node.Info.Id = node.Id.Id
+ node.Info.SoftVersion = "v1.9.0-beta"
+
+ return node
+}
+
+func NewPair() (client Node, server Node) {
+ c, s := net.Pipe()
+
+ client = NewNode(c)
+ server = NewNode(s)
+ return
+}
+
+func TestHandshakeNormal(t *testing.T) {
+ client, server := NewPair()
+ versions := []string{"v1.8.0", "v1.7.0", "v1.9.0", "v1.9.0-beta", "v1.20"}
+
+ for i := 0; i < 100; i++ {
+ client.Info.SoftVersion = versions[rand.Intn(len(versions))]
+ server.Info.SoftVersion = versions[rand.Intn(len(versions))]
+
+ wg := sync.WaitGroup{}
+ wg.Add(2)
+ result := make([]struct {
+ info [2]*peer.PeerInfo
+ err error
+ }, 2)
+ go func() {
+ info, err := HandshakeClient(client.Info, client.Id, client.Conn)
+ result[0].err = err
+ result[0].info = [2]*peer.PeerInfo{info, server.Info}
+ wg.Done()
+ }()
+ go func() {
+ info, err := HandshakeServer(server.Info, server.Id, server.Conn)
+ result[1].err = err
+ result[1].info = [2]*peer.PeerInfo{info, client.Info}
+ wg.Done()
+ }()
+ wg.Wait()
+
+ for _, res := range result {
+ assert.Nil(t, res.err)
+ assert.Equal(t, res.info[0].Id.ToUint64(), res.info[1].Id.ToUint64())
+ }
+ }
+}
+
+func TestHandshakeTimeout(t *testing.T) {
+ client, _ := NewPair()
+
+ _, err := HandshakeClient(client.Info, client.Id, client.Conn)
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "deadline exceeded")
+}
+
+func TestHandshakeWrongMsg(t *testing.T) {
+ client, server := NewPair()
+ go func() {
+ err := sendMsg(client.Conn, &types.Addr{})
+ assert.Nil(t, err)
+ }()
+
+ _, err := HandshakeServer(server.Info, server.Id, server.Conn)
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "expected version message")
+}
+
+func TestVersion(t *testing.T) {
+ assert.True(t, supportDHT(common.MIN_VERSION_FOR_DHT))
+ assert.True(t, supportDHT("1.9.1"))
+ assert.True(t, supportDHT("v1.10.0"))
+ assert.True(t, supportDHT("v1.10"))
+ assert.True(t, supportDHT("v2.0"))
+ assert.True(t, supportDHT("v1.9.1"))
+ assert.True(t, supportDHT("1.9.1-beta"))
+ assert.True(t, supportDHT("v1.9.1-beta"))
+ assert.True(t, supportDHT("1.9.1-beta-9"))
+ assert.True(t, supportDHT("1.9.1-beta-9-geeaeewwf"))
+
+ assert.False(t, supportDHT("1.9.1-alpha"))
+ assert.False(t, supportDHT("1.8.0-beta-9-geeaeewwf"))
+ assert.False(t, supportDHT("1.8.0"))
+}
diff --git a/p2pserver/link/link.go b/p2pserver/link/link.go
new file mode 100644
index 0000000..8e67ded
--- /dev/null
+++ b/p2pserver/link/link.go
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package link
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "net"
+ "time"
+
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/message/types"
+)
+
+//Link used to establish
+type Link struct {
+ id common.PeerId
+ addr string // The address of the node
+ conn net.Conn // Connect socket with the peer node
+ time time.Time // The latest time the node activity
+ recvChan chan *types.MsgPayload //msgpayload channel
+ reqRecord map[string]int64 //Map RequestId to Timestamp, using for rejecting duplicate request in specific time
+}
+
+func NewLink() *Link {
+ link := &Link{
+ reqRecord: make(map[string]int64),
+ }
+ return link
+}
+
+//SetID set peer id to link
+func (this *Link) SetID(id common.PeerId) {
+ this.id = id
+}
+
+//GetID return if from peer
+func (this *Link) GetID() common.PeerId {
+ return this.id
+}
+
+//If there is connection return true
+func (this *Link) Valid() bool {
+ return this.conn != nil
+}
+
+//set message channel for link layer
+func (this *Link) SetChan(msgchan chan *types.MsgPayload) {
+ this.recvChan = msgchan
+}
+
+//get address
+func (this *Link) GetAddr() string {
+ return this.addr
+}
+
+//set address
+func (this *Link) SetAddr(addr string) {
+ this.addr = addr
+}
+
+//get connection
+func (this *Link) GetConn() net.Conn {
+ return this.conn
+}
+
+//set connection
+func (this *Link) SetConn(conn net.Conn) {
+ this.conn = conn
+}
+
+//record latest message time
+func (this *Link) UpdateRXTime(t time.Time) {
+ this.time = t
+}
+
+//GetRXTime return the latest message time
+func (this *Link) GetRXTime() time.Time {
+ return this.time
+}
+
+func (this *Link) Rx() {
+ conn := this.conn
+ if conn == nil {
+ return
+ }
+
+ reader := bufio.NewReaderSize(conn, common.MAX_BUF_LEN)
+
+ for {
+ msg, payloadSize, err := types.ReadMessage(reader)
+ if err != nil {
+ log.Infof("[p2p]error read from %s :%s", this.GetAddr(), err.Error())
+ break
+ }
+
+ t := time.Now()
+ this.UpdateRXTime(t)
+
+ if !this.needSendMsg(msg) {
+ log.Debugf("skip handle msgType:%s from:%d", msg.CmdType(), this.id)
+ continue
+ }
+
+ this.addReqRecord(msg)
+ this.recvChan <- &types.MsgPayload{
+ Id: this.id,
+ Addr: this.addr,
+ PayloadSize: payloadSize,
+ Payload: msg,
+ }
+
+ }
+
+ this.CloseConn()
+}
+
+//close connection
+func (this *Link) CloseConn() {
+ if this.conn != nil {
+ this.conn.Close()
+ this.conn = nil
+ }
+}
+
+func (this *Link) Send(msg types.Message) error {
+ sink := comm.NewZeroCopySink(nil)
+ types.WriteMessage(sink, msg)
+
+ return this.SendRaw(sink.Bytes())
+}
+
+func (this *Link) SendRaw(rawPacket []byte) error {
+ conn := this.conn
+ if conn == nil {
+ return errors.New("[p2p]tx link invalid")
+ }
+ nByteCnt := len(rawPacket)
+ log.Tracef("[p2p]TX buf length: %d\n", nByteCnt)
+
+ nCount := nByteCnt / common.PER_SEND_LEN
+ if nCount == 0 {
+ nCount = 1
+ }
+ _ = conn.SetWriteDeadline(time.Now().Add(time.Duration(nCount*common.WRITE_DEADLINE) * time.Second))
+ _, err := conn.Write(rawPacket)
+ if err != nil {
+ log.Infof("[p2p] error sending messge to %s :%s", this.GetAddr(), err.Error())
+ this.CloseConn()
+ return err
+ }
+
+ return nil
+}
+
+//needSendMsg check whether the msg is needed to push to channel
+func (this *Link) needSendMsg(msg types.Message) bool {
+ if msg.CmdType() != common.GET_DATA_TYPE {
+ return true
+ }
+ var dataReq = msg.(*types.DataReq)
+ reqID := fmt.Sprintf("%x%s", dataReq.DataType, dataReq.Hash.ToHexString())
+ now := time.Now().Unix()
+
+ if t, ok := this.reqRecord[reqID]; ok {
+ if int(now-t) < common.REQ_INTERVAL {
+ return false
+ }
+ }
+ return true
+}
+
+//addReqRecord add request record by removing outdated request records
+func (this *Link) addReqRecord(msg types.Message) {
+ if msg.CmdType() != common.GET_DATA_TYPE {
+ return
+ }
+ now := time.Now().Unix()
+ if len(this.reqRecord) >= common.MAX_REQ_RECORD_SIZE-1 {
+ for id := range this.reqRecord {
+ t := this.reqRecord[id]
+ if int(now-t) > common.REQ_INTERVAL {
+ delete(this.reqRecord, id)
+ }
+ }
+ }
+ var dataReq = msg.(*types.DataReq)
+ reqID := fmt.Sprintf("%x%s", dataReq.DataType, dataReq.Hash.ToHexString())
+ this.reqRecord[reqID] = now
+}
diff --git a/p2pserver/link/link_test.go b/p2pserver/link/link_test.go
new file mode 100644
index 0000000..a8475e5
--- /dev/null
+++ b/p2pserver/link/link_test.go
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package link
+
+import (
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/ontio/ontology-crypto/keypair"
+ "github.com/ontio/ontology/account"
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/log"
+ ct "github.com/ontio/ontology/core/types"
+ "github.com/ontio/ontology/p2pserver/common"
+ mt "github.com/ontio/ontology/p2pserver/message/types"
+)
+
+var (
+ cliLink *Link
+ serverLink *Link
+ cliChan chan *mt.MsgPayload
+ serverChan chan *mt.MsgPayload
+ cliAddr string
+ serAddr string
+)
+
+func init() {
+ log.InitLog(log.InfoLog, log.Stdout)
+
+ cliLink = NewLink()
+ serverLink = NewLink()
+ id := common.PseudoPeerIdFromUint64(0x733936)
+ cliLink.SetID(id)
+ id2 := common.PseudoPeerIdFromUint64(0x8274950)
+ serverLink.SetID(id2)
+
+ cliChan = make(chan *mt.MsgPayload, 100)
+ serverChan = make(chan *mt.MsgPayload, 100)
+ //listen ip addr
+ cliAddr = "127.0.0.1:50338"
+ serAddr = "127.0.0.1:50339"
+}
+
+func TestNewLink(t *testing.T) {
+ id := 0x74936295
+
+ if cliLink.GetID().ToUint64() != 0x733936 {
+ t.Fatal("link GetID failed")
+ }
+ i := common.PseudoPeerIdFromUint64(uint64(id))
+ cliLink.SetID(i)
+ if cliLink.GetID().ToUint64() != uint64(id) {
+ t.Fatal("link SetID failed")
+ }
+
+ cliLink.SetChan(cliChan)
+ serverLink.SetChan(serverChan)
+
+ cliLink.UpdateRXTime(time.Now())
+
+ msg := &mt.MsgPayload{
+ Id: cliLink.GetID(),
+ Addr: cliLink.GetAddr(),
+ Payload: &mt.NotFound{comm.UINT256_EMPTY},
+ }
+ go func() {
+ time.Sleep(5000000)
+ cliChan <- msg
+ }()
+
+ timeout := time.NewTimer(time.Second)
+ select {
+ case <-cliChan:
+ t.Log("read data from channel")
+ case <-timeout.C:
+ timeout.Stop()
+ t.Fatal("can`t read data from link channel")
+ }
+
+}
+
+func TestUnpackBufNode(t *testing.T) {
+ cliLink.SetChan(cliChan)
+
+ msgType := "block"
+
+ var msg mt.Message
+
+ switch msgType {
+ case "addr":
+ var newaddrs []common.PeerAddr
+ for i := 0; i < 10000000; i++ {
+ idd := common.PseudoPeerIdFromUint64(uint64(i))
+ newaddrs = append(newaddrs, common.PeerAddr{
+ Time: time.Now().Unix(),
+ ID: idd,
+ })
+ }
+ var addr mt.Addr
+ addr.NodeAddrs = newaddrs
+ msg = &addr
+ case "consensuspayload":
+ acct := account.NewAccount("SHA256withECDSA")
+ key := acct.PubKey()
+ payload := mt.ConsensusPayload{
+ Owner: key,
+ }
+ for i := 0; uint32(i) < 200000000; i++ {
+ byteInt := rand.Intn(256)
+ payload.Data = append(payload.Data, byte(byteInt))
+ }
+
+ msg = &mt.Consensus{payload}
+ case "consensus":
+ acct := account.NewAccount("SHA256withECDSA")
+ key := acct.PubKey()
+ payload := &mt.ConsensusPayload{
+ Owner: key,
+ }
+ for i := 0; uint32(i) < 200000000; i++ {
+ byteInt := rand.Intn(256)
+ payload.Data = append(payload.Data, byte(byteInt))
+ }
+ consensus := mt.Consensus{
+ Cons: *payload,
+ }
+ msg = &consensus
+ case "blkheader":
+ var headers []*ct.Header
+ blkHeader := &mt.BlkHeader{}
+ for i := 0; uint32(i) < 100000000; i++ {
+ header := &ct.Header{}
+ header.Height = uint32(i)
+ header.Bookkeepers = make([]keypair.PublicKey, 0)
+ header.SigData = make([][]byte, 0)
+ headers = append(headers, header)
+ }
+ blkHeader.BlkHdr = headers
+ msg = blkHeader
+ case "tx":
+ trn := &mt.Trn{}
+ rawTXBytes, _ := comm.HexToBytes("00d1af758596f401000000000000204e000000000000b09ba6a4fe99eb2b2dc1d86a6d453423a6be03f02e0101011552c1126765744469736b506c61796572734c697374676a6f1082c6cec3a1bcbb5a3892cf770061e4b98200014241015d434467639fd8e7b4331d2f3fc0d4168e2d68a203593c6399f5746d2324217aeeb3db8ff31ba0fdb1b13aa6f4c3cd25f7b3d0d26c144bbd75e2963d0a443629232103fdcae8110c9a60d1fc47f8111a12c1941e1f3584b0b0028157736ed1eecd101eac")
+ tx, _ := ct.TransactionFromRawBytes(rawTXBytes)
+ trn.Txn = tx
+ msg = trn
+ case "block":
+ var blk ct.Block
+ mBlk := &mt.Block{}
+ var txs []*ct.Transaction
+ header := ct.Header{}
+ header.Height = uint32(1)
+ header.Bookkeepers = make([]keypair.PublicKey, 0)
+ header.SigData = make([][]byte, 0)
+ blk.Header = &header
+
+ rawTXBytes, _ := comm.HexToBytes("00d1af758596f401000000000000204e000000000000b09ba6a4fe99eb2b2dc1d86a6d453423a6be03f02e0101011552c1126765744469736b506c61796572734c697374676a6f1082c6cec3a1bcbb5a3892cf770061e4b98200014241015d434467639fd8e7b4331d2f3fc0d4168e2d68a203593c6399f5746d2324217aeeb3db8ff31ba0fdb1b13aa6f4c3cd25f7b3d0d26c144bbd75e2963d0a443629232103fdcae8110c9a60d1fc47f8111a12c1941e1f3584b0b0028157736ed1eecd101eac")
+ tx, _ := ct.TransactionFromRawBytes(rawTXBytes)
+ txs = append(txs, tx)
+
+ blk.Transactions = txs
+ mBlk.Blk = &blk
+
+ msg = mBlk
+ }
+
+ sink := comm.NewZeroCopySink(nil)
+ mt.WriteMessage(sink, msg)
+}
diff --git a/p2pserver/message/msg_pack/msg_pack.go b/p2pserver/message/msg_pack/msg_pack.go
new file mode 100644
index 0000000..c1e760c
--- /dev/null
+++ b/p2pserver/message/msg_pack/msg_pack.go
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package msgpack
+
+import (
+ "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/log"
+ ct "github.com/ontio/ontology/core/types"
+ msgCommon "github.com/ontio/ontology/p2pserver/common"
+ mt "github.com/ontio/ontology/p2pserver/message/types"
+)
+
+//Peer address package
+func NewAddrs(nodeAddrs []msgCommon.PeerAddr) mt.Message {
+ log.Trace()
+ var addr mt.Addr
+ addr.NodeAddrs = nodeAddrs
+
+ return &addr
+}
+
+//Peer address request package
+func NewAddrReq() mt.Message {
+ log.Trace()
+ var msg mt.AddrReq
+ return &msg
+}
+
+///block package
+func NewBlock(bk *ct.Block, ccMsg *ct.CrossChainMsg, merkleRoot common.Uint256) mt.Message {
+ log.Trace()
+ var blk mt.Block
+ blk.Blk = bk
+ blk.MerkleRoot = merkleRoot
+ blk.CCMsg = ccMsg
+
+ return &blk
+}
+
+//blk hdr package
+func NewHeaders(headers []*ct.RawHeader) mt.Message {
+ log.Trace()
+ var blkHdr mt.RawBlockHeader
+ blkHdr.BlkHdr = headers
+
+ return &blkHdr
+}
+
+//blk hdr req package
+func NewHeadersReq(curHdrHash common.Uint256) mt.Message {
+ log.Trace()
+ var h mt.HeadersReq
+ h.Len = 1
+ h.HashEnd = curHdrHash
+
+ return &h
+}
+
+////Consensus info package
+func NewConsensus(cp *mt.ConsensusPayload) mt.Message {
+ log.Trace()
+ var cons mt.Consensus
+ cons.Cons = *cp
+
+ return &cons
+}
+
+//InvPayload
+func NewInvPayload(invType common.InventoryType, msg []common.Uint256) *mt.InvPayload {
+ log.Trace()
+ return &mt.InvPayload{
+ InvType: invType,
+ Blk: msg,
+ }
+}
+
+//Inv request package
+func NewInv(invPayload *mt.InvPayload) mt.Message {
+ log.Trace()
+ var inv mt.Inv
+ inv.P.Blk = invPayload.Blk
+ inv.P.InvType = invPayload.InvType
+
+ return &inv
+}
+
+//NotFound package
+func NewNotFound(hash common.Uint256) mt.Message {
+ log.Trace()
+ var notFound mt.NotFound
+ notFound.Hash = hash
+
+ return ¬Found
+}
+
+//ping msg package
+func NewPingMsg(height uint64) *mt.Ping {
+ log.Trace()
+ var ping mt.Ping
+ ping.Height = uint64(height)
+
+ return &ping
+}
+
+//pong msg package
+func NewPongMsg(height uint64) *mt.Pong {
+ log.Trace()
+ var pong mt.Pong
+ pong.Height = uint64(height)
+
+ return &pong
+}
+
+//Transaction package
+func NewTxn(txn *ct.Transaction) mt.Message {
+ log.Trace()
+ var trn mt.Trn
+ trn.Txn = txn
+
+ return &trn
+}
+
+//transaction request package
+func NewTxnDataReq(hash common.Uint256) mt.Message {
+ log.Trace()
+ var dataReq mt.DataReq
+ dataReq.DataType = common.TRANSACTION
+ dataReq.Hash = hash
+
+ return &dataReq
+}
+
+//block request package
+func NewBlkDataReq(hash common.Uint256) mt.Message {
+ log.Trace()
+ var dataReq mt.DataReq
+ dataReq.DataType = common.BLOCK
+ dataReq.Hash = hash
+
+ return &dataReq
+}
+
+//consensus request package
+func NewConsensusDataReq(hash common.Uint256) mt.Message {
+ log.Trace()
+ var dataReq mt.DataReq
+ dataReq.DataType = common.CONSENSUS
+ dataReq.Hash = hash
+
+ return &dataReq
+}
+
+func NewFindNodeReq(id msgCommon.PeerId) mt.Message {
+ req := mt.FindNodeReq{
+ TargetID: id,
+ }
+
+ return &req
+}
diff --git a/p2pserver/message/types/address.go b/p2pserver/message/types/address.go
new file mode 100644
index 0000000..134ed16
--- /dev/null
+++ b/p2pserver/message/types/address.go
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ "github.com/ontio/ontology/common"
+ comm "github.com/ontio/ontology/p2pserver/common"
+)
+
+type Addr struct {
+ NodeAddrs []comm.PeerAddr
+}
+
+//Serialize message payload
+func (this Addr) Serialization(sink *common.ZeroCopySink) {
+ num := uint64(len(this.NodeAddrs))
+ sink.WriteUint64(num)
+
+ for _, addr := range this.NodeAddrs {
+ sink.WriteInt64(addr.Time)
+ sink.WriteUint64(addr.Services)
+ sink.WriteBytes(addr.IpAddr[:])
+ sink.WriteUint16(addr.Port)
+ sink.WriteUint16(addr.ConsensusPort)
+ sink.WriteUint64(addr.ID.ToUint64())
+ }
+}
+
+func (this *Addr) CmdType() string {
+ return comm.ADDR_TYPE
+}
+
+func (this *Addr) Deserialization(source *common.ZeroCopySource) error {
+ count, eof := source.NextUint64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ for i := 0; i < int(count); i++ {
+ var addr comm.PeerAddr
+ addr.Time, eof = source.NextInt64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ addr.Services, eof = source.NextUint64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ buf, _ := source.NextBytes(uint64(len(addr.IpAddr[:])))
+ copy(addr.IpAddr[:], buf)
+ addr.Port, eof = source.NextUint16()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ addr.ConsensusPort, eof = source.NextUint16()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ id, eof := source.NextUint64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ addr.ID = comm.PseudoPeerIdFromUint64(id)
+
+ this.NodeAddrs = append(this.NodeAddrs, addr)
+ }
+
+ if count > comm.MAX_ADDR_NODE_CNT {
+ count = comm.MAX_ADDR_NODE_CNT
+ }
+ this.NodeAddrs = this.NodeAddrs[:count]
+
+ return nil
+}
diff --git a/p2pserver/message/types/address_req.go b/p2pserver/message/types/address_req.go
new file mode 100644
index 0000000..5385ab7
--- /dev/null
+++ b/p2pserver/message/types/address_req.go
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "github.com/ontio/ontology/common"
+ comm "github.com/ontio/ontology/p2pserver/common"
+)
+
+type AddrReq struct{}
+
+//Serialize message payload
+func (this AddrReq) Serialization(sink *common.ZeroCopySink) {
+}
+
+func (this *AddrReq) CmdType() string {
+ return comm.GetADDR_TYPE
+}
+
+//Deserialize message payload
+func (this *AddrReq) Deserialization(source *common.ZeroCopySource) error {
+ return nil
+}
diff --git a/p2pserver/message/types/address_req_test.go b/p2pserver/message/types/address_req_test.go
new file mode 100644
index 0000000..6991bcc
--- /dev/null
+++ b/p2pserver/message/types/address_req_test.go
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "testing"
+)
+
+func TestAddrReqSerializationDeserialization(t *testing.T) {
+ var msg AddrReq
+
+ MessageTest(t, &msg)
+}
diff --git a/p2pserver/message/types/address_test.go b/p2pserver/message/types/address_test.go
new file mode 100644
index 0000000..2f87287
--- /dev/null
+++ b/p2pserver/message/types/address_test.go
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "bytes"
+ "net"
+ "testing"
+
+ "github.com/ontio/ontology/common"
+ comm "github.com/ontio/ontology/p2pserver/common"
+ "github.com/stretchr/testify/assert"
+)
+
+func MessageTest(t *testing.T, msg Message) {
+ sink := common.NewZeroCopySink(nil)
+ WriteMessage(sink, msg)
+
+ demsg, _, err := ReadMessage(bytes.NewBuffer(sink.Bytes()))
+ assert.Nil(t, err)
+
+ assert.Equal(t, msg, demsg)
+}
+
+func TestAddressSerializationDeserialization(t *testing.T) {
+ var msg Addr
+ var addr [16]byte
+ ip := net.ParseIP("192.168.0.1")
+ ip.To16()
+ copy(addr[:], ip[:16])
+ id := comm.PseudoPeerIdFromUint64(987654321)
+ nodeAddr := comm.PeerAddr{
+ Time: 12345678,
+ Services: 100,
+ IpAddr: addr,
+ Port: 8080,
+ ConsensusPort: 8081,
+ ID: id,
+ }
+ msg.NodeAddrs = append(msg.NodeAddrs, nodeAddr)
+
+ MessageTest(t, &msg)
+}
diff --git a/p2pserver/message/types/block.go b/p2pserver/message/types/block.go
new file mode 100644
index 0000000..4534318
--- /dev/null
+++ b/p2pserver/message/types/block.go
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "fmt"
+
+ "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/core/types"
+ comm "github.com/ontio/ontology/p2pserver/common"
+)
+
+type Block struct {
+ Blk *types.Block
+ MerkleRoot common.Uint256
+ CCMsg *types.CrossChainMsg
+}
+
+//Serialize message payload
+func (this *Block) Serialization(sink *common.ZeroCopySink) {
+ this.Blk.Serialization(sink)
+ sink.WriteHash(this.MerkleRoot)
+ sink.WriteBool(this.CCMsg != nil)
+ if this.CCMsg != nil {
+ this.CCMsg.Serialization(sink)
+ }
+}
+
+func (this *Block) CmdType() string {
+ return comm.BLOCK_TYPE
+}
+
+//Deserialize message payload
+func (this *Block) Deserialization(source *common.ZeroCopySource) error {
+ this.Blk = new(types.Block)
+ err := this.Blk.Deserialization(source)
+ if err != nil {
+ return fmt.Errorf("read Blk error. err:%v", err)
+ }
+ var eof bool
+ this.MerkleRoot, eof = source.NextHash()
+ if eof {
+ // to accept old node's block
+ this.MerkleRoot = common.UINT256_EMPTY
+ }
+ hasCCM, irr, eof := source.NextBool()
+ if irr || eof {
+ // to accept old node's cross msg
+ return nil
+ }
+ var ccMsg *types.CrossChainMsg
+ if hasCCM {
+ ccMsg = new(types.CrossChainMsg)
+ if err := ccMsg.Deserialization(source); err != nil {
+ return err
+ }
+ }
+ this.CCMsg = ccMsg
+
+ return nil
+}
diff --git a/p2pserver/message/types/block_header.go b/p2pserver/message/types/block_header.go
new file mode 100644
index 0000000..45c7bff
--- /dev/null
+++ b/p2pserver/message/types/block_header.go
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/ontio/ontology/common"
+ ct "github.com/ontio/ontology/core/types"
+ comm "github.com/ontio/ontology/p2pserver/common"
+)
+
+type BlkHeader struct {
+ BlkHdr []*ct.Header
+}
+
+type RawBlockHeader struct {
+ BlkHdr []*ct.RawHeader
+}
+
+func (this *RawBlockHeader) Serialization(sink *common.ZeroCopySink) {
+ sink.WriteUint32(uint32(len(this.BlkHdr)))
+
+ for _, header := range this.BlkHdr {
+ header.Serialization(sink)
+ }
+}
+func (this *RawBlockHeader) Deserialization(source *common.ZeroCopySource) error {
+ panic("[block_header] unsupport")
+}
+
+func (this *RawBlockHeader) CmdType() string {
+ return comm.HEADERS_TYPE
+}
+
+//Serialize message payload
+func (this BlkHeader) Serialization(sink *common.ZeroCopySink) {
+ sink.WriteUint32(uint32(len(this.BlkHdr)))
+
+ for _, header := range this.BlkHdr {
+ header.Serialization(sink)
+ }
+}
+
+func (this *BlkHeader) CmdType() string {
+ return comm.HEADERS_TYPE
+}
+
+//Deserialize message payload
+func (this *BlkHeader) Deserialization(source *common.ZeroCopySource) error {
+ var count uint32
+ count, eof := source.NextUint32()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ for i := 0; i < int(count); i++ {
+ var headers ct.Header
+ err := headers.Deserialization(source)
+ if err != nil {
+ return fmt.Errorf("deserialze BlkHeader error: %v", err)
+ }
+ this.BlkHdr = append(this.BlkHdr, &headers)
+ }
+ return nil
+}
diff --git a/p2pserver/message/types/block_headers_req.go b/p2pserver/message/types/block_headers_req.go
new file mode 100644
index 0000000..7946ccf
--- /dev/null
+++ b/p2pserver/message/types/block_headers_req.go
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ "github.com/ontio/ontology/common"
+ comm "github.com/ontio/ontology/p2pserver/common"
+)
+
+type HeadersReq struct {
+ Len uint8
+ HashStart common.Uint256
+ HashEnd common.Uint256
+}
+
+//Serialize message payload
+func (this *HeadersReq) Serialization(sink *common.ZeroCopySink) {
+ sink.WriteUint8(this.Len)
+ sink.WriteHash(this.HashStart)
+ sink.WriteHash(this.HashEnd)
+}
+
+func (this *HeadersReq) CmdType() string {
+ return comm.GET_HEADERS_TYPE
+}
+
+//Deserialize message payload
+func (this *HeadersReq) Deserialization(source *common.ZeroCopySource) error {
+ var eof bool
+ this.Len, eof = source.NextUint8()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.HashStart, eof = source.NextHash()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.HashEnd, eof = source.NextHash()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ return nil
+}
diff --git a/p2pserver/message/types/block_headers_req_test.go b/p2pserver/message/types/block_headers_req_test.go
new file mode 100644
index 0000000..8c612a9
--- /dev/null
+++ b/p2pserver/message/types/block_headers_req_test.go
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "testing"
+
+ cm "github.com/ontio/ontology/common"
+)
+
+func TestBlkHdrReqSerializationDeserialization(t *testing.T) {
+ var msg HeadersReq
+ msg.Len = 1
+
+ hashstr := "8932da73f52b1e22f30c609988ed1f693b6144f74fed9a2a20869afa7abfdf5e"
+ msg.HashStart, _ = cm.Uint256FromHexString(hashstr)
+
+ MessageTest(t, &msg)
+}
diff --git a/p2pserver/message/types/blocks_req.go b/p2pserver/message/types/blocks_req.go
new file mode 100644
index 0000000..0c0476e
--- /dev/null
+++ b/p2pserver/message/types/blocks_req.go
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type BlocksReq struct {
+ HeaderHashCount uint8
+ HashStart comm.Uint256
+ HashStop comm.Uint256
+}
+
+//Serialize message payload
+func (this *BlocksReq) Serialization(sink *comm.ZeroCopySink) {
+ sink.WriteUint8(this.HeaderHashCount)
+ sink.WriteHash(this.HashStart)
+ sink.WriteHash(this.HashStop)
+}
+
+func (this *BlocksReq) CmdType() string {
+ return common.GET_BLOCKS_TYPE
+}
+
+//Deserialize message payload
+func (this *BlocksReq) Deserialization(source *comm.ZeroCopySource) error {
+ var eof bool
+ this.HeaderHashCount, eof = source.NextUint8()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.HashStart, eof = source.NextHash()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.HashStop, eof = source.NextHash()
+
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
diff --git a/p2pserver/message/types/blocks_req_test.go b/p2pserver/message/types/blocks_req_test.go
new file mode 100644
index 0000000..47e0f7c
--- /dev/null
+++ b/p2pserver/message/types/blocks_req_test.go
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "testing"
+
+ cm "github.com/ontio/ontology/common"
+)
+
+func TestBlkReqSerializationDeserialization(t *testing.T) {
+ var msg BlocksReq
+ msg.HeaderHashCount = 1
+
+ hashstr := "8932da73f52b1e22f30c609988ed1f693b6144f74fed9a2a20869afa7abfdf5e"
+ msg.HashStart, _ = cm.Uint256FromHexString(hashstr)
+
+ MessageTest(t, &msg)
+}
diff --git a/p2pserver/message/types/consensus.go b/p2pserver/message/types/consensus.go
new file mode 100644
index 0000000..b387587
--- /dev/null
+++ b/p2pserver/message/types/consensus.go
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type Consensus struct {
+ Cons ConsensusPayload
+}
+
+//Serialize message payload
+func (this *Consensus) Serialization(sink *comm.ZeroCopySink) {
+ this.Cons.Serialization(sink)
+}
+
+func (this *Consensus) CmdType() string {
+ return common.CONSENSUS_TYPE
+}
+
+//Deserialize message payload
+func (this *Consensus) Deserialization(source *comm.ZeroCopySource) error {
+ return this.Cons.Deserialization(source)
+}
diff --git a/p2pserver/message/types/consensus_payload.go b/p2pserver/message/types/consensus_payload.go
new file mode 100644
index 0000000..dc484ef
--- /dev/null
+++ b/p2pserver/message/types/consensus_payload.go
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/ontio/ontology-crypto/keypair"
+ "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/core/signature"
+ "github.com/ontio/ontology/errors"
+ common2 "github.com/ontio/ontology/p2pserver/common"
+)
+
+type ConsensusPayload struct {
+ Version uint32
+ PrevHash common.Uint256
+ Height uint32
+ BookkeeperIndex uint16
+ Timestamp uint32
+ Data []byte
+ Owner keypair.PublicKey
+ Signature []byte
+ PeerId common2.PeerId
+ hash common.Uint256
+}
+
+//get the consensus payload hash
+func (this *ConsensusPayload) Hash() common.Uint256 {
+ return common.Uint256{}
+}
+
+//Check whether header is correct
+func (this *ConsensusPayload) Verify() error {
+ sink := common.NewZeroCopySink(nil)
+ this.SerializationUnsigned(sink)
+
+ err := signature.Verify(this.Owner, sink.Bytes(), this.Signature)
+ if err != nil {
+ return errors.NewDetailErr(err, errors.ErrNetVerifyFail, fmt.Sprintf("signature verify error. buf:%v", sink.Bytes()))
+ }
+ return nil
+}
+
+//serialize the consensus payload
+func (this *ConsensusPayload) ToArray() []byte {
+ return common.SerializeToBytes(this)
+}
+
+func (this *ConsensusPayload) GetMessage() []byte {
+ //TODO: GetMessage
+ //return sig.GetHashData(cp)
+ return []byte{}
+}
+
+func (this *ConsensusPayload) Serialization(sink *common.ZeroCopySink) {
+ this.SerializationUnsigned(sink)
+ buf := keypair.SerializePublicKey(this.Owner)
+ sink.WriteVarBytes(buf)
+ sink.WriteVarBytes(this.Signature)
+}
+
+//Deserialize message payload
+func (this *ConsensusPayload) Deserialization(source *common.ZeroCopySource) error {
+ err := this.DeserializationUnsigned(source)
+ if err != nil {
+ return err
+ }
+ buf, _, irregular, eof := source.NextVarBytes()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ if irregular {
+ return common.ErrIrregularData
+ }
+
+ this.Owner, err = keypair.DeserializePublicKey(buf)
+ if err != nil {
+ return errors.NewDetailErr(err, errors.ErrNetUnPackFail, "deserialize publickey error")
+ }
+
+ this.Signature, _, irregular, eof = source.NextVarBytes()
+ if irregular {
+ return common.ErrIrregularData
+ }
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ return nil
+}
+
+func (this *ConsensusPayload) SerializationUnsigned(sink *common.ZeroCopySink) {
+ sink.WriteUint32(this.Version)
+ sink.WriteHash(this.PrevHash)
+ sink.WriteUint32(this.Height)
+ sink.WriteUint16(this.BookkeeperIndex)
+ sink.WriteUint32(this.Timestamp)
+ sink.WriteVarBytes(this.Data)
+}
+
+func (this *ConsensusPayload) DeserializationUnsigned(source *common.ZeroCopySource) error {
+ var irregular, eof bool
+ this.Version, eof = source.NextUint32()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.PrevHash, eof = source.NextHash()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.Height, eof = source.NextUint32()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.BookkeeperIndex, eof = source.NextUint16()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.Timestamp, eof = source.NextUint32()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.Data, _, irregular, eof = source.NextVarBytes()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ if irregular {
+ return common.ErrIrregularData
+ }
+
+ return nil
+}
diff --git a/p2pserver/message/types/data_req.go b/p2pserver/message/types/data_req.go
new file mode 100644
index 0000000..a683cd9
--- /dev/null
+++ b/p2pserver/message/types/data_req.go
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ "github.com/ontio/ontology/common"
+ comm "github.com/ontio/ontology/p2pserver/common"
+)
+
+type DataReq struct {
+ DataType common.InventoryType
+ Hash common.Uint256
+}
+
+//Serialize message payload
+func (this DataReq) Serialization(sink *common.ZeroCopySink) {
+ sink.WriteByte(byte(this.DataType))
+ sink.WriteHash(this.Hash)
+}
+
+func (this *DataReq) CmdType() string {
+ return comm.GET_DATA_TYPE
+}
+
+//Deserialize message payload
+func (this *DataReq) Deserialization(source *common.ZeroCopySource) error {
+ ty, eof := source.NextByte()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.DataType = common.InventoryType(ty)
+
+ this.Hash, eof = source.NextHash()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ return nil
+}
diff --git a/p2pserver/message/types/data_req_test.go b/p2pserver/message/types/data_req_test.go
new file mode 100644
index 0000000..6b42509
--- /dev/null
+++ b/p2pserver/message/types/data_req_test.go
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "testing"
+
+ cm "github.com/ontio/ontology/common"
+)
+
+func TestDataReqSerializationDeserialization(t *testing.T) {
+ var msg DataReq
+ msg.DataType = 0x02
+
+ hashstr := "8932da73f52b1e22f30c609988ed1f693b6144f74fed9a2a20869afa7abfdf5e"
+ bhash, _ := cm.HexToBytes(hashstr)
+ copy(msg.Hash[:], bhash)
+
+ MessageTest(t, &msg)
+}
diff --git a/p2pserver/message/types/find_node.go b/p2pserver/message/types/find_node.go
new file mode 100644
index 0000000..4915982
--- /dev/null
+++ b/p2pserver/message/types/find_node.go
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ "github.com/ontio/ontology/common"
+ ncomm "github.com/ontio/ontology/p2pserver/common"
+)
+
+type FindNodeReq struct {
+ TargetID ncomm.PeerId
+}
+
+// Serialization message payload
+func (req FindNodeReq) Serialization(sink *common.ZeroCopySink) {
+ req.TargetID.Serialization(sink)
+}
+
+// CmdType return this message type
+func (req *FindNodeReq) CmdType() string {
+ return ncomm.FINDNODE_TYPE
+}
+
+// Deserialization message payload
+func (req *FindNodeReq) Deserialization(source *common.ZeroCopySource) error {
+ return req.TargetID.Deserialization(source)
+}
+
+type FindNodeResp struct {
+ TargetID ncomm.PeerId
+ Success bool
+ Address string
+ CloserPeers []ncomm.PeerIDAddressPair
+}
+
+// Serialization message payload
+func (resp FindNodeResp) Serialization(sink *common.ZeroCopySink) {
+ resp.TargetID.Serialization(sink)
+ sink.WriteBool(resp.Success)
+ sink.WriteString(resp.Address)
+ sink.WriteUint32(uint32(len(resp.CloserPeers)))
+ for _, curPeer := range resp.CloserPeers {
+ curPeer.ID.Serialization(sink)
+ sink.WriteString(curPeer.Address)
+ }
+}
+
+// CmdType return this message type
+func (resp *FindNodeResp) CmdType() string {
+ return ncomm.FINDNODE_RESP_TYPE
+}
+
+// Deserialization message payload
+func (resp *FindNodeResp) Deserialization(source *common.ZeroCopySource) error {
+ err := resp.TargetID.Deserialization(source)
+ if err != nil {
+ return err
+ }
+
+ succ, _, eof := source.NextBool()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ resp.Success = succ
+
+ addr, _, _, eof := source.NextString()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ resp.Address = addr
+
+ numCloser, eof := source.NextUint32()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ for i := 0; i < int(numCloser); i++ {
+ var curpa ncomm.PeerIDAddressPair
+ id := ncomm.PeerId{}
+ err = id.Deserialization(source)
+ if err != nil {
+ return err
+ }
+ curpa.ID = id
+ addr, _, _, eof := source.NextString()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ curpa.Address = addr
+
+ resp.CloserPeers = append(resp.CloserPeers, curpa)
+ }
+
+ return nil
+}
diff --git a/p2pserver/message/types/find_node_test.go b/p2pserver/message/types/find_node_test.go
new file mode 100644
index 0000000..03d704a
--- /dev/null
+++ b/p2pserver/message/types/find_node_test.go
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "testing"
+
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+func TestFindNodeRequest(t *testing.T) {
+ var req FindNodeReq
+ req.TargetID = common.PeerId{}
+
+ MessageTest(t, &req)
+}
+
+func TestFindNodeResponse(t *testing.T) {
+ var resp FindNodeResp
+ resp.TargetID = common.PeerId{}
+ resp.Address = "127.0.0.1:1222"
+ id := common.PseudoPeerIdFromUint64(uint64(0x456))
+ resp.CloserPeers = []common.PeerIDAddressPair{
+ common.PeerIDAddressPair{
+ ID: id,
+ Address: "127.0.0.1:4222",
+ },
+ }
+ resp.Success = true
+
+ MessageTest(t, &resp)
+}
diff --git a/p2pserver/message/types/inventory.go b/p2pserver/message/types/inventory.go
new file mode 100644
index 0000000..13250ef
--- /dev/null
+++ b/p2pserver/message/types/inventory.go
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ "github.com/ontio/ontology/common"
+ p2pCommon "github.com/ontio/ontology/p2pserver/common"
+)
+
+var LastInvHash common.Uint256
+
+type InvPayload struct {
+ InvType common.InventoryType
+ Blk []common.Uint256
+}
+
+type Inv struct {
+ P InvPayload
+}
+
+func (this Inv) invType() common.InventoryType {
+ return this.P.InvType
+}
+
+func (this *Inv) CmdType() string {
+ return p2pCommon.INV_TYPE
+}
+
+//Serialize message payload
+func (this Inv) Serialization(sink *common.ZeroCopySink) {
+ sink.WriteUint8(uint8(this.P.InvType))
+
+ blkCnt := uint32(len(this.P.Blk))
+ sink.WriteUint32(blkCnt)
+ for _, hash := range this.P.Blk {
+ sink.WriteHash(hash)
+ }
+}
+
+//Deserialize message payload
+func (this *Inv) Deserialization(source *common.ZeroCopySource) error {
+ var eof bool
+ invType, eof := source.NextUint8()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ this.P.InvType = common.InventoryType(invType)
+ blkCnt, eof := source.NextUint32()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ for i := 0; i < int(blkCnt); i++ {
+ hash, eof := source.NextHash()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.Blk = append(this.P.Blk, hash)
+ }
+
+ if blkCnt > p2pCommon.MAX_INV_BLK_CNT {
+ blkCnt = p2pCommon.MAX_INV_BLK_CNT
+ }
+ this.P.Blk = this.P.Blk[:blkCnt]
+ return nil
+}
diff --git a/p2pserver/message/types/message.go b/p2pserver/message/types/message.go
new file mode 100644
index 0000000..0b0626d
--- /dev/null
+++ b/p2pserver/message/types/message.go
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/config"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type Message interface {
+ Serialization(sink *comm.ZeroCopySink)
+ Deserialization(source *comm.ZeroCopySource) error
+ CmdType() string
+}
+
+//MsgPayload in link channel
+type MsgPayload struct {
+ Id common.PeerId //peer ID
+ Addr string //link address
+ PayloadSize uint32 //payload size
+ Payload Message //msg payload
+}
+
+type messageHeader struct {
+ Magic uint32
+ CMD [common.MSG_CMD_LEN]byte // The message type
+ Length uint32
+ Checksum [common.CHECKSUM_LEN]byte
+}
+
+func readMessageHeader(reader io.Reader) (messageHeader, error) {
+ msgh := messageHeader{}
+ hdrBytes := make([]byte, comm.UINT32_SIZE+common.MSG_CMD_LEN+comm.UINT32_SIZE+common.CHECKSUM_LEN)
+
+ if _, err := io.ReadFull(reader, hdrBytes); err != nil {
+ return msgh, err
+ }
+
+ source := comm.NewZeroCopySource(hdrBytes)
+ msgh.Magic, _ = source.NextUint32()
+ cmd, _ := source.NextBytes(common.MSG_CMD_LEN)
+ copy(msgh.CMD[:], cmd)
+ msgh.Length, _ = source.NextUint32()
+ checksum, _ := source.NextBytes(common.CHECKSUM_LEN)
+ copy(msgh.Checksum[:], checksum)
+ return msgh, nil
+}
+
+func writeMessageHeaderInto(sink *comm.ZeroCopySink, msgh messageHeader) {
+ sink.WriteUint32(msgh.Magic)
+ sink.WriteBytes(msgh.CMD[:])
+ sink.WriteUint32(msgh.Length)
+ sink.WriteBytes(msgh.Checksum[:])
+}
+
+func newMessageHeader(cmd string, length uint32, checksum [common.CHECKSUM_LEN]byte) messageHeader {
+ msgh := messageHeader{}
+ msgh.Magic = config.DefConfig.P2PNode.NetworkMagic
+ copy(msgh.CMD[:], cmd)
+ msgh.Checksum = checksum
+ msgh.Length = length
+ return msgh
+}
+
+func WriteMessage(sink *comm.ZeroCopySink, msg Message) {
+ pstart := sink.Size()
+ sink.NextBytes(common.MSG_HDR_LEN) // can not save the buf, since it may reallocate in sink
+ msg.Serialization(sink)
+ pend := sink.Size()
+ total := pend - pstart
+ payLen := total - common.MSG_HDR_LEN
+
+ sink.BackUp(total)
+ buf := sink.NextBytes(total)
+ checksum := common.Checksum(buf[common.MSG_HDR_LEN:])
+ hdr := newMessageHeader(msg.CmdType(), uint32(payLen), checksum)
+
+ sink.BackUp(total)
+ writeMessageHeaderInto(sink, hdr)
+ sink.NextBytes(payLen)
+}
+
+func ReadMessage(reader io.Reader) (Message, uint32, error) {
+ hdr, err := readMessageHeader(reader)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ magic := config.DefConfig.P2PNode.NetworkMagic
+ if hdr.Magic != magic {
+ return nil, 0, fmt.Errorf("unmatched magic number %d, expected %d", hdr.Magic, magic)
+ }
+
+ if hdr.Length > common.MAX_PAYLOAD_LEN {
+ return nil, 0, fmt.Errorf("msg payload length:%d exceed max payload size: %d",
+ hdr.Length, common.MAX_PAYLOAD_LEN)
+ }
+
+ buf := make([]byte, hdr.Length)
+ _, err = io.ReadFull(reader, buf)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ checksum := common.Checksum(buf)
+ if checksum != hdr.Checksum {
+ return nil, 0, fmt.Errorf("message checksum mismatch: %x != %x ", hdr.Checksum, checksum)
+ }
+
+ cmdType := string(bytes.TrimRight(hdr.CMD[:], string(0)))
+ msg, err := MakeEmptyMessage(cmdType)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ // the buf is referenced by msg to avoid reallocation, so can not reused
+ source := comm.NewZeroCopySource(buf)
+ err = msg.Deserialization(source)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ return msg, hdr.Length, nil
+}
+
+func MakeEmptyMessage(cmdType string) (Message, error) {
+ switch cmdType {
+ case common.PING_TYPE:
+ return &Ping{}, nil
+ case common.VERSION_TYPE:
+ return &Version{}, nil
+ case common.VERACK_TYPE:
+ return &VerACK{}, nil
+ case common.ADDR_TYPE:
+ return &Addr{}, nil
+ case common.GetADDR_TYPE:
+ return &AddrReq{}, nil
+ case common.PONG_TYPE:
+ return &Pong{}, nil
+ case common.GET_HEADERS_TYPE:
+ return &HeadersReq{}, nil
+ case common.HEADERS_TYPE:
+ return &BlkHeader{}, nil
+ case common.INV_TYPE:
+ return &Inv{}, nil
+ case common.GET_DATA_TYPE:
+ return &DataReq{}, nil
+ case common.BLOCK_TYPE:
+ return &Block{}, nil
+ case common.TX_TYPE:
+ return &Trn{}, nil
+ case common.CONSENSUS_TYPE:
+ return &Consensus{}, nil
+ case common.NOT_FOUND_TYPE:
+ return &NotFound{}, nil
+ case common.GET_BLOCKS_TYPE:
+ return &BlocksReq{}, nil
+ case common.FINDNODE_TYPE:
+ return &FindNodeReq{}, nil
+ case common.FINDNODE_RESP_TYPE:
+ return &FindNodeResp{}, nil
+ case common.UPDATE_KADID_TYPE:
+ return &UpdatePeerKeyId{}, nil
+ default:
+ return nil, errors.New("unsupported cmd type:" + cmdType)
+ }
+
+}
diff --git a/p2pserver/message/types/message_test.go b/p2pserver/message/types/message_test.go
new file mode 100644
index 0000000..814c679
--- /dev/null
+++ b/p2pserver/message/types/message_test.go
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package types
+
+import (
+ "bytes"
+ "encoding/binary"
+ "io"
+ "testing"
+
+ common2 "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMsgHdrSerializationDeserialization(t *testing.T) {
+ hdr := newMessageHeader("hdrtest", 0, common.Checksum(nil))
+
+ sink := common2.NewZeroCopySink(nil)
+ writeMessageHeaderInto(sink, hdr)
+
+ dehdr, err := readMessageHeader(bytes.NewBuffer(sink.Bytes()))
+ assert.Nil(t, err)
+
+ assert.Equal(t, hdr, dehdr)
+
+}
+
+func readMessageHeader_old(reader io.Reader) (messageHeader, error) {
+ msgh := messageHeader{}
+ err := binary.Read(reader, binary.LittleEndian, &msgh)
+ return msgh, err
+}
+
+func TestMsgHdr2(t *testing.T) {
+ hdr := newMessageHeader("hdrtest1", 20, common.Checksum([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}))
+
+ sink := common2.NewZeroCopySink(nil)
+ writeMessageHeaderInto(sink, hdr)
+
+ hdr1, err := readMessageHeader(bytes.NewBuffer(sink.Bytes()))
+ if err != nil {
+ t.Fatalf("read hdr: %s", err)
+ }
+ if hdr1.Length != hdr.Length ||
+ hdr1.Magic != hdr.Magic ||
+ hdr1.CMD != hdr.CMD ||
+ hdr1.Checksum != hdr.Checksum {
+ t.Fatalf("invalid hdr1: %v", hdr1)
+ }
+}
+
+func BenchmarkReadMessageUseSink(b *testing.B) {
+ b.StopTimer()
+ hdr := newMessageHeader("hdrtest2", 20, common.Checksum([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}))
+
+ sink := common2.NewZeroCopySink(nil)
+ writeMessageHeaderInto(sink, hdr)
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ readMessageHeader(bytes.NewBuffer(sink.Bytes()))
+ }
+}
+
+func BenchmarkReadMessageBinaryRead(b *testing.B) {
+ b.StopTimer()
+ hdr := newMessageHeader("hdrtest2", 20, common.Checksum([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}))
+
+ sink := common2.NewZeroCopySink(nil)
+ writeMessageHeaderInto(sink, hdr)
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ readMessageHeader_old(bytes.NewBuffer(sink.Bytes()))
+ }
+}
diff --git a/p2pserver/message/types/notfound.go b/p2pserver/message/types/notfound.go
new file mode 100644
index 0000000..8cb6af5
--- /dev/null
+++ b/p2pserver/message/types/notfound.go
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ "github.com/ontio/ontology/common"
+ comm "github.com/ontio/ontology/p2pserver/common"
+)
+
+type NotFound struct {
+ Hash common.Uint256
+}
+
+//Serialize message payload
+func (this NotFound) Serialization(sink *common.ZeroCopySink) {
+ sink.WriteHash(this.Hash)
+}
+
+func (this NotFound) CmdType() string {
+ return comm.NOT_FOUND_TYPE
+}
+
+//Deserialize message payload
+func (this *NotFound) Deserialization(source *common.ZeroCopySource) error {
+ var eof bool
+ this.Hash, eof = source.NextHash()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ return nil
+}
diff --git a/p2pserver/message/types/notfound_test.go b/p2pserver/message/types/notfound_test.go
new file mode 100644
index 0000000..2eed2b2
--- /dev/null
+++ b/p2pserver/message/types/notfound_test.go
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "testing"
+
+ cm "github.com/ontio/ontology/common"
+)
+
+func Uint256ParseFromBytes(f []byte) cm.Uint256 {
+ if len(f) != 32 {
+ return cm.Uint256{}
+ }
+
+ var hash [32]uint8
+ for i := 0; i < 32; i++ {
+ hash[i] = f[i]
+ }
+ return cm.Uint256(hash)
+}
+
+func TestNotFoundSerializationDeserialization(t *testing.T) {
+ var msg NotFound
+ str := "123456"
+ hash := []byte(str)
+ msg.Hash = Uint256ParseFromBytes(hash)
+
+ MessageTest(t, &msg)
+}
diff --git a/p2pserver/message/types/ping.go b/p2pserver/message/types/ping.go
new file mode 100644
index 0000000..357d329
--- /dev/null
+++ b/p2pserver/message/types/ping.go
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type Ping struct {
+ Height uint64
+}
+
+//Serialize message payload
+func (this Ping) Serialization(sink *comm.ZeroCopySink) {
+ sink.WriteUint64(this.Height)
+}
+
+func (this *Ping) CmdType() string {
+ return common.PING_TYPE
+}
+
+//Deserialize message payload
+func (this *Ping) Deserialization(source *comm.ZeroCopySource) error {
+ var eof bool
+ this.Height, eof = source.NextUint64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ return nil
+}
diff --git a/p2pserver/message/types/ping_test.go b/p2pserver/message/types/ping_test.go
new file mode 100644
index 0000000..3a28c0c
--- /dev/null
+++ b/p2pserver/message/types/ping_test.go
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "testing"
+)
+
+func TestPingSerializationDeserialization(t *testing.T) {
+ var msg Ping
+ msg.Height = 1
+
+ MessageTest(t, &msg)
+}
diff --git a/p2pserver/message/types/pong.go b/p2pserver/message/types/pong.go
new file mode 100644
index 0000000..e8a71dc
--- /dev/null
+++ b/p2pserver/message/types/pong.go
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type Pong struct {
+ Height uint64
+}
+
+//Serialize message payload
+func (this Pong) Serialization(sink *comm.ZeroCopySink) {
+ sink.WriteUint64(this.Height)
+}
+
+func (this Pong) CmdType() string {
+ return common.PONG_TYPE
+}
+
+//Deserialize message payload
+func (this *Pong) Deserialization(source *comm.ZeroCopySource) error {
+ var eof bool
+ this.Height, eof = source.NextUint64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ return nil
+}
diff --git a/p2pserver/message/types/pong_test.go b/p2pserver/message/types/pong_test.go
new file mode 100644
index 0000000..f815bb8
--- /dev/null
+++ b/p2pserver/message/types/pong_test.go
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "testing"
+)
+
+func TestPongSerializationDeserialization(t *testing.T) {
+ var msg Pong
+ msg.Height = 1
+
+ MessageTest(t, &msg)
+}
diff --git a/p2pserver/message/types/transaction.go b/p2pserver/message/types/transaction.go
new file mode 100644
index 0000000..bdc5c68
--- /dev/null
+++ b/p2pserver/message/types/transaction.go
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/core/types"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+// Transaction message
+type Trn struct {
+ Txn *types.Transaction
+}
+
+//Serialize message payload
+func (this Trn) Serialization(sink *comm.ZeroCopySink) {
+ this.Txn.Serialization(sink)
+}
+
+func (this *Trn) CmdType() string {
+ return common.TX_TYPE
+}
+
+//Deserialize message payload
+func (this *Trn) Deserialization(source *comm.ZeroCopySource) error {
+ tx := &types.Transaction{}
+ err := tx.Deserialization(source)
+ if err != nil {
+ return err
+ }
+
+ this.Txn = tx
+ return nil
+}
diff --git a/p2pserver/message/types/update_kadid.go b/p2pserver/message/types/update_kadid.go
new file mode 100644
index 0000000..d68605c
--- /dev/null
+++ b/p2pserver/message/types/update_kadid.go
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ common2 "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type UpdatePeerKeyId struct {
+ //TODO remove this legecy field when upgrade network layer protocal
+ KadKeyId *common.PeerKeyId
+}
+
+//Serialize message payload
+func (this *UpdatePeerKeyId) Serialization(sink *common2.ZeroCopySink) {
+ this.KadKeyId.Serialization(sink)
+}
+
+func (this *UpdatePeerKeyId) Deserialization(source *common2.ZeroCopySource) error {
+ this.KadKeyId = &common.PeerKeyId{}
+ return this.KadKeyId.Deserialization(source)
+}
+
+func (this *UpdatePeerKeyId) CmdType() string {
+ return common.UPDATE_KADID_TYPE
+}
diff --git a/p2pserver/message/types/verack.go b/p2pserver/message/types/verack.go
new file mode 100644
index 0000000..ee3ebca
--- /dev/null
+++ b/p2pserver/message/types/verack.go
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type VerACK struct {
+ //TODO remove this legecy field when upgrade network layer protocal
+ isConsensus bool
+}
+
+//Serialize message payload
+func (this *VerACK) Serialization(sink *comm.ZeroCopySink) {
+ sink.WriteBool(this.isConsensus)
+}
+
+func (this *VerACK) CmdType() string {
+ return common.VERACK_TYPE
+}
+
+//Deserialize message payload
+func (this *VerACK) Deserialization(source *comm.ZeroCopySource) error {
+ var irregular, eof bool
+ this.isConsensus, irregular, eof = source.NextBool()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ if irregular {
+ return comm.ErrIrregularData
+ }
+
+ return nil
+}
diff --git a/p2pserver/message/types/verack_test.go b/p2pserver/message/types/verack_test.go
new file mode 100644
index 0000000..bb31abd
--- /dev/null
+++ b/p2pserver/message/types/verack_test.go
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "testing"
+)
+
+func TestVerackSerializationDeserialization(t *testing.T) {
+ var msg VerACK
+ msg.isConsensus = false
+
+ MessageTest(t, &msg)
+}
diff --git a/p2pserver/message/types/version.go b/p2pserver/message/types/version.go
new file mode 100644
index 0000000..17c9a83
--- /dev/null
+++ b/p2pserver/message/types/version.go
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package types
+
+import (
+ "io"
+
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type VersionPayload struct {
+ Version uint32
+ Services uint64
+ TimeStamp int64
+ SyncPort uint16
+ HttpInfoPort uint16
+ //TODO remove this legecy field
+ ConsPort uint16
+ Cap [32]byte
+ Nonce uint64
+ StartHeight uint64
+ Relay uint8
+ IsConsensus bool
+ SoftVersion string
+}
+
+type Version struct {
+ P VersionPayload
+}
+
+//Serialize message payload
+func (this *Version) Serialization(sink *comm.ZeroCopySink) {
+ sink.WriteUint32(this.P.Version)
+ sink.WriteUint64(this.P.Services)
+ sink.WriteInt64(this.P.TimeStamp)
+ sink.WriteUint16(this.P.SyncPort)
+ sink.WriteUint16(this.P.HttpInfoPort)
+ sink.WriteUint16(this.P.ConsPort)
+ sink.WriteBytes(this.P.Cap[:])
+ sink.WriteUint64(this.P.Nonce)
+ sink.WriteUint64(this.P.StartHeight)
+ sink.WriteUint8(this.P.Relay)
+ sink.WriteBool(this.P.IsConsensus)
+ sink.WriteString(this.P.SoftVersion)
+}
+
+func (this *Version) CmdType() string {
+ return common.VERSION_TYPE
+}
+
+//Deserialize message payload
+func (this *Version) Deserialization(source *comm.ZeroCopySource) error {
+ var irregular, eof bool
+ this.P.Version, eof = source.NextUint32()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.Services, eof = source.NextUint64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.TimeStamp, eof = source.NextInt64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.SyncPort, eof = source.NextUint16()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.HttpInfoPort, eof = source.NextUint16()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.ConsPort, eof = source.NextUint16()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ var buf []byte
+ buf, eof = source.NextBytes(uint64(len(this.P.Cap[:])))
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+ copy(this.P.Cap[:], buf)
+
+ this.P.Nonce, eof = source.NextUint64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.StartHeight, eof = source.NextUint64()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.Relay, eof = source.NextUint8()
+ if eof {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.IsConsensus, irregular, eof = source.NextBool()
+ if eof || irregular {
+ return io.ErrUnexpectedEOF
+ }
+
+ this.P.SoftVersion, _, irregular, eof = source.NextString()
+ if eof || irregular {
+ this.P.SoftVersion = ""
+ }
+
+ return nil
+}
diff --git a/p2pserver/mock/dialer.go b/p2pserver/mock/dialer.go
new file mode 100644
index 0000000..5b6a1db
--- /dev/null
+++ b/p2pserver/mock/dialer.go
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package mock
+
+import (
+ "errors"
+ "net"
+
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/connect_controller"
+)
+
+type dialer struct {
+ id common.PeerId
+ address string
+ network *network
+}
+
+var _ connect_controller.Dialer = &dialer{}
+
+func (d *dialer) Dial(nodeAddr string) (net.Conn, error) {
+ d.network.Lock()
+ defer d.network.Unlock()
+ l, exist := d.network.listeners[nodeAddr]
+
+ if !exist {
+ return nil, errors.New("can not be reached")
+ }
+
+ if _, allow := d.network.canEstablish[combineKey(d.id, l.id)]; !allow {
+ return nil, errors.New("can not be reached")
+ }
+
+ c, s := net.Pipe()
+
+ cw := connWraper{c, d.address, d.network, l.address}
+ sw := connWraper{s, l.address, d.network, d.address}
+ l.PushToAccept(sw)
+
+ return cw, nil
+}
+
+func (n *network) NewDialer(pid common.PeerId) connect_controller.Dialer {
+ host := n.nextFakeIP()
+ return n.NewDialerWithHost(pid, host)
+}
+
+func (n *network) NewDialerWithHost(pid common.PeerId, host string) connect_controller.Dialer {
+ addr := host + ":" + n.nextPortString()
+
+ d := &dialer{
+ id: pid,
+ address: addr,
+ network: n,
+ }
+
+ return d
+}
+
+func (d *dialer) ID() common.PeerId {
+ return d.id
+}
diff --git a/p2pserver/mock/discovery_test.go b/p2pserver/mock/discovery_test.go
new file mode 100644
index 0000000..315242c
--- /dev/null
+++ b/p2pserver/mock/discovery_test.go
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package mock
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/message/types"
+ msgTypes "github.com/ontio/ontology/p2pserver/message/types"
+ "github.com/ontio/ontology/p2pserver/net/netserver"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/peer"
+ "github.com/ontio/ontology/p2pserver/protocols/bootstrap"
+ "github.com/ontio/ontology/p2pserver/protocols/discovery"
+ "github.com/stretchr/testify/assert"
+)
+
+func init() {
+ common.Difficulty = 1
+}
+
+type DiscoveryProtocol struct {
+ MaskPeers []string
+ RefleshInterval time.Duration
+ seeds []string
+
+ discovery *discovery.Discovery
+ bootstrap *bootstrap.BootstrapService
+}
+
+func NewDiscoveryProtocol(seeds []string, maskPeers []string) *DiscoveryProtocol {
+ return &DiscoveryProtocol{seeds: seeds, MaskPeers: maskPeers}
+}
+
+func (self *DiscoveryProtocol) start(net p2p.P2P) {
+ self.discovery = discovery.NewDiscovery(net, self.MaskPeers, self.RefleshInterval)
+ self.bootstrap = bootstrap.NewBootstrapService(net, self.seeds)
+ go self.discovery.Start()
+ go self.bootstrap.Start()
+}
+
+func (self *DiscoveryProtocol) HandleSystemMessage(net p2p.P2P, msg p2p.SystemMessage) {
+ switch m := msg.(type) {
+ case p2p.NetworkStart:
+ self.start(net)
+ case p2p.PeerConnected:
+ self.discovery.OnAddPeer(m.Info)
+ self.bootstrap.OnAddPeer(m.Info)
+ case p2p.PeerDisConnected:
+ self.discovery.OnDelPeer(m.Info)
+ self.bootstrap.OnDelPeer(m.Info)
+ case p2p.NetworkStop:
+ self.discovery.Stop()
+ self.bootstrap.Stop()
+ }
+}
+
+func (self *DiscoveryProtocol) HandlePeerMessage(ctx *p2p.Context, msg msgTypes.Message) {
+ log.Trace("[p2p]receive message", ctx.Sender().GetAddr(), ctx.Sender().GetID())
+ switch m := msg.(type) {
+ case *types.AddrReq:
+ self.discovery.AddrReqHandle(ctx)
+ case *msgTypes.FindNodeResp:
+ self.discovery.FindNodeResponseHandle(ctx, m)
+ case *msgTypes.FindNodeReq:
+ self.discovery.FindNodeHandle(ctx, m)
+ default:
+ msgType := msg.CmdType()
+ log.Warn("unknown message handler for the msg: ", msgType)
+ }
+}
+
+func TestDiscoveryNode(t *testing.T) {
+ N := 5
+ net := NewNetwork()
+ seedNode := NewDiscoveryNode(nil, net)
+ var nodes []*netserver.NetServer
+ go seedNode.Start()
+ seedAddr := seedNode.GetHostInfo().Addr
+ log.Errorf("seed addr: %s", seedAddr)
+ for i := 0; i < N; i++ {
+ node := NewDiscoveryNode([]string{seedAddr}, net)
+ net.AllowConnect(seedNode.GetHostInfo().Id, node.GetHostInfo().Id)
+ go node.Start()
+ nodes = append(nodes, node)
+ }
+
+ time.Sleep(time.Second * 1)
+ assert.Equal(t, seedNode.GetConnectionCnt(), uint32(N))
+ for i, node := range nodes {
+ assert.Equal(t, node.GetConnectionCnt(), uint32(1), fmt.Sprintf("node %d", i))
+ }
+
+ log.Info("start allow node connection")
+ for i := 0; i < len(nodes); i++ {
+ for j := i + 1; j < len(nodes); j++ {
+ net.AllowConnect(nodes[i].GetHostInfo().Id, nodes[j].GetHostInfo().Id)
+ }
+ }
+ time.Sleep(time.Second * 1)
+ for i, node := range nodes {
+ assert.True(t, node.GetConnectionCnt() > uint32(N/3), fmt.Sprintf("node %d", i))
+ }
+}
+
+func NewDiscoveryNode(seeds []string, net Network) *netserver.NetServer {
+ seedId := common.RandPeerKeyId()
+ info := peer.NewPeerInfo(seedId.Id, 0, 0, true, 0,
+ 0, 0, "1.10", "")
+
+ dis := NewDiscoveryProtocol(seeds, nil)
+ dis.RefleshInterval = time.Millisecond * 10
+
+ return NewNode(seedId, info, dis, net, nil)
+}
diff --git a/p2pserver/mock/interface.go b/p2pserver/mock/interface.go
new file mode 100644
index 0000000..b40f959
--- /dev/null
+++ b/p2pserver/mock/interface.go
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package mock
+
+import (
+ "net"
+ "strconv"
+
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/connect_controller"
+ "github.com/ontio/ontology/p2pserver/net/netserver"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/peer"
+)
+
+type Network interface {
+ // NewListener will gen random ip to listen
+ NewListener(id common.PeerId) (string, net.Listener)
+ NewListenerWithHost(id common.PeerId, host string) (string, net.Listener)
+
+ // NewDialer will gen random source IP
+ NewDialer(id common.PeerId) connect_controller.Dialer
+ NewDialerWithHost(id common.PeerId, host string) connect_controller.Dialer
+ AllowConnect(id1, id2 common.PeerId)
+ DeliverRate(percent uint)
+}
+
+func NewNode(keyId *common.PeerKeyId, localInfo *peer.PeerInfo, proto p2p.Protocol, nw Network, reservedPeers []string) *netserver.NetServer {
+ addr, listener := nw.NewListener(keyId.Id)
+ host, port, _ := net.SplitHostPort(addr)
+ dialer := nw.NewDialerWithHost(keyId.Id, host)
+ localInfo.Addr = addr
+ iport, _ := strconv.Atoi(port)
+ localInfo.Port = uint16(iport)
+ opt := connect_controller.NewConnCtrlOption().MaxInBoundPerIp(10).
+ MaxInBound(20).MaxOutBound(20).WithDialer(dialer).ReservedOnly(reservedPeers)
+ return netserver.NewCustomNetServer(keyId, localInfo, proto, listener, opt)
+}
diff --git a/p2pserver/mock/listener.go b/p2pserver/mock/listener.go
new file mode 100644
index 0000000..598e64c
--- /dev/null
+++ b/p2pserver/mock/listener.go
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package mock
+
+import (
+ "errors"
+ "net"
+
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+type Listener struct {
+ id common.PeerId
+ conn chan net.Conn
+ address string
+}
+
+var _ net.Listener = &Listener{}
+var _ net.Addr = &Listener{}
+
+func (n *network) NewListener(id common.PeerId) (string, net.Listener) {
+ ip := n.nextFakeIP()
+ return n.NewListenerWithHost(id, ip)
+}
+
+func (n *network) NewListenerWithHost(id common.PeerId, host string) (string, net.Listener) {
+ hostport := host + ":" + n.nextPortString()
+
+ ret := &Listener{
+ id: id,
+ address: hostport,
+ conn: make(chan net.Conn),
+ }
+
+ n.Lock()
+ n.listeners[hostport] = ret
+ n.Unlock()
+
+ return hostport, ret
+}
+
+func (l *Listener) Accept() (net.Conn, error) {
+ select {
+ case conn, ok := <-l.conn:
+ if ok {
+ return conn, nil
+ }
+ return nil, errors.New("closed channel")
+ }
+}
+
+func (l *Listener) Close() error {
+ close(l.conn)
+ return nil
+}
+
+func (l *Listener) Addr() net.Addr {
+ // listeners's local listen address is useless
+ return l
+}
+
+func (l *Listener) Network() string {
+ return "tcp"
+}
+
+func (l *Listener) String() string {
+ return l.address
+}
+
+func (l *Listener) ID() string {
+ return l.id.ToHexString()
+}
+
+func (l *Listener) PushToAccept(conn net.Conn) {
+ go func() {
+ l.conn <- conn
+ }()
+}
diff --git a/p2pserver/mock/mocknet.go b/p2pserver/mock/mocknet.go
new file mode 100644
index 0000000..208e97e
--- /dev/null
+++ b/p2pserver/mock/mocknet.go
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package mock
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "net"
+ "strconv"
+ "sync"
+ "sync/atomic"
+
+ "github.com/ontio/ontology/p2pserver/common"
+)
+
+func init() {
+ common.Difficulty = 1
+}
+
+type network struct {
+ sync.RWMutex
+ canEstablish map[string]struct{}
+ listeners map[string]*Listener
+ startID uint32
+}
+
+var _ Network = &network{}
+
+func NewNetwork() Network {
+ ret := &network{
+ // id -> [id...]
+ canEstablish: make(map[string]struct{}),
+ // host:port -> Listener
+ listeners: make(map[string]*Listener),
+ startID: 0,
+ }
+
+ return ret
+}
+
+func (n *network) nextID() uint32 {
+ return atomic.AddUint32(&n.startID, 1)
+}
+
+func (n *network) nextFakeIP() string {
+ id := n.nextID()
+ b := make([]byte, 4)
+ binary.BigEndian.PutUint32(b, id)
+ ip := net.IP(b)
+
+ return ip.String()
+}
+
+func (n *network) nextPort() uint16 {
+ port := make([]byte, 2)
+ rand.Read(port)
+ return binary.BigEndian.Uint16(port)
+}
+
+func (n *network) nextPortString() string {
+ port := n.nextPort()
+ return strconv.Itoa(int(port))
+}
+
+func combineKey(id1, id2 common.PeerId) string {
+ s1 := id1.ToHexString()
+ s2 := id2.ToHexString()
+
+ if s1 <= s2 {
+ return s1 + "|" + s2
+ }
+ return s2 + "|" + s1
+}
+
+func (n *network) AllowConnect(id1, id2 common.PeerId) {
+ n.Lock()
+ defer n.Unlock()
+
+ n.canEstablish[combineKey(id1, id2)] = struct{}{}
+}
+
+// DeliverRate TODO
+func (n *network) DeliverRate(percent uint) {
+
+}
+
+type connWraper struct {
+ net.Conn
+ address string
+ network *network
+ remote string
+}
+
+var _ net.Addr = &connWraper{}
+
+func (cw *connWraper) Network() string {
+ return "tcp"
+}
+
+func (cw *connWraper) String() string {
+ return cw.address
+}
+
+func (cw connWraper) LocalAddr() net.Addr {
+ return &cw
+}
+
+func (cw connWraper) RemoteAddr() net.Addr {
+ w := &connWraper{
+ address: cw.remote,
+ }
+ return w
+}
diff --git a/p2pserver/mock/mocknet_test.go b/p2pserver/mock/mocknet_test.go
new file mode 100644
index 0000000..b0e970e
--- /dev/null
+++ b/p2pserver/mock/mocknet_test.go
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package mock
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "testing"
+
+ "net"
+
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/stretchr/testify/require"
+)
+
+func genPeerID() common.PeerId {
+ b := make([]byte, 8)
+ rand.Read(b)
+ id := binary.BigEndian.Uint64(b)
+ return common.PseudoPeerIdFromUint64(id)
+}
+
+func TestNetwork(t *testing.T) {
+ a := require.New(t)
+ dp := genPeerID()
+ lp := genPeerID()
+
+ n := NewNetwork()
+ d := n.NewDialer(dp)
+ laddr, l := n.NewListener(lp)
+
+ // before allow
+ dconn, err := d.Dial(laddr)
+ a.Nil(dconn, "connection should be nil")
+ a.NotNil(err, "err shuld not be nil")
+ a.Contains(err.Error(), "can not be reached", "can not dial to remote address")
+
+ // after allow
+ n.AllowConnect(dp, lp)
+ dconn, err = d.Dial(laddr)
+ a.Nil(err, "should be nil")
+ a.Equal(dconn.RemoteAddr().String(), l.Addr().String(), "dialer remote should be the listeners address")
+
+ lconn, err := l.Accept()
+ a.Nil(err, "accept should get one conn")
+ a.NotNil(lconn, "should be a real conn")
+ a.Equal(lconn.RemoteAddr().String(), dconn.LocalAddr().String(), "accept conn remote should be the dialer address")
+ a.Equal(lconn.LocalAddr().String(), dconn.RemoteAddr().String(), "remote should match")
+
+ // dial again
+ dconn2, err := d.Dial(laddr)
+ a.Nil(err, "dial again should be OK")
+ a.Equal(dconn.LocalAddr().String(), dconn2.LocalAddr().String(), "dialer source ip should be same")
+
+ lconn2, err := l.Accept()
+ a.Nil(err, "accept should get one conn")
+ a.NotNil(lconn2, "should be a real conn")
+ a.Equal(lconn2.RemoteAddr().String(), dconn2.LocalAddr().String(), "remote should match")
+ a.Equal(dconn2.RemoteAddr().String(), lconn2.LocalAddr().String(), "remote should match")
+
+ // Accept after close
+ l.Close()
+ lconn2, err = l.Accept()
+ a.NotNil(err, "closed 'stream' should accept none")
+ a.Nil(lconn2, "should be nil")
+}
+
+func TestNetIP(t *testing.T) {
+ a := require.New(t)
+ ip := net.ParseIP("0.0.0.1")
+ a.Equal(ip.String(), "0.0.0.1")
+ _, err := net.LookupHost("1.0.0.1")
+ assert.Nil(t, err)
+}
diff --git a/p2pserver/mock/reserved_test.go b/p2pserver/mock/reserved_test.go
new file mode 100644
index 0000000..d37f3dc
--- /dev/null
+++ b/p2pserver/mock/reserved_test.go
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package mock
+
+import (
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/net/netserver"
+ "github.com/ontio/ontology/p2pserver/peer"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestReserved(t *testing.T) {
+ //topo
+ /**
+ normal —————— normal
+ \ reserved /
+ \ | /
+ \ seed /
+ |
+ |
+ normal
+ */
+ N := 4
+ net := NewNetwork()
+ seedNode := NewReservedNode(nil, net, nil)
+
+ var nodes []*netserver.NetServer
+ go seedNode.Start()
+ seedAddr := seedNode.GetHostInfo().Addr
+ seedIP := strings.Split(seedAddr, ":")[0]
+ for i := 0; i < N; i++ {
+ var node *netserver.NetServer
+ var reserved []string
+ if i == 0 {
+ reserved = []string{seedIP}
+ }
+ node = NewReservedNode([]string{seedAddr}, net, reserved)
+ net.AllowConnect(seedNode.GetHostInfo().Id, node.GetHostInfo().Id)
+ go node.Start()
+ nodes = append(nodes, node)
+ }
+
+ for i := 0; i < N; i++ {
+ for j := i + 1; j < N; j++ {
+ net.AllowConnect(nodes[i].GetHostInfo().Id, nodes[j].GetHostInfo().Id)
+ }
+ }
+
+ time.Sleep(time.Second * 10)
+ assert.Equal(t, uint32(N), seedNode.GetConnectionCnt())
+ assert.Equal(t, uint32(1), nodes[0].GetConnectionCnt())
+ for i := 1; i < N; i++ {
+ assert.Equal(t, uint32(N-1), nodes[i].GetConnectionCnt())
+ assert.False(t, hasPeerId(nodes[i].GetNeighborAddrs(), nodes[0].GetID()))
+ }
+}
+
+func hasPeerId(pas []common.PeerAddr, id common.PeerId) bool {
+ for _, pa := range pas {
+ if pa.ID == id {
+ return true
+ }
+ }
+ return false
+}
+
+func NewReservedNode(seeds []string, net Network, reservedPeers []string) *netserver.NetServer {
+ seedId := common.RandPeerKeyId()
+ info := peer.NewPeerInfo(seedId.Id, 0, 0, true, 0,
+ 0, 0, "1.10", "")
+ dis := NewDiscoveryProtocol(seeds, nil)
+ dis.RefleshInterval = time.Millisecond * 1000
+ return NewNode(seedId, info, dis, net, reservedPeers)
+}
diff --git a/p2pserver/net/netserver/nbr_peers.go b/p2pserver/net/netserver/nbr_peers.go
new file mode 100644
index 0000000..5e266c3
--- /dev/null
+++ b/p2pserver/net/netserver/nbr_peers.go
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package netserver
+
+import (
+ "net"
+ "sync"
+ "sync/atomic"
+
+ "github.com/ontio/ontology/p2pserver/peer"
+
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/message/types"
+)
+
+// Conn is a net.Conn wrapper to do some clean up when Close.
+type Conn struct {
+ net.Conn
+ session uint64
+ id common.PeerId
+ closed bool
+ netServer *NetServer
+}
+
+// Close overwrite net.Conn
+func (self *Conn) Close() error {
+ self.netServer.Np.Lock()
+ defer self.netServer.Np.Unlock()
+ if self.closed {
+ return nil
+ }
+
+ n := self.netServer.Np.List[self.id]
+ if n.Peer == nil {
+ log.Fatalf("connection %s not in net server", self.id.ToHexString())
+ } else if n.session == self.session { // connection not replaced
+ delete(self.netServer.Np.List, self.id)
+ // need handle asynchronously since we hold Np.Lock
+ log.Infof("remove peer %s from net server", self.id.ToHexString())
+ go self.netServer.notifyPeerDisconnected(n.Peer.Info)
+ }
+
+ self.closed = true
+ return self.Conn.Close()
+}
+
+type connectedPeer struct {
+ session uint64
+ Peer *peer.Peer
+}
+
+//NbrPeers: The neigbor list
+type NbrPeers struct {
+ sync.RWMutex
+ List map[common.PeerId]connectedPeer
+
+ nextSessionId uint64
+}
+
+func (self *NbrPeers) getSessionId() uint64 {
+ return atomic.AddUint64(&self.nextSessionId, 1)
+}
+
+func NewNbrPeers() *NbrPeers {
+ return &NbrPeers{
+ List: make(map[common.PeerId]connectedPeer),
+ }
+}
+
+//Broadcast tranfer msg buffer to all establish Peer
+func (this *NbrPeers) Broadcast(msg types.Message) {
+ sink := comm.NewZeroCopySink(nil)
+ types.WriteMessage(sink, msg)
+
+ this.RLock()
+ defer this.RUnlock()
+ for _, node := range this.List {
+ if node.Peer.GetRelay() {
+ go node.Peer.SendRaw(msg.CmdType(), sink.Bytes())
+ }
+ }
+}
+
+//NodeExisted return when Peer in nbr list
+func (this *NbrPeers) NodeExisted(uid common.PeerId) bool {
+ _, ok := this.List[uid]
+ return ok
+}
+
+//GetPeer return Peer according to id
+func (this *NbrPeers) GetPeer(id common.PeerId) *peer.Peer {
+ this.Lock()
+ defer this.Unlock()
+ n, exist := this.List[id]
+ if !exist {
+ return nil
+ }
+ return n.Peer
+}
+
+func (self *NbrPeers) ReplacePeer(p *peer.Peer, net *NetServer) *peer.Peer {
+ var result *peer.Peer
+ self.Lock()
+ defer self.Unlock()
+
+ n := self.List[p.Info.Id]
+ result = n.Peer
+
+ conn := &Conn{
+ Conn: p.Link.GetConn(),
+ session: self.getSessionId(),
+ id: p.Info.Id,
+ netServer: net,
+ }
+ p.Link.SetConn(conn)
+ self.List[p.Info.Id] = connectedPeer{session: conn.session, Peer: p}
+
+ return result
+}
+
+//GetNeighborAddrs return all establish Peer address
+func (this *NbrPeers) GetNeighborAddrs() []common.PeerAddr {
+ this.RLock()
+ defer this.RUnlock()
+
+ var addrs []common.PeerAddr
+ for _, node := range this.List {
+ p := node.Peer
+ var addr common.PeerAddr
+ addr.IpAddr, _ = p.GetAddr16()
+ addr.Time = p.GetTimeStamp()
+ addr.Services = p.GetServices()
+ addr.Port = p.GetPort()
+ addr.ID = p.GetID()
+ addrs = append(addrs, addr)
+ }
+
+ return addrs
+}
+
+//GetNeighborHeights return the id-height map of nbr peers
+func (this *NbrPeers) GetNeighborHeights() map[common.PeerId]uint64 {
+ this.RLock()
+ defer this.RUnlock()
+
+ hm := make(map[common.PeerId]uint64)
+ for _, p := range this.List {
+ n := p.Peer
+ hm[n.GetID()] = n.GetHeight()
+ }
+ return hm
+}
+
+//GetNeighborMostHeight return the most height of nbr peers
+func (this *NbrPeers) GetNeighborMostHeight() uint64 {
+ this.RLock()
+ defer this.RUnlock()
+ mostHeight := uint64(0)
+ for _, p := range this.List {
+ n := p.Peer
+ height := n.GetHeight()
+ if mostHeight < height {
+ mostHeight = height
+ }
+ }
+ return mostHeight
+}
+
+//GetNeighbors return all establish peers in nbr list
+func (this *NbrPeers) GetNeighbors() []*peer.Peer {
+ this.RLock()
+ defer this.RUnlock()
+ var peers []*peer.Peer
+ for _, p := range this.List {
+ n := p.Peer
+ peers = append(peers, n)
+ }
+ return peers
+}
+
+//GetNbrNodeCnt return count of establish peers in nbrlist
+func (this *NbrPeers) GetNbrNodeCnt() uint32 {
+ this.RLock()
+ defer this.RUnlock()
+ return uint32(len(this.List))
+}
diff --git a/p2pserver/net/netserver/netserver.go b/p2pserver/net/netserver/netserver.go
new file mode 100644
index 0000000..bccb8e5
--- /dev/null
+++ b/p2pserver/net/netserver/netserver.go
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package netserver
+
+import (
+ "errors"
+ "net"
+ "time"
+
+ "github.com/ontio/ontology/common/config"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/connect_controller"
+ "github.com/ontio/ontology/p2pserver/message/types"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/peer"
+)
+
+//NewNetServer return the net object in p2p
+func NewNetServer(protocol p2p.Protocol, conf *config.P2PNodeConfig) (*NetServer, error) {
+ n := &NetServer{
+ NetChan: make(chan *types.MsgPayload, common.CHAN_CAPABILITY),
+ base: &peer.PeerInfo{},
+ Np: NewNbrPeers(),
+ protocol: protocol,
+ stopRecvCh: make(chan bool),
+ }
+
+ err := n.init(conf)
+ if err != nil {
+ return nil, err
+ }
+ return n, nil
+}
+
+func NewCustomNetServer(id *common.PeerKeyId, info *peer.PeerInfo, proto p2p.Protocol,
+ listener net.Listener, opt connect_controller.ConnCtrlOption) *NetServer {
+ n := &NetServer{
+ base: info,
+ listener: listener,
+ protocol: proto,
+ NetChan: make(chan *types.MsgPayload, common.CHAN_CAPABILITY),
+ Np: NewNbrPeers(),
+ stopRecvCh: make(chan bool),
+ }
+ n.connCtrl = connect_controller.NewConnectController(info, id, opt)
+
+ return n
+}
+
+//NetServer represent all the actions in net layer
+type NetServer struct {
+ base *peer.PeerInfo
+ listener net.Listener
+ protocol p2p.Protocol
+ NetChan chan *types.MsgPayload
+ Np *NbrPeers
+
+ connCtrl *connect_controller.ConnectController
+
+ stopRecvCh chan bool // To stop sync channel
+}
+
+// processMessage loops to handle the message from the network
+func (this *NetServer) processMessage(channel chan *types.MsgPayload,
+ stopCh chan bool) {
+ for {
+ select {
+ case data, ok := <-channel:
+ if ok {
+ sender := this.GetPeer(data.Id)
+ if sender == nil {
+ log.Warnf("[router] remote peer %s invalid.", data.Id.ToHexString())
+ continue
+ }
+
+ ctx := p2p.NewContext(sender, this, data.PayloadSize)
+ go this.protocol.HandlePeerMessage(ctx, data.Payload)
+ }
+ case <-stopCh:
+ return
+ }
+ }
+}
+
+//init initializes attribute of network server
+func (this *NetServer) init(conf *config.P2PNodeConfig) error {
+ keyId := common.RandPeerKeyId()
+
+ httpInfo := conf.HttpInfoPort
+ nodePort := conf.NodePort
+ if nodePort == 0 {
+ log.Error("[p2p]link port invalid")
+ return errors.New("[p2p]invalid link port")
+ }
+
+ this.base = peer.NewPeerInfo(keyId.Id, common.PROTOCOL_VERSION, common.SERVICE_NODE, true, httpInfo,
+ nodePort, 0, config.Version, "")
+
+ option, err := connect_controller.ConnCtrlOptionFromConfig(conf)
+ if err != nil {
+ return err
+ }
+ this.connCtrl = connect_controller.NewConnectController(this.base, keyId, option)
+
+ syncPort := this.base.Port
+ if syncPort == 0 {
+ log.Error("[p2p]sync port invalid")
+ return errors.New("[p2p]sync port invalid")
+ }
+ this.listener, err = connect_controller.NewListener(syncPort, config.DefConfig.P2PNode)
+ if err != nil {
+ log.Error("[p2p]failed to create sync listener")
+ return errors.New("[p2p]failed to create sync listener")
+ }
+
+ log.Infof("[p2p]init peer ID to %s", this.base.Id.ToHexString())
+
+ return nil
+}
+
+//InitListen start listening on the config port
+func (this *NetServer) Start() error {
+ this.protocol.HandleSystemMessage(this, p2p.NetworkStart{})
+ go this.startNetAccept(this.listener)
+ log.Infof("[p2p]start listen on sync port %d", this.base.Port)
+ go this.processMessage(this.NetChan, this.stopRecvCh)
+
+ log.Debug("[p2p]MessageRouter start to parse p2p message...")
+ return nil
+}
+
+//GetVersion return self peer`s version
+func (this *NetServer) GetHostInfo() *peer.PeerInfo {
+ return this.base
+}
+
+//GetId return peer`s id
+func (this *NetServer) GetID() common.PeerId {
+ return this.base.Id
+}
+
+// SetHeight sets the local's height
+func (this *NetServer) SetHeight(height uint64) {
+ this.base.Height = height
+}
+
+// GetHeight return peer's heigh
+func (this *NetServer) GetHeight() uint64 {
+ return this.base.Height
+}
+
+// GetPeer returns a peer with the peer id
+func (this *NetServer) GetPeer(id common.PeerId) *peer.Peer {
+ return this.Np.GetPeer(id)
+}
+
+//GetNeighborAddrs return all the nbr peer`s addr
+func (this *NetServer) GetNeighborAddrs() []common.PeerAddr {
+ return this.Np.GetNeighborAddrs()
+}
+
+//GetConnectionCnt return the total number of valid connections
+func (this *NetServer) GetConnectionCnt() uint32 {
+ return this.Np.GetNbrNodeCnt()
+}
+
+//GetMaxPeerBlockHeight return the most height of valid connections
+func (this *NetServer) GetMaxPeerBlockHeight() uint64 {
+ return this.Np.GetNeighborMostHeight()
+}
+
+func (this *NetServer) ReplacePeer(remotePeer *peer.Peer) {
+ old := this.Np.ReplacePeer(remotePeer, this)
+ if old != nil {
+ old.Close()
+ }
+}
+
+//GetNeighbors return all nbr peer
+func (this *NetServer) GetNeighbors() []*peer.Peer {
+ return this.Np.GetNeighbors()
+}
+
+//Broadcast called by actor, broadcast msg
+func (this *NetServer) Broadcast(msg types.Message) {
+ this.Np.Broadcast(msg)
+}
+
+//Tx sendMsg data buf to peer
+func (this *NetServer) Send(p *peer.Peer, msg types.Message) error {
+ if p != nil {
+ return p.Send(msg)
+ }
+ log.Warn("[p2p]sendMsg to a invalid peer")
+ return errors.New("[p2p]sendMsg to a invalid peer")
+}
+
+//Connect used to connect net address under sync or cons mode
+func (this *NetServer) Connect(addr string) {
+ err := this.connect(addr)
+ if err != nil {
+ log.Debugf("%s connecting to %s failed, err: %s", this.base.Addr, addr, err)
+ }
+}
+
+//Connect used to connect net address under sync or cons mode
+func (this *NetServer) connect(addr string) error {
+ peerInfo, conn, err := this.connCtrl.Connect(addr)
+ if err != nil {
+ return err
+ }
+ remotePeer := createPeer(peerInfo, conn)
+
+ remotePeer.AttachChan(this.NetChan)
+ this.ReplacePeer(remotePeer)
+ go remotePeer.Link.Rx()
+
+ this.protocol.HandleSystemMessage(this, p2p.PeerConnected{Info: remotePeer.Info})
+ return nil
+}
+
+func (this *NetServer) notifyPeerConnected(p *peer.PeerInfo) {
+ this.protocol.HandleSystemMessage(this, p2p.PeerConnected{Info: p})
+}
+
+func (this *NetServer) notifyPeerDisconnected(p *peer.PeerInfo) {
+ this.protocol.HandleSystemMessage(this, p2p.PeerDisConnected{Info: p})
+}
+
+//Stop stop all net layer logic
+func (this *NetServer) Stop() {
+ peers := this.Np.GetNeighbors()
+ for _, p := range peers {
+ p.Close()
+ }
+
+ if this.listener != nil {
+ _ = this.listener.Close()
+ }
+ close(this.stopRecvCh)
+ this.protocol.HandleSystemMessage(this, p2p.NetworkStop{})
+}
+
+func (this *NetServer) handleClientConnection(conn net.Conn) error {
+ peerInfo, conn, err := this.connCtrl.AcceptConnect(conn)
+ if err != nil {
+ return err
+ }
+ remotePeer := createPeer(peerInfo, conn)
+ remotePeer.AttachChan(this.NetChan)
+ this.ReplacePeer(remotePeer)
+
+ go remotePeer.Link.Rx()
+ this.protocol.HandleSystemMessage(this, p2p.PeerConnected{Info: remotePeer.Info})
+ return nil
+}
+
+//startNetAccept accepts the sync connection from the inbound peer
+func (this *NetServer) startNetAccept(listener net.Listener) {
+ for {
+ conn, err := listener.Accept()
+
+ if err != nil {
+ log.Error("[p2p]error accepting ", err.Error())
+ return
+ }
+
+ go func() {
+ if err := this.handleClientConnection(conn); err != nil {
+ log.Warnf("[p2p] client connect error: %s", err)
+ _ = conn.Close()
+ }
+ }()
+ }
+}
+
+//GetOutConnRecordLen return length of outConnRecord
+func (this *NetServer) GetOutConnRecordLen() uint {
+ return this.connCtrl.OutboundsCount()
+}
+
+//check own network address
+func (this *NetServer) IsOwnAddress(addr string) bool {
+ return addr == this.connCtrl.OwnAddress()
+}
+
+func createPeer(info *peer.PeerInfo, conn net.Conn) *peer.Peer {
+ remotePeer := peer.NewPeer()
+ remotePeer.SetInfo(info)
+ remotePeer.Link.UpdateRXTime(time.Now())
+ remotePeer.Link.SetAddr(conn.RemoteAddr().String())
+ remotePeer.Link.SetConn(conn)
+ remotePeer.Link.SetID(info.Id)
+
+ return remotePeer
+}
+
+func (ns *NetServer) ConnectController() *connect_controller.ConnectController {
+ return ns.connCtrl
+}
+
+func (ns *NetServer) Protocol() p2p.Protocol {
+ return ns.protocol
+}
+
+func (this *NetServer) SendTo(p common.PeerId, msg types.Message) {
+ peer := this.GetPeer(p)
+ if peer != nil {
+ this.Send(peer, msg)
+ }
+}
diff --git a/p2pserver/net/netserver/netserver_test.go b/p2pserver/net/netserver/netserver_test.go
new file mode 100644
index 0000000..be034f6
--- /dev/null
+++ b/p2pserver/net/netserver/netserver_test.go
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package netserver
+
+import "testing"
+
+func TestReconnect(t *testing.T) {
+
+}
diff --git a/p2pserver/net/protocol/protocol.go b/p2pserver/net/protocol/protocol.go
new file mode 100644
index 0000000..ce67f16
--- /dev/null
+++ b/p2pserver/net/protocol/protocol.go
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package p2p
+
+import (
+ "github.com/ontio/ontology/p2pserver/message/types"
+ "github.com/ontio/ontology/p2pserver/peer"
+)
+
+type Context struct {
+ sender *peer.Peer
+ net P2P
+ MsgSize uint32
+}
+
+func NewContext(sender *peer.Peer, net P2P, msgSize uint32) *Context {
+ return &Context{sender, net, msgSize}
+}
+
+func (self *Context) Sender() *peer.Peer {
+ return self.sender
+}
+
+func (self *Context) Network() P2P {
+ return self.net
+}
+
+type Protocol interface {
+ HandlePeerMessage(ctx *Context, msg types.Message)
+ HandleSystemMessage(net P2P, msg SystemMessage)
+}
+
+type SystemMessage interface {
+ systemMessage()
+}
+
+type implSystemMessage struct{}
+
+func (self implSystemMessage) systemMessage() {}
+
+type PeerConnected struct {
+ Info *peer.PeerInfo
+ implSystemMessage
+}
+
+type PeerDisConnected struct {
+ Info *peer.PeerInfo
+ implSystemMessage
+}
+
+type NetworkStart struct {
+ implSystemMessage
+}
+
+type NetworkStop struct {
+ implSystemMessage
+}
diff --git a/p2pserver/net/protocol/server.go b/p2pserver/net/protocol/server.go
new file mode 100644
index 0000000..a0026e3
--- /dev/null
+++ b/p2pserver/net/protocol/server.go
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+// Package p2p provides an network interface
+package p2p
+
+import (
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/message/types"
+ "github.com/ontio/ontology/p2pserver/peer"
+)
+
+//P2P represent the net interface of p2p package
+type P2P interface {
+ Connect(addr string)
+ GetHostInfo() *peer.PeerInfo
+ GetID() common.PeerId
+ GetNeighbors() []*peer.Peer
+ GetNeighborAddrs() []common.PeerAddr
+ GetConnectionCnt() uint32
+ GetMaxPeerBlockHeight() uint64
+ GetPeer(id common.PeerId) *peer.Peer
+ SetHeight(uint64)
+ Send(p *peer.Peer, msg types.Message) error
+ SendTo(p common.PeerId, msg types.Message)
+ GetOutConnRecordLen() uint
+ Broadcast(msg types.Message)
+ IsOwnAddress(addr string) bool
+}
diff --git a/p2pserver/p2pserver.go b/p2pserver/p2pserver.go
new file mode 100644
index 0000000..20a5b56
--- /dev/null
+++ b/p2pserver/p2pserver.go
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package p2pserver
+
+import (
+ "strings"
+ "time"
+
+ "github.com/ontio/ontology/common/config"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/core/ledger"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/net/netserver"
+ p2pnet "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/protocols"
+)
+
+//P2PServer control all network activities
+type P2PServer struct {
+ network *netserver.NetServer
+}
+
+//NewServer return a new p2pserver according to the pubkey
+func NewServer() (*P2PServer, error) {
+ ld := ledger.DefLedger
+
+ protocol := protocols.NewMsgHandler(ld)
+ n, err := netserver.NewNetServer(protocol, config.DefConfig.P2PNode)
+ if err != nil {
+ return nil, err
+ }
+
+ p := &P2PServer{
+ network: n,
+ }
+
+ return p, nil
+}
+
+//Start create all services
+func (this *P2PServer) Start() error {
+ return this.network.Start()
+}
+
+//Stop halt all service by send signal to channels
+func (this *P2PServer) Stop() {
+ this.network.Stop()
+}
+
+// GetNetwork returns the low level netserver
+func (this *P2PServer) GetNetwork() p2pnet.P2P {
+ return this.network
+}
+
+//WaitForPeersStart check whether enough peer linked in loop
+func (this *P2PServer) WaitForPeersStart() {
+ periodTime := config.DEFAULT_GEN_BLOCK_TIME / common.UPDATE_RATE_PER_BLOCK
+ for {
+ log.Info("[p2p]Wait for minimum connection...")
+ if this.reachMinConnection() {
+ break
+ }
+
+ <-time.After(time.Second * (time.Duration(periodTime)))
+ }
+}
+
+//reachMinConnection return whether net layer have enough link under different config
+func (this *P2PServer) reachMinConnection() bool {
+ if !config.DefConfig.Consensus.EnableConsensus {
+ //just sync
+ return true
+ }
+ consensusType := strings.ToLower(config.DefConfig.Genesis.ConsensusType)
+ if consensusType == "" {
+ consensusType = "dbft"
+ }
+ minCount := config.DBFT_MIN_NODE_NUM
+ switch consensusType {
+ case "dbft":
+ case "solo":
+ minCount = config.SOLO_MIN_NODE_NUM
+ case "vbft":
+ minCount = config.VBFT_MIN_NODE_NUM
+
+ }
+ return int(this.network.GetConnectionCnt())+1 >= minCount
+}
diff --git a/p2pserver/peer/peer.go b/p2pserver/peer/peer.go
new file mode 100644
index 0000000..b5675a9
--- /dev/null
+++ b/p2pserver/peer/peer.go
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package peer
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ comm "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ conn "github.com/ontio/ontology/p2pserver/link"
+ "github.com/ontio/ontology/p2pserver/message/types"
+)
+
+// PeerInfo provides the basic information of a peer
+type PeerInfo struct {
+ Id common.PeerId
+ Version uint32
+ Services uint64
+ Relay bool
+ HttpInfoPort uint16
+ Port uint16
+ Height uint64
+ SoftVersion string
+ Addr string
+}
+
+func NewPeerInfo(id common.PeerId, version uint32, services uint64, relay bool, httpInfoPort uint16,
+ port uint16, height uint64, softVersion string, addr string) *PeerInfo {
+ return &PeerInfo{
+ Id: id,
+ Version: version,
+ Services: services,
+ Relay: relay,
+ HttpInfoPort: httpInfoPort,
+ Port: port,
+ Height: height,
+ SoftVersion: softVersion,
+ Addr: addr,
+ }
+}
+
+// RemoteListen get remote service port
+func (pi *PeerInfo) RemoteListenAddress() string {
+ host, _, err := net.SplitHostPort(pi.Addr)
+ if err != nil {
+ return ""
+ }
+
+ sb := strings.Builder{}
+ sb.WriteString(host)
+ sb.WriteString(":")
+ sb.WriteString(strconv.Itoa(int(pi.Port)))
+
+ return sb.String()
+}
+
+//Peer represent the node in p2p
+type Peer struct {
+ Info *PeerInfo
+ Link *conn.Link
+ connLock sync.RWMutex
+}
+
+//NewPeer return new peer without publickey initial
+func NewPeer() *Peer {
+ p := &Peer{
+ Info: &PeerInfo{},
+ Link: conn.NewLink(),
+ }
+ return p
+}
+
+func (self *Peer) SetInfo(info *PeerInfo) {
+ self.Info = info
+}
+
+func (self *PeerInfo) String() string {
+ return fmt.Sprintf("id=%s, version=%s", self.Id.ToHexString(), self.SoftVersion)
+}
+
+//DumpInfo print all information of peer
+func (this *Peer) DumpInfo() {
+ log.Debug("[p2p]Node Info:")
+ log.Debug("[p2p]\t id = ", this.GetID())
+ log.Debug("[p2p]\t addr = ", this.Info.Addr)
+ log.Debug("[p2p]\t version = ", this.GetVersion())
+ log.Debug("[p2p]\t services = ", this.GetServices())
+ log.Debug("[p2p]\t port = ", this.GetPort())
+ log.Debug("[p2p]\t relay = ", this.GetRelay())
+ log.Debug("[p2p]\t height = ", this.GetHeight())
+ log.Debug("[p2p]\t softVersion = ", this.GetSoftVersion())
+}
+
+//GetVersion return peer`s version
+func (this *Peer) GetVersion() uint32 {
+ return this.Info.Version
+}
+
+//GetHeight return peer`s block height
+func (this *Peer) GetHeight() uint64 {
+ return this.Info.Height
+}
+
+//SetHeight set height to peer
+func (this *Peer) SetHeight(height uint64) {
+ this.Info.Height = height
+}
+
+//GetPort return Peer`s sync port
+func (this *Peer) GetPort() uint16 {
+ return this.Info.Port
+}
+
+//SendTo call sync link to send buffer
+func (this *Peer) SendRaw(msgType string, msgPayload []byte) error {
+ if this.Link != nil && this.Link.Valid() {
+ return this.Link.SendRaw(msgPayload)
+ }
+ return errors.New("[p2p]sync link invalid")
+}
+
+//Close halt sync connection
+func (this *Peer) Close() {
+ this.connLock.Lock()
+ this.Link.CloseConn()
+ this.connLock.Unlock()
+}
+
+//GetID return peer`s id
+func (this *Peer) GetID() common.PeerId {
+ return this.Info.Id
+}
+
+//GetRelay return peer`s relay state
+func (this *Peer) GetRelay() bool {
+ return this.Info.Relay
+}
+
+//GetServices return peer`s service state
+func (this *Peer) GetServices() uint64 {
+ return this.Info.Services
+}
+
+//GetTimeStamp return peer`s latest contact time in ticks
+func (this *Peer) GetTimeStamp() int64 {
+ return this.Link.GetRXTime().UnixNano()
+}
+
+//GetContactTime return peer`s latest contact time in Time struct
+func (this *Peer) GetContactTime() time.Time {
+ return this.Link.GetRXTime()
+}
+
+//GetAddr return peer`s sync link address
+func (this *Peer) GetAddr() string {
+ return this.Info.Addr
+}
+
+//GetAddr16 return peer`s sync link address in []byte
+func (this *Peer) GetAddr16() ([16]byte, error) {
+ var result [16]byte
+ addrIp, err := common.ParseIPAddr(this.GetAddr())
+ if err != nil {
+ return result, err
+ }
+ ip := net.ParseIP(addrIp).To16()
+ if ip == nil {
+ log.Warn("[p2p]parse ip address error\n", this.GetAddr())
+ return result, errors.New("[p2p]parse ip address error")
+ }
+
+ copy(result[:], ip[:16])
+ return result, nil
+}
+
+func (this *Peer) GetSoftVersion() string {
+ return this.Info.SoftVersion
+}
+
+//AttachChan set msg chan to sync link
+func (this *Peer) AttachChan(msgchan chan *types.MsgPayload) {
+ this.Link.SetChan(msgchan)
+}
+
+//Send transfer buffer by sync or cons link
+func (this *Peer) Send(msg types.Message) error {
+ sink := comm.NewZeroCopySink(nil)
+ types.WriteMessage(sink, msg)
+
+ return this.SendRaw(msg.CmdType(), sink.Bytes())
+}
+
+//GetHttpInfoPort return peer`s httpinfo port
+func (this *Peer) GetHttpInfoPort() uint16 {
+ return this.Info.HttpInfoPort
+}
+
+//SetHttpInfoPort set peer`s httpinfo port
+func (this *Peer) SetHttpInfoPort(port uint16) {
+ this.Info.HttpInfoPort = port
+}
+
+//UpdateInfo update peer`s information
+func (this *Peer) UpdateInfo(t time.Time, version uint32, services uint64,
+ syncPort uint16, kid common.PeerId, relay uint8, height uint64, softVer string) {
+ this.Info.Id = kid
+ this.Info.Version = version
+ this.Info.Services = services
+ this.Info.Port = syncPort
+ this.Info.SoftVersion = softVer
+ this.Info.Relay = relay != 0
+ this.Info.Height = height
+
+ this.Link.UpdateRXTime(t)
+}
diff --git a/p2pserver/protocols/block_sync/block_sync.go b/p2pserver/protocols/block_sync/block_sync.go
new file mode 100644
index 0000000..40ac946
--- /dev/null
+++ b/p2pserver/protocols/block_sync/block_sync.go
@@ -0,0 +1,1010 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package block_sync
+
+import (
+ "math"
+ "sort"
+ "sync"
+ "time"
+
+ "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/core/ledger"
+ "github.com/ontio/ontology/core/types"
+ p2pComm "github.com/ontio/ontology/p2pserver/common"
+ msgpack "github.com/ontio/ontology/p2pserver/message/msg_pack"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/peer"
+)
+
+const (
+ SYNC_MAX_HEADER_FORWARD_SIZE = 5000 //keep CurrentHeaderHeight - CurrentBlockHeight <= SYNC_MAX_HEADER_FORWARD_SIZE
+ SYNC_MAX_FLIGHT_HEADER_SIZE = 1 //Number of headers on flight
+ SYNC_MAX_FLIGHT_BLOCK_SIZE = 50 //Number of blocks on flight
+ SYNC_MAX_BLOCK_CACHE_SIZE = 500 //Cache size of block wait to commit to ledger
+ SYNC_HEADER_REQUEST_TIMEOUT = 2 //s, Request header timeout time. If header haven't receive after SYNC_HEADER_REQUEST_TIMEOUT second, retry
+ SYNC_BLOCK_REQUEST_TIMEOUT = 2 //s, Request block timeout time. If block haven't received after SYNC_BLOCK_REQUEST_TIMEOUT second, retry
+ SYNC_NEXT_BLOCK_TIMES = 3 //Request times of next height block
+ SYNC_NEXT_BLOCKS_HEIGHT = 2 //for current block height plus next
+ SYNC_NODE_RECORD_SPEED_CNT = 3 //Record speed count for accuracy
+ SYNC_NODE_RECORD_TIME_CNT = 3 //Record request time for accuracy
+ SYNC_NODE_SPEED_INIT = 100 * 1024 //Init a big speed (100MB/s) for every node in first round
+ SYNC_MAX_ERROR_RESP_TIMES = 5 //Max error headers/blocks response times, if reaches, delete it
+ SYNC_MAX_HEIGHT_OFFSET = 5 //Offset of the max height and current height
+)
+
+//NodeWeight record some params of node, using for sort
+type NodeWeight struct {
+ id p2pComm.PeerId //NodeID
+ speed []float32 //Record node request-response speed, using for calc the avg speed, unit kB/s
+ timeoutCnt int //Node response timeout count
+ errorRespCnt int //Node response error data count
+ reqTime []int64 //Record request time, using for calc the avg req time interval, unit millisecond
+}
+
+//NewNodeWeight new a nodeweight
+func NewNodeWeight(id p2pComm.PeerId) *NodeWeight {
+ s := make([]float32, 0, SYNC_NODE_RECORD_SPEED_CNT)
+ for i := 0; i < SYNC_NODE_RECORD_SPEED_CNT; i++ {
+ s = append(s, float32(SYNC_NODE_SPEED_INIT))
+ }
+ r := make([]int64, 0, SYNC_NODE_RECORD_TIME_CNT)
+ now := time.Now().UnixNano() / int64(time.Millisecond)
+ for i := 0; i < SYNC_NODE_RECORD_TIME_CNT; i++ {
+ r = append(r, now)
+ }
+ return &NodeWeight{
+ id: id,
+ speed: s,
+ timeoutCnt: 0,
+ errorRespCnt: 0,
+ reqTime: r,
+ }
+}
+
+//AddTimeoutCnt incre timeout count
+func (this *NodeWeight) AddTimeoutCnt() {
+ this.timeoutCnt++
+}
+
+//AddErrorRespCnt incre receive error header/block count
+func (this *NodeWeight) AddErrorRespCnt() {
+ this.errorRespCnt++
+}
+
+//GetErrorRespCnt get the error response count
+func (this *NodeWeight) GetErrorRespCnt() int {
+ return this.errorRespCnt
+}
+
+//AppendNewReqTime append new request time
+func (this *NodeWeight) AppendNewReqtime() {
+ copy(this.reqTime[0:SYNC_NODE_RECORD_TIME_CNT-1], this.reqTime[1:])
+ this.reqTime[SYNC_NODE_RECORD_TIME_CNT-1] = time.Now().UnixNano() / int64(time.Millisecond)
+}
+
+//addNewSpeed apend the new speed to tail, remove the oldest one
+func (this *NodeWeight) AppendNewSpeed(s float32) {
+ copy(this.speed[0:SYNC_NODE_RECORD_SPEED_CNT-1], this.speed[1:])
+ this.speed[SYNC_NODE_RECORD_SPEED_CNT-1] = s
+}
+
+//Weight calculate node's weight for sort. Highest weight node will be accessed first for next request.
+func (this *NodeWeight) Weight() float32 {
+ avgSpeed := float32(0.0)
+ for _, s := range this.speed {
+ avgSpeed += s
+ }
+ avgSpeed = avgSpeed / float32(len(this.speed))
+
+ avgInterval := float32(0.0)
+ now := time.Now().UnixNano() / int64(time.Millisecond)
+ for _, t := range this.reqTime {
+ avgInterval += float32(now - t)
+ }
+ avgInterval = avgInterval / float32(len(this.reqTime))
+ w := avgSpeed + avgInterval
+ return w
+}
+
+//NodeWeights implement sorting
+type NodeWeights []*NodeWeight
+
+func (nws NodeWeights) Len() int {
+ return len(nws)
+}
+
+func (nws NodeWeights) Swap(i, j int) {
+ nws[i], nws[j] = nws[j], nws[i]
+}
+func (nws NodeWeights) Less(i, j int) bool {
+ ni := nws[i]
+ nj := nws[j]
+ return ni.Weight() < nj.Weight() && ni.errorRespCnt >= nj.errorRespCnt && ni.timeoutCnt >= nj.timeoutCnt
+}
+
+//SyncFlightInfo record the info of fight object(header or block)
+type SyncFlightInfo struct {
+ Height uint32 //BlockHeight of HeaderHeight
+ nodeId p2pComm.PeerId //The current node to send msg
+ startTime time.Time //Request start time
+ failedNodes map[p2pComm.PeerId]int //Map nodeId => timeout times
+ totalFailed int //Total timeout times
+ lock sync.RWMutex
+}
+
+//NewSyncFlightInfo return a new SyncFlightInfo instance
+func NewSyncFlightInfo(height uint32, nodeId p2pComm.PeerId) *SyncFlightInfo {
+ return &SyncFlightInfo{
+ Height: height,
+ nodeId: nodeId,
+ startTime: time.Now(),
+ failedNodes: make(map[p2pComm.PeerId]int),
+ }
+}
+
+//GetNodeId return current node id for sending msg
+func (this *SyncFlightInfo) GetNodeId() p2pComm.PeerId {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ return this.nodeId
+}
+
+//SetNodeId set a new node id
+func (this *SyncFlightInfo) SetNodeId(nodeId p2pComm.PeerId) {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ this.nodeId = nodeId
+}
+
+//MarkFailedNode mark node failed, after request timeout
+func (this *SyncFlightInfo) MarkFailedNode() {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ this.failedNodes[this.nodeId] += 1
+ this.totalFailed++
+}
+
+//GetFailedTimes return failed times of a node
+func (this *SyncFlightInfo) GetFailedTimes(nodeId p2pComm.PeerId) int {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ times, ok := this.failedNodes[nodeId]
+ if !ok {
+ return 0
+ }
+ return times
+}
+
+//GetTotalFailedTimes return the total failed times of request
+func (this *SyncFlightInfo) GetTotalFailedTimes() int {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ return this.totalFailed
+}
+
+//ResetStartTime
+func (this *SyncFlightInfo) ResetStartTime() {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ this.startTime = time.Now()
+}
+
+//GetStartTime return the start time of request
+func (this *SyncFlightInfo) GetStartTime() time.Time {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ return this.startTime
+}
+
+//BlockInfo is used for saving block information in cache
+type BlockInfo struct {
+ nodeID p2pComm.PeerId
+ block *types.Block
+ crossChainMsg *types.CrossChainMsg
+ merkleRoot common.Uint256
+}
+
+//BlockSyncMgr is the manager class to deal with block sync
+type BlockSyncMgr struct {
+ flightBlocks map[common.Uint256][]*SyncFlightInfo //Map BlockHash => []SyncFlightInfo, using for manager all of those block flights
+ flightHeaders map[uint32]*SyncFlightInfo //Map HeaderHeight => SyncFlightInfo, using for manager all of those header flights
+ blocksCache *BlockCache //Map BlockHash => BlockInfo, using for cache the blocks receive from net, and waiting for commit to ledger
+ server p2p.P2P //Pointer to the local node
+ syncBlockLock bool //Help to avoid send block sync request duplicate
+ syncHeaderLock bool //Help to avoid send header sync request duplicate
+ saveBlockLock bool //Help to avoid saving block concurrently
+ exitCh chan interface{} //ExitCh to receive exit signal
+ ledger *ledger.Ledger //ledger
+ lock sync.RWMutex //lock
+ nodeWeights map[p2pComm.PeerId]*NodeWeight //Map NodeID => NodeStatus, using for getNextNode
+}
+
+//NewBlockSyncMgr return a BlockSyncMgr instance
+func NewBlockSyncMgr(server p2p.P2P, ld *ledger.Ledger) *BlockSyncMgr {
+ return &BlockSyncMgr{
+ flightBlocks: make(map[common.Uint256][]*SyncFlightInfo),
+ flightHeaders: make(map[uint32]*SyncFlightInfo),
+ blocksCache: NewBlockCache(),
+ server: server,
+ ledger: ld,
+ exitCh: make(chan interface{}, 1),
+ nodeWeights: make(map[p2pComm.PeerId]*NodeWeight),
+ }
+}
+
+type BlockCache struct {
+ emptyBlockAmount int
+ blocksCache map[uint32]*BlockInfo //Map BlockHeight => BlockInfo, using for cache the blocks receive from net, and waiting for commit to ledger
+}
+
+func NewBlockCache() *BlockCache {
+ return &BlockCache{
+ emptyBlockAmount: 0,
+ blocksCache: make(map[uint32]*BlockInfo),
+ }
+}
+
+func (this *BlockCache) addBlock(nodeID p2pComm.PeerId, block *types.Block, ccMsg *types.CrossChainMsg,
+ merkleRoot common.Uint256) bool {
+ this.delBlockLocked(block.Header.Height)
+ blockInfo := &BlockInfo{
+ nodeID: nodeID,
+ block: block,
+ crossChainMsg: ccMsg,
+ merkleRoot: merkleRoot,
+ }
+ this.blocksCache[block.Header.Height] = blockInfo
+ if block.Header.TransactionsRoot == common.UINT256_EMPTY {
+ this.emptyBlockAmount += 1
+ }
+ return true
+}
+
+func (this *BlockSyncMgr) clearBlocks(curBlockHeight uint32) {
+ this.lock.Lock()
+ this.blocksCache.clearBlocks(curBlockHeight)
+ this.lock.Unlock()
+}
+
+func (this *BlockCache) clearBlocks(curBlockHeight uint32) {
+ for height := range this.blocksCache {
+ if height < curBlockHeight {
+ this.delBlockLocked(height)
+ }
+ }
+}
+
+func (this *BlockCache) getBlock(blockHeight uint32) (p2pComm.PeerId, *types.Block, *types.CrossChainMsg,
+ common.Uint256) {
+ blockInfo, ok := this.blocksCache[blockHeight]
+ if !ok {
+ return p2pComm.PeerId{}, nil, nil, common.UINT256_EMPTY
+ }
+ return blockInfo.nodeID, blockInfo.block, blockInfo.crossChainMsg, blockInfo.merkleRoot
+}
+
+func (this *BlockCache) delBlockLocked(blockHeight uint32) {
+ blockInfo, ok := this.blocksCache[blockHeight]
+ if ok {
+ if blockInfo.block.Header.TransactionsRoot == common.UINT256_EMPTY {
+ this.emptyBlockAmount -= 1
+ }
+ }
+ delete(this.blocksCache, blockHeight)
+}
+
+func (this *BlockCache) isInBlockCache(blockHeight uint32) bool {
+ _, ok := this.blocksCache[blockHeight]
+ return ok
+}
+
+func (this *BlockCache) getNonEmptyBlockCount() int {
+ return len(this.blocksCache) - this.emptyBlockAmount
+}
+func (this *BlockSyncMgr) getNonEmptyBlockCount() int {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ return this.blocksCache.getNonEmptyBlockCount()
+}
+
+//Start to sync
+func (this *BlockSyncMgr) Start() {
+ go this.sync()
+ ticker := time.NewTicker(time.Second)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-this.exitCh:
+ return
+ case <-ticker.C:
+ go this.checkTimeout()
+ go this.sync()
+ go this.saveBlock()
+ }
+ }
+}
+
+func (this *BlockSyncMgr) checkTimeout() {
+ now := time.Now()
+ headerTimeoutFlights := make(map[uint32]*SyncFlightInfo)
+ blockTimeoutFlights := make(map[common.Uint256][]*SyncFlightInfo)
+ this.lock.RLock()
+ for height, flightInfo := range this.flightHeaders {
+ if int(now.Sub(flightInfo.startTime).Seconds()) >= SYNC_HEADER_REQUEST_TIMEOUT {
+ headerTimeoutFlights[height] = flightInfo
+ }
+ }
+ for blockHash, flightInfos := range this.flightBlocks {
+ for _, flightInfo := range flightInfos {
+ if int(now.Sub(flightInfo.startTime).Seconds()) >= SYNC_BLOCK_REQUEST_TIMEOUT {
+ blockTimeoutFlights[blockHash] = append(blockTimeoutFlights[blockHash], flightInfo)
+ }
+ }
+ }
+ this.lock.RUnlock()
+
+ curHeaderHeight := this.ledger.GetCurrentHeaderHeight()
+ curBlockHeight := this.ledger.GetCurrentBlockHeight()
+
+ for height, flightInfo := range headerTimeoutFlights {
+ this.addTimeoutCnt(flightInfo.GetNodeId())
+ if height <= curHeaderHeight {
+ this.delFlightHeader(height)
+ continue
+ }
+ flightInfo.ResetStartTime()
+ flightInfo.MarkFailedNode()
+ log.Tracef("[p2p]checkTimeout sync headers from id:%d :%d timeout after:%d s Times:%d", flightInfo.GetNodeId(), height, SYNC_HEADER_REQUEST_TIMEOUT, flightInfo.GetTotalFailedTimes())
+ reqNode := this.getNodeWithMinFailedTimes(flightInfo, curBlockHeight)
+ if reqNode == nil {
+ break
+ }
+ flightInfo.SetNodeId(reqNode.GetID())
+
+ headerHash := this.ledger.GetCurrentHeaderHash()
+ msg := msgpack.NewHeadersReq(headerHash)
+ err := this.server.Send(reqNode, msg)
+ if err != nil {
+ log.Warn("[p2p]checkTimeout failed to send a new headersReq:s", err)
+ } else {
+ this.appendReqTime(reqNode.GetID())
+ }
+ }
+ for blockHash, flightInfos := range blockTimeoutFlights {
+ for _, flightInfo := range flightInfos {
+ this.addTimeoutCnt(flightInfo.GetNodeId())
+ if flightInfo.Height <= curBlockHeight {
+ this.delFlightBlock(blockHash)
+ continue
+ }
+ flightInfo.ResetStartTime()
+ flightInfo.MarkFailedNode()
+ log.Tracef("[p2p]checkTimeout sync height:%d block:0x%x timeout after:%d s times:%d", flightInfo.Height, blockHash, SYNC_BLOCK_REQUEST_TIMEOUT, flightInfo.GetTotalFailedTimes())
+ reqNode := this.getNodeWithMinFailedTimes(flightInfo, curBlockHeight)
+ if reqNode == nil {
+ break
+ }
+ flightInfo.SetNodeId(reqNode.GetID())
+
+ msg := msgpack.NewBlkDataReq(blockHash)
+ err := this.server.Send(reqNode, msg)
+ if err != nil {
+ log.Warnf("[p2p]checkTimeout reqNode ID:%d Send error:%s", reqNode.GetID(), err)
+ continue
+ } else {
+ this.appendReqTime(reqNode.GetID())
+ }
+ }
+ }
+}
+
+func (this *BlockSyncMgr) sync() {
+ this.syncHeader()
+ this.syncBlock()
+}
+
+func (this *BlockSyncMgr) syncHeader() {
+ //if !this.server.reachMinConnection() {
+ // return
+ //}
+ if this.tryGetSyncHeaderLock() {
+ return
+ }
+ defer this.releaseSyncHeaderLock()
+
+ if this.getFlightHeaderCount() >= SYNC_MAX_FLIGHT_HEADER_SIZE {
+ return
+ }
+ curBlockHeight := this.ledger.GetCurrentBlockHeight()
+
+ curHeaderHeight := this.ledger.GetCurrentHeaderHeight()
+ //Waiting for block catch up header
+ if curHeaderHeight-curBlockHeight >= SYNC_MAX_HEADER_FORWARD_SIZE {
+ return
+ }
+ NextHeaderId := curHeaderHeight + 1
+ reqNode := this.getNextNode(NextHeaderId)
+ if reqNode == nil {
+ return
+ }
+ this.addFlightHeader(reqNode.GetID(), NextHeaderId)
+
+ headerHash := this.ledger.GetCurrentHeaderHash()
+ msg := msgpack.NewHeadersReq(headerHash)
+ err := this.server.Send(reqNode, msg)
+ if err != nil {
+ log.Warn("[p2p]syncHeader failed to send a new headersReq")
+ } else {
+ this.appendReqTime(reqNode.GetID())
+ }
+
+ log.Infof("Header sync request height:%d", NextHeaderId)
+}
+
+func (this *BlockSyncMgr) syncBlock() {
+ if this.tryGetSyncBlockLock() {
+ return
+ }
+ defer this.releaseSyncBlockLock()
+
+ availCount := SYNC_MAX_FLIGHT_BLOCK_SIZE - this.getFlightBlockCount()
+ if availCount <= 0 {
+ return
+ }
+ curBlockHeight := this.ledger.GetCurrentBlockHeight()
+ curHeaderHeight := this.ledger.GetCurrentHeaderHeight()
+ count := int(curHeaderHeight - curBlockHeight)
+ if count <= 0 {
+ return
+ }
+ if count > availCount {
+ count = availCount
+ }
+ cacheCap := SYNC_MAX_BLOCK_CACHE_SIZE - this.getNonEmptyBlockCount()
+ if count > cacheCap {
+ count = cacheCap
+ }
+
+ counter := 1
+ i := uint32(0)
+ reqTimes := 1
+ for {
+ if counter > count {
+ break
+ }
+ i++
+ nextBlockHeight := curBlockHeight + i
+ nextBlockHash := this.ledger.GetBlockHash(nextBlockHeight)
+ if nextBlockHash == common.UINT256_EMPTY {
+ return
+ }
+ if this.isBlockOnFlight(nextBlockHash) {
+ if nextBlockHeight <= curBlockHeight+SYNC_NEXT_BLOCKS_HEIGHT {
+ //request more nodes for next block height
+ reqTimes = SYNC_NEXT_BLOCK_TIMES
+ } else {
+ continue
+ }
+ }
+ if this.isInBlockCache(nextBlockHeight) {
+ continue
+ }
+ if nextBlockHeight <= curBlockHeight+SYNC_NEXT_BLOCKS_HEIGHT {
+ reqTimes = SYNC_NEXT_BLOCK_TIMES
+ }
+ for t := 0; t < reqTimes; t++ {
+ reqNode := this.getNextNode(nextBlockHeight)
+ if reqNode == nil {
+ return
+ }
+ this.addFlightBlock(reqNode.GetID(), nextBlockHeight, nextBlockHash)
+ msg := msgpack.NewBlkDataReq(nextBlockHash)
+ err := this.server.Send(reqNode, msg)
+ if err != nil {
+ log.Warnf("[p2p]syncBlock Height:%d ReqBlkData error:%s", nextBlockHeight, err)
+ return
+ } else {
+ this.appendReqTime(reqNode.GetID())
+ }
+ }
+ counter++
+ reqTimes = 1
+ }
+}
+
+//OnHeaderReceive receive header from net
+func (this *BlockSyncMgr) OnHeaderReceive(fromID p2pComm.PeerId, headers []*types.Header) {
+ if len(headers) == 0 {
+ return
+ }
+ log.Infof("Header receive height:%d - %d", headers[0].Height, headers[len(headers)-1].Height)
+ height := headers[0].Height
+ curHeaderHeight := this.ledger.GetCurrentHeaderHeight()
+
+ //Means another gorountinue is adding header
+ if height <= curHeaderHeight {
+ return
+ }
+ if !this.isHeaderOnFlight(height) {
+ return
+ }
+ err := this.ledger.AddHeaders(headers)
+ this.delFlightHeader(height)
+ if err != nil {
+ this.addErrorRespCnt(fromID)
+ n := this.getNodeWeight(fromID)
+ if n != nil && n.GetErrorRespCnt() >= SYNC_MAX_ERROR_RESP_TIMES {
+ this.delNode(fromID)
+ }
+ log.Warnf("[p2p]OnHeaderReceive AddHeaders error:%s", err)
+ return
+ }
+ sort.Slice(headers, func(i, j int) bool {
+ return headers[i].Height < headers[j].Height
+ })
+ curHeaderHeight = this.ledger.GetCurrentHeaderHeight()
+ curBlockHeight := this.ledger.GetCurrentBlockHeight()
+ for _, header := range headers {
+ //handle empty block
+ if header.TransactionsRoot == common.UINT256_EMPTY {
+ log.Trace("[p2p]OnHeaderReceive empty block Height:%d", header.Height)
+ height := header.Height
+ blockHash := header.Hash()
+ this.delFlightBlock(blockHash)
+ nextHeader := curHeaderHeight + 1
+ if height > nextHeader {
+ break
+ }
+ if height <= curBlockHeight {
+ continue
+ }
+ block := &types.Block{
+ Header: header,
+ }
+ this.addBlockCache(fromID, block, nil, common.UINT256_EMPTY)
+ }
+ }
+ go this.saveBlock()
+ this.syncHeader()
+}
+
+// OnBlockReceive receive block from net
+func (this *BlockSyncMgr) OnBlockReceive(fromID p2pComm.PeerId, blockSize uint32, block *types.Block, ccMsg *types.CrossChainMsg,
+ merkleRoot common.Uint256) {
+ height := block.Header.Height
+ blockHash := block.Hash()
+ log.Tracef("[p2p]OnBlockReceive Height:%d", height)
+ flightInfo := this.getFlightBlock(blockHash, fromID)
+ if flightInfo != nil {
+ t := (time.Now().UnixNano() - flightInfo.GetStartTime().UnixNano()) / int64(time.Millisecond)
+ s := float32(blockSize) / float32(t) * 1000.0 / 1024.0
+ this.addNewSpeed(fromID, s)
+ }
+
+ this.delFlightBlock(blockHash)
+ curHeaderHeight := this.ledger.GetCurrentHeaderHeight()
+ nextHeader := curHeaderHeight + 1
+ if height > nextHeader {
+ return
+ }
+ curBlockHeight := this.ledger.GetCurrentBlockHeight()
+ if height <= curBlockHeight {
+ return
+ }
+
+ this.addBlockCache(fromID, block, ccMsg, merkleRoot)
+ go this.saveBlock()
+ this.syncBlock()
+}
+
+//OnAddPeer to node list when a new node added
+func (this *BlockSyncMgr) OnAddNode(nodeId p2pComm.PeerId) {
+ log.Debugf("[p2p]OnAddNode:%d", nodeId)
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ w := NewNodeWeight(nodeId)
+ this.nodeWeights[nodeId] = w
+}
+
+//OnDelNode remove from node list. When the node disconnect
+func (this *BlockSyncMgr) OnDelNode(nodeId p2pComm.PeerId) {
+ this.delNode(nodeId)
+ log.Infof("OnDelNode:%d", nodeId)
+}
+
+//delNode remove from node list
+func (this *BlockSyncMgr) delNode(nodeId p2pComm.PeerId) {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ delete(this.nodeWeights, nodeId)
+ log.Infof("delNode:%d", nodeId)
+ if len(this.nodeWeights) == 0 {
+ log.Warnf("no sync nodes")
+ }
+ log.Infof("OnDelNode:%d", nodeId)
+}
+
+func (this *BlockSyncMgr) tryGetSyncHeaderLock() bool {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ if this.syncHeaderLock {
+ return true
+ }
+ this.syncHeaderLock = true
+ return false
+}
+
+func (this *BlockSyncMgr) releaseSyncHeaderLock() {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ this.syncHeaderLock = false
+}
+
+func (this *BlockSyncMgr) tryGetSyncBlockLock() bool {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ if this.syncBlockLock {
+ return true
+ }
+ this.syncBlockLock = true
+ return false
+}
+
+func (this *BlockSyncMgr) releaseSyncBlockLock() {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ this.syncBlockLock = false
+}
+
+func (this *BlockSyncMgr) addBlockCache(nodeID p2pComm.PeerId, block *types.Block, ccMsg *types.CrossChainMsg,
+ merkleRoot common.Uint256) bool {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ return this.blocksCache.addBlock(nodeID, block, ccMsg, merkleRoot)
+}
+
+func (this *BlockSyncMgr) getBlockCache(blockHeight uint32) (p2pComm.PeerId, *types.Block, *types.CrossChainMsg,
+ common.Uint256) {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ return this.blocksCache.getBlock(blockHeight)
+}
+
+func (this *BlockSyncMgr) delBlockCache(blockHeight uint32) {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ this.blocksCache.delBlockLocked(blockHeight)
+}
+
+func (this *BlockSyncMgr) tryGetSaveBlockLock() bool {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ if this.saveBlockLock {
+ return true
+ }
+ this.saveBlockLock = true
+ return false
+}
+
+func (this *BlockSyncMgr) releaseSaveBlockLock() {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ this.saveBlockLock = false
+}
+
+func (this *BlockSyncMgr) saveBlock() {
+ if this.tryGetSaveBlockLock() {
+ return
+ }
+ defer this.releaseSaveBlockLock()
+ curBlockHeight := this.ledger.GetCurrentBlockHeight()
+ nextBlockHeight := curBlockHeight + 1
+ this.clearBlocks(curBlockHeight)
+ for {
+ fromID, nextBlock, ccMsg, merkleRoot := this.getBlockCache(nextBlockHeight)
+ if nextBlock == nil {
+ return
+ }
+ err := this.ledger.AddBlock(nextBlock, ccMsg, merkleRoot)
+ this.delBlockCache(nextBlockHeight)
+ if err != nil {
+ this.addErrorRespCnt(fromID)
+ n := this.getNodeWeight(fromID)
+ if n != nil && n.GetErrorRespCnt() >= SYNC_MAX_ERROR_RESP_TIMES {
+ this.delNode(fromID)
+ }
+ log.Warnf("[p2p]saveBlock Height:%d AddBlock error:%s", nextBlockHeight, err)
+ reqNode := this.getNextNode(nextBlockHeight)
+ if reqNode == nil {
+ return
+ }
+ this.addFlightBlock(reqNode.GetID(), nextBlockHeight, nextBlock.Hash())
+ msg := msgpack.NewBlkDataReq(nextBlock.Hash())
+ err := this.server.Send(reqNode, msg)
+ if err != nil {
+ log.Warn("[p2p]require new block error:", err)
+ return
+ } else {
+ this.appendReqTime(reqNode.GetID())
+ }
+ return
+ }
+ nextBlockHeight++
+ this.pingOutsyncNodes(nextBlockHeight - 1)
+ }
+}
+
+func (this *BlockSyncMgr) isInBlockCache(blockHeight uint32) bool {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ return this.blocksCache.isInBlockCache(blockHeight)
+}
+
+func (this *BlockSyncMgr) addFlightHeader(nodeId p2pComm.PeerId, height uint32) {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ this.flightHeaders[height] = NewSyncFlightInfo(height, nodeId)
+}
+
+func (this *BlockSyncMgr) getFlightHeader(height uint32) *SyncFlightInfo {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ info, ok := this.flightHeaders[height]
+ if !ok {
+ return nil
+ }
+ return info
+}
+
+func (this *BlockSyncMgr) delFlightHeader(height uint32) bool {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ _, ok := this.flightHeaders[height]
+ if !ok {
+ return false
+ }
+ delete(this.flightHeaders, height)
+ return true
+}
+
+func (this *BlockSyncMgr) getFlightHeaderCount() int {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ return len(this.flightHeaders)
+}
+
+func (this *BlockSyncMgr) isHeaderOnFlight(height uint32) bool {
+ flightInfo := this.getFlightHeader(height)
+ return flightInfo != nil
+}
+
+func (this *BlockSyncMgr) addFlightBlock(nodeId p2pComm.PeerId, height uint32, blockHash common.Uint256) {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ this.flightBlocks[blockHash] = append(this.flightBlocks[blockHash], NewSyncFlightInfo(height, nodeId))
+}
+
+func (this *BlockSyncMgr) getFlightBlocks(blockHash common.Uint256) []*SyncFlightInfo {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ info, ok := this.flightBlocks[blockHash]
+ if !ok {
+ return nil
+ }
+ return info
+}
+
+func (this *BlockSyncMgr) getFlightBlock(blockHash common.Uint256, nodeId p2pComm.PeerId) *SyncFlightInfo {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ infos, ok := this.flightBlocks[blockHash]
+ if !ok {
+ return nil
+ }
+ for _, info := range infos {
+ if info.GetNodeId() == nodeId {
+ return info
+ }
+ }
+ return nil
+}
+
+func (this *BlockSyncMgr) delFlightBlock(blockHash common.Uint256) bool {
+ this.lock.Lock()
+ defer this.lock.Unlock()
+ _, ok := this.flightBlocks[blockHash]
+ if !ok {
+ return false
+ }
+ delete(this.flightBlocks, blockHash)
+ return true
+}
+
+func (this *BlockSyncMgr) getFlightBlockCount() int {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ cnt := 0
+ for hash := range this.flightBlocks {
+ cnt += len(this.flightBlocks[hash])
+ }
+ return cnt
+}
+
+func (this *BlockSyncMgr) isBlockOnFlight(blockHash common.Uint256) bool {
+ flightInfos := this.getFlightBlocks(blockHash)
+ return len(flightInfos) != 0
+}
+
+func (this *BlockSyncMgr) getNextNode(nextBlockHeight uint32) *peer.Peer {
+ weights := this.getAllNodeWeights()
+ sort.Sort(sort.Reverse(weights))
+ nodelist := make([]p2pComm.PeerId, 0)
+ for _, n := range weights {
+ nodelist = append(nodelist, n.id)
+ }
+ nextNodeIndex := 0
+ triedNode := make(map[p2pComm.PeerId]bool)
+ for {
+ var nextNodeId p2pComm.PeerId
+ nextNodeIndex, nextNodeId = getNextNodeId(nextNodeIndex, nodelist)
+ if nextNodeId.IsEmpty() {
+ return nil
+ }
+ _, ok := triedNode[nextNodeId]
+ if ok {
+ return nil
+ }
+ triedNode[nextNodeId] = true
+ n := this.server.GetPeer(nextNodeId)
+ if n == nil {
+ continue
+ }
+ nodeBlockHeight := n.GetHeight()
+ if nextBlockHeight <= uint32(nodeBlockHeight) {
+ return n
+ }
+ }
+}
+
+func (this *BlockSyncMgr) getNodeWithMinFailedTimes(flightInfo *SyncFlightInfo, curBlockHeight uint32) *peer.Peer {
+ var minFailedTimes = math.MaxInt64
+ var minFailedTimesNode *peer.Peer
+ triedNode := make(map[p2pComm.PeerId]bool)
+ for {
+ nextNode := this.getNextNode(curBlockHeight + 1)
+ if nextNode == nil {
+ return nil
+ }
+ failedTimes := flightInfo.GetFailedTimes(nextNode.GetID())
+ if failedTimes == 0 {
+ return nextNode
+ }
+ _, ok := triedNode[nextNode.GetID()]
+ if ok {
+ return minFailedTimesNode
+ }
+ triedNode[nextNode.GetID()] = true
+ if failedTimes < minFailedTimes {
+ minFailedTimes = failedTimes
+ minFailedTimesNode = nextNode
+ }
+ }
+}
+
+//Stop to sync
+func (this *BlockSyncMgr) Stop() {
+ close(this.exitCh)
+}
+
+//getNodeWeight get nodeweight by id
+func (this *BlockSyncMgr) getNodeWeight(nodeId p2pComm.PeerId) *NodeWeight {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ return this.nodeWeights[nodeId]
+}
+
+//getAllNodeWeights get all nodeweight and return a slice
+func (this *BlockSyncMgr) getAllNodeWeights() NodeWeights {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ weights := make(NodeWeights, 0, len(this.nodeWeights))
+ for _, w := range this.nodeWeights {
+ weights = append(weights, w)
+ }
+ return weights
+}
+
+//addTimeoutCnt incre a node's timeout count
+func (this *BlockSyncMgr) addTimeoutCnt(nodeId p2pComm.PeerId) {
+ n := this.getNodeWeight(nodeId)
+ if n != nil {
+ n.AddTimeoutCnt()
+ }
+}
+
+//addErrorRespCnt incre a node's error resp count
+func (this *BlockSyncMgr) addErrorRespCnt(nodeId p2pComm.PeerId) {
+ n := this.getNodeWeight(nodeId)
+ if n != nil {
+ n.AddErrorRespCnt()
+ }
+}
+
+//appendReqTime append a node's request time
+func (this *BlockSyncMgr) appendReqTime(nodeId p2pComm.PeerId) {
+ n := this.getNodeWeight(nodeId)
+ if n != nil {
+ n.AppendNewReqtime()
+ }
+}
+
+//addNewSpeed apend the new speed to tail, remove the oldest one
+func (this *BlockSyncMgr) addNewSpeed(nodeId p2pComm.PeerId, speed float32) {
+ n := this.getNodeWeight(nodeId)
+ if n != nil {
+ n.AppendNewSpeed(speed)
+ }
+}
+
+//pingOutsyncNodes send ping msg to lower height nodes for syncing
+func (this *BlockSyncMgr) pingOutsyncNodes(curHeight uint32) {
+ peers := make([]*peer.Peer, 0)
+ this.lock.RLock()
+ maxHeight := curHeight
+ for id := range this.nodeWeights {
+ peer := this.server.GetPeer(id)
+ if peer == nil {
+ continue
+ }
+ peerHeight := uint32(peer.GetHeight())
+ if peerHeight >= maxHeight {
+ maxHeight = peerHeight
+ }
+ if peerHeight < curHeight {
+ peers = append(peers, peer)
+ }
+ }
+ this.lock.RUnlock()
+ if curHeight > maxHeight-SYNC_MAX_HEIGHT_OFFSET && len(peers) > 0 {
+ pingTo(this.server, curHeight, peers)
+ }
+}
+
+//Using polling for load balance
+func getNextNodeId(nextNodeIndex int, nodeList []p2pComm.PeerId) (int, p2pComm.PeerId) {
+ num := len(nodeList)
+ if num == 0 {
+ return 0, p2pComm.PeerId{}
+ }
+ if nextNodeIndex >= num {
+ nextNodeIndex = 0
+ }
+ index := nextNodeIndex
+ nextNodeIndex++
+ return nextNodeIndex, nodeList[index]
+}
+
+func pingTo(net p2p.P2P, height uint32, peers []*peer.Peer) {
+ ping := msgpack.NewPingMsg(uint64(height))
+ for _, p := range peers {
+ go net.Send(p, ping)
+ }
+}
diff --git a/p2pserver/protocols/bootstrap/bootstrap.go b/p2pserver/protocols/bootstrap/bootstrap.go
new file mode 100644
index 0000000..22bcb25
--- /dev/null
+++ b/p2pserver/protocols/bootstrap/bootstrap.go
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package bootstrap
+
+import (
+ "math/rand"
+ "net"
+ "strconv"
+ "time"
+
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ msgpack "github.com/ontio/ontology/p2pserver/message/msg_pack"
+ "github.com/ontio/ontology/p2pserver/message/types"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/peer"
+)
+
+const activeConnect = 4 // when connection num less than this value, we connect seeds node actively.
+
+type BootstrapService struct {
+ seeds []string
+ connected uint
+ net p2p.P2P
+ quit chan bool
+}
+
+func NewBootstrapService(net p2p.P2P, seeds []string) *BootstrapService {
+ return &BootstrapService{
+ seeds: seeds,
+ net: net,
+ quit: make(chan bool),
+ }
+}
+
+func (self *BootstrapService) Start() {
+ go self.connectSeedService()
+}
+
+func (self *BootstrapService) Stop() {
+ close(self.quit)
+}
+
+func (self *BootstrapService) OnAddPeer(info *peer.PeerInfo) {
+ self.connected += 1
+}
+
+func (self *BootstrapService) OnDelPeer(info *peer.PeerInfo) {
+ self.connected -= 1
+}
+
+//connectSeedService make sure seed peer be connected
+func (self *BootstrapService) connectSeedService() {
+ t := time.NewTimer(0) // let it timeout to start connect immediately
+ for {
+ select {
+ case <-t.C:
+ self.connectSeeds()
+ t.Stop()
+ if self.connected >= activeConnect {
+ t.Reset(time.Second * time.Duration(10*common.CONN_MONITOR))
+ } else {
+ t.Reset(time.Second * common.CONN_MONITOR)
+ }
+ case <-self.quit:
+ t.Stop()
+ return
+ }
+ }
+}
+
+//connectSeeds connect the seeds in seedlist and call for nbr list
+func (self *BootstrapService) connectSeeds() {
+ seedNodes := make([]string, 0)
+ for _, n := range self.seeds {
+ ip, err := common.ParseIPAddr(n)
+ if err != nil {
+ log.Warnf("[p2p]seed peer %s address format is wrong", n)
+ continue
+ }
+ ns, err := net.LookupHost(ip)
+ if err != nil {
+ log.Warnf("[p2p]resolve err: %s", err.Error())
+ continue
+ }
+ port, err := common.ParseIPPort(n)
+ if err != nil {
+ log.Warnf("[p2p]seed peer %s address format is wrong", n)
+ continue
+ }
+ seedNodes = append(seedNodes, ns[0]+port)
+ }
+
+ connPeers := make(map[string]*peer.Peer)
+ nps := self.net.GetNeighbors()
+ for _, tn := range nps {
+ ipAddr, _ := tn.GetAddr16()
+ ip := net.IP(ipAddr[:])
+ addrString := ip.To16().String() + ":" + strconv.Itoa(int(tn.GetPort()))
+ connPeers[addrString] = tn
+ }
+
+ seedConnList := make([]*peer.Peer, 0)
+ seedDisconn := make([]string, 0)
+ isSeed := false
+ for _, nodeAddr := range seedNodes {
+ if p, ok := connPeers[nodeAddr]; ok {
+ seedConnList = append(seedConnList, p)
+ } else {
+ seedDisconn = append(seedDisconn, nodeAddr)
+ }
+
+ if self.net.IsOwnAddress(nodeAddr) {
+ isSeed = true
+ }
+ }
+
+ if len(seedConnList) > 0 {
+ rand.Seed(time.Now().UnixNano())
+ // close NewAddrReq
+ index := rand.Intn(len(seedConnList))
+ self.reqNbrList(seedConnList[index])
+ if isSeed && len(seedDisconn) > 0 {
+ index := rand.Intn(len(seedDisconn))
+ go self.net.Connect(seedDisconn[index])
+ }
+ } else { //not found
+ for _, nodeAddr := range seedNodes {
+ go self.net.Connect(nodeAddr)
+ }
+ }
+}
+
+func (this *BootstrapService) reqNbrList(p *peer.Peer) {
+ id := p.GetID()
+ var msg types.Message
+ if id.IsPseudoPeerId() {
+ msg = msgpack.NewAddrReq()
+ } else {
+ msg = msgpack.NewFindNodeReq(this.net.GetID())
+ }
+
+ go this.net.SendTo(id, msg)
+}
diff --git a/p2pserver/protocols/discovery/discovery.go b/p2pserver/protocols/discovery/discovery.go
new file mode 100644
index 0000000..643ef97
--- /dev/null
+++ b/p2pserver/protocols/discovery/discovery.go
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package discovery
+
+import (
+ "net"
+ "strconv"
+ "time"
+
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ "github.com/ontio/ontology/p2pserver/dht"
+ msgpack "github.com/ontio/ontology/p2pserver/message/msg_pack"
+ "github.com/ontio/ontology/p2pserver/message/types"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/peer"
+ "github.com/scylladb/go-set/strset"
+)
+
+type Discovery struct {
+ dht *dht.DHT
+ net p2p.P2P
+ id common.PeerId
+ quit chan bool
+ maskSet *strset.Set
+}
+
+func NewDiscovery(net p2p.P2P, maskLst []string, refleshInterval time.Duration) *Discovery {
+ dht := dht.NewDHT(net.GetID())
+ if refleshInterval != 0 {
+ dht.RtRefreshPeriod = refleshInterval
+ }
+ return &Discovery{
+ id: net.GetID(),
+ dht: dht,
+ net: net,
+ quit: make(chan bool),
+ maskSet: strset.New(maskLst...),
+ }
+}
+
+func (self *Discovery) Start() {
+ go self.findSelf()
+ go self.refreshCPL()
+}
+
+func (self *Discovery) Stop() {
+ close(self.quit)
+}
+
+func (self *Discovery) OnAddPeer(info *peer.PeerInfo) {
+ self.dht.Update(info.Id, info.RemoteListenAddress())
+}
+
+func (self *Discovery) OnDelPeer(info *peer.PeerInfo) {
+ self.dht.Remove(info.Id)
+}
+
+func (self *Discovery) findSelf() {
+ tick := time.NewTicker(self.dht.RtRefreshPeriod)
+ defer tick.Stop()
+
+ for {
+ select {
+ case <-tick.C:
+ log.Debug("[dht] start to find myself")
+ closer := self.dht.BetterPeers(self.id, dht.AlphaValue)
+ for _, curPair := range closer {
+ log.Debugf("[dht] find closr peer %s", curPair.ID.ToHexString())
+
+ var msg types.Message
+ if curPair.ID.IsPseudoPeerId() {
+ msg = msgpack.NewAddrReq()
+ } else {
+ msg = msgpack.NewFindNodeReq(self.id)
+ }
+ self.net.SendTo(curPair.ID, msg)
+ }
+ case <-self.quit:
+ return
+ }
+ }
+}
+
+func (self *Discovery) refreshCPL() {
+ tick := time.NewTicker(self.dht.RtRefreshPeriod)
+ defer tick.Stop()
+ for {
+ select {
+ case <-tick.C:
+ for curCPL := range self.dht.RouteTable().Buckets {
+ log.Debugf("[dht] start to refresh bucket: %d", curCPL)
+ randPeer := self.dht.RouteTable().GenRandKadId(uint(curCPL))
+ closer := self.dht.BetterPeers(randPeer, dht.AlphaValue)
+ for _, pair := range closer {
+ log.Debugf("[dht] find closr peer %s", pair.ID.ToHexString())
+ var msg types.Message
+ if pair.ID.IsPseudoPeerId() {
+ msg = msgpack.NewAddrReq()
+ } else {
+ msg = msgpack.NewFindNodeReq(randPeer)
+ }
+ self.net.SendTo(pair.ID, msg)
+ }
+ }
+ case <-self.quit:
+ return
+ }
+ }
+}
+
+func (self *Discovery) FindNodeHandle(ctx *p2p.Context, freq *types.FindNodeReq) {
+ // we recv message must from establised peer
+ remotePeer := ctx.Sender()
+
+ var fresp types.FindNodeResp
+ // check the target is my self
+ log.Debugf("[dht] find node for peerid: %d", freq.TargetID)
+
+ if freq.TargetID == self.id {
+ fresp.Success = true
+ fresp.TargetID = freq.TargetID
+ // you've already connected with me so there's no need to give you my address
+ // omit the address
+ if err := remotePeer.Send(&fresp); err != nil {
+ log.Warn(err)
+ }
+ return
+ }
+
+ fresp.TargetID = freq.TargetID
+ // search dht
+ fresp.CloserPeers = self.dht.BetterPeers(freq.TargetID, dht.AlphaValue)
+
+ //hide mask node if necessary
+ remoteAddr, _ := remotePeer.GetAddr16()
+ remoteIP := net.IP(remoteAddr[:])
+
+ // mask peer see everyone, but other's will not see mask node
+ // if remotePeer is in msk-list, give them everything
+ // not in mask set means they are in the other side
+ if self.maskSet.Size() > 0 && !self.maskSet.Has(remoteIP.String()) {
+ mskedAddrs := make([]common.PeerIDAddressPair, 0)
+ // filter out the masked node
+ for _, pair := range fresp.CloserPeers {
+ ip, _, err := net.SplitHostPort(pair.Address)
+ if err != nil {
+ continue
+ }
+ // hide mask node
+ if self.maskSet.Has(ip) {
+ continue
+ }
+ mskedAddrs = append(mskedAddrs, pair)
+ }
+ // replace with masked nodes
+ fresp.CloserPeers = mskedAddrs
+ }
+
+ log.Debugf("[dht] find %d more closer peers:", len(fresp.CloserPeers))
+ for _, curpa := range fresp.CloserPeers {
+ log.Debugf(" dht: pid: %s, addr: %s", curpa.ID.ToHexString(), curpa.Address)
+ }
+
+ if err := remotePeer.Send(&fresp); err != nil {
+ log.Warn(err)
+ }
+}
+
+func (self *Discovery) FindNodeResponseHandle(ctx *p2p.Context, fresp *types.FindNodeResp) {
+ if fresp.Success {
+ log.Debugf("[p2p dht] %s", "find peer success, do nothing")
+ return
+ }
+ p2p := ctx.Network()
+ // we should connect to closer peer to ask them them where should we go
+ for _, curpa := range fresp.CloserPeers {
+ // already connected
+ if p2p.GetPeer(curpa.ID) != nil {
+ continue
+ }
+ // do nothing about
+ if curpa.ID == p2p.GetID() {
+ continue
+ }
+ log.Debugf("[dht] try to connect to another peer by dht: %s ==> %s", curpa.ID.ToHexString(), curpa.Address)
+ go p2p.Connect(curpa.Address)
+ }
+}
+
+// neighborAddresses get address from dht routing table
+func (self *Discovery) neighborAddresses() []common.PeerAddr {
+ // e.g. ["127.0.0.1:20338"]
+ ipPortAdds := self.dht.RouteTable().ListPeers()
+ ret := []common.PeerAddr{}
+ for _, curIPPort := range ipPortAdds {
+ host, port, err := net.SplitHostPort(curIPPort.Address)
+ if err != nil {
+ continue
+ }
+
+ ipadd := net.ParseIP(host)
+ if ipadd == nil {
+ continue
+ }
+
+ p, err := strconv.Atoi(port)
+ if err != nil {
+ continue
+ }
+
+ curAddr := common.PeerAddr{
+ Port: uint16(p),
+ }
+ copy(curAddr.IpAddr[:], ipadd.To16())
+
+ ret = append(ret, curAddr)
+ }
+
+ return ret
+}
+
+func (self *Discovery) AddrReqHandle(ctx *p2p.Context) {
+ remotePeer := ctx.Sender()
+
+ addrs := self.neighborAddresses()
+
+ // get remote peer IP
+ // if get remotePeerAddr failed, do masking anyway
+ remoteAddr, _ := remotePeer.GetAddr16()
+ remoteIP := net.IP(remoteAddr[:])
+
+ // mask peer see everyone, but other's will not see mask node
+ // if remotePeer is in msk-list, give them everthing
+ // not in mask set means they are in the other side
+ if self.maskSet.Size() > 0 && !self.maskSet.Has(remoteIP.String()) {
+ mskedAddrs := make([]common.PeerAddr, 0)
+ for _, addr := range addrs {
+ ip := net.IP(addr.IpAddr[:])
+ address := ip.To16().String()
+ // hide mask node
+ if self.maskSet.Has(address) {
+ continue
+ }
+ mskedAddrs = append(mskedAddrs, addr)
+ }
+ // replace with mskedAddrs
+ addrs = mskedAddrs
+ }
+
+ msg := msgpack.NewAddrs(addrs)
+ err := remotePeer.Send(msg)
+
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+}
+
+func (self *Discovery) AddrHandle(ctx *p2p.Context, msg *types.Addr) {
+ p2p := ctx.Network()
+ for _, v := range msg.NodeAddrs {
+ if v.Port == 0 || v.ID == p2p.GetID() {
+ continue
+ }
+ ip := net.IP(v.IpAddr[:])
+ address := ip.To16().String() + ":" + strconv.Itoa(int(v.Port))
+
+ if self.dht.Contains(v.ID) {
+ continue
+ }
+
+ log.Debug("[p2p]connect ip address:", address)
+ go p2p.Connect(address)
+ }
+}
diff --git a/p2pserver/protocols/heatbeat/heartbeat.go b/p2pserver/protocols/heatbeat/heartbeat.go
new file mode 100644
index 0000000..0cc7709
--- /dev/null
+++ b/p2pserver/protocols/heatbeat/heartbeat.go
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package heatbeat
+
+import (
+ "time"
+
+ "github.com/ontio/ontology/common/config"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/core/ledger"
+ "github.com/ontio/ontology/p2pserver/common"
+ msgpack "github.com/ontio/ontology/p2pserver/message/msg_pack"
+ "github.com/ontio/ontology/p2pserver/message/types"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+)
+
+type HeartBeat struct {
+ net p2p.P2P
+ id common.PeerId
+ quit chan bool
+ ledger *ledger.Ledger //ledger
+}
+
+func NewHeartBeat(net p2p.P2P, ld *ledger.Ledger) *HeartBeat {
+ return &HeartBeat{
+ id: net.GetID(),
+ net: net,
+ quit: make(chan bool),
+ ledger: ld,
+ }
+}
+
+func (self *HeartBeat) Start() {
+ go self.heartBeatService()
+}
+
+func (self *HeartBeat) Stop() {
+ close(self.quit)
+}
+func (this *HeartBeat) heartBeatService() {
+ var periodTime uint = config.DEFAULT_GEN_BLOCK_TIME / common.UPDATE_RATE_PER_BLOCK
+ t := time.NewTicker(time.Second * (time.Duration(periodTime)))
+
+ for {
+ select {
+ case <-t.C:
+ this.ping()
+ this.timeout()
+ case <-this.quit:
+ t.Stop()
+ return
+ }
+ }
+}
+
+func (this *HeartBeat) ping() {
+ height := this.ledger.GetCurrentBlockHeight()
+ ping := msgpack.NewPingMsg(uint64(height))
+ go this.net.Broadcast(ping)
+}
+
+//timeout trace whether some peer be long time no response
+func (this *HeartBeat) timeout() {
+ peers := this.net.GetNeighbors()
+ var periodTime uint = config.DEFAULT_GEN_BLOCK_TIME / common.UPDATE_RATE_PER_BLOCK
+ for _, p := range peers {
+ t := p.GetContactTime()
+ if t.Before(time.Now().Add(-1 * time.Second *
+ time.Duration(periodTime) * common.KEEPALIVE_TIMEOUT)) {
+ log.Warnf("[p2p]keep alive timeout!!!lost remote peer %d - %s from %s", p.GetID(), p.Link.GetAddr(), t.String())
+ p.Close()
+ }
+ }
+}
+
+func (this *HeartBeat) PingHandle(ctx *p2p.Context, ping *types.Ping) {
+ remotePeer := ctx.Sender()
+ remotePeer.SetHeight(ping.Height)
+ p2p := ctx.Network()
+
+ height := ledger.DefLedger.GetCurrentBlockHeight()
+ p2p.SetHeight(uint64(height))
+ msg := msgpack.NewPongMsg(uint64(height))
+
+ err := remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ }
+}
+
+func (this *HeartBeat) PongHandle(ctx *p2p.Context, pong *types.Pong) {
+ remotePeer := ctx.Network()
+ remotePeer.SetHeight(pong.Height)
+}
diff --git a/p2pserver/protocols/msg_handler.go b/p2pserver/protocols/msg_handler.go
new file mode 100644
index 0000000..1b058f7
--- /dev/null
+++ b/p2pserver/protocols/msg_handler.go
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package protocols
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+
+ lru "github.com/hashicorp/golang-lru"
+ "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/config"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/core/ledger"
+ "github.com/ontio/ontology/core/types"
+ actor "github.com/ontio/ontology/p2pserver/actor/req"
+ msgCommon "github.com/ontio/ontology/p2pserver/common"
+ msgpack "github.com/ontio/ontology/p2pserver/message/msg_pack"
+ msgTypes "github.com/ontio/ontology/p2pserver/message/types"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/protocols/block_sync"
+ "github.com/ontio/ontology/p2pserver/protocols/bootstrap"
+ "github.com/ontio/ontology/p2pserver/protocols/discovery"
+ "github.com/ontio/ontology/p2pserver/protocols/heatbeat"
+ "github.com/ontio/ontology/p2pserver/protocols/recent_peers"
+ "github.com/ontio/ontology/p2pserver/protocols/reconnect"
+)
+
+//respCache cache for some response data
+var respCache *lru.ARCCache
+
+//Store txHash, using for rejecting duplicate tx
+// thread safe
+var txCache, _ = lru.NewARC(msgCommon.MAX_TX_CACHE_SIZE)
+
+type MsgHandler struct {
+ blockSync *block_sync.BlockSyncMgr
+ reconnect *reconnect.ReconnectService
+ discovery *discovery.Discovery
+ heatBeat *heatbeat.HeartBeat
+ bootstrap *bootstrap.BootstrapService
+ persistRecentPeerService *recent_peers.PersistRecentPeerService
+ ledger *ledger.Ledger
+}
+
+func NewMsgHandler(ld *ledger.Ledger) *MsgHandler {
+ return &MsgHandler{ledger: ld}
+}
+
+func (self *MsgHandler) start(net p2p.P2P) {
+ self.blockSync = block_sync.NewBlockSyncMgr(net, self.ledger)
+ self.reconnect = reconnect.NewReconectService(net)
+ self.discovery = discovery.NewDiscovery(net, config.DefConfig.P2PNode.ReservedCfg.MaskPeers, 0)
+ seeds := config.DefConfig.Genesis.SeedList
+ self.bootstrap = bootstrap.NewBootstrapService(net, seeds)
+ self.heatBeat = heatbeat.NewHeartBeat(net, self.ledger)
+ self.persistRecentPeerService = recent_peers.NewPersistRecentPeerService(net)
+ go self.persistRecentPeerService.Start()
+ go self.blockSync.Start()
+ go self.reconnect.Start()
+ go self.discovery.Start()
+ go self.heatBeat.Start()
+ go self.bootstrap.Start()
+}
+
+func (self *MsgHandler) stop() {
+ self.blockSync.Stop()
+ self.reconnect.Stop()
+ self.discovery.Stop()
+ self.persistRecentPeerService.Stop()
+ self.heatBeat.Stop()
+ self.bootstrap.Stop()
+}
+
+func (self *MsgHandler) HandleSystemMessage(net p2p.P2P, msg p2p.SystemMessage) {
+ switch m := msg.(type) {
+ case p2p.NetworkStart:
+ self.start(net)
+ case p2p.PeerConnected:
+ self.blockSync.OnAddNode(m.Info.Id)
+ self.reconnect.OnAddPeer(m.Info)
+ self.discovery.OnAddPeer(m.Info)
+ self.bootstrap.OnAddPeer(m.Info)
+ self.persistRecentPeerService.AddNodeAddr(m.Info.Addr + strconv.Itoa(int(m.Info.Port)))
+ case p2p.PeerDisConnected:
+ self.blockSync.OnDelNode(m.Info.Id)
+ self.reconnect.OnDelPeer(m.Info)
+ self.discovery.OnDelPeer(m.Info)
+ self.bootstrap.OnDelPeer(m.Info)
+ self.persistRecentPeerService.DelNodeAddr(m.Info.Addr + strconv.Itoa(int(m.Info.Port)))
+ case p2p.NetworkStop:
+ self.stop()
+ }
+}
+
+func (self *MsgHandler) HandlePeerMessage(ctx *p2p.Context, msg msgTypes.Message) {
+ log.Trace("[p2p]receive message", ctx.Sender().GetAddr(), ctx.Sender().GetID())
+ switch m := msg.(type) {
+ case *msgTypes.AddrReq:
+ self.discovery.AddrReqHandle(ctx)
+ case *msgTypes.FindNodeResp:
+ self.discovery.FindNodeResponseHandle(ctx, m)
+ case *msgTypes.FindNodeReq:
+ self.discovery.FindNodeHandle(ctx, m)
+ case *msgTypes.HeadersReq:
+ HeadersReqHandle(ctx, m)
+ case *msgTypes.Ping:
+ self.heatBeat.PingHandle(ctx, m)
+ case *msgTypes.Pong:
+ self.heatBeat.PongHandle(ctx, m)
+ case *msgTypes.BlkHeader:
+ self.blockSync.OnHeaderReceive(ctx.Sender().GetID(), m.BlkHdr)
+ case *msgTypes.Block:
+ self.blockHandle(ctx, m)
+ case *msgTypes.Consensus:
+ ConsensusHandle(ctx, m)
+ case *msgTypes.Trn:
+ TransactionHandle(ctx, m)
+ case *msgTypes.Addr:
+ self.discovery.AddrHandle(ctx, m)
+ case *msgTypes.DataReq:
+ DataReqHandle(ctx, m)
+ case *msgTypes.Inv:
+ InvHandle(ctx, m)
+ case *msgTypes.NotFound:
+ log.Debug("[p2p]receive notFound message, hash is ", m.Hash)
+ default:
+ msgType := msg.CmdType()
+ if msgType == msgCommon.VERACK_TYPE || msgType == msgCommon.VERSION_TYPE {
+ log.Infof("receive message: %s from peer %s", msgType, ctx.Sender().GetAddr())
+ } else {
+ log.Warn("unknown message handler for the msg: ", msgType)
+ }
+ }
+}
+
+// HeaderReqHandle handles the header sync req from peer
+func HeadersReqHandle(ctx *p2p.Context, headersReq *msgTypes.HeadersReq) {
+ startHash := headersReq.HashStart
+ stopHash := headersReq.HashEnd
+
+ headers, err := GetHeadersFromHash(startHash, stopHash)
+ if err != nil {
+ log.Warnf("HeadersReqHandle error: %s,startHash:%s,stopHash:%s", err.Error(), startHash.ToHexString(), stopHash.ToHexString())
+ return
+ }
+ remotePeer := ctx.Sender()
+ msg := msgpack.NewHeaders(headers)
+ err = remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+}
+
+// blockHandle handles the block message from peer
+func (self *MsgHandler) blockHandle(ctx *p2p.Context, block *msgTypes.Block) {
+ stateHashHeight := config.GetStateHashCheckHeight(config.DefConfig.P2PNode.NetworkId)
+ if block.Blk.Header.Height >= stateHashHeight && block.MerkleRoot == common.UINT256_EMPTY {
+ remotePeer := ctx.Sender()
+ remotePeer.Close()
+ return
+ }
+
+ self.blockSync.OnBlockReceive(ctx.Sender().GetID(), ctx.MsgSize, block.Blk, block.CCMsg, block.MerkleRoot)
+}
+
+// ConsensusHandle handles the consensus message from peer
+func ConsensusHandle(ctx *p2p.Context, consensus *msgTypes.Consensus) {
+ if actor.ConsensusPid != nil {
+ if err := consensus.Cons.Verify(); err != nil {
+ log.Warn(err)
+ return
+ }
+ consensus.Cons.PeerId = ctx.Sender().GetID()
+ actor.ConsensusPid.Tell(&consensus.Cons)
+ }
+}
+
+// TransactionHandle handles the transaction message from peer
+func TransactionHandle(ctx *p2p.Context, trn *msgTypes.Trn) {
+ if !txCache.Contains(trn.Txn.Hash()) {
+ txCache.Add(trn.Txn.Hash(), nil)
+ actor.AddTransaction(trn.Txn)
+ } else {
+ log.Tracef("[p2p]receive duplicate Transaction message, txHash: %x\n", trn.Txn.Hash())
+ }
+}
+
+// DataReqHandle handles the data req(block/Transaction) from peer
+func DataReqHandle(ctx *p2p.Context, dataReq *msgTypes.DataReq) {
+ remotePeer := ctx.Sender()
+ reqType := common.InventoryType(dataReq.DataType)
+ hash := dataReq.Hash
+ switch reqType {
+ case common.BLOCK:
+ reqID := fmt.Sprintf("%x%s", reqType, hash.ToHexString())
+ data := getRespCacheValue(reqID)
+ var msg msgTypes.Message
+ if data != nil {
+ switch data.(type) {
+ case *msgTypes.Block:
+ msg = data.(*msgTypes.Block)
+ }
+ }
+ if msg == nil {
+ var merkleRoot common.Uint256
+ block, err := ledger.DefLedger.GetBlockByHash(hash)
+ if err != nil || block == nil || block.Header == nil {
+ log.Debug("[p2p]can't get block by hash: ", hash, " ,send not found message")
+ msg := msgpack.NewNotFound(hash)
+ err := remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+ return
+ }
+ ccMsg, err := ledger.DefLedger.GetCrossChainMsg(block.Header.Height - 1)
+ if err != nil {
+ log.Debugf("[p2p]failed to get cross chain message at height %v, err %v",
+ block.Header.Height-1, err)
+ msg := msgpack.NewNotFound(hash)
+ err := remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+ return
+ }
+ merkleRoot, err = ledger.DefLedger.GetStateMerkleRoot(block.Header.Height)
+ if err != nil {
+ log.Debugf("[p2p]failed to get state merkel root at height %v, err %v",
+ block.Header.Height, err)
+ msg := msgpack.NewNotFound(hash)
+ err := remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+ return
+ }
+ msg = msgpack.NewBlock(block, ccMsg, merkleRoot)
+ saveRespCache(reqID, msg)
+ }
+ err := remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+
+ case common.TRANSACTION:
+ txn, err := ledger.DefLedger.GetTransaction(hash)
+ if err != nil {
+ log.Debug("[p2p]Can't get transaction by hash: ",
+ hash, " ,send not found message")
+ msg := msgpack.NewNotFound(hash)
+ err = remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+ }
+ msg := msgpack.NewTxn(txn)
+ err = remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+ }
+}
+
+// InvHandle handles the inventory message(block,
+// transaction and consensus) from peer.
+func InvHandle(ctx *p2p.Context, inv *msgTypes.Inv) {
+ remotePeer := ctx.Sender()
+ if len(inv.P.Blk) == 0 {
+ log.Debug("[p2p]empty inv payload in InvHandle")
+ return
+ }
+ var id common.Uint256
+ str := inv.P.Blk[0].ToHexString()
+ log.Debugf("[p2p]the inv type: 0x%x block len: %d, %s\n",
+ inv.P.InvType, len(inv.P.Blk), str)
+
+ invType := common.InventoryType(inv.P.InvType)
+ switch invType {
+ case common.TRANSACTION:
+ log.Debug("[p2p]receive transaction message", id)
+ // TODO check the ID queue
+ id = inv.P.Blk[0]
+ trn, err := ledger.DefLedger.GetTransaction(id)
+ if trn == nil || err != nil {
+ msg := msgpack.NewTxnDataReq(id)
+ err = remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+ }
+ case common.BLOCK:
+ log.Debug("[p2p]receive block message")
+ for _, id = range inv.P.Blk {
+ log.Debug("[p2p]receive inv-block message, hash is ", id)
+ // TODO check the ID queue
+ isContainBlock, err := ledger.DefLedger.IsContainBlock(id)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+ if !isContainBlock && msgTypes.LastInvHash != id {
+ msgTypes.LastInvHash = id
+ // send the block request
+ log.Infof("[p2p]inv request block hash: %x", id)
+ msg := msgpack.NewBlkDataReq(id)
+ err = remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+ }
+ }
+ case common.CONSENSUS:
+ log.Debug("[p2p]receive consensus message")
+ id = inv.P.Blk[0]
+ msg := msgpack.NewConsensusDataReq(id)
+ err := remotePeer.Send(msg)
+ if err != nil {
+ log.Warn(err)
+ return
+ }
+ default:
+ log.Warn("[p2p]receive unknown inventory message")
+ }
+
+}
+
+//get blk hdrs from starthash to stophash
+func GetHeadersFromHash(startHash common.Uint256, stopHash common.Uint256) ([]*types.RawHeader, error) {
+ var count uint32 = 0
+ var headers []*types.RawHeader
+ var startHeight uint32
+ var stopHeight uint32
+ curHeight := ledger.DefLedger.GetCurrentHeaderHeight()
+ if startHash == common.UINT256_EMPTY {
+ if stopHash == common.UINT256_EMPTY {
+ if curHeight > msgCommon.MAX_BLK_HDR_CNT {
+ count = msgCommon.MAX_BLK_HDR_CNT
+ } else {
+ count = curHeight
+ }
+ } else {
+ bkStop, err := ledger.DefLedger.GetRawHeaderByHash(stopHash)
+ if err != nil || bkStop == nil {
+ return nil, err
+ }
+ stopHeight = bkStop.Height
+ count = curHeight - stopHeight
+ if count > msgCommon.MAX_BLK_HDR_CNT {
+ count = msgCommon.MAX_BLK_HDR_CNT
+ }
+ }
+ } else {
+ bkStart, err := ledger.DefLedger.GetRawHeaderByHash(startHash)
+ if err != nil || bkStart == nil {
+ return nil, err
+ }
+ startHeight = bkStart.Height
+ if stopHash != common.UINT256_EMPTY {
+ bkStop, err := ledger.DefLedger.GetRawHeaderByHash(stopHash)
+ if err != nil || bkStop == nil {
+ return nil, err
+ }
+ stopHeight = bkStop.Height
+
+ // avoid unsigned integer underflow
+ if startHeight < stopHeight {
+ return nil, errors.New("[p2p]do not have header to send")
+ }
+ count = startHeight - stopHeight
+
+ if count >= msgCommon.MAX_BLK_HDR_CNT {
+ count = msgCommon.MAX_BLK_HDR_CNT
+ stopHeight = startHeight - msgCommon.MAX_BLK_HDR_CNT
+ }
+ } else {
+
+ if startHeight > msgCommon.MAX_BLK_HDR_CNT {
+ count = msgCommon.MAX_BLK_HDR_CNT
+ } else {
+ count = startHeight
+ }
+ }
+ }
+
+ var i uint32
+ for i = 1; i <= count; i++ {
+ hash := ledger.DefLedger.GetBlockHash(stopHeight + i)
+ header, err := ledger.DefLedger.GetHeaderByHash(hash)
+ if err != nil {
+ log.Debugf("[p2p]net_server GetBlockWithHeight failed with err=%s, hash=%x,height=%d\n", err.Error(), hash, stopHeight+i)
+ return nil, err
+ }
+
+ sink := common.NewZeroCopySink(nil)
+ header.Serialization(sink)
+
+ hd := &types.RawHeader{
+ Height: header.Height,
+ Payload: sink.Bytes(),
+ }
+ headers = append(headers, hd)
+ }
+
+ return headers, nil
+}
+
+//getRespCacheValue get response data from cache
+func getRespCacheValue(key string) interface{} {
+ if respCache == nil {
+ return nil
+ }
+ data, ok := respCache.Get(key)
+ if ok {
+ return data
+ }
+ return nil
+}
+
+//saveRespCache save response msg to cache
+func saveRespCache(key string, value interface{}) bool {
+ if respCache == nil {
+ var err error
+ respCache, err = lru.NewARC(msgCommon.MAX_RESP_CACHE_SIZE)
+ if err != nil {
+ return false
+ }
+ }
+ respCache.Add(key, value)
+ return true
+}
+
+func (mh *MsgHandler) ReconnectService() *reconnect.ReconnectService {
+ return mh.reconnect
+}
diff --git a/p2pserver/protocols/recent_peers/recent_peers.go b/p2pserver/protocols/recent_peers/recent_peers.go
new file mode 100644
index 0000000..79fce11
--- /dev/null
+++ b/p2pserver/protocols/recent_peers/recent_peers.go
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should contains received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+package recent_peers
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "os"
+ "sync"
+ "time"
+
+ common2 "github.com/ontio/ontology/common"
+ "github.com/ontio/ontology/common/config"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+)
+
+type PersistRecentPeerService struct {
+ net p2p.P2P
+ quit chan bool
+ recentPeers map[uint32][]*RecentPeer
+ lock sync.RWMutex
+}
+
+func (this *PersistRecentPeerService) contains(addr string) bool {
+ this.lock.RLock()
+ defer this.lock.RUnlock()
+ netID := config.DefConfig.P2PNode.NetworkMagic
+ for i := 0; i < len(this.recentPeers[netID]); i++ {
+ if this.recentPeers[netID][i].Addr == addr {
+ return true
+ }
+ }
+ return false
+}
+
+func (this *PersistRecentPeerService) AddNodeAddr(addr string) {
+ if this.contains(addr) {
+ return
+ }
+ this.lock.Lock()
+ netID := config.DefConfig.P2PNode.NetworkMagic
+ this.recentPeers[netID] = append(this.recentPeers[netID],
+ &RecentPeer{
+ Addr: addr,
+ Birth: time.Now().Unix(),
+ })
+ this.lock.Unlock()
+}
+
+func (this *PersistRecentPeerService) DelNodeAddr(addr string) {
+ this.lock.Lock()
+ netID := config.DefConfig.P2PNode.NetworkMagic
+ for i := 0; i < len(this.recentPeers[netID]); i++ {
+ if this.recentPeers[netID][i].Addr == addr {
+ this.recentPeers[netID] = append(this.recentPeers[netID][:i], this.recentPeers[netID][i+1:]...)
+ }
+ }
+ this.lock.Unlock()
+}
+
+type RecentPeer struct {
+ Addr string
+ Birth int64
+}
+
+func (this *PersistRecentPeerService) saveToFile() {
+ temp := make(map[uint32][]string)
+ for networkId, rps := range this.recentPeers {
+ temp[networkId] = make([]string, 0)
+ for _, rp := range rps {
+ elapse := time.Now().Unix() - rp.Birth
+ if elapse > common.RecentPeerElapseLimit {
+ temp[networkId] = append(temp[networkId], rp.Addr)
+ }
+ }
+ }
+ buf, err := json.Marshal(temp)
+ if err != nil {
+ log.Warn("[p2p]package recent peer fail: ", err)
+ return
+ }
+ err = ioutil.WriteFile(common.RECENT_FILE_NAME, buf, os.ModePerm)
+ if err != nil {
+ log.Warn("[p2p]write recent peer fail: ", err)
+ }
+}
+
+func NewPersistRecentPeerService(net p2p.P2P) *PersistRecentPeerService {
+ return &PersistRecentPeerService{
+ net: net,
+ quit: make(chan bool),
+ }
+}
+
+func (self *PersistRecentPeerService) Stop() {
+ close(self.quit)
+}
+
+func (this *PersistRecentPeerService) loadRecentPeers() {
+ this.recentPeers = make(map[uint32][]*RecentPeer)
+ if common2.FileExisted(common.RECENT_FILE_NAME) {
+ buf, err := ioutil.ReadFile(common.RECENT_FILE_NAME)
+ if err != nil {
+ log.Warn("[p2p]read %s fail:%s, connect recent peers cancel", common.RECENT_FILE_NAME, err.Error())
+ return
+ }
+
+ temp := make(map[uint32][]string)
+ err = json.Unmarshal(buf, &temp)
+ if err != nil {
+ log.Warn("[p2p]parse recent peer file fail: ", err)
+ return
+ }
+ for networkId, addrs := range temp {
+ for _, addr := range addrs {
+ this.recentPeers[networkId] = append(this.recentPeers[networkId], &RecentPeer{
+ Addr: addr,
+ Birth: time.Now().Unix(),
+ })
+ }
+ }
+ }
+}
+
+func (this *PersistRecentPeerService) Start() {
+ this.loadRecentPeers()
+ this.tryRecentPeers()
+ go this.syncUpRecentPeers()
+}
+
+//tryRecentPeers try connect recent contact peer when service start
+func (this *PersistRecentPeerService) tryRecentPeers() {
+ netID := config.DefConfig.P2PNode.NetworkMagic
+ if len(this.recentPeers[netID]) > 0 {
+ log.Info("[p2p] try to connect recent peer")
+ }
+ for _, v := range this.recentPeers[netID] {
+ go this.net.Connect(v.Addr)
+ }
+}
+
+//syncUpRecentPeers sync up recent peers periodically
+func (this *PersistRecentPeerService) syncUpRecentPeers() {
+ periodTime := common.RECENT_TIMEOUT
+ t := time.NewTicker(time.Second * (time.Duration(periodTime)))
+ for {
+ select {
+ case <-t.C:
+ this.saveToFile()
+ case <-this.quit:
+ t.Stop()
+ return
+ }
+ }
+}
diff --git a/p2pserver/protocols/reconnect/reconnect.go b/p2pserver/protocols/reconnect/reconnect.go
new file mode 100644
index 0000000..0d104bc
--- /dev/null
+++ b/p2pserver/protocols/reconnect/reconnect.go
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2018 The ontology Authors
+ * This file is part of The ontology library.
+ *
+ * The ontology is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ontology is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The ontology. If not, see .
+ */
+
+package reconnect
+
+import (
+ "fmt"
+ "math/rand"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/ontio/ontology/common/config"
+ "github.com/ontio/ontology/common/log"
+ "github.com/ontio/ontology/p2pserver/common"
+ p2p "github.com/ontio/ontology/p2pserver/net/protocol"
+ "github.com/ontio/ontology/p2pserver/peer"
+)
+
+//ReconnectService contain addr need to reconnect
+type ReconnectService struct {
+ sync.RWMutex
+ MaxRetryCount uint
+ RetryAddrs map[string]int
+ net p2p.P2P
+ quit chan bool
+}
+
+func NewReconectService(net p2p.P2P) *ReconnectService {
+ return &ReconnectService{
+ net: net,
+ MaxRetryCount: common.MAX_RETRY_COUNT,
+ quit: make(chan bool),
+ RetryAddrs: make(map[string]int),
+ }
+}
+
+func (self *ReconnectService) Start() {
+ go self.keepOnlineService()
+}
+
+func (self *ReconnectService) Stop() {
+ close(self.quit)
+}
+
+func (this *ReconnectService) keepOnlineService() {
+ tick := time.NewTicker(time.Second * common.CONN_MONITOR)
+ defer tick.Stop()
+ for {
+ select {
+ case <-tick.C:
+ this.retryInactivePeer()
+ case <-this.quit:
+ return
+ }
+ }
+}
+
+func getPeerListenAddr(p *peer.PeerInfo) (string, error) {
+ addrIp, err := common.ParseIPAddr(p.Addr)
+ if err != nil {
+ return "", fmt.Errorf("failed to parse addr: %s", p.Addr)
+ }
+ nodeAddr := addrIp + ":" + strconv.Itoa(int(p.Port))
+ return nodeAddr, nil
+}
+
+func (self *ReconnectService) OnAddPeer(p *peer.PeerInfo) {
+ nodeAddr, err := getPeerListenAddr(p)
+ if err != nil {
+ log.Errorf("failed to parse addr: %s", p.Addr)
+ return
+ }
+ self.Lock()
+ delete(self.RetryAddrs, nodeAddr)
+ self.Unlock()
+}
+
+func (self *ReconnectService) OnDelPeer(p *peer.PeerInfo) {
+ nodeAddr, err := getPeerListenAddr(p)
+ if err != nil {
+ log.Errorf("failed to parse addr: %s", p.Addr)
+ return
+ }
+ self.Lock()
+ self.RetryAddrs[nodeAddr] = 0
+ self.Unlock()
+}
+
+func (this *ReconnectService) retryInactivePeer() {
+ net := this.net
+ connCount := net.GetOutConnRecordLen()
+ if connCount >= config.DefConfig.P2PNode.MaxConnOutBound {
+ log.Warnf("[p2p]Connect: out connections(%d) reach max limit(%d)", connCount,
+ config.DefConfig.P2PNode.MaxConnOutBound)
+ return
+ }
+
+ //try connect
+ if len(this.RetryAddrs) > 0 {
+ this.Lock()
+
+ list := make(map[string]int)
+ addrs := make([]string, 0, len(this.RetryAddrs))
+ for addr, v := range this.RetryAddrs {
+ v += 1
+ addrs = append(addrs, addr)
+ if v < common.MAX_RETRY_COUNT {
+ list[addr] = v
+ }
+ }
+
+ this.RetryAddrs = list
+ this.Unlock()
+ for _, addr := range addrs {
+ rand.Seed(time.Now().UnixNano())
+ log.Debug("[p2p]Try to reconnect peer, peer addr is ", addr)
+ <-time.After(time.Duration(rand.Intn(common.CONN_MAX_BACK)) * time.Millisecond)
+ log.Debug("[p2p]Back off time`s up, start connect node")
+ net.Connect(addr)
+ }
+ }
+}
+
+func (self *ReconnectService) ReconnectCount() int {
+ self.RLock()
+ defer self.RUnlock()
+ return len(self.RetryAddrs)
+}