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) +}