├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── base.go ├── p2p │ └── p2p.go └── root.go ├── common ├── types │ ├── address.go │ ├── big.go │ ├── block.go │ ├── doublecache.go │ ├── doublecache_test.go │ ├── encode.go │ ├── hashes.go │ ├── hashes_test.go │ ├── size.go │ ├── size_test.go │ ├── transaction.go │ └── try_mutex.go └── util │ ├── bytes.go │ ├── compare.go │ ├── hexutil.go │ ├── hexutil_test.go │ ├── json.go │ ├── json_example_test.go │ └── json_test.go ├── config.toml ├── config ├── config.go └── config_test.go ├── crypto ├── aes.go ├── aes_test.go ├── keys.go ├── keys_test.go ├── math.go ├── math_test.go ├── randg.go ├── randg_test.go ├── scrypt.go ├── scrypt_test.go ├── sha3.go ├── sha3 │ ├── LICENSE │ ├── PATENTS │ ├── doc.go │ ├── hashes.go │ ├── keccakf.go │ ├── keccakf_amd64.go │ ├── keccakf_amd64.s │ ├── register.go │ ├── sha3.go │ ├── sha3_test.go │ ├── shake.go │ ├── testdata │ │ └── keccakKats.json.deflate │ ├── xor.go │ ├── xor_generic.go │ └── xor_unaligned.go ├── sha3_test.go ├── uuid.go └── uuid_test.go ├── database ├── database.go ├── database_test.go ├── interface.go ├── memory_database.go ├── memory_database_iterator.go └── memory_database_test.go ├── filesystem ├── dirs.go ├── dirs_test.go └── dirs_test_helpers.go ├── go.mod ├── go.sum ├── log ├── log.go └── zap.go ├── metrics ├── common.go ├── common_test.go └── server.go ├── nattraversal ├── upnp.go └── upnp │ └── goupnp_wrapper.go ├── p2p ├── config │ ├── config.go │ └── params.go ├── connectionpool │ ├── connectionpool.go │ └── connectionpool_test.go ├── discovery │ ├── addrbook.go │ ├── addrbook_test.go │ ├── discovery.go │ ├── discovery_mock.go │ ├── discovery_test.go │ ├── getaddress.go │ ├── known_address.go │ ├── known_address_test.go │ ├── network.go │ ├── node_test.go │ ├── ping.go │ ├── protocol.go │ ├── protocol_test.go │ ├── refresher.go │ ├── refresher_test.go │ ├── store.go │ └── store_test.go ├── gossip │ ├── protocol.go │ └── protocol_test.go ├── integration_suite.go ├── integration_test.go ├── message.go ├── metrics │ └── prometheus.go ├── net │ ├── conn.go │ ├── conn_mock.go │ ├── conn_test.go │ ├── errors.go │ ├── handshake.go │ ├── msgcon.go │ ├── network.go │ ├── network_mock.go │ ├── network_test.go │ ├── readwritecloseaddresser_mock_test.go │ ├── session.go │ ├── session_cache.go │ ├── session_cache_test.go │ ├── session_mock.go │ ├── udp.go │ ├── udp_test.go │ └── wire │ │ ├── delimited │ │ ├── chan.go │ │ ├── chan_test.go │ │ ├── copy.go │ │ ├── delimited.go │ │ └── delimited_test.go │ │ └── formatter.go ├── node │ ├── helpers.go │ ├── localnode.go │ ├── localnodestore.go │ ├── localnodestore_test.go │ ├── node.go │ └── node_test.go ├── p2p.go ├── p2pcrypto │ ├── crypto.go │ └── crypto_test.go ├── peers.go ├── peers_test.go ├── server │ ├── msgserver.go │ └── msgserver_test.go ├── service │ ├── service.go │ └── sim.go ├── switch.go ├── switch_test.go ├── udp.go ├── udp_test.go └── version │ ├── version.go │ └── version_test.go ├── priorityq ├── priorityq.go └── priorityq_test.go ├── rand └── rand.go ├── scripts ├── check-go-version.sh ├── genproto.sh ├── install-protobuf.sh └── verify-protoc-gen-go.sh ├── setup_env.sh ├── signing ├── signer.go └── signer_test.go ├── sim_config.toml └── timesync ├── clock.go ├── clock_test.go ├── config └── config.go ├── converter.go ├── converter_test.go ├── ntp.go ├── ntp_test.go ├── ticker.go └── ticker_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /devtools 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Libonomy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINARY := go-libonomy 2 | COMMIT = $(shell git rev-parse HEAD) 3 | SHA = $(shell git rev-parse --short HEAD) 4 | CURR_DIR = $(shell pwd) 5 | CURR_DIR_WIN = $(shell cd) 6 | BIN_DIR = $(CURR_DIR)/build 7 | BIN_DIR_WIN = $(CURR_DIR_WIN)/build 8 | export GO111MODULE = on 9 | 10 | PKGS = $(shell go list ./...) 11 | 12 | PLATFORMS := windows linux darwin 13 | os = $(word 1, $@) 14 | 15 | 16 | 17 | install: 18 | ifeq ($(OS),Windows_NT) 19 | setup_env.bat 20 | else 21 | ./setup_env.sh 22 | endif 23 | .PHONY: install 24 | 25 | 26 | p2p-build: 27 | ifeq ($(OS),WINDOWS_NT) 28 | cd cmd/p2p ; go build -o $(BIN_DIR_WIN)/p2p-simulate.exe; cd .. 29 | else 30 | cd cmd/p2p ; go build -o $(BIN_DIR)/p2p-simulate; cd .. 31 | endif 32 | .PHONY: p2p 33 | 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Libonomy-p2p Simulator 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | 6 | Thank You for visiting our open source project. 7 | 8 | Libonomy is one of a kind blockchain which through innovation and creativity has achieved 9 | all the set goals with great success. The kind of innovation that attracts and reaches out to 10 | masses of people who have knowledge and understanding that this system is fulfilling its role 11 | of greatness. 12 | Many only talk about the next level blockchain technology that would fix all the existing 13 | problems all in one, Libonomy is already on a road to fulfill this ideology. 14 | 15 | We are designing and coding an autonomous blockchain that will run on first ever AI based consensus algorithm, which will make 16 | libonomy worlds first every autonomous, interoperable and scalable blockchain. 17 | To learn more about Libonomy visit [https://libonomy.com](https://libonomy.com). 18 | 19 | To learn more about the libonomy upcoming blockchain [Read this](https://libonomy.com/assets/pdf/yellow-paper.pdf). 20 | 21 | ### Benefits 22 | The major difference is that Libonomy Blockchain does not use any of the previous consensus 23 | algorithms that have been used for a very long time. All of these consensus algorithms have 24 | their own drawbacks, Libonomy believes in providing an error-free consensus engine that has 25 | been architecture very carefully. It uses Artificial Intelligence – automated, computer 26 | generated engine that saves time, energy and gives high throughput, is scalable, interoperable and autonomous. 27 | 28 | ### Project Status 29 | We are working hard towards our first patch - which is public testnet running libonomy AI consensus algorithm. 30 | Current version is the simulation of the p2p protocol that is being developed by the libonomy developers. In the upcoming 31 | days our team will be release the full node running the consensus algithm with which users will be able to carry out the transactions as well. 32 | 33 | ### Important 34 | This repository is currently under development and from time to time the file structure is meant to be updated. On releasing 35 | further features and repositories we will be announcing on our social media platforms and on website. 36 | 37 | ### How to begin 38 | 39 | ```bash 40 | git clone git@github.com:libonomy/libonomy-p2p.git 41 | ``` 42 | _-- or --_ 43 | 44 | Fork the project from https://github.com/libonomy/libonomy-p2p 45 | 46 | Go 1.14's Modules are being used in the package so it is best to place the code **outside** your `$GOPATH`. 47 | 48 | ### Setting Up Local Environment 49 | 50 | Install [Go 1.14 or later](https://golang.org/dl/) for your platform, if you haven't already. 51 | 52 | Ensure that `$GOPATH` is set correctly and that the `$GOPATH/bin` directory appears in `$PATH`. 53 | 54 | Before building we need to install `protoc` (ProtoBuf compiler) and some tools required to generate ProtoBufs. 55 | ```bash 56 | make install 57 | ``` 58 | This will invoke `setup_env.sh` which supports Linux . 59 | The configurations for other platforms will be released soon. 60 | 61 | 62 | 63 | ### Building Simulator 64 | To make a build of p2p simulator, from the project root directory, use: 65 | ``` 66 | make p2p-build 67 | ``` 68 | 69 | This will (re-)generate protobuf files and build the `libonomoy-p2p` binary, saving it in the `build/` directory. 70 | 71 | 72 | Platform-specific binaries are saved to the `/build` directory. 73 | 74 | ### Connection Establishment 75 | In order to connect to the p2p-simulater you need to provide the parameters for the network configuration which is explained below. 76 | The configuration file is named as sim_config.toml which you can find in the master branch. 77 | 78 | #### Simulater Configuration 79 | 80 | 1. Navigate to build directory under libonomy-p2p after generating build of p2p simulater. 81 | 2. Start the simulater with the command given below 82 | 83 | ```bash 84 | ./p2p-simulate --tcp-port 7152 --config ./sim_config.toml -d ./libo_data 85 | ``` 86 | As soon as you are done you will be connected to the p2p network. 87 | 88 | #### Upcoming Release 89 | - In the coming month we will be releasing the p2p light node for the community. 90 | - AI data extraction algorithm (minimal) 91 | - Wallet implementation in JS 92 | - Testnet Faucets 93 | - ..... 94 | -------------------------------------------------------------------------------- /cmd/p2p/p2p.go: -------------------------------------------------------------------------------- 1 | // package p2p cmd is the main executable for running p2p tests and simulations 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | _ "net/http/pprof" 8 | "os" 9 | 10 | cmdp "github.com/libonomy/libonomy-p2p/cmd" 11 | "github.com/libonomy/libonomy-p2p/log" 12 | "github.com/libonomy/libonomy-p2p/p2p" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // Cmd is the p2p cmd 17 | var Cmd = &cobra.Command{ 18 | Use: "p2p", 19 | Short: "start p2p", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | p2pApp := NewP2PApp() 22 | defer p2pApp.Cleanup() 23 | 24 | p2pApp.Initialize(cmd) 25 | p2pApp.Start(cmd, args) 26 | }, 27 | } 28 | 29 | func init() { 30 | cmdp.AddCommands(Cmd) 31 | } 32 | 33 | // P2PApp is an app struct used to run only p2p functionality 34 | type P2PApp struct { 35 | *cmdp.BaseApp 36 | p2p p2p.Service 37 | closers []io.Closer 38 | } 39 | 40 | // NewP2PApp creates the base app 41 | func NewP2PApp() *P2PApp { 42 | return &P2PApp{BaseApp: cmdp.NewBaseApp()} 43 | } 44 | 45 | // Cleanup closes all services 46 | func (app *P2PApp) Cleanup() { 47 | for _, c := range app.closers { 48 | err := c.Close() 49 | if err != nil { 50 | log.Warning("Error when closing service err=%v") 51 | } 52 | } 53 | // TODO: move to array of cleanup functions and execute all here 54 | } 55 | 56 | // Start creates a p2p instance and starts it with testing features enabled by the config. 57 | func (app *P2PApp) Start(cmd *cobra.Command, args []string) { 58 | // init p2p services 59 | log.JSONLog(true) 60 | log.DebugMode(true) 61 | 62 | log.Info("Initializing P2P services") 63 | 64 | logger := log.NewDefault("P2P_Test") 65 | 66 | swarm, err := p2p.New(cmdp.Ctx, app.Config.P2P, logger, app.Config.DataDir) 67 | if err != nil { 68 | log.Panic("Error init p2p services, err: %v", err) 69 | } 70 | app.p2p = swarm 71 | 72 | // Testing stuff 73 | // api.ApproveAPIGossipMessages(cmdp.Ctx, app.p2p) 74 | // metrics.StartCollectingMetrics(app.Config.MetricsPort) 75 | 76 | // start the node 77 | 78 | err = app.p2p.Start() 79 | defer app.p2p.Shutdown() 80 | 81 | if err != nil { 82 | log.Panic("Error starting p2p services, err: %v", err) 83 | } 84 | 85 | // if app.Config.PprofHTTPServer { 86 | // pprof := &http.Server{} 87 | // pprof.Addr = ":6060" 88 | // pprof.Handler = nil 89 | // go func() { err := pprof.ListenAndServe(); log.Error("error running pprof server err=%v", err) }() 90 | // app.closers = append(app.closers, pprof) 91 | // } 92 | 93 | // start api servers 94 | // if app.Config.API.StartGrpcServer || app.Config.API.StartJSONServer { 95 | // // start grpc if specified or if json rpc specified 96 | // log.Info("Started the GRPC Service") 97 | // grpc := api.NewGrpcService(app.Config.API.GrpcServerPort, app.p2p, nil, nil, nil, nil, nil, nil, nil, 0, nil, nil, nil) 98 | // grpc.StartService() 99 | // app.closers = append(app.closers, grpc) 100 | // } 101 | 102 | // if app.Config.API.StartJSONServer { 103 | // log.Info("Started the JSON Service") 104 | // json := api.NewJSONHTTPServer(app.Config.API.JSONServerPort, app.Config.API.GrpcServerPort) 105 | // json.StartService() 106 | // app.closers = append(app.closers, json) 107 | // } 108 | 109 | <-cmdp.Ctx.Done() 110 | } 111 | 112 | func main() { 113 | if err := Cmd.Execute(); err != nil { 114 | fmt.Fprintln(os.Stderr, err) 115 | os.Exit(1) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /common/types/address.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "math/big" 7 | 8 | "github.com/libonomy/libonomy-p2p/common/util" 9 | "github.com/libonomy/libonomy-p2p/crypto/sha3" 10 | ) 11 | 12 | const ( 13 | // AddressLength is the expected length of the address 14 | AddressLength = 20 15 | ) 16 | 17 | // Address represents the 20 byte address of an libonomy account. 18 | type Address [AddressLength]byte 19 | 20 | // BytesToAddress returns Address with value b. 21 | // If b is larger than len(h), b will be cropped from the left. 22 | func BytesToAddress(b []byte) Address { 23 | var a Address 24 | a.SetBytes(b) 25 | return a 26 | } 27 | 28 | // BigToAddress returns Address with byte values of b. 29 | // If b is larger than len(h), b will be cropped from the left. 30 | func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } 31 | 32 | // HexToAddress returns Address with byte values of s. 33 | // If s is larger than len(h), s will be cropped from the left. 34 | func HexToAddress(s string) Address { return BytesToAddress(util.FromHex(s)) } 35 | 36 | // StringToAddress returns Address with byte values of s. 37 | // If s is larger than len(h), s will be cropped from the left. 38 | // It is identical to HexToAddress, except decoding errors are returned instead of swallowed. 39 | func StringToAddress(s string) (Address, error) { 40 | if len(s) > 1 { 41 | if s[0:2] == "0x" || s[0:2] == "0X" { 42 | s = s[2:] 43 | } 44 | } 45 | if len(s)%2 == 1 { 46 | s = "0" + s 47 | } 48 | bt, err := hex.DecodeString(s) 49 | if err != nil { 50 | return Address{}, err 51 | } 52 | return BytesToAddress(bt), nil 53 | } 54 | 55 | // Bytes gets the string representation of the underlying address. 56 | func (a Address) Bytes() []byte { return a[:] } 57 | 58 | // Big converts an address to a big integer. 59 | func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) } 60 | 61 | // Hash converts an address to a hash by left-padding it with zeros. 62 | func (a Address) Hash() Hash32 { return CalcHash32(a[:]) } 63 | 64 | // Hex returns an EIP55-compliant hex string representation of the address. 65 | func (a Address) Hex() string { 66 | unchecksummed := hex.EncodeToString(a[:]) 67 | sha := sha3.NewKeccak256() 68 | sha.Write([]byte(unchecksummed)) 69 | hash := sha.Sum(nil) 70 | 71 | result := []byte(unchecksummed) 72 | for i := 0; i < len(result); i++ { 73 | hashByte := hash[i/2] 74 | if i%2 == 0 { 75 | hashByte = hashByte >> 4 76 | } else { 77 | hashByte &= 0xf 78 | } 79 | if result[i] > '9' && hashByte > 7 { 80 | result[i] -= 32 81 | } 82 | } 83 | return "0x" + string(result) 84 | } 85 | 86 | // String implements fmt.Stringer. 87 | func (a Address) String() string { 88 | return a.Hex() 89 | } 90 | 91 | // Short returns the first 7 characters of the address hex representation (incl. "0x"), for logging purposes. 92 | func (a Address) Short() string { 93 | hx := a.Hex() 94 | return hx[:util.Min(7, len(hx))] 95 | } 96 | 97 | // Format implements fmt.Formatter, forcing the byte slice to be formatted as is, 98 | // without going through the stringer interface used for logging. 99 | func (a Address) Format(s fmt.State, c rune) { 100 | _, _ = fmt.Fprintf(s, "%"+string(c), a[:]) 101 | } 102 | 103 | // SetBytes sets the address to the value of b. 104 | // If b is larger than len(a) it will panic. 105 | func (a *Address) SetBytes(b []byte) { 106 | if len(b) > len(a) { 107 | b = b[len(b)-AddressLength:] 108 | } 109 | copy(a[AddressLength-len(b):], b) 110 | } 111 | -------------------------------------------------------------------------------- /common/types/big.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package types 18 | 19 | import "math/big" 20 | 21 | // Common big integers often used 22 | var ( 23 | Big1 = big.NewInt(1) 24 | Big2 = big.NewInt(2) 25 | Big3 = big.NewInt(3) 26 | Big0 = big.NewInt(0) 27 | Big32 = big.NewInt(32) 28 | Big256 = big.NewInt(256) 29 | Big257 = big.NewInt(257) 30 | ) 31 | -------------------------------------------------------------------------------- /common/types/doublecache.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // DoubleCache is a structure for storing which keys have been encountered before. It's initialized with a size and it 8 | // stores between size and 2*size keys. Every time the cache size reaches 2*size and a new value is added, the oldest 9 | // size keys are discarded and the size drops back to size. 10 | // DoubleCache is thread safe. 11 | type DoubleCache struct { 12 | size uint 13 | cacheA map[Hash12]struct{} 14 | cacheB map[Hash12]struct{} 15 | cacheMutex sync.RWMutex 16 | } 17 | 18 | // NewDoubleCache returns a new DoubleCache. 19 | func NewDoubleCache(size uint) *DoubleCache { 20 | return &DoubleCache{size: size, cacheA: make(map[Hash12]struct{}, size), cacheB: make(map[Hash12]struct{}, size)} 21 | } 22 | 23 | // GetOrInsert checks if a value is already in the cache, otherwise it adds it. Returns bool whether or not the value 24 | // was found in the cache (true - already in cache, false - wasn't in cache before this was called). 25 | func (a *DoubleCache) GetOrInsert(key Hash12) bool { 26 | a.cacheMutex.Lock() 27 | defer a.cacheMutex.Unlock() 28 | if a.get(key) { 29 | return true 30 | } 31 | a.insert(key) 32 | return false 33 | } 34 | 35 | func (a *DoubleCache) get(key Hash12) bool { 36 | _, ok := a.cacheA[key] 37 | if ok { 38 | return true 39 | } 40 | _, ok = a.cacheB[key] 41 | if ok { 42 | return true 43 | } 44 | return false 45 | } 46 | 47 | func (a *DoubleCache) insert(key Hash12) { 48 | if uint(len(a.cacheA)) < a.size { 49 | a.cacheA[key] = struct{}{} 50 | return 51 | } 52 | if uint(len(a.cacheB)) < a.size { 53 | a.cacheB[key] = struct{}{} 54 | return 55 | } 56 | a.cacheA = a.cacheB 57 | a.cacheB = make(map[Hash12]struct{}, a.size) 58 | a.cacheB[key] = struct{}{} 59 | } 60 | -------------------------------------------------------------------------------- /common/types/doublecache_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func Test_doubleCache(t *testing.T) { 10 | size := uint(10) 11 | c := NewDoubleCache(size) 12 | require.Len(t, c.cacheA, 0) 13 | require.Len(t, c.cacheB, 0) 14 | 15 | for i := uint(0); i < size; i++ { 16 | require.False(t, c.GetOrInsert(CalcMessageHash12([]byte(fmt.Sprintf("LOL%v", i)), "prot"))) 17 | } 18 | 19 | require.Len(t, c.cacheA, int(size)) 20 | 21 | c.GetOrInsert(CalcMessageHash12([]byte(fmt.Sprintf("LOL%v", size+1)), "prot")) 22 | require.Len(t, c.cacheA, int(size)) 23 | require.Len(t, c.cacheB, 1) 24 | 25 | for i := uint(0); i < size-1; i++ { 26 | require.False(t, c.GetOrInsert(CalcMessageHash12([]byte(fmt.Sprintf("LOL%v", size+100+i)), "prot"))) 27 | } 28 | 29 | require.Len(t, c.cacheA, int(size)) 30 | require.Len(t, c.cacheB, int(size)) 31 | 32 | cacheBitems := make(map[Hash12]struct{}, len(c.cacheB)) 33 | for item := range c.cacheB { 34 | cacheBitems[item] = struct{}{} 35 | } 36 | 37 | require.False(t, c.GetOrInsert(CalcMessageHash12([]byte(fmt.Sprintf("LOL%v", size+1337)), "prot"))) 38 | // this should prune cache a which is the oldest and keep cache b items 39 | 40 | require.Len(t, c.cacheB, 1) 41 | require.Len(t, c.cacheA, int(size)) 42 | 43 | for item := range cacheBitems { 44 | _, ok := c.cacheA[item] 45 | require.True(t, ok) 46 | } 47 | 48 | require.True(t, c.GetOrInsert(CalcMessageHash12([]byte(fmt.Sprintf("LOL%v", size+1337)), "prot"))) 49 | require.False(t, c.GetOrInsert(CalcMessageHash12([]byte(fmt.Sprintf("LOL%v", 0)), "prot"))) // already pruned 50 | } 51 | -------------------------------------------------------------------------------- /common/types/encode.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/libonomy/libonomy-p2p/common/util" 9 | xdr "github.com/nullstyle/go-xdr/xdr3" 10 | ) 11 | 12 | // ToBytes returns the BlockID as a byte slice. 13 | func (id BlockID) ToBytes() []byte { return id.AsHash32().Bytes() } 14 | 15 | // ToBytes returns the byte representation of the LayerID, using little endian encoding. 16 | func (l LayerID) ToBytes() []byte { return util.Uint64ToBytes(uint64(l)) } 17 | 18 | // BlockIdsAsBytes serializes a slice of BlockIDs. 19 | func BlockIdsAsBytes(ids []BlockID) ([]byte, error) { 20 | var w bytes.Buffer 21 | SortBlockIDs(ids) 22 | if _, err := xdr.Marshal(&w, &ids); err != nil { 23 | return nil, errors.New("error marshalling block ids ") 24 | } 25 | return w.Bytes(), nil 26 | } 27 | 28 | // BytesToBlockIds deserializes a slice of BlockIDs. 29 | func BytesToBlockIds(blockIds []byte) ([]BlockID, error) { 30 | var ids []BlockID 31 | if _, err := xdr.Unmarshal(bytes.NewReader(blockIds), &ids); err != nil { 32 | return nil, fmt.Errorf("error marshaling layer: %v", err) 33 | } 34 | return ids, nil 35 | } 36 | 37 | // BytesAsAtx deserializes an ActivationTx. 38 | // func BytesAsAtx(b []byte) (*ActivationTx, error) { 39 | // buf := bytes.NewReader(b) 40 | // var atx ActivationTx 41 | // _, err := xdr.Unmarshal(buf, &atx) 42 | // if err != nil { 43 | // return nil, err 44 | // } 45 | // return &atx, nil 46 | // } 47 | 48 | // NIPSTChallengeAsBytes serializes a NIPSTChallenge. 49 | // func NIPSTChallengeAsBytes(challenge *NIPSTChallenge) ([]byte, error) { 50 | // var w bytes.Buffer 51 | // if _, err := xdr.Marshal(&w, challenge); err != nil { 52 | // return nil, fmt.Errorf("error marshalling NIPST Challenge: %v", err) 53 | // } 54 | // return w.Bytes(), nil 55 | // } 56 | 57 | // BytesAsTransaction deserializes a Transaction. 58 | func BytesAsTransaction(buf []byte) (*Transaction, error) { 59 | b := Transaction{} 60 | _, err := xdr.Unmarshal(bytes.NewReader(buf), &b) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return &b, nil 65 | } 66 | 67 | // BytesToInterface deserializes any type. 68 | // ⚠️ Pass the interface by reference 69 | func BytesToInterface(buf []byte, i interface{}) error { 70 | _, err := xdr.Unmarshal(bytes.NewReader(buf), i) 71 | if err != nil { 72 | return err 73 | } 74 | return nil 75 | } 76 | 77 | // InterfaceToBytes serializes any type. 78 | // ⚠️ Pass the interface by reference 79 | func InterfaceToBytes(i interface{}) ([]byte, error) { 80 | var w bytes.Buffer 81 | if _, err := xdr.Marshal(&w, &i); err != nil { 82 | return nil, err 83 | } 84 | return w.Bytes(), nil 85 | } 86 | -------------------------------------------------------------------------------- /common/types/hashes_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestHash(t *testing.T) { 9 | msg1 := []byte("msg1") 10 | msg2 := []byte("msg2") 11 | prot1 := "prot1" 12 | prot2 := "prot2" 13 | 14 | assert.NotEqual(t, CalcMessageHash12(msg1, prot1), CalcMessageHash12(msg1, prot2)) 15 | assert.NotEqual(t, CalcMessageHash12(msg1, prot1), CalcMessageHash12(msg2, prot1)) 16 | } 17 | 18 | func TestConvert32_20Hash(t *testing.T) { 19 | msg := []byte("abcdefghijk") 20 | hash32 := CalcHash32(msg) 21 | hash20 := hash32.ToHash20() 22 | cHash32 := hash20.ToHash32() 23 | hash20b := cHash32.ToHash20() 24 | assert.Equal(t, hash20b, hash20) 25 | } 26 | -------------------------------------------------------------------------------- /common/types/size.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package types 18 | 19 | import ( 20 | "fmt" 21 | ) 22 | 23 | // StorageSize is a wrapper around a float value that supports user friendly 24 | // formatting. 25 | type StorageSize float64 26 | 27 | // String implements the stringer interface. 28 | func (s StorageSize) String() string { 29 | if s > 1000000 { 30 | return fmt.Sprintf("%.2f mB", s/1000000) 31 | } else if s > 1000 { 32 | return fmt.Sprintf("%.2f kB", s/1000) 33 | } else { 34 | return fmt.Sprintf("%.2f B", s) 35 | } 36 | } 37 | 38 | // TerminalString implements log.TerminalStringer, formatting a string for console 39 | // output during logging. 40 | func (s StorageSize) TerminalString() string { 41 | if s > 1000000 { 42 | return fmt.Sprintf("%.2fmB", s/1000000) 43 | } else if s > 1000 { 44 | return fmt.Sprintf("%.2fkB", s/1000) 45 | } else { 46 | return fmt.Sprintf("%.2fB", s) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /common/types/size_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | package types 17 | 18 | import ( 19 | "testing" 20 | ) 21 | 22 | func TestStorageSizeString(t *testing.T) { 23 | tests := []struct { 24 | size StorageSize 25 | str string 26 | }{ 27 | {2381273, "2.38 mB"}, 28 | {2192, "2.19 kB"}, 29 | {12, "12.00 B"}, 30 | } 31 | 32 | for _, test := range tests { 33 | if test.size.String() != test.str { 34 | t.Errorf("%f: got %q, want %q", float64(test.size), test.size.String(), test.str) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /common/types/transaction.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/libonomy/ed25519" 7 | "github.com/libonomy/libonomy-p2p/log" 8 | ) 9 | 10 | // TransactionID is a 32-byte sha256 sum of the transaction, used as an identifier. 11 | type TransactionID Hash32 12 | 13 | // Hash32 returns the TransactionID as a Hash32. 14 | func (id TransactionID) Hash32() Hash32 { 15 | return Hash32(id) 16 | } 17 | 18 | // ShortString returns a the first 5 characters of the ID, for logging purposes. 19 | func (id TransactionID) ShortString() string { 20 | return id.Hash32().ShortString() 21 | } 22 | 23 | // String returns a hexadecimal representation of the TransactionID with "0x" prepended, for logging purposes. 24 | // It implements the fmt.Stringer interface. 25 | func (id TransactionID) String() string { 26 | return id.Hash32().String() 27 | } 28 | 29 | // Bytes returns the TransactionID as a byte slice. 30 | func (id TransactionID) Bytes() []byte { 31 | return id[:] 32 | } 33 | 34 | // Field returns a log field. Implements the LoggableField interface. 35 | func (id TransactionID) Field() log.Field { return id.Hash32().Field("tx_id") } 36 | 37 | // EmptyTransactionID is a canonical empty TransactionID. 38 | var EmptyTransactionID = TransactionID{} 39 | 40 | // Transaction contains all transaction fields, including the signature and cached origin address and transaction ID. 41 | type Transaction struct { 42 | InnerTransaction 43 | Signature [64]byte 44 | origin *Address 45 | id *TransactionID 46 | } 47 | 48 | // Origin returns the transaction's origin address: the public key extracted from the transaction signature. 49 | func (t *Transaction) Origin() Address { 50 | if t.origin == nil { 51 | panic("origin not set") 52 | } 53 | return *t.origin 54 | } 55 | 56 | // SetOrigin sets the cache of the transaction's origin address. 57 | func (t *Transaction) SetOrigin(origin Address) { 58 | t.origin = &origin 59 | } 60 | 61 | // CalcAndSetOrigin extracts the public key from the transaction's signature and caches it as the transaction's origin 62 | // address. 63 | func (t *Transaction) CalcAndSetOrigin() error { 64 | txBytes, err := InterfaceToBytes(&t.InnerTransaction) 65 | if err != nil { 66 | return fmt.Errorf("failed to marshal transaction: %v", err) 67 | } 68 | pubKey, err := ed25519.ExtractPublicKey(txBytes, t.Signature[:]) 69 | if err != nil { 70 | return fmt.Errorf("failed to extract transaction pubkey: %v", err) 71 | } 72 | 73 | t.origin = &Address{} 74 | t.origin.SetBytes(pubKey) 75 | return nil 76 | } 77 | 78 | // ID returns the transaction's ID. If it's not cached, it's calculated, cached and returned. 79 | func (t *Transaction) ID() TransactionID { 80 | if t.id != nil { 81 | return *t.id 82 | } 83 | 84 | txBytes, err := InterfaceToBytes(t) 85 | if err != nil { 86 | panic("failed to marshal transaction: " + err.Error()) 87 | } 88 | id := TransactionID(CalcHash32(txBytes)) 89 | t.id = &id 90 | return id 91 | } 92 | 93 | // Hash32 returns the TransactionID as a Hash32. 94 | func (t *Transaction) Hash32() Hash32 { 95 | return t.ID().Hash32() 96 | } 97 | 98 | // ShortString returns a the first 5 characters of the ID, for logging purposes. 99 | func (t *Transaction) ShortString() string { 100 | return t.ID().ShortString() 101 | } 102 | 103 | // String returns a string representation of the Transaction, for logging purposes. 104 | // It implements the fmt.Stringer interface. 105 | func (t *Transaction) String() string { 106 | return fmt.Sprintf("", 107 | t.ID().ShortString(), t.Origin().Short(), t.Recipient.Short(), t.Amount, t.AccountNonce, t.GasLimit, t.Fee) 108 | } 109 | 110 | // InnerTransaction includes all of a transaction's fields, except the signature (origin and id aren't stored). 111 | type InnerTransaction struct { 112 | AccountNonce uint64 113 | Recipient Address 114 | GasLimit uint64 115 | Fee uint64 116 | Amount uint64 117 | } 118 | 119 | // Reward is a virtual reward transaction, which the node keeps track of for the gRPC api. 120 | type Reward struct { 121 | Layer LayerID 122 | TotalReward uint64 123 | LayerRewardEstimate uint64 124 | } 125 | -------------------------------------------------------------------------------- /common/types/try_mutex.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "unsafe" 7 | ) 8 | 9 | const mutexUnLocked = 0 10 | const mutexLocked = 1 11 | 12 | // TryMutex is a simple sync.Mutex with the ability to try to Lock. 13 | type TryMutex struct { 14 | sync.Mutex 15 | } 16 | 17 | // TryLock tries to lock m. It returns true in case of success, false otherwise. 18 | func (m *TryMutex) TryLock() bool { 19 | return atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.Mutex)), mutexUnLocked, mutexLocked) 20 | } 21 | -------------------------------------------------------------------------------- /common/util/bytes.go: -------------------------------------------------------------------------------- 1 | // Package util provides common utility functions. 2 | package util 3 | 4 | import ( 5 | "encoding/binary" 6 | ) 7 | 8 | // BytesToUint32 interprets a byte slice as uint32, using little endian encoding. 9 | func BytesToUint32(i []byte) uint32 { return binary.LittleEndian.Uint32(i) } 10 | 11 | // Uint32ToBytes returns the byte representation of a uint32, using little endian encoding. 12 | func Uint32ToBytes(i uint32) []byte { 13 | a := make([]byte, 4) 14 | binary.LittleEndian.PutUint32(a, i) 15 | return a 16 | } 17 | 18 | // BytesToUint64 interprets a byte slice as uint64, using little endian encoding. 19 | func BytesToUint64(i []byte) uint64 { return binary.LittleEndian.Uint64(i) } 20 | 21 | // Uint64ToBytes returns the byte representation of a uint64, using little endian encoding. 22 | func Uint64ToBytes(i uint64) []byte { 23 | a := make([]byte, 8) 24 | binary.LittleEndian.PutUint64(a, i) 25 | return a 26 | } 27 | 28 | // Uint64ToBytesBigEndian returns the byte representation of a uint64, using big endian encoding (which is not the 29 | // default for libonomy). 30 | func Uint64ToBytesBigEndian(i uint64) []byte { 31 | a := make([]byte, 8) 32 | binary.BigEndian.PutUint64(a, i) 33 | return a 34 | } 35 | 36 | // CopyBytes returns an exact copy of the provided bytes. 37 | func CopyBytes(b []byte) (copiedBytes []byte) { 38 | if b == nil { 39 | return nil 40 | } 41 | copiedBytes = make([]byte, len(b)) 42 | copy(copiedBytes, b) 43 | 44 | return 45 | } 46 | 47 | // LeftPadBytes zero-pads slice to the left up to length l. 48 | func LeftPadBytes(slice []byte, l int) []byte { 49 | if l <= len(slice) { 50 | return slice 51 | } 52 | 53 | padded := make([]byte, l) 54 | copy(padded[l-len(slice):], slice) 55 | 56 | return padded 57 | } 58 | -------------------------------------------------------------------------------- /common/util/compare.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // Min returns the smaller of the two inputs, both of type int. 4 | func Min(a, b int) int { 5 | if a < b { 6 | return a 7 | } 8 | return b 9 | } 10 | 11 | // Min32 returns the smaller of the two inputs, both of type uint32. 12 | func Min32(a, b uint32) uint32 { 13 | if a < b { 14 | return a 15 | } 16 | return b 17 | } 18 | 19 | // Min64 returns the smaller of the two inputs, both of type uint64. 20 | func Min64(a, b uint64) uint64 { 21 | if a < b { 22 | return a 23 | } 24 | return b 25 | } 26 | -------------------------------------------------------------------------------- /common/util/json_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package util 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | ) 23 | 24 | type MyType [5]byte 25 | 26 | func (v *MyType) UnmarshalText(input []byte) error { 27 | return UnmarshalFixedText("MyType", input, v[:]) 28 | } 29 | 30 | func (v MyType) String() string { 31 | return Bytes(v[:]).String() 32 | } 33 | 34 | func ExampleUnmarshalFixedText() { 35 | var v1, v2 MyType 36 | fmt.Println("v1 error:", json.Unmarshal([]byte(`"0x01"`), &v1)) 37 | fmt.Println("v2 error:", json.Unmarshal([]byte(`"0x0101010101"`), &v2)) 38 | fmt.Println("v2:", v2) 39 | // Output: 40 | // v1 error: hex string has length 2, want 10 for MyType 41 | // v2 error: 42 | // v2: 0x0101010101 43 | } 44 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # sample libonomy config file 2 | # use the config flag to start a node with a config file. 3 | # e.g $./go-libonomy --config ./config.toml 4 | # cli flags will get a higher priority than configured in the file. 5 | 6 | # Main Config 7 | [main] 8 | data-folder = "~/.libonomy-data" 9 | test-mode = false 10 | 11 | # Node Config 12 | [p2p] 13 | security-param = 20 14 | fast-sync = true 15 | tcp-port = 7513 16 | node-id = "" 17 | new-node= false 18 | dial-timeout = "1m" 19 | conn-keepalive = "48h" 20 | network-id = 1 # 0 - MainNet, 1 - TestNet 21 | response-timeout = "2s" 22 | session-timeout = "2s" 23 | max-pending-connections = 50 24 | target-outbound = 10 25 | max-inbound = 100 26 | buffer-size = 100 27 | peers-file = "peers.json" # located under data-dir// not loaded or save if empty string is given. 28 | 29 | # Node Swarm Config 30 | [p2p.swarm] 31 | bootstrap = false 32 | bucketsize = 20 # Routing table bucket size 33 | alpha = 3 # Routing table alpha 34 | randcon = 2 # Number of random connections 35 | bootnodes = [] # example : libonomy://j7qWfWaJRVp25ZsnCu9rJ4PmhigZBtesB4YmQHqqPvt@0.0.0.0:7517?disc=7517 36 | 37 | # Time sync NTP Config 38 | [time] 39 | max-allowed-time-drift = "10s" 40 | ntp-queries = 5 41 | default-timeout-latency = "10s" 42 | 43 | [logging] 44 | p2p = "info" 45 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestLoadConfig(t *testing.T) { 10 | // todo: test more 11 | vip := viper.New() 12 | err := LoadConfig(".asdasda", vip) 13 | assert.Error(t, err) 14 | } 15 | -------------------------------------------------------------------------------- /crypto/aes.go: -------------------------------------------------------------------------------- 1 | // Package crypto provides funcs and types used by other packages to perform crypto related ops 2 | package crypto 3 | 4 | import ( 5 | "bytes" 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "errors" 9 | ) 10 | 11 | // AesCTRXOR is an AES cipher following https://leanpub.com/gocrypto/read#leanpub-auto-aes-cbc . 12 | func AesCTRXOR(key, input, nonce []byte) ([]byte, error) { 13 | aesBlock, err := aes.NewCipher(key) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | stream := cipher.NewCTR(aesBlock, nonce) 19 | output := make([]byte, len(input)) 20 | stream.XORKeyStream(output, input) 21 | return output, nil 22 | } 23 | 24 | // Pkcs7Pad returns input with padding using the pkcs7 padding spec. 25 | func Pkcs7Pad(in []byte) []byte { 26 | padding := 16 - (len(in) % 16) 27 | for i := 0; i < padding; i++ { 28 | in = append(in, byte(padding)) 29 | } 30 | return in 31 | } 32 | 33 | // Pkcs7Unpad returned in without padded data using pkcs7 padding spec. 34 | func Pkcs7Unpad(in []byte) []byte { 35 | if len(in) == 0 { 36 | return nil 37 | } 38 | 39 | padding := in[len(in)-1] 40 | if int(padding) > len(in) || padding > aes.BlockSize { 41 | return nil 42 | } else if padding == 0 { 43 | return nil 44 | } 45 | 46 | for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { 47 | if in[i] != padding { 48 | return nil 49 | } 50 | } 51 | return in[:len(in)-int(padding)] 52 | } 53 | 54 | // AddPKCSPadding Adds padding to a block of data. 55 | func AddPKCSPadding(src []byte) []byte { 56 | padding := aes.BlockSize - len(src)%aes.BlockSize 57 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 58 | return append(src, padtext...) 59 | } 60 | 61 | // RemovePKCSPadding Removes padding from data that was added using AddPKCSPadding. 62 | func RemovePKCSPadding(src []byte) ([]byte, error) { 63 | length := len(src) 64 | padLength := int(src[length-1]) 65 | if padLength > aes.BlockSize || length < aes.BlockSize { 66 | return nil, errors.New("invalid PKCS#7 padding") 67 | } 68 | 69 | return src[:length-padLength], nil 70 | } 71 | -------------------------------------------------------------------------------- /crypto/aes_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/aes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestAesCTRXORandPkcs7Pad(t *testing.T) { 12 | const msg = "does not matter" 13 | msgData := []byte(msg) 14 | 15 | dudtestkey := make([]byte, 17, 17) 16 | var nb = []byte(nil) 17 | // dudtestkey is initially not a multiple of 16 so nil is returned 18 | _, err := AesCTRXOR(dudtestkey, msgData, nb) 19 | assert.NotNil(t, err, fmt.Sprintf("incorrect length should error <%v>", err)) 20 | assert.Equal(t, fmt.Sprintf("%s", err), "crypto/aes: invalid key size 17", fmt.Sprintf("incorrect length should error (%s)", err)) 21 | // Pkcs7Pad makes a good lengthed key from the bad one 22 | happytestkey := Pkcs7Pad(dudtestkey) 23 | assert.Equal(t, len(happytestkey), 32, fmt.Sprintf("Pkcs7Pad incorrect length %d", len(happytestkey))) 24 | // now call AesCTRXOR with good parameters and get a nice return value 25 | happynonce := make([]byte, 16, 16) 26 | happy, err := AesCTRXOR(happytestkey, msgData, happynonce) 27 | // check no error 28 | assert.Nil(t, err, "should be happy aesctr err") 29 | // check something like data is in return value 30 | assert.NotNil(t, happy, "should be happy aesctr data") 31 | } 32 | 33 | func TestPkcs7Unpad(t *testing.T) { 34 | zerolength := []byte("") 35 | // check that zero length payload gives nil 36 | rv := Pkcs7Unpad(zerolength) 37 | assert.Equal(t, len(rv), 0, fmt.Sprintf("zero length payload should return nil but is %v", rv)) 38 | // 26 letters + 7 dots 39 | wrongsize := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ.......") 40 | // trigger padding > blocksize, expect nil 41 | wrongsize[len(wrongsize)-1] = byte(aes.BlockSize + 1) 42 | rv = Pkcs7Unpad(wrongsize) 43 | assert.Equal(t, len(rv), 0, fmt.Sprintf("padding > blocksize should return nil but is %v", rv)) 44 | // trigger padding ==0 , expect nil 45 | wrongsize[len(wrongsize)-1] = 0 46 | rv = Pkcs7Unpad(wrongsize) 47 | assert.Equal(t, len(rv), 0, fmt.Sprintf("padding == 0 should return nil but is %v", rv)) 48 | // trigger non padding byte found during reduction 49 | pad := 7 50 | for i := len(wrongsize) - 1; i > len(wrongsize)-pad; i-- { 51 | wrongsize[i] = byte(pad) 52 | } 53 | rv = Pkcs7Unpad(wrongsize) 54 | assert.Equal(t, len(rv), 0, fmt.Sprintf("non padding byte found should return nil but is %v", rv)) 55 | // actually remove some padding and get a proper good result 56 | for i := len(wrongsize) - 1; i > len(wrongsize)-pad-1; i-- { 57 | wrongsize[i] = byte(pad) 58 | } 59 | rightsize := Pkcs7Unpad(wrongsize) 60 | assert.True(t, len(rightsize) > 0 && len(rightsize) == 26, fmt.Sprintf("should not be sized %d", len(rightsize))) 61 | 62 | } 63 | func TestPKCSPadding(t *testing.T) { 64 | // 26 letters + 7 dots is 33 bytes 65 | wrongsize := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ.......") 66 | // should pad up to a size of a multiple of 16 67 | padout := AddPKCSPadding(wrongsize) 68 | assert.True(t, len(padout) == 48, fmt.Sprintf("should not be sized %d", len(padout))) 69 | unpad, err := RemovePKCSPadding(padout) 70 | assert.True(t, len(unpad) == len(wrongsize), fmt.Sprintf("should not be sized %d", len(padout))) 71 | assert.Nil(t, err, fmt.Sprintf("error from RemovePKCSPadding %d", err)) 72 | // force error by setting pad length incorrectly 73 | breaklength := padout 74 | breaklength[len(breaklength)-1] = byte(17) 75 | breaklengthrv, errb := RemovePKCSPadding(padout) 76 | assert.Equal(t, len(breaklengthrv), 0, fmt.Sprintf("should not be sized %d", len(breaklengthrv))) 77 | assert.NotNil(t, errb, fmt.Sprintf("no error from RemovePKCSPadding %d", errb)) 78 | } 79 | -------------------------------------------------------------------------------- /crypto/keys_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/libonomy/libonomy-p2p/log" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestBasicApi(t *testing.T) { 14 | 15 | badData, _ := hex.DecodeString("1234") 16 | _, err := NewPublicKey(badData) 17 | assert.Error(t, err, "expected error for bad key data") 18 | 19 | _, err = NewPrivateKey(badData) 20 | assert.Error(t, err, "expected error for bad key data") 21 | 22 | _, err = NewPrivateKeyFromString("1234") 23 | assert.Error(t, err, "expected error for bad key data") 24 | 25 | priv, pub, err := GenerateKeyPair() 26 | 27 | assert.Nil(t, err, "failed to generate keys") 28 | log.Debug("priv: %s, pub: %s", priv.Pretty(), pub.Pretty()) 29 | log.Debug("priv: %s, pub: %s", priv.String(), pub.String()) 30 | 31 | pub1 := priv.GetPublicKey() 32 | assert.True(t, bytes.Equal(pub.Bytes(), pub1.Bytes()), fmt.Sprintf("expected same pub key, %s, %s", 33 | pub.String(), pub1.String())) 34 | 35 | // serialization tests 36 | priv1, err := NewPrivateKey(priv.Bytes()) 37 | assert.NoError(t, err, "unexpected error") 38 | assert.True(t, bytes.Equal(priv1.Bytes(), priv.Bytes()), fmt.Sprintf("expected same private key, %s, %s", 39 | priv1.String(), priv.String())) 40 | 41 | priv2, err := NewPrivateKeyFromString(priv.String()) 42 | assert.NoError(t, err, "unexpected error") 43 | assert.True(t, bytes.Equal(priv2.Bytes(), priv.Bytes()), fmt.Sprintf("expected same private key, %s, %s", 44 | priv2.String(), priv.String())) 45 | 46 | pub2, err := NewPublicKey(pub.Bytes()) 47 | assert.Nil(t, err, fmt.Sprintf("New pub key from bin error: %v", err)) 48 | 49 | assert.True(t, bytes.Equal(pub2.Bytes(), pub.Bytes()), fmt.Sprintf("expected same public key, %s, %s", 50 | pub2.String(), pub.String())) 51 | 52 | pub3, err := NewPublicKeyFromString(pub.String()) 53 | 54 | assert.Nil(t, err, fmt.Sprintf("New pub key from bin error: %v", err)) 55 | 56 | assert.True(t, bytes.Equal(pub3.Bytes(), pub.Bytes()), fmt.Sprintf("Expected same public key, %s, %s", 57 | pub3.String(), pub.String())) 58 | } 59 | 60 | func TestCryptoApi(t *testing.T) { 61 | 62 | priv, pub, err := GenerateKeyPair() 63 | 64 | assert.Nil(t, err, "Failed to generate keys") 65 | 66 | const msg = "hello world" 67 | msgData := []byte(msg) 68 | 69 | // test signatures 70 | signature, err := priv.Sign(msgData) 71 | 72 | assert.Nil(t, err, fmt.Sprintf("signing error: %v", err)) 73 | ok, err := pub.Verify(msgData, signature) 74 | assert.Nil(t, err, fmt.Sprintf("sign verification error: %v", err)) 75 | 76 | assert.True(t, ok, "Failed to verify signature") 77 | 78 | ok, err = pub.VerifyString(msgData, hex.EncodeToString(signature)) 79 | assert.Nil(t, err, fmt.Sprintf("sign verification error: %v", err)) 80 | assert.True(t, ok, "Failed to verify signature") 81 | 82 | _, pub2, _ := GenerateKeyPair() 83 | ok, err = pub2.Verify(msgData, signature) 84 | assert.NoError(t, err) 85 | assert.False(t, ok) 86 | 87 | ok, err = pub2.VerifyString(msgData, hex.EncodeToString(signature)) 88 | assert.Nil(t, err, fmt.Sprintf("sign verification error: %v", err)) 89 | assert.False(t, ok, "succeed to verify wrong signature") 90 | 91 | // test encrypting a message for pub by pub - anyone w pub can do that 92 | cypherText, err := pub.Encrypt(msgData) 93 | 94 | assert.Nil(t, err, fmt.Sprintf("enc error: %v", err)) 95 | 96 | // test decryption 97 | clearText, err := priv.Decrypt(cypherText) 98 | assert.Nil(t, err, fmt.Sprintf("dec error: %v", err)) 99 | 100 | assert.True(t, bytes.Equal(msgData, clearText), "expected same dec message") 101 | 102 | } 103 | 104 | func BenchmarkVerify(b *testing.B) { 105 | b.StopTimer() 106 | 107 | priv, pub, err := GenerateKeyPair() 108 | 109 | assert.Nil(b, err, "Failed to generate keys") 110 | 111 | const msg = "hello world" 112 | msgData := []byte(msg) 113 | 114 | // test signatures 115 | signature, err := priv.Sign(msgData) 116 | 117 | assert.Nil(b, err, fmt.Sprintf("signing error: %v", err)) 118 | 119 | b.StartTimer() 120 | for i := 0; i < b.N; i++ { 121 | //ok, err := pub.Verify(msgData, signature) 122 | pub.Verify(msgData, signature) 123 | 124 | } 125 | b.StopTimer() 126 | 127 | } 128 | -------------------------------------------------------------------------------- /crypto/math.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | // MinInt32 returns x if x < y and y otherwise. 4 | func MinInt32(x, y int32) int32 { 5 | if x < y { 6 | return x 7 | } 8 | return y 9 | } 10 | 11 | // MinInt returns x if x < y and y otherwise. 12 | func MinInt(x, y int) int { 13 | if x < y { 14 | return x 15 | } 16 | return y 17 | } 18 | 19 | // MinInt64 returns x if x < y and y otherwise. 20 | func MinInt64(x, y int64) int64 { 21 | if x < y { 22 | return x 23 | } 24 | return y 25 | } 26 | -------------------------------------------------------------------------------- /crypto/math_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMinXY(t *testing.T) { 11 | const MaxUintValue = ^uint(0) 12 | const MinUintValue = 0 13 | const MaxIntValue = int(MaxUintValue >> 1) 14 | const MinIntValue = -MaxIntValue - 1 15 | 16 | const MaxUint32Value = ^uint32(0) 17 | const MinUint32Value = 0 18 | const MaxInt32Value = int32(MaxUint32Value >> 1) 19 | const MinInt32Value = -MaxInt32Value - 1 20 | 21 | const MaxUint64Value = ^uint64(0) 22 | const MinUint64Value = 0 23 | const MaxInt64Value = int64(MaxUint64Value >> 1) 24 | const MinInt64Value = -MaxInt64Value - 1 25 | 26 | const FavouriteInt = 17 27 | const FavouriteInt32 = int32(17) 28 | const FavouriteInt64 = int64(17) 29 | 30 | //int 31 | // biggest vs smallest 32 | try := MinInt(MaxIntValue, MinIntValue) 33 | assert.Equal(t, try, MinIntValue, fmt.Sprintf("big vs small should be %d, is %d", MinIntValue, try)) 34 | // smallest vs biggest 35 | try = MinInt(MinIntValue, MaxIntValue) 36 | assert.Equal(t, try, MinIntValue, fmt.Sprintf("small vs big should be %d, is %d", MinIntValue, try)) 37 | // biggest -1 vs biggest 38 | try = MinInt(MaxIntValue-1, MaxIntValue) 39 | assert.Equal(t, try, MaxIntValue-1, fmt.Sprintf("big vs biggest should be %d, is %d", MaxIntValue-1, try)) 40 | // smallest+1 vs smallest 41 | try = MinInt(MinIntValue+1, MinIntValue) 42 | assert.Equal(t, try, MinIntValue, fmt.Sprintf("tiny vs smallest should be %d, is %d", MinIntValue, try)) 43 | // the same 44 | try = MinInt(FavouriteInt, FavouriteInt) 45 | assert.Equal(t, try, FavouriteInt, fmt.Sprintf("equal values should be %d, is %d", FavouriteInt, try)) 46 | 47 | //int32 48 | // biggest vs smallest 49 | try32 := MinInt32(MaxInt32Value, MinInt32Value) 50 | assert.Equal(t, try32, MinInt32Value, fmt.Sprintf("int32 big vs small should be %d, is %d", MinInt32Value, try32)) 51 | // smallest vs biggest 52 | try32 = MinInt32(MinInt32Value, MaxInt32Value) 53 | assert.Equal(t, try32, MinInt32Value, fmt.Sprintf("int32 small vs big should be %d, is %d", MinInt32Value, try32)) 54 | // biggest -1 vs biggest 55 | try32 = MinInt32(MaxInt32Value-1, MaxInt32Value) 56 | assert.Equal(t, try32, MaxInt32Value-1, fmt.Sprintf("int32 big vs biggest should be %d, is %d", MaxInt32Value-1, try32)) 57 | // smallest+1 vs smallest 58 | try32 = MinInt32(MinInt32Value+1, MinInt32Value) 59 | assert.Equal(t, try32, MinInt32Value, fmt.Sprintf("int32 tiny vs smallest should be %d, is %d", MinInt32Value, try32)) 60 | // the same 61 | try32 = MinInt32(FavouriteInt32, FavouriteInt32) 62 | assert.Equal(t, try32, FavouriteInt32, fmt.Sprintf("int32 equal values should be %d, is %d", FavouriteInt32, try32)) 63 | 64 | //int64 65 | // biggest vs smallest 66 | try64 := MinInt64(MaxInt64Value, MinInt64Value) 67 | assert.Equal(t, try64, MinInt64Value, fmt.Sprintf("int64 big vs small should be %d, is %d", MinInt64Value, try64)) 68 | // smallest vs biggest 69 | try64 = MinInt64(MinInt64Value, MaxInt64Value) 70 | assert.Equal(t, try64, MinInt64Value, fmt.Sprintf("int64 small vs big should be %d, is %d", MinInt64Value, try64)) 71 | // biggest -1 vs biggest 72 | try64 = MinInt64(MaxInt64Value-1, MaxInt64Value) 73 | assert.Equal(t, try64, MaxInt64Value-1, fmt.Sprintf("int64 big vs biggest should be %d, is %d", MaxInt64Value-1, try64)) 74 | // smallest+1 vs smallest 75 | try64 = MinInt64(MinInt64Value+1, MinInt64Value) 76 | assert.Equal(t, try64, MinInt64Value, fmt.Sprintf("int64 tiny vs smallest should be %d, is %d", MinInt64Value, try64)) 77 | // the same 78 | try64 = MinInt64(FavouriteInt64, FavouriteInt64) 79 | assert.Equal(t, try64, FavouriteInt64, fmt.Sprintf("int64 equal values should be %d, is %d", FavouriteInt64, try64)) 80 | 81 | } 82 | -------------------------------------------------------------------------------- /crypto/randg.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "errors" 7 | 8 | "github.com/libonomy/libonomy-p2p/log" 9 | ) 10 | 11 | // GetRandomBytesToBuffer puts n random bytes using go crypto.rand into provided buff slice. 12 | // buff: a slice allocated by called to hold n bytes. 13 | func GetRandomBytesToBuffer(n int, buff []byte) error { 14 | 15 | if n == 0 { 16 | return errors.New("invalid input param - n must be positive") 17 | } 18 | 19 | if len(buff) < n { 20 | return errors.New("invalid input param - buff must be allocated to hold n items") 21 | } 22 | 23 | _, err := rand.Read(buff) 24 | 25 | if err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | // GetRandomBytes returns n random bytes. It returns an error if the system's pgn fails. 33 | func GetRandomBytes(n int) ([]byte, error) { 34 | 35 | if n == 0 { 36 | return nil, errors.New("invalid input param - n must be positive") 37 | } 38 | 39 | b := make([]byte, n) 40 | _, err := rand.Read(b) 41 | 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return b, nil 47 | } 48 | 49 | // GetRandomUInt32 returns a uint32 in the range [0 - max). 50 | func GetRandomUInt32(max uint32) uint32 { 51 | 52 | b := make([]byte, 4) 53 | _, err := rand.Read(b) 54 | 55 | if err != nil { 56 | log.Panic("Failed to get entropy from system: ", err) 57 | } 58 | 59 | data := binary.BigEndian.Uint32(b) 60 | return data % max 61 | } 62 | -------------------------------------------------------------------------------- /crypto/randg_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | "unsafe" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestGetRandomBytesToBuffer(t *testing.T) { 13 | n := 32 14 | tb := make([]byte, n) 15 | eb := make([]byte, n) 16 | // pass in a zero 17 | err := GetRandomBytesToBuffer(0, tb) 18 | assert.Error(t, err, "zero parameter should give error") 19 | // deliberately too large n parameter 20 | err = GetRandomBytesToBuffer(n+1, tb) 21 | assert.Error(t, err, "too large parameter should give error") 22 | // call with good parameters 23 | err = GetRandomBytesToBuffer(n, tb) 24 | assert.Equal(t, err, nil, fmt.Sprintf("unexpected error %v", err)) 25 | assert.False(t, bytes.Equal(tb, eb), "null data from GetRandomBytes") 26 | 27 | } 28 | 29 | func TestGetRandomBytes(t *testing.T) { 30 | n := 32 31 | eb := make([]byte, n) 32 | // pass in a zero 33 | tb, err := GetRandomBytes(0) 34 | assert.Error(t, err, "zero parameter should give error") 35 | // call with good parameters 36 | tb, err = GetRandomBytes(n) 37 | assert.Equal(t, err, nil, fmt.Sprintf("unexpected error %v", err)) 38 | assert.False(t, bytes.Equal(tb, eb), "null data from GetRandomBytes") 39 | } 40 | 41 | func TestGetRandomUInt32(t *testing.T) { 42 | var max = uint32(16384) 43 | x := GetRandomUInt32(max) 44 | assert.True(t, unsafe.Sizeof(x) == 4, "unexpected GetRandomUInt32 failure") 45 | } 46 | -------------------------------------------------------------------------------- /crypto/scrypt.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "golang.org/x/crypto/scrypt" 7 | ) 8 | 9 | // KDParams defines key derivation scheme params. 10 | type KDParams struct { 11 | N int `json:"n"` 12 | R int `json:"r"` 13 | P int `json:"p"` 14 | SaltLen int `json:"saltLen"` 15 | DKLen int `json:"dkLen"` 16 | Salt string `json:"salt"` // hex encoded 17 | } 18 | 19 | // DefaultCypherParams used for key derivation by the app. 20 | var DefaultCypherParams = KDParams{N: 262144, R: 8, P: 1, SaltLen: 16, DKLen: 32} 21 | 22 | // DeriveKeyFromPassword derives a key from password using the provided KDParams params. 23 | func DeriveKeyFromPassword(password string, p KDParams) ([]byte, error) { 24 | 25 | if len(p.Salt) == 0 { 26 | return nil, errors.New("invalid salt length param") 27 | } 28 | 29 | salt, err := hex.DecodeString(p.Salt) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | if len(salt) != p.SaltLen { 35 | return nil, errors.New("missing salt") 36 | } 37 | 38 | dkData, err := scrypt.Key([]byte(password), salt, p.N, p.R, p.P, p.DKLen) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return dkData, nil 44 | } 45 | -------------------------------------------------------------------------------- /crypto/scrypt_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestDeriveKey(t *testing.T) { 13 | // declared in scrypt.go 14 | var dead = DefaultCypherParams 15 | var good = DefaultCypherParams 16 | const pass = "beagles" 17 | 18 | //test for hex decode error 19 | dead.Salt = "IJKL" 20 | data, err := DeriveKeyFromPassword(pass, dead) 21 | assert.True(t, bytes.Equal(data, []byte("")), fmt.Sprintf("hex decode error should give nil but gave %v", data)) 22 | assert.Error(t, err, fmt.Sprint("hex decode should give error")) 23 | 24 | dead.SaltLen = 4 25 | dead.Salt = "ABCD" 26 | // force error in scrypt.Key 27 | // N must be a power of two so this should give error 28 | dead.N = 3 29 | data, err = DeriveKeyFromPassword(pass, dead) 30 | assert.True(t, bytes.Equal(data, []byte("")), fmt.Sprintf("scrypt.Key error should give [] but gave %v", data)) 31 | assert.Error(t, err, fmt.Sprint("scrypt.Key should give error")) 32 | 33 | // test derivation without a valid set salt 34 | data, err = DeriveKeyFromPassword(pass, good) 35 | assert.Error(t, err, "expected no salt error") 36 | 37 | s, err := GetRandomBytes(good.SaltLen) 38 | assert.NoError(t, err, "failed to generate salt") 39 | good.Salt = hex.EncodeToString(s) 40 | 41 | // try good parameters and get valid result 42 | data, err = DeriveKeyFromPassword(pass, good) 43 | assert.Nil(t, err, fmt.Sprintf("scrypt good output error %v", err)) 44 | assert.True(t, len(data) == good.DKLen, fmt.Sprintf("return value size %d, %d expected", len(data), good.DKLen)) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /crypto/sha3.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "github.com/libonomy/libonomy-p2p/common/types" 5 | "github.com/libonomy/libonomy-p2p/crypto/sha3" 6 | ) 7 | 8 | // Sha256 is a SHA-3-256 (not sha-256) hasher. It returns a 32 bytes (256 bits) hash of data. 9 | // data: arbitrary length bytes slice 10 | func Sha256(data ...[]byte) []byte { 11 | d := sha3.New256() 12 | for _, b := range data { 13 | d.Write(b) 14 | } 15 | return d.Sum(nil) 16 | } 17 | 18 | // Keccak256Hash calculates and returns the Keccak256 hash of the input data, 19 | // converting it to an internal Hash data structure. 20 | func Keccak256Hash(data ...[]byte) (h types.Hash32) { 21 | d := sha3.NewKeccak256() 22 | for _, b := range data { 23 | d.Write(b) 24 | } 25 | d.Sum(h[:0]) 26 | return h 27 | } 28 | 29 | // Keccak256 calculates and returns the Keccak256 hash of the input data. 30 | func Keccak256(data ...[]byte) []byte { 31 | d := sha3.NewKeccak256() 32 | for _, b := range data { 33 | d.Write(b) 34 | } 35 | return d.Sum(nil) 36 | } 37 | -------------------------------------------------------------------------------- /crypto/sha3/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /crypto/sha3/PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /crypto/sha3/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package sha3 implements the SHA-3 fixed-output-length hash functions and 6 | // the SHAKE variable-output-length hash functions defined by FIPS-202. 7 | // 8 | // Both types of hash function use the "sponge" construction and the Keccak 9 | // permutation. For a detailed specification see http://keccak.noekeon.org/ 10 | // 11 | // 12 | // Guidance 13 | // 14 | // If you aren't sure what function you need, use SHAKE256 with at least 64 15 | // bytes of output. The SHAKE instances are faster than the SHA3 instances; 16 | // the latter have to allocate memory to conform to the hash.Hash interface. 17 | // 18 | // If you need a secret-key MAC (message authentication code), prepend the 19 | // secret key to the input, hash with SHAKE256 and read at least 32 bytes of 20 | // output. 21 | // 22 | // 23 | // Security strengths 24 | // 25 | // The SHA3-x (x equals 224, 256, 384, or 512) functions have a security 26 | // strength against preimage attacks of x bits. Since they only produce "x" 27 | // bits of output, their collision-resistance is only "x/2" bits. 28 | // 29 | // The SHAKE-256 and -128 functions have a generic security strength of 256 and 30 | // 128 bits against all attacks, provided that at least 2x bits of their output 31 | // is used. Requesting more than 64 or 32 bytes of output, respectively, does 32 | // not increase the collision-resistance of the SHAKE functions. 33 | // 34 | // 35 | // The sponge construction 36 | // 37 | // A sponge builds a pseudo-random function from a public pseudo-random 38 | // permutation, by applying the permutation to a state of "rate + capacity" 39 | // bytes, but hiding "capacity" of the bytes. 40 | // 41 | // A sponge starts out with a zero state. To hash an input using a sponge, up 42 | // to "rate" bytes of the input are XORed into the sponge's state. The sponge 43 | // is then "full" and the permutation is applied to "empty" it. This process is 44 | // repeated until all the input has been "absorbed". The input is then padded. 45 | // The digest is "squeezed" from the sponge in the same way, except that output 46 | // output is copied out instead of input being XORed in. 47 | // 48 | // A sponge is parameterized by its generic security strength, which is equal 49 | // to half its capacity; capacity + rate is equal to the permutation's width. 50 | // Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means 51 | // that the security strength of a sponge instance is equal to (1600 - bitrate) / 2. 52 | // 53 | // 54 | // Recommendations 55 | // 56 | // The SHAKE functions are recommended for most new uses. They can produce 57 | // output of arbitrary length. SHAKE256, with an output length of at least 58 | // 64 bytes, provides 256-bit security against all attacks. The Keccak team 59 | // recommends it for most applications upgrading from SHA2-512. (NIST chose a 60 | // much stronger, but much slower, sponge instance for SHA3-512.) 61 | // 62 | // The SHA-3 functions are "drop-in" replacements for the SHA-2 functions. 63 | // They produce output of the same length, with the same security strengths 64 | // against all attacks. This means, in particular, that SHA3-256 only has 65 | // 128-bit collision resistance, because its output length is 32 bytes. 66 | package sha3 67 | -------------------------------------------------------------------------------- /crypto/sha3/hashes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package sha3 6 | 7 | // This file provides functions for creating instances of the SHA-3 8 | // and SHAKE hash functions, as well as utility functions for hashing 9 | // bytes. 10 | 11 | import ( 12 | "hash" 13 | ) 14 | 15 | // NewKeccak256 creates a new Keccak-256 hash. 16 | func NewKeccak256() hash.Hash { return &state{rate: 136, outputLen: 32, dsbyte: 0x01} } 17 | 18 | // NewKeccak512 creates a new Keccak-512 hash. 19 | func NewKeccak512() hash.Hash { return &state{rate: 72, outputLen: 64, dsbyte: 0x01} } 20 | 21 | // New224 creates a new SHA3-224 hash. 22 | // Its generic security strength is 224 bits against preimage attacks, 23 | // and 112 bits against collision attacks. 24 | func New224() hash.Hash { return &state{rate: 144, outputLen: 28, dsbyte: 0x06} } 25 | 26 | // New256 creates a new SHA3-256 hash. 27 | // Its generic security strength is 256 bits against preimage attacks, 28 | // and 128 bits against collision attacks. 29 | func New256() hash.Hash { return &state{rate: 136, outputLen: 32, dsbyte: 0x06} } 30 | 31 | // New384 creates a new SHA3-384 hash. 32 | // Its generic security strength is 384 bits against preimage attacks, 33 | // and 192 bits against collision attacks. 34 | func New384() hash.Hash { return &state{rate: 104, outputLen: 48, dsbyte: 0x06} } 35 | 36 | // New512 creates a new SHA3-512 hash. 37 | // Its generic security strength is 512 bits against preimage attacks, 38 | // and 256 bits against collision attacks. 39 | func New512() hash.Hash { return &state{rate: 72, outputLen: 64, dsbyte: 0x06} } 40 | 41 | // Sum224 returns the SHA3-224 digest of the data. 42 | func Sum224(data []byte) (digest [28]byte) { 43 | h := New224() 44 | h.Write(data) 45 | h.Sum(digest[:0]) 46 | return 47 | } 48 | 49 | // Sum256 returns the SHA3-256 digest of the data. 50 | func Sum256(data []byte) (digest [32]byte) { 51 | h := New256() 52 | h.Write(data) 53 | h.Sum(digest[:0]) 54 | return 55 | } 56 | 57 | // Sum384 returns the SHA3-384 digest of the data. 58 | func Sum384(data []byte) (digest [48]byte) { 59 | h := New384() 60 | h.Write(data) 61 | h.Sum(digest[:0]) 62 | return 63 | } 64 | 65 | // Sum512 returns the SHA3-512 digest of the data. 66 | func Sum512(data []byte) (digest [64]byte) { 67 | h := New512() 68 | h.Write(data) 69 | h.Sum(digest[:0]) 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /crypto/sha3/keccakf_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build amd64,!appengine,!gccgo 6 | 7 | package sha3 8 | 9 | // This function is implemented in keccakf_amd64.s. 10 | 11 | //go:noescape 12 | 13 | func keccakF1600(state *[25]uint64) 14 | -------------------------------------------------------------------------------- /crypto/sha3/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.4 6 | 7 | package sha3 8 | 9 | import ( 10 | "crypto" 11 | ) 12 | 13 | func init() { 14 | crypto.RegisterHash(crypto.SHA3_224, New224) 15 | crypto.RegisterHash(crypto.SHA3_256, New256) 16 | crypto.RegisterHash(crypto.SHA3_384, New384) 17 | crypto.RegisterHash(crypto.SHA3_512, New512) 18 | } 19 | -------------------------------------------------------------------------------- /crypto/sha3/shake.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package sha3 6 | 7 | // This file defines the ShakeHash interface, and provides 8 | // functions for creating SHAKE instances, as well as utility 9 | // functions for hashing bytes to arbitrary-length output. 10 | 11 | import ( 12 | "io" 13 | ) 14 | 15 | // ShakeHash defines the interface to hash functions that 16 | // support arbitrary-length output. 17 | type ShakeHash interface { 18 | // Write absorbs more data into the hash's state. It panics if input is 19 | // written to it after output has been read from it. 20 | io.Writer 21 | 22 | // Read reads more output from the hash; reading affects the hash's 23 | // state. (ShakeHash.Read is thus very different from Hash.Sum) 24 | // It never returns an error. 25 | io.Reader 26 | 27 | // Clone returns a copy of the ShakeHash in its current state. 28 | Clone() ShakeHash 29 | 30 | // Reset resets the ShakeHash to its initial state. 31 | Reset() 32 | } 33 | 34 | func (d *state) Clone() ShakeHash { 35 | return d.clone() 36 | } 37 | 38 | // NewShake128 creates a new SHAKE128 variable-output-length ShakeHash. 39 | // Its generic security strength is 128 bits against all attacks if at 40 | // least 32 bytes of its output are used. 41 | func NewShake128() ShakeHash { return &state{rate: 168, dsbyte: 0x1f} } 42 | 43 | // NewShake256 creates a new SHAKE128 variable-output-length ShakeHash. 44 | // Its generic security strength is 256 bits against all attacks if 45 | // at least 64 bytes of its output are used. 46 | func NewShake256() ShakeHash { return &state{rate: 136, dsbyte: 0x1f} } 47 | 48 | // ShakeSum128 writes an arbitrary-length digest of data into hash. 49 | func ShakeSum128(hash, data []byte) { 50 | h := NewShake128() 51 | h.Write(data) 52 | h.Read(hash) 53 | } 54 | 55 | // ShakeSum256 writes an arbitrary-length digest of data into hash. 56 | func ShakeSum256(hash, data []byte) { 57 | h := NewShake256() 58 | h.Write(data) 59 | h.Read(hash) 60 | } 61 | -------------------------------------------------------------------------------- /crypto/sha3/testdata/keccakKats.json.deflate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libonomy/libonomy-p2p/541903056d1514ed74af5d8b9c7a19b058989324/crypto/sha3/testdata/keccakKats.json.deflate -------------------------------------------------------------------------------- /crypto/sha3/xor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !amd64,!386,!ppc64le appengine 6 | 7 | package sha3 8 | 9 | var ( 10 | xorIn = xorInGeneric 11 | copyOut = copyOutGeneric 12 | xorInUnaligned = xorInGeneric 13 | copyOutUnaligned = copyOutGeneric 14 | ) 15 | 16 | const xorImplementationUnaligned = "generic" 17 | -------------------------------------------------------------------------------- /crypto/sha3/xor_generic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package sha3 6 | 7 | import "encoding/binary" 8 | 9 | // xorInGeneric xors the bytes in buf into the state; it 10 | // makes no non-portable assumptions about memory layout 11 | // or alignment. 12 | func xorInGeneric(d *state, buf []byte) { 13 | n := len(buf) / 8 14 | 15 | for i := 0; i < n; i++ { 16 | a := binary.LittleEndian.Uint64(buf) 17 | d.a[i] ^= a 18 | buf = buf[8:] 19 | } 20 | } 21 | 22 | // copyOutGeneric copies ulint64s to a byte buffer. 23 | func copyOutGeneric(d *state, b []byte) { 24 | for i := 0; len(b) >= 8; i++ { 25 | binary.LittleEndian.PutUint64(b, d.a[i]) 26 | b = b[8:] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crypto/sha3/xor_unaligned.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build amd64 386 ppc64le 6 | // +build !appengine 7 | 8 | package sha3 9 | 10 | import "unsafe" 11 | 12 | func xorInUnaligned(d *state, buf []byte) { 13 | bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0])) 14 | n := len(buf) 15 | if n >= 72 { 16 | d.a[0] ^= bw[0] 17 | d.a[1] ^= bw[1] 18 | d.a[2] ^= bw[2] 19 | d.a[3] ^= bw[3] 20 | d.a[4] ^= bw[4] 21 | d.a[5] ^= bw[5] 22 | d.a[6] ^= bw[6] 23 | d.a[7] ^= bw[7] 24 | d.a[8] ^= bw[8] 25 | } 26 | if n >= 104 { 27 | d.a[9] ^= bw[9] 28 | d.a[10] ^= bw[10] 29 | d.a[11] ^= bw[11] 30 | d.a[12] ^= bw[12] 31 | } 32 | if n >= 136 { 33 | d.a[13] ^= bw[13] 34 | d.a[14] ^= bw[14] 35 | d.a[15] ^= bw[15] 36 | d.a[16] ^= bw[16] 37 | } 38 | if n >= 144 { 39 | d.a[17] ^= bw[17] 40 | } 41 | if n >= 168 { 42 | d.a[18] ^= bw[18] 43 | d.a[19] ^= bw[19] 44 | d.a[20] ^= bw[20] 45 | } 46 | } 47 | 48 | func copyOutUnaligned(d *state, buf []byte) { 49 | ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) 50 | copy(buf, ab[:]) 51 | } 52 | 53 | var ( 54 | xorIn = xorInUnaligned 55 | copyOut = copyOutUnaligned 56 | ) 57 | 58 | const xorImplementationUnaligned = "unaligned" 59 | -------------------------------------------------------------------------------- /crypto/sha3_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestSha256(t *testing.T) { 12 | 13 | // test vectors keys and values MUST be valid hex strings 14 | // source: https://www.di-mgt.com.au/sha_testvectors.html#testvectors 15 | testVectors := map[string]string{ 16 | 17 | "": "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", 18 | "616263": "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", 19 | "6162636462636465636465666465666765666768666768696768696A68696A6B696A6B6C6A6B6C6D6B6C6D6E6C6D6E6F6D6E6F706E6F7071": "41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376", 20 | "61626364656667686263646566676869636465666768696a6465666768696a6b65666768696a6b6c666768696a6b6c6d6768696a6b6c6d6e68696a6b6c6d6e6f696a6b6c6d6e6f706a6b6c6d6e6f70716b6c6d6e6f7071726c6d6e6f707172736d6e6f70717273746e6f707172737475": "916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18", 21 | } 22 | 23 | for k, v := range testVectors { 24 | data, err := hex.DecodeString(k) 25 | assert.NoError(t, err, "invalid input data") 26 | result := Sha256(data) 27 | expected, err := hex.DecodeString(v) 28 | assert.NoError(t, err, "invalid hex string %s", v) 29 | assert.True(t, bytes.Equal(result, expected), "unexpected result") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crypto/uuid.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import "github.com/google/uuid" 4 | 5 | // UUID is a 16-len byte array represnting a UUID 6 | type UUID [16]byte 7 | 8 | // UUIDString returns a new random type-4 UUID string. 9 | func UUIDString() string { 10 | return uuid.New().String() 11 | } 12 | 13 | // NewUUID returns a new random type-4 UUID raw bytes. 14 | func NewUUID() [16]byte { 15 | return uuid.New() 16 | } 17 | -------------------------------------------------------------------------------- /crypto/uuid_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/uuid" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestUUID(t *testing.T) { 11 | id := UUIDString() 12 | id1, err := uuid.Parse(id) 13 | assert.NoError(t, err, "unexpected error") 14 | id1Str := id1.String() 15 | assert.Equal(t, id, id1Str, "expected same uuid") 16 | 17 | id2 := NewUUID() 18 | assert.Equal(t, len(id2), 16, "expected 16") 19 | 20 | } 21 | -------------------------------------------------------------------------------- /database/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | package database 17 | 18 | import "github.com/syndtr/goleveldb/leveldb/iterator" 19 | 20 | // IdealBatchSize is the best batch size 21 | // Code using batches should try to add this much data to the batch. 22 | // The value was determined empirically. 23 | const IdealBatchSize = 100 * 1024 24 | 25 | // Putter wraps the database write operation supported by both batches and regular databases. 26 | type Putter interface { 27 | Put(key []byte, value []byte) error 28 | } 29 | 30 | // Deleter wraps the database delete operation supported by both batches and regular databases. 31 | type Deleter interface { 32 | Delete(key []byte) error 33 | } 34 | 35 | // Database wraps all database operations. All methods are safe for concurrent use. 36 | type Database interface { 37 | Putter 38 | Deleter 39 | Get(key []byte) ([]byte, error) 40 | Has(key []byte) (bool, error) 41 | Close() 42 | NewBatch() Batch 43 | Find(key []byte) Iterator 44 | } 45 | 46 | // Batch is a write-only database that commits changes to its host database 47 | // when Write is called. Batch cannot be used concurrently. 48 | type Batch interface { 49 | Putter 50 | Deleter 51 | ValueSize() int // amount of data in the batch 52 | Write() error 53 | // Reset resets the batch for reuse 54 | Reset() 55 | } 56 | 57 | // Iterator defined basic iterator interface 58 | type Iterator interface { 59 | iterator.IteratorSeeker 60 | Key() []byte 61 | Value() []byte 62 | } 63 | -------------------------------------------------------------------------------- /database/memory_database_iterator.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // MemDatabaseIterator is an iterator for memory database 8 | type MemDatabaseIterator struct { 9 | keys [][]byte 10 | db map[string][]byte 11 | index int 12 | } 13 | 14 | // Key returns the key of the current item iterator is pointing at 15 | func (iter *MemDatabaseIterator) Key() []byte { 16 | return iter.keys[iter.index] 17 | } 18 | 19 | // Value returns the value of the current item iterator is pointing at 20 | func (iter *MemDatabaseIterator) Value() []byte { 21 | key := iter.keys[iter.index] 22 | 23 | return iter.db[string(key)] 24 | } 25 | 26 | // Next advances iterator to next item 27 | func (iter *MemDatabaseIterator) Next() bool { 28 | if iter.index == len(iter.keys)-1 { 29 | return false 30 | } 31 | 32 | iter.index++ 33 | return true 34 | } 35 | 36 | // First moves the iterator to first object 37 | func (iter *MemDatabaseIterator) First() bool { 38 | if len(iter.db) == 0 { 39 | iter.index = -1 40 | return false 41 | } 42 | 43 | iter.index = 0 44 | return true 45 | } 46 | 47 | // Last moves the iterator to last object 48 | func (iter *MemDatabaseIterator) Last() bool { 49 | size := len(iter.keys) 50 | if size == 0 { 51 | iter.index = 0 52 | return false 53 | } 54 | 55 | iter.index = size - 1 56 | return true 57 | } 58 | 59 | // Prev moves the iterator one item back 60 | func (iter *MemDatabaseIterator) Prev() bool { 61 | iter.index-- 62 | if iter.index < 0 { 63 | iter.index = -1 64 | return false 65 | } 66 | 67 | return true 68 | } 69 | 70 | // Seek returns true if key is found in iterator object 71 | func (iter *MemDatabaseIterator) Seek(key []byte) bool { 72 | size := len(iter.keys) 73 | if size == 0 { 74 | iter.index = 0 75 | return false 76 | } 77 | for k := range iter.db { 78 | if reflect.DeepEqual(k, key) { 79 | return true 80 | } 81 | } 82 | 83 | return false 84 | } 85 | 86 | // Release is a stub to comply with DB interface 87 | func (iter *MemDatabaseIterator) Release() { return } 88 | 89 | // Error is a stub to comply with DB interface 90 | func (iter *MemDatabaseIterator) Error() error { return nil } 91 | -------------------------------------------------------------------------------- /database/memory_database_test.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func checkRow(key []byte, value []byte, iter *MemDatabaseIterator, t *testing.T) { 10 | assert.Equal(t, key, iter.Key(), fmt.Sprintf("have key : %s, need: %s", iter.Key(), key)) 11 | assert.Equal(t, value, iter.Value(), fmt.Sprintf("have value : %s, need: %s", iter.Value(), value)) 12 | } 13 | 14 | func TestMemoryDB_Iterator(t *testing.T) { 15 | firstKey := []byte("first key") 16 | secondKey := []byte("second key") 17 | firstValue := []byte("first value") 18 | secondValue := []byte("second value") 19 | 20 | db := NewMemDatabase() 21 | db.Put(firstKey, firstValue) 22 | db.Put(secondKey, secondValue) 23 | 24 | iter := db.NewMemDatabaseIterator() 25 | iter.Next() 26 | checkRow(firstKey, firstValue, iter, t) 27 | iter.Next() 28 | checkRow(secondKey, secondValue, iter, t) 29 | } 30 | -------------------------------------------------------------------------------- /filesystem/dirs.go: -------------------------------------------------------------------------------- 1 | // Package filesystem provides functionality for interacting with directories and files in a cross-platform manner. 2 | package filesystem 3 | 4 | import ( 5 | "os" 6 | "os/user" 7 | "path" 8 | "strings" 9 | ) 10 | 11 | // Using a function pointer to get the current user so we can more easily mock in tests 12 | var currentUser = user.Current 13 | 14 | // Directory and paths funcs 15 | 16 | // OwnerReadWriteExec is a standard owner read / write / exec file permission. 17 | const OwnerReadWriteExec = 0700 18 | 19 | // OwnerReadWrite is a standard owner read / write file permission. 20 | const OwnerReadWrite = 0600 21 | 22 | // PathExists returns true iff file exists in local store and is accessible. 23 | func PathExists(path string) bool { 24 | _, err := os.Stat(path) 25 | if os.IsNotExist(err) { 26 | return false 27 | } 28 | return err == nil 29 | } 30 | 31 | // GetUserHomeDirectory returns the current user's home directory if one is set by the system. 32 | func GetUserHomeDirectory() string { 33 | 34 | if home := os.Getenv("HOME"); home != "" { 35 | return home 36 | } 37 | if usr, err := currentUser(); err == nil { 38 | return usr.HomeDir 39 | } 40 | return "" 41 | } 42 | 43 | // GetCanonicalPath returns an os-specific full path following these rules: 44 | // - replace ~ with user's home dir path 45 | // - expand any ${vars} or $vars 46 | // - resolve relative paths /.../ 47 | // p: source path name 48 | func GetCanonicalPath(p string) string { 49 | 50 | if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { 51 | if home := GetUserHomeDirectory(); home != "" { 52 | p = home + p[1:] 53 | } 54 | } 55 | return path.Clean(os.ExpandEnv(p)) 56 | } 57 | 58 | // GetFullDirectoryPath gets the OS specific full path for a named directory. 59 | // The directory is created if it doesn't exist. 60 | func GetFullDirectoryPath(name string) (string, error) { 61 | 62 | aPath := GetCanonicalPath(name) 63 | 64 | // create dir if it doesn't exist 65 | err := os.MkdirAll(aPath, OwnerReadWriteExec) 66 | 67 | return aPath, err 68 | } 69 | 70 | // ExistOrCreate creates the given path if it does not exist. 71 | func ExistOrCreate(path string) (err error) { 72 | if _, err := os.Stat(path); os.IsNotExist(err) { 73 | err := os.MkdirAll(path, OwnerReadWriteExec) 74 | if err != nil { 75 | return err 76 | } 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /filesystem/dirs_test.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "os" 5 | "os/user" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/google/uuid" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | var RootFolder = "/" 15 | 16 | func TestPathExists(t *testing.T) { 17 | tempFile := filepath.Join(os.TempDir(), "testdir"+uuid.New().String()+"_"+t.Name()) 18 | _, err := os.Create(tempFile) 19 | require.NoError(t, err, "couldn't create temp path") 20 | assert.True(t, PathExists(tempFile), "expecting existence of path") 21 | os.RemoveAll(tempFile) 22 | } 23 | 24 | func TestGetFullDirectoryPath(t *testing.T) { 25 | tempFile := filepath.Join(os.TempDir(), "testdir"+uuid.New().String()+"_"+t.Name()) 26 | err := os.MkdirAll(tempFile, os.ModeDir) 27 | assert.NoError(t, err, "creating temp dir failed") 28 | aPath, err := GetFullDirectoryPath(tempFile) 29 | assert.Equal(t, tempFile, aPath, "Path is different") 30 | assert.NoError(t, err) 31 | os.RemoveAll(tempFile) 32 | } 33 | 34 | func TestGetUserHomeDirectory(t *testing.T) { 35 | users := TestUsers() 36 | SetupTestHooks(users) 37 | usr, err := currentUser() 38 | assert.NoError(t, err, "getting current user failed") 39 | testCases := []struct { 40 | user *user.User 41 | home string 42 | expectedHomeDir string 43 | }{ 44 | {users["alice"], users["alice"].HomeDir, "/home/alice"}, 45 | {users["bob"], users["bob"].HomeDir, "/home/bob"}, 46 | {users["michael"], users["michael"].HomeDir, usr.HomeDir}, 47 | } 48 | 49 | for _, testCase := range testCases { 50 | os.Setenv("HOME", testCase.home) 51 | actual := GetUserHomeDirectory() 52 | assert.Equal(t, testCase.expectedHomeDir, actual, "HOME is different") 53 | } 54 | 55 | TearDownTestHooks() 56 | usr, err = currentUser() 57 | assert.NoError(t, err, "getting current user failed") 58 | assert.Equal(t, usr.HomeDir, GetUserHomeDirectory(), "HOME is different") 59 | } 60 | 61 | func TestGetCanonicalPath(t *testing.T) { 62 | users := TestUsers() 63 | SetupTestHooks(users) 64 | usr, err := currentUser() 65 | assert.NoError(t, err, "getting current user failed") 66 | 67 | testCases := []struct { 68 | user *user.User 69 | path string 70 | expected string 71 | }{ 72 | {users["alice"], "", "."}, 73 | {users["bob"], ".", "."}, 74 | {users["alice"], "libonomy", "libonomy"}, 75 | {users["bob"], "libonomy/app/config", "libonomy/app/config"}, 76 | {users["alice"], "libonomy/../test", "test"}, 77 | {users["bob"], "libonomy/../..", ".."}, 78 | {users["bob"], "libonomy/.././../test", ".." + RootFolder + "test"}, 79 | {users["alice"], "a/b/../c/d/..", "a/c"}, 80 | {usr, "~/libonomy/test/../config/app/..", "~" + usr.HomeDir + RootFolder + "libonomy/config"}, 81 | {users["bob"], "~/libonomy/test/../config/app/..", users["bob"].HomeDir + RootFolder + "libonomy/config"}, 82 | {users["alice"], RootFolder + "libonomy", RootFolder + "libonomy"}, 83 | {users["bob"], RootFolder + "libonomy/app/config", RootFolder + "libonomy/app/config"}, 84 | {users["alice"], RootFolder + "libonomy/../test", RootFolder + "test"}, 85 | } 86 | 87 | for _, testCase := range testCases { 88 | os.Setenv("HOME", testCase.user.HomeDir) 89 | actual := GetCanonicalPath(testCase.path) 90 | assert.Equal(t, testCase.expected, actual, "") 91 | } 92 | TearDownTestHooks() 93 | } 94 | -------------------------------------------------------------------------------- /filesystem/dirs_test_helpers.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "os/user" 5 | ) 6 | 7 | // TestUsers returns a map of users for testing 8 | func TestUsers() map[string]*user.User { 9 | return map[string]*user.User{ 10 | "alice": { 11 | Uid: "100", 12 | Gid: "500", 13 | Username: "alice", 14 | Name: "Alice Smith", 15 | HomeDir: "/home/alice", 16 | }, 17 | "bob": { 18 | Uid: "200", 19 | Gid: "500", 20 | Username: "bob", 21 | Name: "Bob Jones", 22 | HomeDir: "/home/bob", 23 | }, 24 | "michael": { 25 | Uid: "300", 26 | Gid: "500", 27 | Username: "michael", 28 | Name: "Michael Smith", 29 | HomeDir: "", 30 | }, 31 | } 32 | } 33 | 34 | // SetupTestHooks sets current user to mock user to test 35 | func SetupTestHooks(users map[string]*user.User) { 36 | currentUser = func() (*user.User, error) { 37 | return users["michael"], nil 38 | } 39 | } 40 | 41 | // TearDownTestHooks sets current user back 42 | func TearDownTestHooks() { 43 | currentUser = user.Current 44 | } 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/libonomy/libonomy-p2p 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/btcsuite/btcd v0.20.1-beta 7 | github.com/btcsuite/btcutil v1.0.2 8 | github.com/go-kit/kit v0.10.0 9 | github.com/golang/protobuf v1.4.2 10 | github.com/google/uuid v1.1.1 11 | github.com/huin/goupnp v1.0.0 12 | github.com/libonomy/ed25519 v0.0.0-20200515113020-867f8a7820c3 13 | github.com/libonomy/sha256-simd v0.0.0-20200515114307-df94ecd9f4af 14 | github.com/nullstyle/go-xdr v0.0.0-20180726165426-f4c839f75077 15 | github.com/prometheus/client_golang v1.6.0 16 | github.com/spf13/cobra v1.0.0 17 | github.com/spf13/pflag v1.0.5 18 | github.com/spf13/viper v1.7.0 19 | github.com/stretchr/testify v1.5.1 20 | github.com/syndtr/goleveldb v1.0.0 21 | go.uber.org/zap v1.15.0 22 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 23 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a 24 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 25 | ) 26 | -------------------------------------------------------------------------------- /metrics/common.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/go-kit/kit/metrics" 5 | prmkit "github.com/go-kit/kit/metrics/prometheus" 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | const ( 10 | // Namespace is the basic namespace where all metrics are defined under. 11 | Namespace = "libonomy" 12 | // ResultLabel makes a consistent name for results. 13 | ResultLabel = "result" 14 | ) 15 | 16 | // Enabled variable is used to turn on or of the metrics server. when set to false nop metrics are used. 17 | var Enabled = false 18 | 19 | // Gauge is a metric type used to represent a numeric value 20 | type Gauge metrics.Gauge 21 | 22 | // Counter is a metric type used to represent a monotonically increased/decreased numeric value. 23 | type Counter metrics.Counter 24 | 25 | // Histogram is a metric type used to sum up multiple observations over time. 26 | type Histogram metrics.Histogram 27 | 28 | // NewCounter creates a Counter metrics under the global namespace returns nop if metrics are disabled. 29 | func NewCounter(name, subsystem, help string, labels []string) Counter { 30 | if !Enabled { 31 | return nopCounter{} 32 | } 33 | return prmkit.NewCounterFrom(prometheus.CounterOpts{Namespace: Namespace, Subsystem: subsystem, Name: name, Help: help}, labels) 34 | } 35 | 36 | // NewGauge creates a Gauge metrics under the global namespace returns nop if metrics are disabled. 37 | func NewGauge(name, subsystem, help string, labels []string) Gauge { 38 | if !Enabled { 39 | return nopGauge{} 40 | } 41 | return prmkit.NewGaugeFrom(prometheus.GaugeOpts{Namespace: Namespace, Subsystem: subsystem, Name: name, Help: help}, labels) 42 | } 43 | 44 | // NewHistogram creates a Histogram metrics under the global namespace returns nop if metrics are disabled. 45 | func NewHistogram(name, subsystem, help string, labels []string) Histogram { 46 | if !Enabled { 47 | return nopHistogram{} 48 | } 49 | return prmkit.NewHistogramFrom(prometheus.HistogramOpts{Namespace: Namespace, Subsystem: subsystem, Name: name, Help: help}, labels) 50 | } 51 | 52 | // Cache is a basic cache interface that we can wrap to meter. 53 | type Cache interface { 54 | Add(key, value interface{}) (evicted bool) 55 | Get(key interface{}) (value interface{}, ok bool) 56 | } 57 | 58 | // MeteredCache is a wrapper around a cache that monitors size, hits and misses. 59 | type MeteredCache struct { 60 | hits Counter 61 | miss Counter 62 | count Gauge 63 | cache Cache 64 | } 65 | 66 | // NewMeteredCache wraps cache with metrics are returns a monitored cache. 67 | func NewMeteredCache(cache Cache, subsystem, name, help string, labels []string) *MeteredCache { 68 | total := NewCounter(name+"_queries", subsystem, help, labels) 69 | hits := total.With(ResultLabel, "hit") 70 | miss := total.With(ResultLabel, "miss") 71 | count := NewGauge(name+"_count", subsystem, help, labels) 72 | return &MeteredCache{ 73 | hits, 74 | miss, 75 | count, 76 | cache, 77 | } 78 | } 79 | 80 | // Add adds a key-value to the cache and increases the counter if needed. 81 | func (m *MeteredCache) Add(key, value interface{}) bool { 82 | evicted := m.cache.Add(key, value) 83 | if !evicted { 84 | m.count.Add(1) 85 | } 86 | return evicted 87 | } 88 | 89 | // Get returns a value from the cache and counts a hit or a miss. 90 | func (m *MeteredCache) Get(key interface{}) (value interface{}, ok bool) { 91 | v, k := m.cache.Get(key) 92 | if k { 93 | m.hits.Add(1) 94 | } else { 95 | m.miss.Add(1) 96 | } 97 | return v, k 98 | } 99 | 100 | // Nops 101 | 102 | type nopCounter struct { 103 | } 104 | 105 | func (n nopCounter) With(labelValues ...string) metrics.Counter { 106 | return n 107 | } 108 | 109 | func (n nopCounter) Add(value float64) { 110 | 111 | } 112 | 113 | type nopGauge struct { 114 | } 115 | 116 | func (n nopGauge) With(labelValues ...string) metrics.Gauge { 117 | return n 118 | } 119 | 120 | func (n nopGauge) Set(value float64) { 121 | 122 | } 123 | 124 | func (n nopGauge) Add(value float64) { 125 | 126 | } 127 | 128 | type nopHistogram struct { 129 | } 130 | 131 | func (n nopHistogram) With(labelValues ...string) metrics.Histogram { 132 | return n 133 | } 134 | 135 | func (n nopHistogram) Observe(value float64) { 136 | 137 | } 138 | -------------------------------------------------------------------------------- /metrics/common_test.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/go-kit/kit/metrics" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | type mockCache struct { 10 | AddFunc func(key, value interface{}) bool 11 | GetFunc func(key interface{}) (value interface{}, ok bool) 12 | } 13 | 14 | func (mc *mockCache) Add(key, value interface{}) bool { 15 | if mc.AddFunc != nil { 16 | return mc.AddFunc(key, value) 17 | } 18 | return false 19 | } 20 | func (mc *mockCache) Get(key interface{}) (value interface{}, ok bool) { 21 | if mc.GetFunc != nil { 22 | return mc.GetFunc(key) 23 | } 24 | return nil, false 25 | } 26 | 27 | var _ Cache = (*mockCache)(nil) 28 | 29 | type mockCounter struct { 30 | WithFunc func(labelValues ...string) metrics.Counter 31 | AddFunc func(delta float64) 32 | } 33 | 34 | func (mcnt *mockCounter) Add(delta float64) { 35 | if mcnt.AddFunc != nil { 36 | mcnt.AddFunc(delta) 37 | } 38 | } 39 | 40 | func (mcnt *mockCounter) With(labelValues ...string) metrics.Counter { 41 | if mcnt.WithFunc != nil { 42 | return mcnt.WithFunc(labelValues...) 43 | } 44 | return mcnt 45 | } 46 | 47 | type mockGauge struct { 48 | WithFunc func(labelValues ...string) metrics.Gauge 49 | AddFunc func(delta float64) 50 | SetFunc func(delta float64) 51 | } 52 | 53 | func (mcnt *mockGauge) Add(delta float64) { 54 | if mcnt.AddFunc != nil { 55 | mcnt.AddFunc(delta) 56 | } 57 | } 58 | 59 | func (mcnt *mockGauge) Set(delta float64) { 60 | if mcnt.SetFunc != nil { 61 | mcnt.SetFunc(delta) 62 | } 63 | } 64 | 65 | func (mcnt *mockGauge) With(labelValues ...string) metrics.Gauge { 66 | if mcnt.WithFunc != nil { 67 | return mcnt.WithFunc(labelValues...) 68 | } 69 | return mcnt 70 | } 71 | 72 | func TestMeteredCache_Add(t *testing.T) { 73 | mc := &mockCache{ 74 | AddFunc: nil, 75 | GetFunc: nil, 76 | } 77 | 78 | mtrd := NewMeteredCache(mc, "testCache", "mytest", "cachefortest", nil) 79 | 80 | added := float64(0) 81 | 82 | mtrd.count = &mockGauge{ 83 | WithFunc: nil, 84 | AddFunc: func(delta float64) { 85 | added += delta 86 | }, 87 | SetFunc: nil, 88 | } 89 | 90 | b := mtrd.Add("key", "value") 91 | require.False(t, b) 92 | require.Equal(t, float64(1), added) 93 | 94 | mc.AddFunc = func(key, value interface{}) bool { 95 | return true 96 | } 97 | 98 | b = mtrd.Add("key", "value") 99 | require.True(t, b) 100 | require.Equal(t, float64(1), added) 101 | } 102 | 103 | func TestMeteredCache_Get(t *testing.T) { 104 | mc := &mockCache{ 105 | AddFunc: nil, 106 | GetFunc: nil, 107 | } 108 | 109 | mtrd := NewMeteredCache(mc, "testCache", "mytest", "cachefortest", nil) 110 | 111 | hits := float64(0) 112 | 113 | mtrd.hits = &mockCounter{ 114 | WithFunc: nil, 115 | AddFunc: func(delta float64) { 116 | hits += delta 117 | }, 118 | } 119 | miss := float64(0) 120 | 121 | mtrd.miss = &mockCounter{ 122 | WithFunc: nil, 123 | AddFunc: func(delta float64) { 124 | miss += delta 125 | }, 126 | } 127 | 128 | v, ok := mtrd.Get("KEYY") 129 | require.Nil(t, v) 130 | require.False(t, ok) 131 | require.Equal(t, float64(1), miss) 132 | 133 | mc.GetFunc = func(key interface{}) (value interface{}, ok bool) { 134 | return "value", true 135 | } 136 | 137 | v, ok = mtrd.Get("KEYY") 138 | require.Equal(t, "value", v) 139 | require.True(t, ok) 140 | require.Equal(t, float64(1), hits) 141 | } 142 | -------------------------------------------------------------------------------- /metrics/server.go: -------------------------------------------------------------------------------- 1 | // Package metrics defines telemetry primitives to be used across components. it uses the prometheus format. 2 | package metrics 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/libonomy/libonomy-p2p/log" 9 | "github.com/prometheus/client_golang/prometheus/promhttp" 10 | ) 11 | 12 | // StartCollectingMetrics begins listening and supplying metrics on localhost:`metricsPort`/metrics 13 | func StartCollectingMetrics(metricsPort int) { 14 | http.Handle("/metrics", promhttp.Handler()) 15 | go func() { 16 | err := http.ListenAndServe(fmt.Sprintf(":%v", metricsPort), nil) 17 | log.With().Warning("Metrics server stopped: %v", log.Err(err)) 18 | }() 19 | } 20 | -------------------------------------------------------------------------------- /nattraversal/upnp.go: -------------------------------------------------------------------------------- 1 | // Package nattraversal provides tools for acquiring ports on a Network Address Translator (NAT). 2 | package nattraversal 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "os/user" 8 | 9 | "github.com/libonomy/libonomy-p2p/nattraversal/upnp" 10 | ) 11 | 12 | // GetExternalIP returns a string formatted external IP address from a UPnP-enabled NAT on the network, if such device 13 | // is discovered. 14 | func GetExternalIP() (string, error) { 15 | // connect to router 16 | d, err := upnp.Discover() 17 | if err != nil { 18 | return "", fmt.Errorf("failed to connect to router: %v", err) 19 | } 20 | 21 | // discover external IP 22 | ip, err := d.ExternalIP() 23 | if err != nil { 24 | return "", fmt.Errorf("failed to discover external IP: %v", err) 25 | } 26 | 27 | return ip, nil 28 | } 29 | 30 | func getPortDesc() string { 31 | hostname, err := os.Hostname() 32 | if err != nil { 33 | hostname = "unknown" 34 | } 35 | username := "unknown" 36 | current, err := user.Current() 37 | if err == nil { 38 | username = current.Username 39 | } 40 | portDesc := fmt.Sprintf("libonomy:%s@%s", username, hostname) 41 | return portDesc 42 | } 43 | 44 | // UPNPGateway is the interface of a UPnP gateway. It supports forwarding a port and clearing a forwarding rule. 45 | type UPNPGateway interface { 46 | Forward(port uint16, desc string) error 47 | Clear(port uint16) error 48 | } 49 | 50 | // DiscoverUPNPGateway returns a UPNPGateway if one is found on the network. This process is long - about 30 seconds and 51 | // is expected to fail in some network setups. 52 | func DiscoverUPNPGateway() (UPNPGateway, error) { 53 | return upnp.Discover() 54 | } 55 | 56 | // AcquirePortFromGateway sets up UDP and TCP forwarding rules for the given port on the given gateway. 57 | func AcquirePortFromGateway(gateway UPNPGateway, port uint16) error { 58 | err := gateway.Forward(port, getPortDesc()) 59 | if err != nil { 60 | return fmt.Errorf("failed to forward port %d: %v", port, err) 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /p2p/config/config.go: -------------------------------------------------------------------------------- 1 | // Package config defines configuration used in the p2p package 2 | package config 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/libonomy/libonomy-p2p/log" 8 | ) 9 | 10 | const ( 11 | // P2PDirectoryPath is the name of the directory where we store p2p stuff 12 | P2PDirectoryPath = "p2p" 13 | // NodeDataFileName is the name of the file we store the the p2p identity keys 14 | NodeDataFileName = "id.json" 15 | // NodesDirectoryName is the name of the directory we store nodes identities under 16 | NodesDirectoryName = "nodes" 17 | // UnlimitedMsgSize is a constant used to check whether message size is set to unlimited size 18 | UnlimitedMsgSize = 0 19 | ) 20 | 21 | // Values specifies default values for node config params. 22 | var ( 23 | Values = DefaultConfig() 24 | ) 25 | 26 | func init() { 27 | // set default config params based on runtime here 28 | } 29 | 30 | func duration(duration string) (dur time.Duration) { 31 | dur, err := time.ParseDuration(duration) 32 | if err != nil { 33 | log.Error("Could not parse duration string returning 0, error:", err) 34 | } 35 | return dur 36 | } 37 | 38 | // Config defines the configuration options for the libonomy peer-to-peer networking layer 39 | type Config struct { 40 | TCPPort int `mapstructure:"tcp-port"` 41 | AcquirePort bool `mapstructure:"acquire-port"` 42 | NodeID string `mapstructure:"node-id"` 43 | DialTimeout time.Duration `mapstructure:"dial-timeout"` 44 | ConnKeepAlive time.Duration `mapstructure:"conn-keepalive"` 45 | NetworkID int8 `mapstructure:"network-id"` 46 | ResponseTimeout time.Duration `mapstructure:"response-timeout"` 47 | SessionTimeout time.Duration `mapstructure:"session-timeout"` 48 | MaxPendingConnections int `mapstructure:"max-pending-connections"` 49 | OutboundPeersTarget int `mapstructure:"outbound-target"` 50 | MaxInboundPeers int `mapstructure:"max-inbound"` 51 | SwarmConfig SwarmConfig `mapstructure:"swarm"` 52 | BufferSize int `mapstructure:"buffer-size"` 53 | MsgSizeLimit int `mapstructure:"msg-size-limit"` // in bytes 54 | } 55 | 56 | // SwarmConfig specifies swarm config params. 57 | type SwarmConfig struct { 58 | Gossip bool `mapstructure:"gossip"` 59 | Bootstrap bool `mapstructure:"bootstrap"` 60 | RoutingTableBucketSize int `mapstructure:"bucketsize"` 61 | RoutingTableAlpha int `mapstructure:"alpha"` 62 | RandomConnections int `mapstructure:"randcon"` 63 | BootstrapNodes []string `mapstructure:"bootnodes"` 64 | PeersFile string `mapstructure:"peers-file"` 65 | } 66 | 67 | // DefaultConfig defines the default p2p configuration 68 | func DefaultConfig() Config { 69 | 70 | // SwarmConfigValues defines default values for swarm config params. 71 | var SwarmConfigValues = SwarmConfig{ 72 | Gossip: true, 73 | Bootstrap: false, 74 | RoutingTableBucketSize: 20, 75 | RoutingTableAlpha: 3, 76 | RandomConnections: 5, 77 | BootstrapNodes: []string{}, // these should be the libonomy foundation bootstrap nodes 78 | PeersFile: "peers.json", // located under data-dir// not loaded or save if empty string is given. 79 | } 80 | 81 | return Config{ 82 | TCPPort: 7513, 83 | AcquirePort: true, 84 | NodeID: "", 85 | DialTimeout: duration("1m"), 86 | ConnKeepAlive: duration("48h"), 87 | NetworkID: TestNet, 88 | ResponseTimeout: duration("60s"), 89 | SessionTimeout: duration("15s"), 90 | MaxPendingConnections: 100, 91 | OutboundPeersTarget: 10, 92 | MaxInboundPeers: 100, 93 | SwarmConfig: SwarmConfigValues, 94 | BufferSize: 10000, 95 | MsgSizeLimit: UnlimitedMsgSize, 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /p2p/config/params.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // params are non-configurable (hard-coded) consts. To create a configurable param use Config. 4 | // add all node params here (non-configurable consts) - ideally most node params should be configurable. 5 | const ( 6 | ClientVersion = "go-libonomy/0.0.1" 7 | MinClientVersion = "0.0.1" 8 | ) 9 | 10 | // NetworkID represents the network that the node lives in 11 | // that indicates what nodes it can communicate with, and which bootstrap nodes to use 12 | const ( 13 | MainNet = iota 14 | TestNet 15 | ) 16 | -------------------------------------------------------------------------------- /p2p/discovery/addrbook_test.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "github.com/libonomy/libonomy-p2p/p2p/config" 5 | "github.com/stretchr/testify/require" 6 | 7 | "testing" 8 | ) 9 | 10 | func testAddrBook(name string) *addrBook { 11 | book := newAddrBook(config.DefaultConfig().SwarmConfig, "", GetTestLogger(name)) 12 | book.localAddresses = append(book.localAddresses, generateDiscNode()) 13 | return book 14 | } 15 | 16 | func TestStartStop(t *testing.T) { 17 | n := newAddrBook(config.DefaultConfig().SwarmConfig, "", GetTestLogger("starttest")) 18 | n.Start() 19 | n.Stop() 20 | } 21 | 22 | func TestAttempt(t *testing.T) { 23 | n := testAddrBook("attemptest") 24 | 25 | nd := generateDiscNode() 26 | // Add a new address and get it 27 | n.AddAddress(nd, nd) 28 | 29 | ka := n.GetAddress() 30 | 31 | if !ka.LastAttempt().IsZero() { 32 | t.Errorf("Address should not have attempts, but does") 33 | } 34 | 35 | na := ka.na 36 | n.Attempt(na.PublicKey()) 37 | 38 | if ka.LastAttempt().IsZero() { 39 | t.Errorf("Address should have an attempt, but does not") 40 | } 41 | } 42 | 43 | func TestGood(t *testing.T) { 44 | n := testAddrBook("testgood") 45 | addrsToAdd := 64 * 64 46 | 47 | addrs := generateDiscNodes(addrsToAdd) 48 | 49 | n.AddAddresses(addrs, n.localAddresses[0]) 50 | for _, addr := range addrs { 51 | n.Good(addr.PublicKey()) 52 | } 53 | 54 | numAddrs := n.NumAddresses() 55 | if numAddrs >= addrsToAdd { 56 | t.Errorf("Number of addresses is too many: %d vs %d", numAddrs, addrsToAdd) 57 | } 58 | 59 | numCache := len(n.AddressCache()) 60 | if numCache >= numAddrs/4 { 61 | t.Errorf("Number of addresses in cache: got %d, want %d", numCache, numAddrs/4) 62 | } 63 | } 64 | 65 | func TestGetAddress(t *testing.T) { 66 | n := testAddrBook("getaddress") 67 | 68 | // Get an address from an empty set (should error) 69 | if rv := n.GetAddress(); rv != nil { 70 | t.Errorf("GetAddress failed: got: %v want: %v\n", rv, nil) 71 | } 72 | n2 := generateDiscNode() 73 | 74 | // Add a new address and get it 75 | n.AddAddress(n2, n.localAddresses[0]) 76 | 77 | ka := n.GetAddress() 78 | if ka == nil { 79 | t.Fatalf("Did not get an address where there is one in the pool") 80 | } 81 | if !ka.na.IP.Equal(n2.IP) { 82 | t.Errorf("Wrong IP: got %v, want %v", ka.na.IP.String(), n2.IP.String()) 83 | } 84 | 85 | // Mark this as a good address and get it 86 | n.Good(ka.na.PublicKey()) 87 | ka = n.GetAddress() 88 | if ka == nil { 89 | t.Fatalf("Did not get an address where there is one in the pool") 90 | } 91 | if !ka.na.IP.Equal(n2.IP) { 92 | t.Errorf("Wrong IP: got %v, want %v", ka.na.IP.String(), n2.IP.String()) 93 | } 94 | 95 | numAddrs := n.NumAddresses() 96 | if numAddrs != 1 { 97 | t.Errorf("Wrong number of addresses: got %d, want %d", numAddrs, 1) 98 | } 99 | } 100 | 101 | func Test_Lookup(t *testing.T) { 102 | n := testAddrBook("lookup") 103 | 104 | n2 := generateDiscNode() 105 | 106 | c, err := n.Lookup(n2.PublicKey()) 107 | require.Error(t, err) 108 | require.Nil(t, c) 109 | 110 | // Add a new address and get it 111 | n.AddAddress(n2, n.localAddresses[0]) 112 | 113 | ka := n.GetAddress() 114 | if ka == nil { 115 | t.Fatalf("Did not get an address where there is one in the pool") 116 | } 117 | if !ka.na.IP.Equal(n2.IP) { 118 | t.Errorf("Wrong IP: got %v, want %v", ka.na.IP.String(), n2.IP.String()) 119 | } 120 | 121 | n.AddAddresses(generateDiscNodes(100), n.localAddresses[0]) 122 | 123 | got, err := n.Lookup(n2.PublicKey()) 124 | require.NoError(t, err) 125 | require.Equal(t, got, n2) 126 | } 127 | 128 | func Test_LocalAddreses(t *testing.T) { 129 | n := testAddrBook(t.Name()) 130 | 131 | addr := n.localAddresses[0] 132 | addr2 := generateDiscNode() 133 | n.AddLocalAddress(addr2) 134 | 135 | require.True(t, n.IsLocalAddress(addr2)) 136 | require.True(t, n.IsLocalAddress(addr)) 137 | 138 | n.AddAddress(addr2, addr) 139 | n.AddAddress(addr, addr2) 140 | 141 | _, err := n.Lookup(addr.PublicKey()) 142 | require.Error(t, err) 143 | _, err = n.Lookup(addr2.PublicKey()) 144 | require.Error(t, err) 145 | 146 | addr3 := generateDiscNode() 147 | n.AddAddress(addr3, addr) 148 | nd, err := n.Lookup(addr3.PublicKey()) 149 | require.NoError(t, err) 150 | require.NotNil(t, nd) 151 | require.Equal(t, nd.ID, addr3.ID) 152 | } 153 | -------------------------------------------------------------------------------- /p2p/discovery/getaddress.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/libonomy/libonomy-p2p/common/types" 8 | "github.com/libonomy/libonomy-p2p/log" 9 | "github.com/libonomy/libonomy-p2p/p2p/node" 10 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 11 | "github.com/libonomy/libonomy-p2p/p2p/server" 12 | ) 13 | 14 | // todo : calculate real udp max message size 15 | 16 | func (p *protocol) newGetAddressesRequestHandler() func(msg server.Message) []byte { 17 | return func(msg server.Message) []byte { 18 | t := time.Now() 19 | plogger := p.logger.WithFields(log.String("type", "getaddresses"), log.String("from", msg.Sender().String())) 20 | plogger.Debug("got request") 21 | 22 | // TODO: if we don't know who is that peer (a.k.a first time we hear from this address) 23 | // we must ensure that he's indeed listening on that address = check last pong 24 | 25 | results := p.table.AddressCache() 26 | // remove the sender from the list 27 | for i, addr := range results { 28 | if addr.PublicKey() == msg.Sender() { 29 | results[i] = results[len(results)-1] 30 | results = results[:len(results)-1] 31 | break 32 | } 33 | } 34 | 35 | //todo: limit results to message size 36 | //todo: what to do if we have no addresses? 37 | resp, err := types.InterfaceToBytes(results) 38 | 39 | if err != nil { 40 | plogger.Error("Error marshaling response message (GetAddress) %v", err) 41 | return nil 42 | } 43 | 44 | plogger.With().Debug("Sending response", log.Int("size", len(results)), log.Duration("time_to_make", time.Since(t))) 45 | return resp 46 | } 47 | } 48 | 49 | // GetAddresses Send a get address request to a remote node, it will block and return the results returned from the node. 50 | func (p *protocol) GetAddresses(server p2pcrypto.PublicKey) ([]*node.Info, error) { 51 | start := time.Now() 52 | var err error 53 | 54 | plogger := p.logger.WithFields(log.String("type", "getaddresses"), log.String("to", server.String())) 55 | 56 | plogger.Debug("sending request") 57 | 58 | // response handler 59 | ch := make(chan []*node.Info) 60 | resHandler := func(msg []byte) { 61 | defer close(ch) 62 | nodes := make([]*node.Info, 0, getAddrMax) 63 | err := types.BytesToInterface(msg, &nodes) 64 | //todo: check that we're not pass max results ? 65 | if err != nil { 66 | plogger.Warning("could not deserialize bytes to Info, skipping packet err=", err) 67 | return 68 | } 69 | 70 | if len(nodes) > getAddrMax { 71 | plogger.Warning("addresses response from %v size is too large, ignoring. got: %v, expected: < %v", server.String(), len(nodes), getAddrMax) 72 | return 73 | } 74 | 75 | ch <- nodes 76 | } 77 | 78 | err = p.msgServer.SendRequest(GetAddresses, []byte(""), server, resHandler) 79 | 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | timeout := time.NewTimer(MessageTimeout) 85 | select { 86 | case nodes := <-ch: 87 | if nodes == nil { 88 | return nil, errors.New("empty result set") 89 | } 90 | plogger.With().Debug("getaddress_time_to_recv", log.Int("len", len(nodes)), log.Duration("time_elapsed", time.Since(start))) 91 | return nodes, nil 92 | case <-timeout.C: 93 | return nil, errors.New("request timed out") 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /p2p/discovery/known_address.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "math" 5 | "time" 6 | 7 | "github.com/libonomy/libonomy-p2p/p2p/node" 8 | ) 9 | 10 | // KnownAddress tracks information about a known network address that is used 11 | // to determine how viable an address is. 12 | type KnownAddress struct { 13 | na *node.Info 14 | srcAddr *node.Info 15 | attempts int 16 | lastSeen time.Time 17 | lastattempt time.Time 18 | lastsuccess time.Time 19 | tried bool 20 | refs int // reference count of new buckets 21 | } 22 | 23 | // NodeInfo returns the internal Info struct of this address. 24 | func (ka *KnownAddress) NodeInfo() *node.Info { 25 | return ka.na 26 | } 27 | 28 | // LastAttempt returns the last time the known address was attempted. 29 | func (ka *KnownAddress) LastAttempt() time.Time { 30 | return ka.lastattempt 31 | } 32 | 33 | // chance returns the selection probability for a known address. The priority 34 | // depends upon how recently the address has been seen, how recently it was last 35 | // attempted and how often attempts to connect to it have failed. 36 | func (ka *KnownAddress) chance() float64 { 37 | now := time.Now() 38 | lastAttempt := now.Sub(ka.lastattempt) 39 | 40 | if lastAttempt < 0 { 41 | lastAttempt = 0 42 | } 43 | 44 | c := 1.0 45 | 46 | // Very recent attempts are less likely to be retried. 47 | if lastAttempt < 10*time.Minute { 48 | c *= 0.01 49 | } 50 | 51 | // deprioritize 66% after each failed attempt, but at most 1/28th to avoid the search taking forever or overly penalizing outages. 52 | c *= math.Pow(0.66, math.Min(float64(ka.attempts), 8)) 53 | 54 | // TODO : do this without floats ? 55 | 56 | return c 57 | } 58 | 59 | // isBad returns true if the address in question has not been tried in the last 60 | // minute and meets one of the following criteria: 61 | // 1) It claims to be from the future 62 | // 2) It hasn't been seen in over a month 63 | // 3) It has failed at least three times and never succeeded 64 | // 4) It has failed ten times in the last week 65 | // All addresses that meet these criteria are assumed to be worthless and not 66 | // worth keeping hold of. 67 | func (ka *KnownAddress) isBad() bool { 68 | if ka.lastattempt.After(time.Now().Add(-1 * time.Minute)) { 69 | return false 70 | } 71 | 72 | //// From the future? 73 | if ka.lastSeen.After(time.Now().Add(10 * time.Minute)) { 74 | return true 75 | } 76 | 77 | // Over a month old? 78 | if ka.lastSeen.Before(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) { 79 | return true 80 | } 81 | 82 | // Never succeeded? 83 | if ka.lastsuccess.IsZero() && ka.attempts >= numRetries { 84 | return true 85 | } 86 | 87 | // Hasn't succeeded in too long? 88 | if !ka.lastsuccess.After(time.Now().Add(-1*minBadDays*time.Hour*24)) && 89 | ka.attempts >= maxFailures { 90 | return true 91 | } 92 | 93 | return false 94 | } 95 | -------------------------------------------------------------------------------- /p2p/discovery/node_test.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | -------------------------------------------------------------------------------- /p2p/discovery/ping.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "net" 7 | "time" 8 | 9 | "github.com/libonomy/libonomy-p2p/common/types" 10 | "github.com/libonomy/libonomy-p2p/log" 11 | 12 | "github.com/libonomy/libonomy-p2p/p2p/node" 13 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 14 | "github.com/libonomy/libonomy-p2p/p2p/server" 15 | ) 16 | 17 | func (p *protocol) newPingRequestHandler() func(msg server.Message) []byte { 18 | return func(msg server.Message) []byte { 19 | plogger := p.logger.WithFields(log.String("type", "ping"), log.String("from", msg.Sender().String())) 20 | plogger.Debug("handle request") 21 | pinged := &node.Info{} 22 | err := types.BytesToInterface(msg.Bytes(), pinged) 23 | if err != nil { 24 | plogger.Error("failed to deserialize ping message err=", err) 25 | return nil 26 | } 27 | 28 | if err := p.verifyPinger(msg.Metadata().FromAddress, pinged); err != nil { 29 | plogger.Error("msg contents were not valid err=", err) 30 | return nil 31 | } 32 | 33 | //pong 34 | payload, err := types.InterfaceToBytes(p.local) 35 | // TODO: include the resolved To address 36 | if err != nil { 37 | plogger.Error("Error marshaling response message (Ping)") 38 | return nil 39 | } 40 | 41 | plogger.Debug("Sending pong message") 42 | return payload 43 | } 44 | } 45 | 46 | func (p *protocol) verifyPinger(from net.Addr, pi *node.Info) error { 47 | // todo : Validate ToAddr or drop it. 48 | // todo: check the address provided with an extra ping before updating. ( if we haven't checked it for a while ) 49 | // todo: decide on best way to know our ext address 50 | 51 | if err := pi.Valid(); err != nil { 52 | return err 53 | } 54 | 55 | //TODO: only accept local (unspecified/loopback) IPs from other local ips. 56 | ipfrom, _, _ := net.SplitHostPort(from.String()) 57 | pi.IP = net.ParseIP(ipfrom) 58 | 59 | // inbound ping is the actual source of this node info 60 | p.table.AddAddress(pi, pi) 61 | return nil 62 | } 63 | 64 | // Ping notifies `peer` about our p2p identity. 65 | func (p *protocol) Ping(peer p2pcrypto.PublicKey) error { 66 | plogger := p.logger.WithFields(log.String("type", "ping"), log.String("to", peer.String())) 67 | 68 | plogger.Debug("send request") 69 | 70 | data, err := types.InterfaceToBytes(p.local) 71 | if err != nil { 72 | return err 73 | } 74 | ch := make(chan []byte) 75 | foo := func(msg []byte) { 76 | defer close(ch) 77 | plogger.Debug("handle response") 78 | sender := &node.Info{} 79 | err := types.BytesToInterface(msg, sender) 80 | 81 | if err != nil { 82 | plogger.Warning("got unreadable pong. err=%v", err) 83 | return 84 | } 85 | 86 | // todo: if we pinged it we already have id so no need to update 87 | // todo : but what if id or listen address has changed ? 88 | 89 | ch <- sender.ID.Bytes() 90 | } 91 | 92 | err = p.msgServer.SendRequest(PingPong, data, peer, foo) 93 | 94 | if err != nil { 95 | return err 96 | } 97 | 98 | timeout := time.NewTimer(MessageTimeout) // todo: check whether this is useless because of `requestLifetime` 99 | select { 100 | case id := <-ch: 101 | if id == nil { 102 | return errors.New("failed sending message") 103 | } 104 | if !bytes.Equal(id, peer.Bytes()) { 105 | return errors.New("got pong with different public key") 106 | } 107 | case <-timeout.C: 108 | return errors.New("ping timeouted") 109 | } 110 | 111 | return nil 112 | } 113 | -------------------------------------------------------------------------------- /p2p/discovery/protocol.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/libonomy/libonomy-p2p/log" 8 | "github.com/libonomy/libonomy-p2p/p2p/node" 9 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 10 | "github.com/libonomy/libonomy-p2p/p2p/server" 11 | "github.com/libonomy/libonomy-p2p/p2p/service" 12 | ) 13 | 14 | type protocolRoutingTable interface { 15 | GetAddress() *KnownAddress 16 | AddAddresses(n []*node.Info, src *node.Info) 17 | AddAddress(n *node.Info, src *node.Info) 18 | AddressCache() []*node.Info 19 | } 20 | 21 | type protocol struct { 22 | local *node.Info 23 | table protocolRoutingTable 24 | logger log.Log 25 | msgServer *server.MessageServer 26 | } 27 | 28 | func (p *protocol) SetLocalAddresses(tcp, udp int) { 29 | p.local.ProtocolPort = uint16(tcp) 30 | p.local.DiscoveryPort = uint16(udp) 31 | } 32 | 33 | // Name is the name if the protocol. 34 | const Name = "/udp/v2/discovery" 35 | 36 | // MessageBufSize is the buf size we give to the messages channel 37 | const MessageBufSize = 1000 38 | 39 | // MessageTimeout is the timeout we tolerate when waiting for a message reply 40 | const MessageTimeout = time.Second * 5 // TODO: Parametrize 41 | 42 | // PingPong is the ping protocol ID 43 | const PingPong = 0 44 | 45 | // GetAddresses is the findnode protocol ID 46 | const GetAddresses = 1 47 | 48 | // newProtocol is a constructor for a protocol protocol provider. 49 | func newProtocol(local p2pcrypto.PublicKey, rt protocolRoutingTable, svc server.Service, log log.Log) *protocol { 50 | s := server.NewMsgServer(svc, Name, MessageTimeout, make(chan service.DirectMessage, MessageBufSize), log) 51 | d := &protocol{ 52 | local: &node.Info{ID: local.Array(), IP: net.IPv4zero, ProtocolPort: 7513, DiscoveryPort: 7513}, 53 | table: rt, 54 | msgServer: s, 55 | logger: log, 56 | } 57 | 58 | // XXX Reminder: for discovery protocol to work you must call SetLocalAddresses with updated ports from the socket. 59 | 60 | d.msgServer.RegisterMsgHandler(PingPong, d.newPingRequestHandler()) 61 | d.msgServer.RegisterMsgHandler(GetAddresses, d.newGetAddressesRequestHandler()) 62 | return d 63 | } 64 | 65 | // Close stops the message server protocol from serving requests. 66 | func (p *protocol) Close() { 67 | p.msgServer.Close() 68 | } 69 | -------------------------------------------------------------------------------- /p2p/discovery/store_test.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | 8 | "github.com/libonomy/libonomy-p2p/log" 9 | "github.com/libonomy/libonomy-p2p/p2p/config" 10 | "github.com/libonomy/libonomy-p2p/p2p/node" 11 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 12 | ) 13 | 14 | // assertAddr ensures that the two addresses match. The timestamp is not 15 | // checked as it does not affect uniquely identifying a specific address. 16 | func assertAddr(t *testing.T, got, expected *node.Info) { 17 | if got.ID != expected.ID { 18 | t.Fatalf("expected ID %v, got %v", expected.ID.String(), got.ID.String()) 19 | } 20 | if !got.IP.Equal(expected.IP) { 21 | t.Fatalf("expected address IP %v, got %v", expected.IP, got.IP) 22 | } 23 | if got.DiscoveryPort != expected.DiscoveryPort { 24 | t.Fatalf("expected address port %d, got %d", expected.DiscoveryPort, 25 | got.DiscoveryPort) 26 | } 27 | if got.ProtocolPort != expected.ProtocolPort { 28 | t.Fatalf("expected address port %d, got %d", expected.ProtocolPort, 29 | got.ProtocolPort) 30 | } 31 | } 32 | 33 | // assertAddrs ensures that the manager's address cache matches the given 34 | // expected addresses. 35 | func assertAddrs(t *testing.T, addrMgr *addrBook, 36 | expectedAddrs map[p2pcrypto.PublicKey]*node.Info) { 37 | 38 | t.Helper() 39 | 40 | addrs := addrMgr.getAddresses() 41 | 42 | if len(addrs) != len(expectedAddrs) { 43 | t.Fatalf("expected to lookup %d addresses, found %d", 44 | len(expectedAddrs), len(addrs)) 45 | } 46 | 47 | for _, addr := range addrs { 48 | addrStr := addr.ID 49 | expectedAddr, ok := expectedAddrs[addr.PublicKey()] 50 | if !ok { 51 | t.Fatalf("expected to lookup address %v", addrStr) 52 | } 53 | 54 | assertAddr(t, addr, expectedAddr) 55 | } 56 | } 57 | 58 | // TestAddrManagerSerialization ensures that we can properly serialize and 59 | // deserialize the manager's current address cache. 60 | func TestAddrManagerSerialization(t *testing.T) { 61 | 62 | lg := log.New("addrbook_serialize_test", "", "") 63 | cfg := config.DefaultConfig() 64 | 65 | // We'll start by creating our address manager backed by a temporary 66 | // directory. 67 | tempDir, err := ioutil.TempDir("", "addrbook") 68 | if err != nil { 69 | t.Fatalf("unable to create temp dir: %v", err) 70 | } 71 | defer os.RemoveAll(tempDir) 72 | 73 | filePath := tempDir + "/" + defaultPeersFileName 74 | 75 | addrMgr := newAddrBook(cfg.SwarmConfig, "", lg) 76 | 77 | // We'll be adding 5 random addresses to the manager. 78 | const numAddrs = 5 79 | 80 | expectedAddrs := make(map[p2pcrypto.PublicKey]*node.Info, numAddrs) 81 | for i := 0; i < numAddrs; i++ { 82 | addr := node.GenerateRandomNodeData() 83 | expectedAddrs[addr.PublicKey()] = addr 84 | addrMgr.AddAddress(addr, node.GenerateRandomNodeData()) 85 | } 86 | 87 | // Now that the addresses have been added, we should be able to retrieve 88 | // them. 89 | assertAddrs(t, addrMgr, expectedAddrs) 90 | // 91 | //// Then, we'll persist these addresses to disk and restart the address 92 | //// manager. 93 | addrMgr.savePeers(filePath) 94 | addrMgr = newAddrBook(cfg.SwarmConfig, "", lg) 95 | 96 | // Finally, we'll read all of the addresses from disk and ensure they 97 | // match as expected. 98 | addrMgr.loadPeers(filePath) 99 | assertAddrs(t, addrMgr, expectedAddrs) 100 | } 101 | -------------------------------------------------------------------------------- /p2p/message.go: -------------------------------------------------------------------------------- 1 | package p2p 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 8 | "github.com/libonomy/libonomy-p2p/p2p/service" 9 | ) 10 | 11 | type directProtocolMessage struct { 12 | metadata service.P2PMetadata 13 | sender p2pcrypto.PublicKey 14 | data service.Data 15 | } 16 | 17 | func (pm directProtocolMessage) Metadata() service.P2PMetadata { 18 | return pm.metadata 19 | } 20 | 21 | func (pm directProtocolMessage) Sender() p2pcrypto.PublicKey { 22 | return pm.sender 23 | } 24 | 25 | func (pm directProtocolMessage) Data() service.Data { 26 | return pm.data 27 | } 28 | 29 | func (pm directProtocolMessage) Bytes() []byte { 30 | return pm.data.Bytes() 31 | } 32 | 33 | type gossipProtocolMessage struct { 34 | sender p2pcrypto.PublicKey 35 | data service.Data 36 | validationChan chan service.MessageValidation 37 | } 38 | 39 | func (pm gossipProtocolMessage) Sender() p2pcrypto.PublicKey { 40 | return pm.sender // DirectSender 41 | } 42 | 43 | func (pm gossipProtocolMessage) Data() service.Data { 44 | return pm.data 45 | } 46 | 47 | func (pm gossipProtocolMessage) Bytes() []byte { 48 | return pm.data.Bytes() 49 | } 50 | 51 | func (pm gossipProtocolMessage) ValidationCompletedChan() chan service.MessageValidation { 52 | return pm.validationChan 53 | } 54 | 55 | func (pm gossipProtocolMessage) ReportValidation(protocol string) { 56 | if pm.validationChan != nil { 57 | pm.validationChan <- service.NewMessageValidation(pm.sender, pm.Bytes(), protocol) 58 | } 59 | } 60 | 61 | // ProtocolMessageMetadata is a general p2p message wrapper 62 | type ProtocolMessageMetadata struct { 63 | NextProtocol string 64 | ClientVersion string 65 | Timestamp int64 66 | AuthPubkey []byte 67 | NetworkID int32 68 | } 69 | 70 | // Payload holds either a byte array or a wrapped req-res message. 71 | type Payload struct { 72 | Payload []byte 73 | Wrapped *service.DataMsgWrapper 74 | } 75 | 76 | // ProtocolMessage is a pair of metadata and a a payload. 77 | type ProtocolMessage struct { 78 | Metadata *ProtocolMessageMetadata 79 | Payload *Payload 80 | } 81 | 82 | // CreatePayload is a helper function to format a payload for sending. 83 | func CreatePayload(data service.Data) (*Payload, error) { 84 | switch x := data.(type) { 85 | case service.DataBytes: 86 | if x.Payload == nil { 87 | return nil, fmt.Errorf("cant send empty payload") 88 | } 89 | return &Payload{Payload: x.Bytes()}, nil 90 | case *service.DataMsgWrapper: 91 | return &Payload{Wrapped: x}, nil 92 | case nil: 93 | return nil, fmt.Errorf("cant send empty payload") 94 | default: 95 | } 96 | return nil, fmt.Errorf("cant determine paylaod type") 97 | } 98 | 99 | // ExtractData is a helper function to extract the payload data from a message payload. 100 | func ExtractData(pm *Payload) (service.Data, error) { 101 | var data service.Data 102 | if payload := pm.Payload; payload != nil { 103 | data = &service.DataBytes{Payload: payload} 104 | } else if wrap := pm.Wrapped; wrap != nil { 105 | data = &service.DataMsgWrapper{Req: wrap.Req, MsgType: wrap.MsgType, ReqID: wrap.ReqID, Payload: wrap.Payload} 106 | } else { 107 | return nil, errors.New("not valid data type") 108 | } 109 | return data, nil 110 | } 111 | -------------------------------------------------------------------------------- /p2p/metrics/prometheus.go: -------------------------------------------------------------------------------- 1 | // Package metrics defines metric reporting for the p2p component. 2 | package metrics 3 | 4 | import ( 5 | "github.com/go-kit/kit/metrics" 6 | "github.com/go-kit/kit/metrics/prometheus" 7 | mt "github.com/libonomy/libonomy-p2p/metrics" 8 | stdprometheus "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | const ( 12 | // Namespace is the metrics namespace //todo: figure out if this can be used better. 13 | Namespace = "libonomy" 14 | // MetricsSubsystem is a subsystem shared by all metrics exposed by this 15 | // package. 16 | MetricsSubsystem = "p2p" 17 | 18 | typeLabel = "type" 19 | messageTypeLabel = "message_type" 20 | 21 | // ProtocolLabel holds the name we use to add a protocol label value 22 | ProtocolLabel = "protocol" 23 | 24 | // PeerIDLabel holds the name we use to add a protocol label value 25 | PeerIDLabel = "peer_id" 26 | ) 27 | 28 | var ( 29 | // PropagationQueueLen is the current size of the gossip queue 30 | PropagationQueueLen = mt.NewGauge("propagate_queue_len", MetricsSubsystem, "Number of messages in the gossip queue", nil) 31 | // QueueLength is the current size of protocol queues 32 | QueueLength = mt.NewGauge("protocol_queue_len", MetricsSubsystem, "len of protocol queues", []string{ProtocolLabel}) 33 | ) 34 | 35 | // todo: maybe add functions that attach peer_id and protocol. (or other labels) without writing label names. 36 | 37 | // TotalPeers is the total number of peers we have connected 38 | var ( 39 | totalPeers = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ 40 | Namespace: Namespace, 41 | Subsystem: MetricsSubsystem, 42 | Name: "peers", 43 | Help: "Number of peers.", 44 | }, []string{typeLabel}) 45 | 46 | // OutboundPeers is the number of outbound peers we have connected 47 | OutboundPeers = totalPeers.With(typeLabel, "outbound") 48 | 49 | // InboundPeers is the number of inbound peers we have connected 50 | InboundPeers = totalPeers.With(typeLabel, "inbound") 51 | 52 | // PeerRecv is the num of bytes received from peer 53 | PeerRecv = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ 54 | Namespace: Namespace, 55 | Subsystem: MetricsSubsystem, 56 | Name: "peer_receive_bytes_total", 57 | Help: "Number of bytes received from a given peer.", 58 | }, []string{PeerIDLabel}) 59 | 60 | // PeerSend is the num of bytes sent to peer 61 | PeerSend metrics.Counter = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ 62 | Namespace: Namespace, 63 | Subsystem: MetricsSubsystem, 64 | Name: "peer_send_bytes_total", 65 | Help: "Number of bytes sent to a given peer.", 66 | }, []string{PeerIDLabel}) 67 | 68 | totalGossipMessages = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ 69 | Namespace: Namespace, 70 | Subsystem: MetricsSubsystem, 71 | Name: "total_gossip_messages", 72 | Help: "Number of gossip messages recieved", 73 | }, []string{ProtocolLabel, messageTypeLabel}) 74 | 75 | // NewGossipMessages is a metric for newly received gossip messages 76 | NewGossipMessages = totalGossipMessages.With(messageTypeLabel, "new") 77 | // OldGossipMessages is a metric for old messages received (duplicates) 78 | OldGossipMessages = totalGossipMessages.With(messageTypeLabel, "old") 79 | // InvalidGossipMessages is a metric for invalid messages received 80 | InvalidGossipMessages = totalGossipMessages.With(messageTypeLabel, "invalid") 81 | 82 | // AddrbookSize is the current size of the discovery 83 | AddrbookSize = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ 84 | Namespace: Namespace, 85 | Subsystem: MetricsSubsystem, 86 | Name: "addrbook_size", 87 | Help: "Number of peers in the discovery", 88 | }, []string{}) 89 | ) 90 | -------------------------------------------------------------------------------- /p2p/net/conn_mock.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sync/atomic" 7 | "time" 8 | 9 | "github.com/libonomy/libonomy-p2p/crypto" 10 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 11 | ) 12 | 13 | // ConnectionMock mocks connections. 14 | type ConnectionMock struct { 15 | id string 16 | remotePub p2pcrypto.PublicKey 17 | session NetworkSession 18 | 19 | created time.Time 20 | 21 | Addr net.Addr 22 | 23 | sendDelayMs int 24 | sendRes error 25 | sendCnt int32 26 | 27 | closed bool 28 | } 29 | 30 | // NewConnectionMock creates a ConnectionMock. 31 | func NewConnectionMock(key p2pcrypto.PublicKey) *ConnectionMock { 32 | return &ConnectionMock{ 33 | id: crypto.UUIDString(), 34 | remotePub: key, 35 | closed: false, 36 | created: time.Now(), 37 | } 38 | } 39 | 40 | // ID mocks the connection interface. 41 | func (cm ConnectionMock) ID() string { 42 | return cm.id 43 | } 44 | 45 | // Created mocks the connection interface. 46 | func (cm ConnectionMock) Created() time.Time { 47 | return cm.created 48 | } 49 | 50 | // SetCreated mutate the mock. 51 | func (cm ConnectionMock) SetCreated(time2 time.Time) { 52 | cm.created = time2 53 | } 54 | 55 | // RemotePublicKey mocks the interface. 56 | func (cm ConnectionMock) RemotePublicKey() p2pcrypto.PublicKey { 57 | return cm.remotePub 58 | } 59 | 60 | // SetRemotePublicKey mutates the mock. 61 | func (cm *ConnectionMock) SetRemotePublicKey(key p2pcrypto.PublicKey) { 62 | cm.remotePub = key 63 | } 64 | 65 | // RemoteAddr mocks the interface. 66 | func (cm *ConnectionMock) RemoteAddr() net.Addr { 67 | return cm.Addr 68 | } 69 | 70 | // SetSession mutates the mock. 71 | func (cm *ConnectionMock) SetSession(session NetworkSession) { 72 | cm.session = session 73 | } 74 | 75 | // Session mocks the interface. 76 | func (cm ConnectionMock) Session() NetworkSession { 77 | return cm.session 78 | } 79 | 80 | // IncomingChannel mocks the interface. 81 | func (cm ConnectionMock) IncomingChannel() chan []byte { 82 | return nil 83 | } 84 | 85 | // SetSendDelay mutates the mock. 86 | func (cm *ConnectionMock) SetSendDelay(delayMs int) { 87 | cm.sendDelayMs = delayMs 88 | } 89 | 90 | // SetSendResult mutates the mock. 91 | func (cm *ConnectionMock) SetSendResult(err error) { 92 | cm.sendRes = err 93 | } 94 | 95 | // SendCount mutates the mock. 96 | func (cm ConnectionMock) SendCount() int32 { 97 | return atomic.LoadInt32(&cm.sendCnt) 98 | } 99 | 100 | // Send mocks the interface. 101 | func (cm *ConnectionMock) Send(m []byte) error { 102 | atomic.AddInt32(&cm.sendCnt, int32(1)) 103 | time.Sleep(time.Duration(cm.sendDelayMs) * time.Millisecond) 104 | return cm.sendRes 105 | } 106 | 107 | // Closed mocks the interface. 108 | func (cm ConnectionMock) Closed() bool { 109 | return cm.closed 110 | } 111 | 112 | // Close mocks the interface. 113 | func (cm *ConnectionMock) Close() error { 114 | if cm.closed { 115 | return errors.New("already closed") 116 | } 117 | cm.closed = true 118 | return nil 119 | } 120 | 121 | func (cm *ConnectionMock) beginEventProcessing() { 122 | 123 | } 124 | 125 | // String mocks the interface 126 | func (cm ConnectionMock) String() string { 127 | return cm.id 128 | } 129 | -------------------------------------------------------------------------------- /p2p/net/errors.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | // Temporary checks whether the given error should be considered temporary. 4 | func Temporary(err error) bool { 5 | tErr, ok := err.(interface { 6 | Temporary() bool 7 | }) 8 | return ok && tErr.Temporary() 9 | } 10 | 11 | // ConnectionWithErr is a pair of Connection and an error occurred within the connection 12 | type ConnectionWithErr struct { 13 | Conn Connection 14 | Err error 15 | } 16 | -------------------------------------------------------------------------------- /p2p/net/handshake.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | // HandshakeData is the handshake message struct 4 | type HandshakeData struct { 5 | ClientVersion string 6 | NetworkID int32 7 | Port uint16 8 | } 9 | -------------------------------------------------------------------------------- /p2p/net/readwritecloseaddresser_mock_test.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | // ReadWriteCloseAddresserMock is a ninja robot 9 | type ReadWriteCloseAddresserMock struct { 10 | readIn []byte 11 | readErr error 12 | readCnt int 13 | readChan chan struct{} 14 | 15 | writeWaitChan chan []byte 16 | 17 | writeErr error 18 | writeOut []byte 19 | writeCnt int 20 | 21 | closeRes error 22 | closeCnt int 23 | 24 | remoteAddrRes net.Addr 25 | remoteAddrCnt int 26 | } 27 | 28 | // NewReadWriteCloseAddresserMock is this 29 | func NewReadWriteCloseAddresserMock() *ReadWriteCloseAddresserMock { 30 | return &ReadWriteCloseAddresserMock{ 31 | readChan: make(chan struct{}, 1), 32 | } 33 | } 34 | 35 | // SetReadResult is this 36 | func (rwcam *ReadWriteCloseAddresserMock) SetReadResult(p []byte, err error) { 37 | rwcam.readIn = make([]byte, len(p)) 38 | copy(rwcam.readIn, p) 39 | rwcam.readErr = err 40 | rwcam.readChan <- struct{}{} 41 | } 42 | 43 | // ReadCount is this 44 | func (rwcam *ReadWriteCloseAddresserMock) ReadCount() int { 45 | return rwcam.readCnt 46 | } 47 | 48 | func (rwcam *ReadWriteCloseAddresserMock) SetReadDeadline(t time.Time) error { 49 | return nil 50 | } 51 | 52 | func (rwcam *ReadWriteCloseAddresserMock) SetWriteDeadline(t time.Time) error { 53 | return nil 54 | } 55 | 56 | // Read is this 57 | func (rwcam *ReadWriteCloseAddresserMock) Read(p []byte) (n int, err error) { 58 | rwcam.readCnt++ 59 | <-rwcam.readChan 60 | err = rwcam.readErr 61 | n = 0 62 | if rwcam.readErr == nil { 63 | n = copy(p, rwcam.readIn) 64 | } 65 | return 66 | } 67 | 68 | // SetWriteResult is a mock 69 | func (rwcam *ReadWriteCloseAddresserMock) SetWriteResult(err error) { 70 | rwcam.writeErr = err 71 | } 72 | 73 | // WriteOut is a mock 74 | func (rwcam *ReadWriteCloseAddresserMock) WriteOut() (p []byte) { 75 | p = append(p, rwcam.writeOut...) 76 | return 77 | } 78 | 79 | // WriteCount is a mock 80 | func (rwcam *ReadWriteCloseAddresserMock) WriteCount() int { 81 | return rwcam.writeCnt 82 | } 83 | 84 | // Write is a mock 85 | func (rwcam *ReadWriteCloseAddresserMock) Write(p []byte) (n int, err error) { 86 | rwcam.writeCnt++ 87 | n = 0 88 | err = rwcam.writeErr 89 | if rwcam.writeErr == nil { 90 | rwcam.writeOut = append(rwcam.writeOut, p...) 91 | } 92 | if rwcam.writeWaitChan != nil { 93 | rwcam.writeWaitChan <- p 94 | } 95 | return 96 | } 97 | 98 | // CloseCount oh yeah 99 | func (rwcam *ReadWriteCloseAddresserMock) CloseCount() int { 100 | return rwcam.closeCnt 101 | } 102 | 103 | // Close is mock close 104 | func (rwcam *ReadWriteCloseAddresserMock) Close() error { 105 | rwcam.closeCnt++ 106 | close(rwcam.readChan) 107 | return rwcam.closeRes 108 | } 109 | 110 | func (rwcam *ReadWriteCloseAddresserMock) setRemoteAddrResult(addr net.Addr) { 111 | rwcam.remoteAddrRes = addr 112 | } 113 | 114 | // RemoteAddr is a RemoteAddr mock 115 | func (rwcam *ReadWriteCloseAddresserMock) RemoteAddr() net.Addr { 116 | rwcam.remoteAddrCnt++ 117 | return rwcam.remoteAddrRes 118 | } 119 | -------------------------------------------------------------------------------- /p2p/net/session.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "github.com/libonomy/libonomy-p2p/log" 5 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 6 | ) 7 | 8 | // NetworkSession is an authenticated network session between 2 peers. 9 | // Sessions may be used between 'connections' until they expire. 10 | // Session provides the encryptor/decryptor for all messages exchanged between 2 peers. 11 | // enc/dec is using an ephemeral sym key exchanged securely between the peers via the handshake protocol 12 | // The handshake protocol goal is to create an authenticated network session. 13 | type NetworkSession interface { 14 | ID() p2pcrypto.PublicKey // Unique session id, currently the peer pubkey TODO: @noam use pubkey from conn and remove this 15 | 16 | OpenMessage(boxedMessage []byte) ([]byte, error) // decrypt data using session dec key 17 | SealMessage(message []byte) []byte // encrypt data using session enc key 18 | } 19 | 20 | var _ NetworkSession = (*networkSessionImpl)(nil) 21 | 22 | // TODO: add support for idle session expiration 23 | 24 | // networkSessionImpl implements NetworkSession. 25 | type networkSessionImpl struct { 26 | sharedSecret p2pcrypto.SharedSecret 27 | peerPubkey p2pcrypto.PublicKey 28 | } 29 | 30 | // String returns the session's identifier string. 31 | func (n networkSessionImpl) String() string { 32 | return n.peerPubkey.String() 33 | } 34 | 35 | // ID returns the session's unique id 36 | func (n networkSessionImpl) ID() p2pcrypto.PublicKey { 37 | return n.peerPubkey 38 | } 39 | 40 | // Encrypt encrypts in binary data with the session's sym enc key. 41 | func (n networkSessionImpl) SealMessage(message []byte) []byte { 42 | if n.sharedSecret == nil { 43 | log.Panic("tried to seal a message before initializing session with a shared secret") 44 | } 45 | return n.sharedSecret.Seal(message) 46 | } 47 | 48 | // Decrypt decrypts in binary data that was encrypted with the session's sym enc key. 49 | func (n networkSessionImpl) OpenMessage(boxedMessage []byte) (message []byte, err error) { 50 | if n.sharedSecret == nil { 51 | log.Panic("tried to open a message before initializing session with a shared secret") 52 | } 53 | return n.sharedSecret.Open(boxedMessage) 54 | } 55 | 56 | // NewNetworkSession creates a new network session based on provided data 57 | func NewNetworkSession(sharedSecret p2pcrypto.SharedSecret, peerPubkey p2pcrypto.PublicKey) NetworkSession { 58 | return &networkSessionImpl{ 59 | sharedSecret: sharedSecret, 60 | peerPubkey: peerPubkey, 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /p2p/net/session_cache.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "time" 7 | 8 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 9 | ) 10 | 11 | // simple cache for storing sessions 12 | const maxSessions = 2048 13 | 14 | type storedSession struct { 15 | session NetworkSession 16 | ts time.Time 17 | } 18 | 19 | type dialSessionFunc func(pubkey p2pcrypto.PublicKey) NetworkSession 20 | 21 | type sessionCache struct { 22 | dialFunc dialSessionFunc 23 | sMtx sync.Mutex 24 | sessions map[p2pcrypto.PublicKey]*storedSession 25 | } 26 | 27 | func newSessionCache(dialFunc dialSessionFunc) *sessionCache { 28 | return &sessionCache{dialFunc: dialFunc, 29 | sMtx: sync.Mutex{}, 30 | sessions: make(map[p2pcrypto.PublicKey]*storedSession)} 31 | } 32 | 33 | // GetIfExist gets a session from the cache based on address 34 | func (s *sessionCache) GetIfExist(key p2pcrypto.PublicKey) (NetworkSession, error) { 35 | s.sMtx.Lock() 36 | ns, ok := s.sessions[key] 37 | s.sMtx.Unlock() 38 | if ok { 39 | return ns.session, nil 40 | } 41 | return nil, errors.New("not found") 42 | } 43 | 44 | // expire removes the oldest entry from the cache. *NOTE*: not thread-safe 45 | func (s *sessionCache) expire() { 46 | var key p2pcrypto.PublicKey 47 | for k, v := range s.sessions { 48 | if key == nil || v.ts.Before(s.sessions[key].ts) { 49 | key = k 50 | } 51 | } 52 | delete(s.sessions, key) 53 | } 54 | 55 | // GetOrCreate get's a session if it exists, or try to init a new one based on the address and key given. 56 | // *NOTE*: use of dialFunc might actually trigger message sending. 57 | func (s *sessionCache) GetOrCreate(pubKey p2pcrypto.PublicKey) NetworkSession { 58 | s.sMtx.Lock() 59 | defer s.sMtx.Unlock() 60 | ns, ok := s.sessions[pubKey] 61 | 62 | if ok { 63 | return ns.session 64 | } 65 | 66 | session := s.dialFunc(pubKey) 67 | if session == nil { 68 | return nil 69 | } 70 | 71 | if len(s.sessions) == maxSessions { 72 | s.expire() 73 | } 74 | 75 | s.sessions[pubKey] = &storedSession{session, time.Now()} 76 | return session 77 | } 78 | 79 | func (s *sessionCache) handleIncoming(ns NetworkSession) { 80 | s.sMtx.Lock() 81 | defer s.sMtx.Unlock() 82 | _, ok := s.sessions[ns.ID()] 83 | if ok { 84 | // replace ? doesn't matter. should be the same 85 | return 86 | } 87 | if len(s.sessions) == maxSessions { 88 | s.expire() 89 | } 90 | s.sessions[ns.ID()] = &storedSession{ns, time.Now()} 91 | } 92 | -------------------------------------------------------------------------------- /p2p/net/session_cache_test.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/libonomy/libonomy-p2p/p2p/node" 8 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | var dialErrFunc dialSessionFunc = func(pubkey p2pcrypto.PublicKey) (session NetworkSession) { 13 | return nil 14 | } 15 | 16 | var dialOkFunc dialSessionFunc = func(pubkey p2pcrypto.PublicKey) (session NetworkSession) { 17 | return SessionMock{id: pubkey} 18 | } 19 | 20 | func Test_sessionCache_GetIfExist(t *testing.T) { 21 | sc := newSessionCache(dialErrFunc) 22 | pk := p2pcrypto.NewRandomPubkey() 23 | a, err := sc.GetIfExist(pk) 24 | require.Error(t, err) 25 | require.Nil(t, a) 26 | sc.sessions[pk] = &storedSession{SessionMock{}, time.Now()} 27 | b, err2 := sc.GetIfExist(pk) 28 | require.NoError(t, err2) 29 | require.NotNil(t, b) 30 | } 31 | func Test_sessionCache_GetOrCreate(t *testing.T) { 32 | sc := newSessionCache(dialErrFunc) 33 | rnd := node.GenerateRandomNodeData().PublicKey() 34 | a := sc.GetOrCreate(rnd) 35 | require.Nil(t, a) 36 | 37 | sc.dialFunc = dialOkFunc 38 | 39 | b := sc.GetOrCreate(rnd) 40 | require.NotNil(t, b) 41 | 42 | nds := node.GenerateRandomNodesData(maxSessions + 10) 43 | 44 | for i := 0; i < len(nds); i++ { 45 | //str := strconv.Itoa(i) 46 | e := sc.GetOrCreate(nds[i].PublicKey()) // we don't use address cause there might be dups 47 | require.NotNil(t, e) 48 | } 49 | 50 | require.Equal(t, maxSessions, len(sc.sessions)) 51 | } 52 | 53 | func Test_sessionCache_handleIncoming(t *testing.T) { 54 | sc := newSessionCache(dialErrFunc) 55 | pk := p2pcrypto.NewRandomPubkey() 56 | sc.handleIncoming(SessionMock{id: pk}) 57 | require.True(t, len(sc.sessions) == 1) 58 | sc.handleIncoming(SessionMock{id: pk}) 59 | require.True(t, len(sc.sessions) == 1) 60 | 61 | nds := node.GenerateRandomNodesData(maxSessions + 10) 62 | 63 | for i := 0; i < len(nds); i++ { 64 | sc.handleIncoming(SessionMock{id: nds[i].PublicKey()}) // we don't use address cause there might be dups 65 | } 66 | 67 | require.Equal(t, maxSessions, len(sc.sessions)) 68 | 69 | } 70 | -------------------------------------------------------------------------------- /p2p/net/session_mock.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 7 | ) 8 | 9 | var _ NetworkSession = (*SessionMock)(nil) 10 | 11 | // SessionMock mocks NetworkSession. 12 | type SessionMock struct { 13 | id p2pcrypto.PublicKey 14 | 15 | SealMessageFunc func(message []byte) []byte 16 | OpenMessageFunc func(boxedMessage []byte) ([]byte, error) 17 | } 18 | 19 | // NewSessionMock creates a new mock for given public key. 20 | func NewSessionMock(pubkey p2pcrypto.PublicKey) *SessionMock { 21 | return &SessionMock{id: pubkey} 22 | } 23 | 24 | // ID is a mock. 25 | func (sm SessionMock) ID() p2pcrypto.PublicKey { 26 | return sm.id 27 | } 28 | 29 | // SealMessage is a mock. 30 | func (sm SessionMock) SealMessage(message []byte) []byte { 31 | if sm.SealMessageFunc != nil { 32 | return sm.SealMessageFunc(message) 33 | } 34 | return nil 35 | } 36 | 37 | // OpenMessage is a mock. 38 | func (sm SessionMock) OpenMessage(boxedMessage []byte) ([]byte, error) { 39 | if sm.OpenMessageFunc != nil { 40 | return sm.OpenMessageFunc(boxedMessage) 41 | } 42 | return nil, errors.New("not impl") 43 | } 44 | 45 | var _ NetworkSession = (*SessionMock)(nil) 46 | -------------------------------------------------------------------------------- /p2p/net/wire/delimited/chan.go: -------------------------------------------------------------------------------- 1 | package delimited 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "sync" 8 | 9 | "github.com/libonomy/libonomy-p2p/log" 10 | ) 11 | 12 | // Chan is a delimited duplex channel. It is used to have a channel interface 13 | // around a delimited.Reader or Writer. 14 | type Chan struct { 15 | connection io.ReadWriteCloser 16 | closeOnce sync.Once 17 | 18 | cmtx sync.RWMutex 19 | closed bool 20 | 21 | outMsgChan chan outMessage 22 | inMsgChan chan []byte 23 | CloseChan chan struct{} 24 | } 25 | 26 | // Satisfy formatter. 27 | 28 | // In exposes the incoming message channel 29 | func (s *Chan) In() chan []byte { 30 | return s.inMsgChan 31 | } 32 | 33 | // Out sends message on the wire, blocking. 34 | func (s *Chan) Out(message []byte) error { 35 | s.cmtx.RLock() 36 | if !s.closed { 37 | outCb := make(chan error) 38 | s.outMsgChan <- outMessage{message, outCb} 39 | s.cmtx.RUnlock() 40 | return <-outCb 41 | } 42 | s.cmtx.RUnlock() 43 | return fmt.Errorf("formatter is closed") 44 | } 45 | 46 | type outMessage struct { 47 | m []byte 48 | r chan error 49 | } 50 | 51 | func (om outMessage) Message() []byte { 52 | return om.m 53 | } 54 | 55 | func (om outMessage) Result() chan error { 56 | return om.r 57 | } 58 | 59 | // NewChan constructs a Chan with a given buffer size. 60 | func NewChan(chanSize int) *Chan { 61 | return &Chan{ 62 | outMsgChan: make(chan outMessage, chanSize), 63 | inMsgChan: make(chan []byte, chanSize), 64 | CloseChan: make(chan struct{}), 65 | } 66 | } 67 | 68 | // Pipe invokes the reader and writer flows, once it's ran Chan can start serving incoming/outgoing messages 69 | func (s *Chan) Pipe(rwc io.ReadWriteCloser) { 70 | s.connection = rwc 71 | go s.readFromReader(rwc) 72 | go s.writeToWriter(rwc) 73 | } 74 | 75 | // ReadFrom wraps the given io.Reader with a delimited.Reader, reads all 76 | // messages, ands sends them down the channel. 77 | func (s *Chan) readFromReader(r io.Reader) { 78 | 79 | mr := NewReader(r) 80 | // single reader, no need for Mutex 81 | Loop: 82 | for { 83 | buf, err := mr.Next() 84 | if err != nil { 85 | log.Debug("conn: Read chan closed err: %v", err) 86 | break Loop 87 | } 88 | 89 | select { 90 | case <-s.CloseChan: 91 | break Loop // told we're done 92 | default: 93 | if buf != nil { 94 | newbuf := make([]byte, len(buf)) 95 | copy(newbuf, buf) 96 | // ok seems fine. send it away 97 | s.inMsgChan <- newbuf 98 | } 99 | } 100 | } 101 | 102 | s.Close() // close writer 103 | close(s.inMsgChan) 104 | } 105 | 106 | // WriteToWriter wraps the given io.Writer with a delimited.Writer, listens on the 107 | // channel and writes all messages to the writer. 108 | func (s *Chan) writeToWriter(w io.Writer) { 109 | // new buffer per message 110 | // if bottleneck, cycle around a set of buffers 111 | mw := NewWriter(w) 112 | 113 | // single writer, no need for Mutex 114 | Loop: 115 | for { 116 | s.cmtx.RLock() 117 | cl := s.closed 118 | s.cmtx.RUnlock() 119 | if cl { 120 | break Loop 121 | } 122 | msg := <-s.outMsgChan 123 | if _, err := mw.WriteRecord(msg.Message()); err != nil { 124 | // unexpected error. tell the client. 125 | msg.Result() <- err 126 | break Loop 127 | } else { 128 | // Report msg was sent 129 | msg.Result() <- nil 130 | } 131 | 132 | } 133 | 134 | cou := len(s.outMsgChan) 135 | for i := 0; i < cou; i++ { 136 | msg := <-s.outMsgChan 137 | msg.Result() <- errors.New("formatter is closed") 138 | } 139 | } 140 | 141 | // Close the Chan 142 | func (s *Chan) Close() { 143 | s.closeOnce.Do(func() { 144 | s.cmtx.Lock() 145 | s.closed = true 146 | s.cmtx.Unlock() 147 | close(s.CloseChan) // close both writer and reader 148 | s.connection.Close() // close internal connection 149 | }) 150 | } 151 | -------------------------------------------------------------------------------- /p2p/net/wire/delimited/chan_test.go: -------------------------------------------------------------------------------- 1 | package delimited 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | type mockc struct { 9 | } 10 | 11 | func (mockc) Write(b []byte) (int, error) { 12 | return len(b), nil 13 | } 14 | 15 | func (mockc) Read(p []byte) (n int, err error) { 16 | return len(p), nil 17 | } 18 | 19 | func (mockc) Close() error { 20 | return nil 21 | } 22 | 23 | func Test_Chan(t *testing.T) { 24 | c := NewChan(1000) 25 | m := mockc{} 26 | c.Pipe(m) 27 | 28 | done := make(chan struct{}, 2000) 29 | 30 | for i := 0; i < 2000; i++ { 31 | //i:=i 32 | go func() { 33 | _ = c.Out([]byte("LALAL")) 34 | done <- struct{}{} 35 | }() 36 | } 37 | 38 | c.Close() 39 | 40 | for i := 0; i < 2000; i++ { 41 | tx := time.NewTimer(10 * time.Second) 42 | select { 43 | case <-done: 44 | continue 45 | case <-tx.C: 46 | t.Fatal("timeout waiting for message") 47 | 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /p2p/net/wire/delimited/copy.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package delimited 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | ) 23 | 24 | // A Source represents a sequence of records. 25 | type Source interface { 26 | // Next returns the next record in the sequence, or io.EOF when no further 27 | // records are available. The slice returned by Next is only required to be 28 | // valid until a subsequent call to Next. 29 | Next() ([]byte, error) 30 | } 31 | 32 | // A Sink represents a receiver of records. 33 | type Sink interface { 34 | // Put delivers a record to the sink. 35 | Put([]byte) error 36 | } 37 | 38 | // Copy sequentially copies each record read from src to sink until src.Next() 39 | // returns io.EOF or another error occurs. 40 | func Copy(sink Sink, src Source) error { 41 | for { 42 | record, err := src.Next() 43 | if err == io.EOF { 44 | return nil 45 | } else if err != nil { 46 | return fmt.Errorf("copy: read error: %v", err) 47 | } 48 | if err := sink.Put(record); err != nil { 49 | return fmt.Errorf("copy: write error: %v", err) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /p2p/net/wire/delimited/delimited.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Package delimited implements a reader and writer for simple streams of 18 | // length-delimited byte records. Each record is written as a varint-encoded 19 | // length in bytes, followed immediately by the record itself. 20 | // 21 | // A stream consists of a sequence of such records packed consecutively without 22 | // additional padding. There are no checksums or compression. 23 | package delimited 24 | 25 | import ( 26 | "bufio" 27 | "encoding/binary" 28 | "fmt" 29 | "io" 30 | 31 | "github.com/golang/protobuf/proto" 32 | ) 33 | 34 | // Reader consumes length-delimited records from a byte source. 35 | // 36 | // Usage: 37 | // rd := delimited.NewReader(r) 38 | // for { 39 | // rec, err := rd.Next() 40 | // if err == io.EOF { 41 | // break 42 | // } else if err != nil { 43 | // log.Fatal(err) 44 | // } 45 | // doStuffWith(rec) 46 | // } 47 | // 48 | type Reader struct { 49 | buf *bufio.Reader 50 | data []byte 51 | } 52 | 53 | // Next returns the next length-delimited record from the input, or io.EOF if 54 | // there are no more records available. Returns io.ErrUnexpectedEOF if a short 55 | // record is found, with a length of n but fewer than n bytes of data. Because 56 | // there is no resynchronization mechanism, it is generally not possible to 57 | // recover from a short record in this format. 58 | // 59 | // The slice returned is valid only until a subsequent call to Next. 60 | func (r *Reader) Next() ([]byte, error) { 61 | size, err := binary.ReadUvarint(r.buf) 62 | if err != nil { 63 | return nil, err 64 | } 65 | if cap(r.data) < int(size) { 66 | r.data = make([]byte, size) 67 | } else { 68 | r.data = r.data[:size] 69 | } 70 | 71 | if _, err := io.ReadFull(r.buf, r.data); err != nil { 72 | return nil, err 73 | } 74 | return r.data, nil 75 | } 76 | 77 | // NextProto consumes the next available record by calling r.Next, and decodes 78 | // it into pb with proto.Unmarshal. 79 | func (r *Reader) NextProto(pb proto.Message) error { 80 | rec, err := r.Next() 81 | if err != nil { 82 | return err 83 | } 84 | return proto.Unmarshal(rec, pb) 85 | } 86 | 87 | // NewReader constructs a new delimited Reader for the records in r. 88 | func NewReader(r io.Reader) *Reader { return &Reader{buf: bufio.NewReader(r)} } 89 | 90 | // A Writer outputs delimited records to an io.Writer. 91 | // 92 | // Basic usage: 93 | // wr := delimited.NewWriter(w) 94 | // for record := range records { 95 | // if err := wr.Put(record); err != nil { 96 | // log.Fatal(err) 97 | // } 98 | // } 99 | // 100 | type Writer struct { 101 | w io.Writer 102 | } 103 | 104 | // Put writes the specified record to the writer. It equivalent to 105 | // WriteRecord, but discards the number of bytes written. 106 | func (w Writer) Put(record []byte) error { 107 | _, err := w.WriteRecord(record) 108 | return err 109 | } 110 | 111 | // PutProto encodes and writes the specified proto.Message to the writer. 112 | func (w Writer) PutProto(msg proto.Message) error { 113 | rec, err := proto.Marshal(msg) 114 | if err != nil { 115 | return fmt.Errorf("error encoding proto: %v", err) 116 | } 117 | return w.Put(rec) 118 | } 119 | 120 | // WriteRecord writes the specified record to the underlying writer, returning 121 | // the total number of bytes written including the length tag. 122 | func (w Writer) WriteRecord(record []byte) (int, error) { 123 | var buf [binary.MaxVarintLen64]byte 124 | v := binary.PutUvarint(buf[:], uint64(len(record))) 125 | 126 | nw, err := w.w.Write(buf[:v]) 127 | if err != nil { 128 | return 0, err 129 | } 130 | dw, err := w.w.Write(record) 131 | if err != nil { 132 | return nw, err 133 | } 134 | return nw + dw, nil 135 | } 136 | 137 | // NewWriter constructs a new delimited Writer that writes records to w. 138 | func NewWriter(w io.Writer) *Writer { return &Writer{w: w} } 139 | -------------------------------------------------------------------------------- /p2p/net/wire/delimited/delimited_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package delimited 18 | 19 | import ( 20 | "bytes" 21 | "errors" 22 | "io" 23 | "reflect" 24 | "strings" 25 | "testing" 26 | ) 27 | 28 | const testData = "\x00\x01A\x02BC\x03DEF" 29 | 30 | func TestGoodReader(t *testing.T) { 31 | r := strings.NewReader(testData) 32 | rd := NewReader(r) 33 | 34 | for _, want := range []string{"", "A", "BC", "DEF"} { 35 | got, err := rd.Next() 36 | if err != nil { 37 | t.Errorf("Unexpected read error: %v", err) 38 | } else if s := string(got); s != want { 39 | t.Errorf("Next record: got %q, want %q", s, want) 40 | } 41 | } 42 | 43 | // The stream should have been fully consumed. 44 | if got, err := rd.Next(); err != io.EOF { 45 | t.Errorf("Next record: got %q [%v], want EOF", string(got), err) 46 | } 47 | } 48 | 49 | func TestCorruptReader(t *testing.T) { 50 | const corrupt = "\x05ABCD" // n = 5, only 4 bytes of data 51 | 52 | r := strings.NewReader(corrupt) 53 | rd := NewReader(r) 54 | 55 | got, err := rd.Next() 56 | if err != io.ErrUnexpectedEOF { 57 | t.Fatalf("Next record: got %q [%v], want %v", string(got), err, io.ErrUnexpectedEOF) 58 | } 59 | t.Logf("Next record gave expected error: %v", err) 60 | } 61 | 62 | func TestGoodWriter(t *testing.T) { 63 | var w bytes.Buffer 64 | wr := NewWriter(&w) 65 | 66 | for _, record := range []string{"", "A", "BC", "DEF"} { 67 | if err := wr.Put([]byte(record)); err != nil { 68 | t.Errorf("Put %q: unexpected error: %v", record, err) 69 | } 70 | } 71 | if got := w.String(); got != testData { 72 | t.Errorf("Writer result: got %q, want %q", got, testData) 73 | } 74 | } 75 | 76 | type errWriter struct { 77 | nc int 78 | err error 79 | } 80 | 81 | func (w *errWriter) Write(data []byte) (int, error) { 82 | if w.err != nil && w.nc == 0 { 83 | return 0, w.err 84 | } 85 | w.nc-- 86 | return len(data), nil 87 | } 88 | 89 | func TestCorruptWriter(t *testing.T) { 90 | bad := errors.New("FAIL") 91 | w := &errWriter{nc: 1, err: bad} 92 | wr := NewWriter(w) 93 | 94 | err := wr.Put([]byte("whatever")) 95 | if err == nil { 96 | t.Fatalf("Put: got error nil, want error %v", bad) 97 | } 98 | t.Logf("Put record gave expected error: %v", err) 99 | } 100 | 101 | func TestRoundTrip(t *testing.T) { 102 | const input = "Some of what a fool thinks often remains." 103 | 104 | // Write all the words in the input as records to a delimited writer. 105 | words := strings.Fields(input) 106 | var buf bytes.Buffer 107 | 108 | wr := NewWriter(&buf) 109 | for _, word := range words { 110 | if err := wr.Put([]byte(word)); err != nil { 111 | t.Errorf("Put %q: unexpected error: %v", word, err) 112 | } 113 | } 114 | t.Logf("After writing, buf=%q len=%d", buf.Bytes(), buf.Len()) 115 | 116 | // Read all the records back from the buffer with a delimited reader. 117 | var got []string 118 | rd := NewReader(&buf) 119 | for { 120 | rec, err := rd.Next() 121 | if err != nil { 122 | if err != io.EOF { 123 | t.Errorf("Next: unexpected error: %v", err) 124 | } 125 | break 126 | } 127 | got = append(got, string(rec)) 128 | } 129 | 130 | // Verify that we got the original words back. 131 | if !reflect.DeepEqual(got, words) { 132 | t.Errorf("Round trip of %q: got %+q, want %+q", input, got, words) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /p2p/net/wire/formatter.go: -------------------------------------------------------------------------------- 1 | package wire 2 | 3 | import "io" 4 | 5 | // TODO: A sync formatter 6 | 7 | // Formatter is a message format interface 8 | type Formatter interface { 9 | Pipe(rw io.ReadWriteCloser) 10 | In() chan []byte 11 | Out(message []byte) error 12 | Close() 13 | } 14 | -------------------------------------------------------------------------------- /p2p/node/helpers.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 10 | "github.com/libonomy/libonomy-p2p/rand" 11 | ) 12 | 13 | var localhost = net.IP{127, 0, 0, 1} 14 | 15 | // ErrFailedToCreate is returned when we fail to create a node 16 | var ErrFailedToCreate = errors.New("failed to create local test node") 17 | 18 | // GenerateTestNode generates a local test node without persisting data to local store and with default config value. 19 | func GenerateTestNode(t *testing.T) (LocalNode, *Info) { 20 | return GenerateTestNodeWithConfig(t) 21 | } 22 | 23 | // GenerateTestNodeWithConfig creates a local test node without persisting data to local store. 24 | func GenerateTestNodeWithConfig(t *testing.T) (LocalNode, *Info) { 25 | 26 | port, err := GetUnboundedPort() 27 | if err != nil { 28 | t.Error("failed to get a port to bind", err) 29 | } 30 | 31 | addr := net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: port} 32 | 33 | var localNode LocalNode 34 | 35 | localNode, err = NewNodeIdentity() 36 | if err != nil { 37 | t.Error(ErrFailedToCreate) 38 | return emptyNode, nil 39 | } 40 | return localNode, &Info{localNode.PublicKey().Array(), addr.IP, uint16(port), uint16(port)} 41 | } 42 | 43 | // GenerateRandomNodeData generates a remote random node data for testing. 44 | func GenerateRandomNodeData() *Info { 45 | rand.Seed(time.Now().UnixNano()) 46 | port := uint16(rand.Int31n(48127) + 1024) 47 | pub := p2pcrypto.NewRandomPubkey() 48 | return NewNode(pub, localhost, port, port) 49 | } 50 | 51 | // GenerateRandomNodesData generates remote nodes data for testing. 52 | func GenerateRandomNodesData(n int) []*Info { 53 | res := make([]*Info, n) 54 | for i := 0; i < n; i++ { 55 | res[i] = GenerateRandomNodeData() 56 | } 57 | return res 58 | } 59 | 60 | // GetUnboundedPort returns a port that is for sure unbounded or an error. 61 | func GetUnboundedPort() (int, error) { 62 | l, e := net.Listen("tcp", ":0") 63 | if e != nil { 64 | return 0, e 65 | } 66 | defer l.Close() 67 | return l.Addr().(*net.TCPAddr).Port, nil 68 | } 69 | -------------------------------------------------------------------------------- /p2p/node/localnode.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 5 | ) 6 | 7 | // LocalNode is a public-private key pair used locally. 8 | type LocalNode struct { 9 | publicKey p2pcrypto.PublicKey 10 | privKey p2pcrypto.PrivateKey 11 | } 12 | 13 | // PublicKey returns the node's public key 14 | func (n LocalNode) PublicKey() p2pcrypto.PublicKey { 15 | return n.publicKey 16 | } 17 | 18 | // PrivateKey returns this node's private key. 19 | func (n LocalNode) PrivateKey() p2pcrypto.PrivateKey { 20 | return n.privKey 21 | } 22 | 23 | var emptyNode LocalNode 24 | 25 | // NewNodeIdentity creates a new local node without attempting to restore node from local store. 26 | func NewNodeIdentity() (LocalNode, error) { 27 | priv, pub, err := p2pcrypto.GenerateKeyPair() 28 | if err != nil { 29 | return emptyNode, err 30 | } 31 | return LocalNode{ 32 | publicKey: pub, 33 | privKey: priv, 34 | }, nil 35 | } 36 | 37 | // Creates a new node from persisted NodeData. 38 | func newLocalNodeFromFile(d *nodeFileData) (LocalNode, error) { 39 | 40 | priv, err := p2pcrypto.NewPrivateKeyFromBase58(d.PrivKey) 41 | if err != nil { 42 | return emptyNode, err 43 | } 44 | 45 | pub, err := p2pcrypto.NewPublicKeyFromBase58(d.PubKey) 46 | if err != nil { 47 | return emptyNode, err 48 | } 49 | 50 | n := LocalNode{ 51 | publicKey: pub, 52 | privKey: priv, 53 | } 54 | 55 | return n, nil 56 | } 57 | -------------------------------------------------------------------------------- /p2p/node/localnodestore.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/libonomy/libonomy-p2p/filesystem" 13 | "github.com/libonomy/libonomy-p2p/log" 14 | "github.com/libonomy/libonomy-p2p/p2p/config" 15 | ) 16 | 17 | // nodeFileData defines persistent node data. 18 | type nodeFileData struct { 19 | PubKey string `json:"pubKey"` 20 | PrivKey string `json:"priKey"` 21 | } 22 | 23 | // Node store - local node data persistence functionality 24 | 25 | // PersistData save node's data to local disk in `path`. 26 | func (n *LocalNode) PersistData(path string) error { 27 | 28 | data := nodeFileData{ 29 | PubKey: n.publicKey.String(), 30 | PrivKey: n.privKey.String(), 31 | } 32 | 33 | finaldata, err := json.MarshalIndent(data, "", " ") 34 | if err != nil { 35 | return err 36 | } 37 | 38 | datadir := filepath.Join(path, config.P2PDirectoryPath, config.NodesDirectoryName, n.publicKey.String()) 39 | 40 | err = filesystem.ExistOrCreate(datadir) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | nodefile := filepath.Join(datadir, config.NodeDataFileName) 46 | 47 | // make sure our node file is written to the os filesystem. 48 | f, err := os.Create(nodefile) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | _, err = f.Write(finaldata) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | err = f.Sync() 59 | if err != nil { 60 | return err 61 | } 62 | 63 | err = f.Close() 64 | if err != nil { 65 | return err 66 | } 67 | 68 | log.Info("Saved p2p node information (%s), Identity - %v", nodefile, n.publicKey.String()) 69 | 70 | return nil 71 | } 72 | 73 | // LoadIdentity loads a specific nodeid from the disk at the given path 74 | func LoadIdentity(path, nodeid string) (LocalNode, error) { 75 | nfd, err := readNodeData(path, nodeid) 76 | if err != nil { 77 | return emptyNode, err 78 | } 79 | 80 | return newLocalNodeFromFile(nfd) 81 | } 82 | 83 | // Read node persisted data based on node id. 84 | func readNodeData(path string, nodeID string) (*nodeFileData, error) { 85 | 86 | nodefile := filepath.Join(path, config.P2PDirectoryPath, config.NodesDirectoryName, nodeID, config.NodeDataFileName) 87 | 88 | if !filesystem.PathExists(nodefile) { 89 | return nil, fmt.Errorf("tried to read node from non-existing path (%v)", nodefile) 90 | } 91 | 92 | data := bytes.NewBuffer(nil) 93 | 94 | f, err := os.Open(nodefile) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | _, err = io.Copy(data, f) 100 | 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | err = f.Close() 106 | 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | var nodeData nodeFileData 112 | err = json.Unmarshal(data.Bytes(), &nodeData) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | return &nodeData, nil 118 | } 119 | 120 | func getLocalNodes(path string) ([]string, error) { 121 | nodesDir := filepath.Join(path, config.P2PDirectoryPath, config.NodesDirectoryName) 122 | 123 | if !filesystem.PathExists(nodesDir) { 124 | return nil, fmt.Errorf("directory not found %v", path) 125 | } 126 | 127 | fls, err := ioutil.ReadDir(nodesDir) 128 | 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | keys := make([]string, len(fls)) 134 | 135 | for i, f := range fls { 136 | keys[i] = f.Name() 137 | } 138 | 139 | return keys, nil 140 | } 141 | 142 | // ReadFirstNodeData reads node data from the data folder. 143 | // Reads a random node from the data folder if more than one node data file is persisted. 144 | // To load a specific node on startup - users need to pass the node id using a cli arg. 145 | func ReadFirstNodeData(path string) (LocalNode, error) { 146 | nds, err := getLocalNodes(path) 147 | if err != nil { 148 | return emptyNode, err 149 | } 150 | 151 | f, err := readNodeData(path, nds[0]) 152 | if err != nil { 153 | return emptyNode, err 154 | } 155 | 156 | return newLocalNodeFromFile(f) 157 | } 158 | -------------------------------------------------------------------------------- /p2p/node/localnodestore_test.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | "github.com/stretchr/testify/require" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestNodeLocalStore(t *testing.T) { 11 | // start clean 12 | 13 | node, err := NewNodeIdentity() 14 | require.NoError(t, err, "failed to create new local node") 15 | 16 | temppath := os.TempDir() + "/" + uuid.New().String() + "_" + t.Name() + "/" 17 | err = node.PersistData(temppath) 18 | require.NoError(t, err, "failed to persist node data") 19 | 20 | readNode, err := readNodeData(temppath, node.publicKey.String()) 21 | require.NoError(t, err, "failed to read node from file") 22 | 23 | rnode, nerr := newLocalNodeFromFile(readNode) 24 | require.NoError(t, nerr, "failed to parse node keys") 25 | require.Equal(t, rnode.publicKey, node.publicKey) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /p2p/node/node_test.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestNew(t *testing.T) { 15 | pu := p2pcrypto.NewRandomPubkey() 16 | address := net.IPv6loopback 17 | 18 | port := uint16(1234) 19 | 20 | node := NewNode(pu, address, port, port) 21 | 22 | assert.Equal(t, node.PublicKey(), pu) 23 | assert.Equal(t, node.IP, address) 24 | } 25 | 26 | func TestNewNodeFromString(t *testing.T) { 27 | address := "126.0.0.1:3572" 28 | pubkey := "DWXX1te9Vr9DNUJVsuQqAhoHgXzXCYvwxfiTHCiyxYF5" 29 | data := fmt.Sprintf("libonomy://%v@%v", pubkey, address) 30 | 31 | node, err := ParseNode(data) 32 | 33 | assert.NoError(t, err) 34 | ip, port, _ := net.SplitHostPort(address) 35 | assert.Equal(t, ip, node.IP.String()) 36 | assert.Equal(t, port, strconv.Itoa(int(node.ProtocolPort))) 37 | assert.Equal(t, pubkey, node.PublicKey().String()) 38 | 39 | pubkey = "r9gJRWVB9JVPap2HKn" 40 | data = fmt.Sprintf("%v/%v", address, pubkey) 41 | node, err = ParseNode(data) 42 | require.Nil(t, node) 43 | require.Error(t, err) 44 | } 45 | 46 | func TestStringFromNode(t *testing.T) { 47 | n := GenerateRandomNodeData() 48 | 49 | str := n.String() 50 | 51 | n2, err := ParseNode(str) 52 | 53 | require.NoError(t, err) 54 | 55 | assert.Equal(t, n2.IP.String(), n.IP.String()) 56 | assert.Equal(t, n2.ID.String(), n.PublicKey().String()) 57 | assert.Equal(t, n2.ProtocolPort, n.ProtocolPort) 58 | assert.Equal(t, n2.DiscoveryPort, n.DiscoveryPort) 59 | } 60 | -------------------------------------------------------------------------------- /p2p/p2p.go: -------------------------------------------------------------------------------- 1 | // Package p2p provides a networking api for creating p2p protocols by enabling sending direct messages to 2 | // a set of provided neighbors or broadcasting a message to all of them. the discovery, connectivity and encryption is 3 | // completely transparent to consumers. NOTE: gossip protocols must take care of their own message validation. 4 | package p2p 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/libonomy/libonomy-p2p/log" 10 | "github.com/libonomy/libonomy-p2p/p2p/config" 11 | "github.com/libonomy/libonomy-p2p/p2p/service" 12 | ) 13 | 14 | // Service is a wrapper for service.Service to expose the Service interface to `p2p` package clients 15 | type Service service.Service 16 | 17 | // New creates a new P2P service a.k.a `Switch` it loads existing node information from the disk or creates a new one. 18 | func New(ctx context.Context, config config.Config, logger log.Log, path string) (*Switch, error) { 19 | return newSwarm(ctx, config, logger, path) // TODO ADD Persist param 20 | } 21 | -------------------------------------------------------------------------------- /p2p/p2pcrypto/crypto_test.go: -------------------------------------------------------------------------------- 1 | package p2pcrypto 2 | 3 | import ( 4 | "bytes" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func TestBox(t *testing.T) { 10 | r := require.New(t) 11 | alicePrivkey, alicePubkey, err := GenerateKeyPair() 12 | r.NoError(err) 13 | 14 | bobPrivkey, bobPubkey, err := GenerateKeyPair() 15 | r.NoError(err) 16 | 17 | aliceSharedSecret := GenerateSharedSecret(alicePrivkey, bobPubkey) 18 | bobSharedSecret := GenerateSharedSecret(bobPrivkey, alicePubkey) 19 | r.Zero(bytes.Compare(aliceSharedSecret.Bytes(), bobSharedSecret.Bytes())) 20 | r.Equal(aliceSharedSecret.String(), bobSharedSecret.String()) 21 | r.Equal(aliceSharedSecret.raw(), bobSharedSecret.raw()) 22 | 23 | secretMessage := []byte("This is a secret -- sh...") 24 | sealed := aliceSharedSecret.Seal(secretMessage) 25 | opened, err := bobSharedSecret.Open(sealed) 26 | r.NoError(err) 27 | r.Equal(string(secretMessage), string(opened)) 28 | } 29 | 30 | func TestPrependPubkey(t *testing.T) { 31 | r := require.New(t) 32 | pubkey := NewRandomPubkey() 33 | 34 | secretMessage := []byte("This is a secret -- sh...") 35 | messageWithPubkey := PrependPubkey(secretMessage, pubkey) 36 | message, extractedPubkey, err := ExtractPubkey(messageWithPubkey) 37 | r.NoError(err) 38 | r.Equal(string(secretMessage), string(message)) 39 | r.Zero(bytes.Compare(pubkey.Bytes(), extractedPubkey.Bytes())) 40 | } 41 | -------------------------------------------------------------------------------- /p2p/peers.go: -------------------------------------------------------------------------------- 1 | package p2p 2 | 3 | import ( 4 | "sync/atomic" 5 | 6 | "github.com/libonomy/libonomy-p2p/log" 7 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 8 | "github.com/libonomy/libonomy-p2p/rand" 9 | ) 10 | 11 | // Peer is represented by a p2p identity public key 12 | type Peer p2pcrypto.PublicKey 13 | 14 | // Peers is used by protocols to manage available peers. 15 | type Peers struct { 16 | log.Log 17 | snapshot *atomic.Value 18 | exit chan struct{} 19 | } 20 | 21 | // NewPeersImpl creates a Peers using specified parameters and returns it 22 | func NewPeersImpl(snapshot *atomic.Value, exit chan struct{}, lg log.Log) *Peers { 23 | return &Peers{snapshot: snapshot, Log: lg, exit: exit} 24 | } 25 | 26 | // PeerSubscriptionProvider is the interface that provides us with peer events channels. 27 | type PeerSubscriptionProvider interface { 28 | SubscribePeerEvents() (conn, disc chan p2pcrypto.PublicKey) 29 | } 30 | 31 | // NewPeers creates a Peers instance that is registered to `s`'s events and updates with them. 32 | func NewPeers(s PeerSubscriptionProvider, lg log.Log) *Peers { 33 | value := atomic.Value{} 34 | value.Store(make([]Peer, 0, 20)) 35 | pi := NewPeersImpl(&value, make(chan struct{}), lg) 36 | newPeerC, expiredPeerC := s.SubscribePeerEvents() 37 | go pi.listenToPeers(newPeerC, expiredPeerC) 38 | return pi 39 | } 40 | 41 | // Close stops listening for events. 42 | func (pi Peers) Close() { 43 | close(pi.exit) 44 | } 45 | 46 | // GetPeers returns a snapshot of the connected peers shuffled. 47 | func (pi Peers) GetPeers() []Peer { 48 | peers := pi.snapshot.Load().([]Peer) 49 | cpy := make([]Peer, len(peers)) 50 | copy(cpy, peers) // if we dont copy we will shuffle orig array 51 | pi.With().Info("now connected", log.Int("n_peers", len(cpy))) 52 | rand.Shuffle(len(cpy), func(i, j int) { cpy[i], cpy[j] = cpy[j], cpy[i] }) // shuffle peers order 53 | return cpy 54 | } 55 | 56 | // PeerCount returns the number of connected peers 57 | func (pi Peers) PeerCount() uint64 { 58 | peers := pi.snapshot.Load().([]Peer) 59 | return uint64(len(peers)) 60 | } 61 | 62 | func (pi *Peers) listenToPeers(newPeerC, expiredPeerC chan p2pcrypto.PublicKey) { 63 | peerSet := make(map[Peer]bool) // set of unique peers 64 | defer pi.Debug("run stopped") 65 | for { 66 | select { 67 | case <-pi.exit: 68 | return 69 | case peer, ok := <-newPeerC: 70 | if !ok { 71 | return 72 | } 73 | pi.With().Debug("new peer", log.String("peer", peer.String())) 74 | peerSet[peer] = true 75 | case peer, ok := <-expiredPeerC: 76 | if !ok { 77 | return 78 | } 79 | pi.With().Debug("expired peer", log.String("peer", peer.String())) 80 | delete(peerSet, peer) 81 | } 82 | keys := make([]Peer, 0, len(peerSet)) 83 | for k := range peerSet { 84 | keys = append(keys, k) 85 | } 86 | pi.snapshot.Store(keys) //swap snapshot 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /p2p/peers_test.go: -------------------------------------------------------------------------------- 1 | package p2p 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync/atomic" 7 | "testing" 8 | "time" 9 | 10 | "github.com/libonomy/libonomy-p2p/log" 11 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 12 | "github.com/libonomy/libonomy-p2p/p2p/service" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func getPeers(p Service) (*Peers, chan p2pcrypto.PublicKey, chan p2pcrypto.PublicKey) { 17 | value := atomic.Value{} 18 | value.Store(make([]Peer, 0, 20)) 19 | pi := &Peers{snapshot: &value, exit: make(chan struct{}), Log: log.NewDefault("peers")} 20 | n, expired := p.SubscribePeerEvents() 21 | go pi.listenToPeers(n, expired) 22 | return pi, n, expired 23 | } 24 | 25 | func TestPeers_GetPeers(t *testing.T) { 26 | pi, n, _ := getPeers(service.NewSimulator().NewNode()) 27 | a := p2pcrypto.NewRandomPubkey() 28 | n <- a 29 | time.Sleep(10 * time.Millisecond) //allow context switch 30 | peers := pi.GetPeers() 31 | defer pi.Close() 32 | assert.True(t, len(peers) == 1, "number of peers incorrect") 33 | assert.True(t, peers[0] == a, "returned wrong peer") 34 | } 35 | 36 | func TestPeers_Close(t *testing.T) { 37 | pi, n, _ := getPeers(service.NewSimulator().NewNode()) 38 | a := p2pcrypto.NewRandomPubkey() 39 | n <- a 40 | time.Sleep(10 * time.Millisecond) //allow context switch 41 | pi.Close() 42 | //_, ok := <-new 43 | //assert.True(t, !ok, "channel 'new' still open") 44 | //_, ok = <-expierd 45 | //assert.True(t, !ok, "channel 'expierd' still open") 46 | } 47 | 48 | func TestPeers_AddPeer(t *testing.T) { 49 | pi, n, _ := getPeers(service.NewSimulator().NewNode()) 50 | a := p2pcrypto.NewRandomPubkey() 51 | b := p2pcrypto.NewRandomPubkey() 52 | c := p2pcrypto.NewRandomPubkey() 53 | d := p2pcrypto.NewRandomPubkey() 54 | e := p2pcrypto.NewRandomPubkey() 55 | n <- a 56 | time.Sleep(10 * time.Millisecond) //allow context switch 57 | peers := pi.GetPeers() 58 | assert.True(t, len(peers) == 1, "number of peers incorrect, length was ", len(peers)) 59 | n <- b 60 | n <- c 61 | n <- d 62 | n <- e 63 | defer pi.Close() 64 | time.Sleep(10 * time.Millisecond) //allow context switch 65 | peers = pi.GetPeers() 66 | assert.True(t, len(peers) == 5, "number of peers incorrect, length was ", len(peers)) 67 | } 68 | 69 | func TestPeers_RemovePeer(t *testing.T) { 70 | pi, n, expierd := getPeers(service.NewSimulator().NewNode()) 71 | a := p2pcrypto.NewRandomPubkey() 72 | b := p2pcrypto.NewRandomPubkey() 73 | c := p2pcrypto.NewRandomPubkey() 74 | d := p2pcrypto.NewRandomPubkey() 75 | e := p2pcrypto.NewRandomPubkey() 76 | n <- a 77 | time.Sleep(10 * time.Millisecond) //allow context switch 78 | peers := pi.GetPeers() 79 | assert.True(t, len(peers) == 1, "number of peers incorrect, length was ", len(peers)) 80 | n <- b 81 | n <- c 82 | n <- d 83 | n <- e 84 | defer pi.Close() 85 | time.Sleep(10 * time.Millisecond) //allow context switch 86 | peers = pi.GetPeers() 87 | assert.True(t, len(peers) == 5, "number of peers incorrect, length was ", len(peers)) 88 | expierd <- b 89 | expierd <- c 90 | expierd <- d 91 | expierd <- e 92 | time.Sleep(10 * time.Millisecond) //allow context switch 93 | peers = pi.GetPeers() 94 | assert.True(t, len(peers) == 1, "number of peers incorrect, length was ", len(peers)) 95 | } 96 | 97 | func TestPeers_RandomPeers(t *testing.T) { 98 | pi, n, _ := getPeers(service.NewSimulator().NewNode()) 99 | a := p2pcrypto.NewRandomPubkey() 100 | b := p2pcrypto.NewRandomPubkey() 101 | c := p2pcrypto.NewRandomPubkey() 102 | d := p2pcrypto.NewRandomPubkey() 103 | e := p2pcrypto.NewRandomPubkey() 104 | n <- a 105 | time.Sleep(10 * time.Millisecond) //allow context switch 106 | peers := pi.GetPeers() 107 | assert.True(t, len(peers) == 1, "number of peers incorrect, length was ", len(peers)) 108 | n <- b 109 | n <- c 110 | n <- d 111 | n <- e 112 | defer pi.Close() 113 | time.Sleep(10 * time.Millisecond) //allow context switch 114 | peers1 := pi.GetPeers() 115 | peers2 := pi.GetPeers() 116 | assert.True(t, len(peers1) == 5, "number of peers incorrect, length was ", len(peers1)) 117 | 118 | for p := range peers1 { 119 | if !bytes.Equal(peers1[p].Bytes(), peers2[p].Bytes()) { 120 | t.Log("test done ") 121 | return 122 | } 123 | t.Log(fmt.Sprintf("index %d same element %s %s", p, peers1[p].String(), peers2[p].String())) 124 | } 125 | 126 | t.Fail() 127 | } 128 | -------------------------------------------------------------------------------- /p2p/server/msgserver_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/libonomy/libonomy-p2p/log" 9 | "github.com/libonomy/libonomy-p2p/p2p/config" 10 | "github.com/libonomy/libonomy-p2p/p2p/service" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | const protocol = "/protocol/test/1.0/" 15 | 16 | func TestProtocol_SendRequest(t *testing.T) { 17 | sim := service.NewSimulator() 18 | n1 := sim.NewNode() 19 | fnd1 := NewMsgServer(n1, protocol, 5*time.Second, make(chan service.DirectMessage, config.Values.BufferSize), log.New("t1", "", "")) 20 | 21 | //handler that returns some bytes on request 22 | 23 | handler := func(msg []byte) []byte { 24 | return []byte("some value to return") 25 | } 26 | // todo test nonbyte handlers 27 | fnd1.RegisterBytesMsgHandler(1, handler) 28 | 29 | n2 := sim.NewNode() 30 | fnd2 := NewMsgServer(n2, protocol, 5*time.Second, make(chan service.DirectMessage, config.Values.BufferSize), log.New("t2", "", "")) 31 | 32 | //send request with handler that converts to string and sends via channel 33 | strCh := make(chan string) 34 | callback := func(msg []byte) { 35 | fmt.Println("callback ...") 36 | strCh <- string(msg) 37 | } 38 | 39 | err := fnd2.SendRequest(1, nil, n1.PublicKey(), callback) 40 | msg := <-strCh 41 | 42 | assert.EqualValues(t, "some value to return", msg, "value received did not match correct value") 43 | assert.NoError(t, err, "Should not return error") 44 | } 45 | 46 | func TestProtocol_CleanOldPendingMessages(t *testing.T) { 47 | sim := service.NewSimulator() 48 | n1 := sim.NewNode() 49 | fnd1 := NewMsgServer(n1, protocol, 5*time.Second, make(chan service.DirectMessage, config.Values.BufferSize), log.New("t3", "", "")) 50 | 51 | //handler that returns some bytes on request 52 | 53 | handler := func(msg []byte) []byte { 54 | time.Sleep(2 * time.Second) 55 | return nil 56 | } 57 | 58 | fnd1.RegisterBytesMsgHandler(1, handler) 59 | 60 | n2 := sim.NewNode() 61 | fnd2 := NewMsgServer(n2, protocol, 10*time.Millisecond, make(chan service.DirectMessage, config.Values.BufferSize), log.New("t4", "", "")) 62 | 63 | //send request with handler that converts to string and sends via channel 64 | strCh := make(chan string) 65 | callback := func(msg []byte) { 66 | fmt.Println("callback ...") 67 | strCh <- string(msg) 68 | } 69 | 70 | err := fnd2.SendRequest(1, nil, n1.PublicKey(), callback) 71 | assert.NoError(t, err, "Should not return error") 72 | assert.EqualValues(t, 1, fnd2.pendingQueue.Len(), "value received did not match correct value1") 73 | 74 | timeout := time.After(3 * time.Second) 75 | // Keep trying until we're timed out or got a result or got an error 76 | 77 | select { 78 | // Got a timeout! fail with a timeout error 79 | case <-timeout: 80 | t.Error("timeout") 81 | return 82 | default: 83 | if fnd2.pendingQueue.Len() == 0 { 84 | assert.EqualValues(t, 0, fnd2.pendingQueue.Len(), "value received did not match correct value2") 85 | } 86 | } 87 | 88 | } 89 | 90 | func TestProtocol_Close(t *testing.T) { 91 | sim := service.NewSimulator() 92 | n1 := sim.NewNode() 93 | fnd1 := NewMsgServer(n1, protocol, 5*time.Second, make(chan service.DirectMessage, config.Values.BufferSize), log.New("t5", "", "")) 94 | 95 | //handler that returns some bytes on request 96 | 97 | handler := func(msg []byte) []byte { 98 | time.Sleep(60 * time.Second) 99 | return nil 100 | } 101 | 102 | fnd1.RegisterBytesMsgHandler(1, handler) 103 | 104 | n2 := sim.NewNode() 105 | fnd2 := NewMsgServer(n2, protocol, 10*time.Millisecond, make(chan service.DirectMessage, config.Values.BufferSize), log.New("t6", "", "")) 106 | 107 | //send request with handler that converts to string and sends via channel 108 | strCh := make(chan string) 109 | callback := func(msg []byte) { 110 | fmt.Println("callback ...") 111 | strCh <- string(msg) 112 | } 113 | 114 | err := fnd2.SendRequest(1, nil, n1.PublicKey(), callback) 115 | assert.NoError(t, err, "Should not return error") 116 | assert.EqualValues(t, 1, fnd2.pendingQueue.Len(), "value received did not match correct value1") 117 | fnd2.Close() 118 | } 119 | -------------------------------------------------------------------------------- /p2p/service/service.go: -------------------------------------------------------------------------------- 1 | // Package service defines basic interfaces to for protocols to consume p2p functionality. 2 | package service 3 | 4 | import ( 5 | "net" 6 | 7 | "github.com/libonomy/libonomy-p2p/p2p/p2pcrypto" 8 | "github.com/libonomy/libonomy-p2p/priorityq" 9 | ) 10 | 11 | // MessageValidation is a gossip message validation event. 12 | type MessageValidation struct { 13 | sender p2pcrypto.PublicKey 14 | msg []byte 15 | prot string 16 | } 17 | 18 | // Message returns the message as bytes 19 | func (mv MessageValidation) Message() []byte { 20 | return mv.msg 21 | } 22 | 23 | // Sender returns the public key of the sender of this message. (might not be the author) 24 | func (mv MessageValidation) Sender() p2pcrypto.PublicKey { 25 | return mv.sender 26 | } 27 | 28 | // Protocol is the protocol this message is targeted to. 29 | func (mv MessageValidation) Protocol() string { 30 | return mv.prot 31 | } 32 | 33 | // P2PMetadata is a generic metadata interface 34 | type P2PMetadata struct { 35 | FromAddress net.Addr 36 | // add here more fields that are needed by protocols 37 | } 38 | 39 | // NewMessageValidation creates a message validation struct to pass to the protocol. 40 | func NewMessageValidation(sender p2pcrypto.PublicKey, msg []byte, prot string) MessageValidation { 41 | return MessageValidation{sender, msg, prot} 42 | } 43 | 44 | // DirectMessage is an interface that represents a simple direct message structure 45 | type DirectMessage interface { 46 | Metadata() P2PMetadata 47 | Sender() p2pcrypto.PublicKey 48 | Bytes() []byte 49 | } 50 | 51 | // GossipMessage is an interface that represents a simple gossip message structure 52 | type GossipMessage interface { 53 | Sender() p2pcrypto.PublicKey 54 | Bytes() []byte 55 | ValidationCompletedChan() chan MessageValidation 56 | ReportValidation(protocol string) 57 | } 58 | 59 | // Service is an interface that represents a networking service (ideally p2p) that we can use to send messages or listen to incoming messages 60 | type Service interface { 61 | Start() error 62 | RegisterGossipProtocol(protocol string, prio priorityq.Priority) chan GossipMessage 63 | RegisterDirectProtocol(protocol string) chan DirectMessage 64 | SubscribePeerEvents() (new chan p2pcrypto.PublicKey, del chan p2pcrypto.PublicKey) 65 | Broadcast(protocol string, payload []byte) error 66 | Shutdown() 67 | } 68 | 69 | // Data is a wrapper around a message that can hold either raw bytes message or a req-res wrapper. 70 | type Data interface { 71 | Bytes() []byte 72 | } 73 | 74 | // DataBytes is a byte array payload wrapper. 75 | type DataBytes struct { 76 | Payload []byte 77 | } 78 | 79 | // DataMsgWrapper is a req-res payload wrapper 80 | type DataMsgWrapper struct { 81 | Req bool 82 | MsgType uint32 83 | ReqID uint64 84 | Payload []byte 85 | } 86 | 87 | // Bytes returns the message as bytes 88 | func (m DataBytes) Bytes() []byte { 89 | return m.Payload 90 | } 91 | 92 | // Bytes returns the message as bytes 93 | func (m DataMsgWrapper) Bytes() []byte { 94 | return m.Payload 95 | } 96 | -------------------------------------------------------------------------------- /p2p/version/version.go: -------------------------------------------------------------------------------- 1 | // Package version includes methods to compare versions between nodes. 2 | package version 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // CheckNodeVersion checks if a request version is more recent then the given min version. returns a bool and an error 12 | func CheckNodeVersion(reqVersion string, minVersion string) (bool, error) { 13 | // TODO : semantic versioning comparison is a pain, refine this or use a lib 14 | 15 | splt := strings.Split(reqVersion, "/") 16 | if len(splt) > 2 { 17 | return false, errors.New("invalid client version") 18 | } 19 | 20 | if len(splt) == 2 { 21 | reqVersion = splt[1] 22 | } 23 | 24 | // if same version string don't do anything 25 | if reqVersion == minVersion { 26 | return true, nil 27 | } 28 | 29 | // take each version number and convert to ints 30 | reqsplit, minsplit := strings.Split(reqVersion, "."), strings.Split(minVersion, ".") 31 | if len(reqsplit) != 3 || len(minsplit) != 3 { 32 | return false, fmt.Errorf("one of provided versions isn't valid %v / %v", reqVersion, minVersion) 33 | } 34 | numa, numb := [3]int64{}, [3]int64{} 35 | for i := 0; i < 3; i++ { 36 | var err error 37 | numa[i], err = strconv.ParseInt(reqsplit[i], 10, 8) 38 | if err != nil { 39 | return false, fmt.Errorf(" could not read version number %v", reqVersion) 40 | } 41 | numb[i], err = strconv.ParseInt(minsplit[i], 10, 8) 42 | if err != nil { 43 | return false, fmt.Errorf(" could not read version number %v", reqVersion) 44 | } 45 | } 46 | 47 | for i := 0; i < 2; i++ { 48 | if numa[i] > numb[i] { 49 | return true, nil 50 | } 51 | 52 | if numa[i] != numb[i] { 53 | return false, nil 54 | } 55 | } 56 | 57 | return false, nil 58 | } 59 | -------------------------------------------------------------------------------- /p2p/version/version_test.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCheckNodeVersion(t *testing.T) { 11 | //t.Skip("We should implement or import deep semantic version comparison") 12 | type versionTest []struct { 13 | version string 14 | minVersion string 15 | } 16 | 17 | testNewClient := versionTest{ 18 | {"someclient/0.0.1", "0.0.1"}, 19 | {"anotherclient/1.0.0", "0.0.1"}, 20 | {"someclient/0.2.1", "0.0.1"}, 21 | {"someclient/1.3.1", "1.0.1"}, 22 | {"someclient/3.0.0", "0.5.3"}, 23 | {"someclient/0.2.0", "0.0.9"}, 24 | {"someclient/5.5.1", "5.0.0"}, 25 | } 26 | 27 | testNotValid := versionTest{ 28 | {"verybadclient/x.d.1", "0.0.1"}, 29 | {"ver/ybadclient/0.0.1", "0.0.1"}, 30 | {"verybadclient/x.2.1", "0.0.1"}, 31 | {"verybadclient/h.d.a", "0.0.1"}, 32 | //{"verybadclient/2.c2.4", "0.0.1" }, 33 | {"verybadclient/x.d.؆", "0.0.1"}, 34 | {"verybadclient/ੳ.ڪ.1", "0.0.1"}, 35 | {"verybadclient/ੳ.ڪ.؆", "0.0.1"}, 36 | } 37 | 38 | testTooOld := versionTest{ 39 | {"oldclient/1.1.1", "2.0.0"}, 40 | {"oldclient/1.1.1", "3.1.1"}, 41 | {"oldclient/1.2.1", "1.2.4"}, 42 | {"oldclient/0.3.1", "1.0.0"}, 43 | {"oldclient/4.4.1", "5.0.0"}, 44 | {"oldclient/1.0.5", "1.1.0"}, 45 | {"oldclient/1.0.9", "1.1.0"}, 46 | } 47 | 48 | getSemver := func(s string) string { 49 | splitted := strings.Split(s, "/") 50 | if len(splitted) != 2 { 51 | return "" 52 | } 53 | return splitted[1] 54 | } 55 | 56 | for _, te := range testNewClient { 57 | ok, err := CheckNodeVersion(getSemver(te.version), te.minVersion) 58 | assert.NoError(t, err, "Should'nt return error") 59 | assert.True(t, ok, "Should return true on same or higher version") 60 | } 61 | 62 | for _, te := range testNotValid { 63 | ok, err := CheckNodeVersion(getSemver(te.version), te.minVersion) 64 | assert.Error(t, err, "not valid version should error") 65 | assert.False(t, ok, "should return false on non valid version") 66 | } 67 | 68 | for _, te := range testTooOld { 69 | ok, err := CheckNodeVersion(getSemver(te.version), te.minVersion) 70 | assert.False(t, ok, "Should return false when version is older than min version") 71 | assert.NoError(t, err, "Shuold'nt return error when client is older") 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /priorityq/priorityq.go: -------------------------------------------------------------------------------- 1 | package priorityq 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrUnknownPriority indicates an incorrect attempt to make a write with an unknown priority 9 | ErrUnknownPriority = errors.New("unknown priority") 10 | 11 | // ErrQueueClosed indicates an attempt to read from a closed priority queue instance 12 | ErrQueueClosed = errors.New("the queue is closed") 13 | ) 14 | 15 | // Priority is the type that indicates the priority of different queues 16 | type Priority int 17 | 18 | const ( 19 | prioritiesCount = 3 // the number of priorities 20 | 21 | // High indicates the highest priority 22 | High = Priority(0) 23 | 24 | // Mid indicates the medium priority 25 | Mid = Priority(1) 26 | 27 | // Low indicates the lowest priority 28 | Low = Priority(2) 29 | ) 30 | 31 | // Queue is the priority queue 32 | type Queue struct { 33 | waitCh chan struct{} // the channel that indicates if there is a message ready in the queue 34 | queues []chan interface{} // the queues where the index is the priority 35 | } 36 | 37 | // New returns a new priority queue where each priority has a buffer of prioQueueLimit 38 | func New(prioQueueLimit int) *Queue { 39 | // set queue for each priority 40 | qs := make([]chan interface{}, prioritiesCount) 41 | for i := range qs { 42 | qs[i] = make(chan interface{}, prioQueueLimit) 43 | } 44 | 45 | return &Queue{ 46 | waitCh: make(chan struct{}, prioQueueLimit*prioritiesCount), 47 | queues: qs, 48 | } 49 | } 50 | 51 | // Write a message m to the associated queue with the provided priority 52 | // This method blocks iff the queue is full 53 | // Returns an error iff a queue does not exist for the provided name 54 | // Note: writing to the pq after a call to close is forbidden and will result in a panic 55 | func (pq *Queue) Write(prio Priority, m interface{}) error { 56 | if int(prio) >= cap(pq.queues) { 57 | return ErrUnknownPriority 58 | } 59 | 60 | pq.queues[prio] <- m 61 | pq.waitCh <- struct{}{} 62 | return nil 63 | } 64 | 65 | // Read returns the next message by priority 66 | // An error is set iff the priority queue has been closed 67 | func (pq *Queue) Read() (interface{}, error) { 68 | <-pq.waitCh // wait for m 69 | 70 | readLoop: 71 | // pick by priority 72 | for _, q := range pq.queues { 73 | if q == nil { // if not set just continue 74 | continue 75 | } 76 | 77 | // if set, try read 78 | select { 79 | case m, ok := <-q: 80 | if !ok { 81 | // channels are starting to close 82 | break readLoop 83 | } 84 | return m, nil 85 | default: // empty, try next 86 | continue 87 | } 88 | } 89 | 90 | // should be unreachable 91 | return nil, ErrQueueClosed 92 | } 93 | 94 | // Close the priority queue 95 | // No messages should be expected to be read after a call to Close 96 | func (pq *Queue) Close() { 97 | for _, q := range pq.queues { 98 | if q != nil { 99 | close(q) 100 | } 101 | } 102 | close(pq.waitCh) 103 | } 104 | -------------------------------------------------------------------------------- /priorityq/priorityq_test.go: -------------------------------------------------------------------------------- 1 | package priorityq 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | var ( 11 | defLen = 1000 12 | ) 13 | 14 | func TestNewPriorityQ(t *testing.T) { 15 | r := require.New(t) 16 | pq := New(defLen) 17 | r.Equal(defLen, cap(pq.queues[High])) 18 | r.Equal(defLen, cap(pq.queues[Mid])) 19 | r.Equal(defLen, cap(pq.queues[Low])) 20 | r.Equal(prioritiesCount, len(pq.queues)) 21 | r.Equal(prioritiesCount, cap(pq.queues)) 22 | r.Equal(defLen*prioritiesCount, cap(pq.waitCh)) 23 | } 24 | 25 | func TestPriorityQ_Write(t *testing.T) { 26 | r := require.New(t) 27 | pq := New(defLen) 28 | wg := sync.WaitGroup{} 29 | 30 | wg.Add(1) 31 | go func() { 32 | for i := 0; i < defLen; i++ { 33 | r.NoError(pq.Write(High, 0)) 34 | } 35 | wg.Done() 36 | }() 37 | 38 | wg.Add(1) 39 | go func() { 40 | for i := 0; i < defLen; i++ { 41 | r.NoError(pq.Write(Low, 1)) 42 | } 43 | wg.Done() 44 | }() 45 | 46 | wg.Wait() 47 | r.Equal(defLen, len(pq.queues[High])) 48 | r.Equal(defLen, len(pq.queues[Low])) 49 | } 50 | 51 | func TestPriorityQ_WriteError(t *testing.T) { 52 | r := require.New(t) 53 | pq := New(defLen) 54 | r.Equal(ErrUnknownPriority, pq.Write(3, 0)) 55 | } 56 | 57 | func TestPriorityQ_Read(t *testing.T) { 58 | r := require.New(t) 59 | pq := New(defLen) 60 | wg := sync.WaitGroup{} 61 | 62 | wg.Add(1) 63 | go func() { 64 | for i := 0; i < defLen; i++ { 65 | r.NoError(pq.Write(High, 0)) 66 | } 67 | wg.Done() 68 | }() 69 | 70 | wg.Add(1) 71 | go func() { 72 | for i := 0; i < defLen; i++ { 73 | r.NoError(pq.Write(Mid, 1)) 74 | } 75 | wg.Done() 76 | }() 77 | 78 | wg.Add(1) 79 | go func() { 80 | for i := 0; i < defLen; i++ { 81 | r.NoError(pq.Write(Low, 2)) 82 | } 83 | wg.Done() 84 | }() 85 | 86 | wg.Wait() 87 | 88 | maxPrio := 0 89 | rg := sync.WaitGroup{} 90 | rg.Add(1) 91 | i := 0 92 | go func() { 93 | m, e := pq.Read() 94 | for e == nil { 95 | prio, ok := m.(int) 96 | //fmt.Println("reading ", m, e, i, len(pq.queues[0]), len(pq.queues[1])) 97 | if !ok { 98 | // should never happen 99 | t.FailNow() 100 | } 101 | 102 | r.False(prio < maxPrio) 103 | maxPrio = prio 104 | i++ 105 | 106 | m, e = pq.Read() 107 | } 108 | rg.Done() 109 | }() 110 | 111 | time.Sleep(1500 * time.Millisecond) 112 | pq.Close() 113 | rg.Wait() 114 | r.Equal(3*defLen, i) 115 | } 116 | 117 | func TestPriorityQ_Close(t *testing.T) { 118 | r := require.New(t) 119 | pq := New(defLen) 120 | prios := []Priority{Low, Mid, High} 121 | for i := 0; i < 1000; i++ { 122 | r.NoError(pq.Write(Priority(i%len(prios)), []byte("LOLOLOLLZ"))) 123 | } 124 | 125 | r.Equal(_getQueueSize(pq), defLen) 126 | 127 | c := make(chan struct{}) 128 | 129 | go func() { 130 | defer func() { c <- struct{}{} }() 131 | time.Sleep(10 * time.Millisecond) 132 | i := 0 133 | for { 134 | _, err := pq.Read() 135 | i++ 136 | if err != nil { 137 | return 138 | } 139 | if i == defLen { 140 | t.Error("Finished all queue while close was called midway") 141 | } 142 | } 143 | }() 144 | 145 | //close it right away 146 | pq.Close() 147 | <-c 148 | } 149 | 150 | func _getQueueSize(pq *Queue) int { 151 | i := 0 152 | for _, q := range pq.queues { 153 | i += len(q) 154 | } 155 | return i 156 | } 157 | -------------------------------------------------------------------------------- /rand/rand.go: -------------------------------------------------------------------------------- 1 | package rand 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | /* 9 | * Top-level convenience functions 10 | */ 11 | 12 | var globalRand = rand.New(rand.NewSource(time.Now().UnixNano()).(rand.Source64)) 13 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 14 | 15 | // Seed uses the provided seed value to initialize the default Source to a 16 | // deterministic state. If Seed is not called, the generator behaves as 17 | // if seeded by Seed(1). Seed values that have the same remainder when 18 | // divided by 2^31-1 generate the same pseudo-random sequence. 19 | // Seed, unlike the Rand.Seed method, is safe for concurrent use. 20 | func Seed(seed int64) { globalRand.Seed(seed) } 21 | 22 | // Int63 returns a non-negative pseudo-random 63-bit integer as an int64 23 | // from the default Source. 24 | func Int63() int64 { return globalRand.Int63() } 25 | 26 | // Uint32 returns a pseudo-random 32-bit value as a uint32 27 | // from the default Source. 28 | func Uint32() uint32 { return globalRand.Uint32() } 29 | 30 | // Uint64 returns a pseudo-random 64-bit value as a uint64 31 | // from the default Source. 32 | func Uint64() uint64 { return globalRand.Uint64() } 33 | 34 | // Int31 returns a non-negative pseudo-random 31-bit integer as an int32 35 | // from the default Source. 36 | func Int31() int32 { return globalRand.Int31() } 37 | 38 | // Int returns a non-negative pseudo-random int from the default Source. 39 | func Int() int { return globalRand.Int() } 40 | 41 | // Int63n returns, as an int64, a non-negative pseudo-random number in [0,n) 42 | // from the default Source. 43 | // It panics if n <= 0. 44 | func Int63n(n int64) int64 { return globalRand.Int63n(n) } 45 | 46 | // Int31n returns, as an int32, a non-negative pseudo-random number in [0,n) 47 | // from the default Source. 48 | // It panics if n <= 0. 49 | func Int31n(n int32) int32 { return globalRand.Int31n(n) } 50 | 51 | // Intn returns, as an int, a non-negative pseudo-random number in [0,n) 52 | // from the default Source. 53 | // It panics if n <= 0. 54 | func Intn(n int) int { return globalRand.Intn(n) } 55 | 56 | // Float64 returns, as a float64, a pseudo-random number in [0.0,1.0) 57 | // from the default Source. 58 | func Float64() float64 { return globalRand.Float64() } 59 | 60 | // Float32 returns, as a float32, a pseudo-random number in [0.0,1.0) 61 | // from the default Source. 62 | func Float32() float32 { return globalRand.Float32() } 63 | 64 | // Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n) 65 | // from the default Source. 66 | func Perm(n int) []int { return globalRand.Perm(n) } 67 | 68 | // Shuffle pseudo-randomizes the order of elements using the default Source. 69 | // n is the number of elements. Shuffle panics if n < 0. 70 | // swap swaps the elements with indexes i and j. 71 | func Shuffle(n int, swap func(i, j int)) { globalRand.Shuffle(n, swap) } 72 | 73 | // Read generates len(p) random bytes from the default Source and 74 | // writes them into p. It always returns len(p) and a nil error. 75 | // Read, unlike the Rand.Read method, is safe for concurrent use. 76 | func Read(p []byte) (n int, err error) { return globalRand.Read(p) } 77 | 78 | // NormFloat64 returns a normally distributed float64 in the range 79 | // [-math.MaxFloat64, +math.MaxFloat64] with 80 | // standard normal distribution (mean = 0, stddev = 1) 81 | // from the default Source. 82 | // To produce a different normal distribution, callers can 83 | // adjust the output using: 84 | // 85 | // sample = NormFloat64() * desiredStdDev + desiredMean 86 | // 87 | func NormFloat64() float64 { return globalRand.NormFloat64() } 88 | 89 | // ExpFloat64 returns an exponentially distributed float64 in the range 90 | // (0, +math.MaxFloat64] with an exponential distribution whose rate parameter 91 | // (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. 92 | // To produce a distribution with a different rate parameter, 93 | // callers can adjust the output using: 94 | // 95 | // sample = ExpFloat64() / desiredRateParameter 96 | // 97 | func ExpFloat64() float64 { return globalRand.ExpFloat64() } 98 | 99 | // String returns an n sized random string 100 | func String(n int) string { 101 | b := make([]rune, n) 102 | for i := range b { 103 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 104 | } 105 | return string(b) 106 | } 107 | -------------------------------------------------------------------------------- /scripts/check-go-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Ensure we use Go installed 3 | errcho() { 4 | RED='\033[0;31m' 5 | NC='\033[0m' # no color 6 | echo -e "${RED}$1${NC}" 7 | } 8 | 9 | if ! (hash go 2>/dev/null) ; then 10 | errcho "Could not find a Go installation, please install Go and try again." 11 | exit 1; 12 | fi 13 | 14 | # Ensure we use Go version 1.11+ 15 | read major minor patch <<< $(go version | sed 's/go version go\([0-9]*\)\.\([0-9]*\).*/\1 \2/') 16 | if [[ ${major} -ne 1 || ${minor} -lt 11 ]]; then 17 | errcho "Go 1.11+ is required (v$major.$minor.$patch is installed at `which go`)" 18 | exit 1; 19 | fi 20 | -------------------------------------------------------------------------------- /scripts/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | ./scripts/verify-protoc-gen-go.sh 3 | 4 | protoc=./devtools/bin/protoc 5 | if [[ -n "$PROTOCPATH" ]]; then 6 | protoc=${PROTOCPATH}; 7 | fi 8 | 9 | compile() { 10 | eval $protoc "$@" 11 | } 12 | 13 | errcho() { 14 | RED='\033[0;31m' 15 | NC='\033[0m' # no color 16 | echo -e "${RED}$1${NC}" 17 | } 18 | 19 | 20 | if [ ! -f $protoc ] ; then 21 | errcho "Could not find protoc in $protoc. Trying to use protoc in PATH." 22 | protoc=protoc # try loading from PATH 23 | if !(hash protoc 2>/dev/null) ; then 24 | errcho "Could not find protoc. Try running 'make install' or setting PROTOCPATH to your protoc bin file." 25 | exit 1; 26 | fi 27 | fi 28 | 29 | echo "using protoc from $protoc" 30 | 31 | protobuf_directories=$(find . -not -path ./.git/ -not -path ./.idea/ -type d -name "pb") 32 | grpc_gateway_path=$(go list -m -f '{{.Dir}}' github.com/grpc-ecosystem/grpc-gateway) 33 | googleapis_path="$grpc_gateway_path/third_party/googleapis" 34 | 35 | while read -r p; do 36 | echo "Generating protobuf for $p" 37 | compile -I. -I$googleapis_path --go_out=plugins=grpc:. $p/*.proto 38 | done <<< "$protobuf_directories" 39 | 40 | compile -I. -I$googleapis_path --grpc-gateway_out=logtostderr=true:. api/pb/api.proto 41 | compile -I. -I$googleapis_path --swagger_out=logtostderr=true:. api/pb/api.proto 42 | -------------------------------------------------------------------------------- /scripts/install-protobuf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Detect OS and architecture 3 | if [[ $(uname -s) == "Linux" ]]; then 4 | os="linux"; 5 | elif [[ $(uname -s) == "Darwin" ]]; then # MacOS 6 | os="osx"; 7 | else 8 | echo "unsupported OS, protoc not installed" 9 | exit 1; 10 | fi 11 | arch=$(uname -m) 12 | protoc_url=https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protoc-3.6.1-${os}-${arch}.zip 13 | 14 | # Make sure you grab the latest version 15 | echo "fetching ${protoc_url}" 16 | curl -L -o "protoc.zip" ${protoc_url} 17 | 18 | # Unzip 19 | echo "extracting..." 20 | unzip -u protoc.zip -d protoc3 21 | 22 | 23 | # create local devtools dir. 24 | mkdir devtools 25 | mkdir devtools/bin 26 | mkdir devtools/include 27 | 28 | echo "moving bin/protoc to ./devtools/bin/protoc" 29 | mv protoc3/bin/* devtools/bin/ 30 | 31 | # Move protoc3/include to /usr/local/include/ 32 | echo "syncing include to ./devtools/include" 33 | rsync -r protoc3/include/ devtools/include/ 34 | 35 | # Cleanup 36 | echo "cleaning up..." 37 | rm protoc.zip 38 | rm -rf protoc3 39 | echo "done" 40 | -------------------------------------------------------------------------------- /scripts/verify-protoc-gen-go.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Ensure protoc-gen-go is installed 3 | errcho() { 4 | RED='\033[0;31m' 5 | NC='\033[0m' # no color 6 | echo -e "${RED}$1${NC}" 7 | } 8 | 9 | if ! (hash protoc-gen-go 2>/dev/null) ; then 10 | errcho "Could not find protoc-gen-go. If you've run \`make install\`, ensure that \$GOPATH/bin is in your shell's \$PATH." 11 | exit 1; 12 | fi 13 | -------------------------------------------------------------------------------- /setup_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | ./scripts/check-go-version.sh 3 | ./scripts/install-protobuf.sh 4 | 5 | go mod download 6 | protobuf_path=$(go list -m -f '{{.Dir}}' github.com/golang/protobuf) 7 | echo "installing protoc-gen-go..." 8 | go install $protobuf_path/protoc-gen-go 9 | 10 | echo "Done" 11 | -------------------------------------------------------------------------------- /signing/signer.go: -------------------------------------------------------------------------------- 1 | package signing 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | "github.com/libonomy/ed25519" 8 | "github.com/libonomy/libonomy-p2p/common/util" 9 | "github.com/libonomy/libonomy-p2p/log" 10 | ) 11 | 12 | const shortStringSize = 5 13 | 14 | // PublicKey is the type describing a public key 15 | type PublicKey struct { 16 | pub []byte 17 | } 18 | 19 | // NewPublicKey constructs a new public key instance from a byte array 20 | func NewPublicKey(pub []byte) *PublicKey { 21 | return &PublicKey{pub} 22 | } 23 | 24 | // Bytes returns the public key as byte array 25 | func (p *PublicKey) Bytes() []byte { 26 | return p.pub 27 | } 28 | 29 | // String returns the public key as an hex representation string 30 | func (p *PublicKey) String() string { 31 | return util.Bytes2Hex(p.Bytes()) 32 | } 33 | 34 | // ShortString returns a representative sub string 35 | func (p *PublicKey) ShortString() string { 36 | s := p.String() 37 | if len(s) < shortStringSize { 38 | return s 39 | } 40 | 41 | return s[:shortStringSize] 42 | } 43 | 44 | // Equals returns true iff the public keys are equal 45 | func (p *PublicKey) Equals(o *PublicKey) bool { 46 | return bytes.Equal(p.Bytes(), o.Bytes()) 47 | } 48 | 49 | // EdSigner represents an ED25519 signer 50 | type EdSigner struct { 51 | privKey ed25519.PrivateKey // the pub & private key 52 | pubKey ed25519.PublicKey // only the pub key part 53 | } 54 | 55 | // NewEdSignerFromBuffer builds a signer from a private key as byte buffer 56 | func NewEdSignerFromBuffer(buff []byte) (*EdSigner, error) { 57 | if len(buff) != ed25519.PrivateKeySize { 58 | log.Error("Could not create EdSigner from the provided buffer: buffer too small") 59 | return nil, errors.New("buffer too small") 60 | } 61 | 62 | sgn := &EdSigner{privKey: buff, pubKey: buff[32:]} 63 | keyPair := ed25519.NewKeyFromSeed(sgn.privKey[:32]) 64 | if !bytes.Equal(keyPair[32:], sgn.pubKey) { 65 | log.Error("Public key and private key does not match") 66 | return nil, errors.New("private and public does not match") 67 | } 68 | 69 | return sgn, nil 70 | } 71 | 72 | // NewEdSigner returns an auto-generated ed signer 73 | func NewEdSigner() *EdSigner { 74 | pub, priv, err := ed25519.GenerateKey(nil) 75 | 76 | if err != nil { 77 | log.Panic("Could not generate key pair err=%v", err) 78 | } 79 | 80 | return &EdSigner{privKey: priv, pubKey: pub} 81 | } 82 | 83 | // Sign signs the provided message 84 | func (es *EdSigner) Sign(m []byte) []byte { 85 | return ed25519.Sign2(es.privKey, m) 86 | } 87 | 88 | // Verify verifies the provided message 89 | func Verify(pubkey *PublicKey, message []byte, sign []byte) bool { 90 | return ed25519.Verify2(ed25519.PublicKey(pubkey.Bytes()), message, sign) 91 | } 92 | 93 | // PublicKey returns the public key of the signer 94 | func (es *EdSigner) PublicKey() *PublicKey { 95 | return NewPublicKey(es.pubKey) 96 | } 97 | 98 | // ToBuffer returns the private key as a byte buffer 99 | func (es *EdSigner) ToBuffer() []byte { 100 | buff := make([]byte, len(es.privKey)) 101 | copy(buff, es.privKey) 102 | 103 | return buff 104 | } 105 | -------------------------------------------------------------------------------- /signing/signer_test.go: -------------------------------------------------------------------------------- 1 | package signing 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/libonomy/ed25519" 7 | "github.com/libonomy/libonomy-p2p/log" 8 | "github.com/libonomy/libonomy-p2p/rand" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestNewEdSignerFromBuffer(t *testing.T) { 13 | b := []byte{1, 2, 3} 14 | _, err := NewEdSignerFromBuffer(b) 15 | assert.NotNil(t, err) 16 | assert.Equal(t, "buffer too small", err.Error()) 17 | b = make([]byte, 64) 18 | _, err = NewEdSignerFromBuffer(b) 19 | assert.NotNil(t, err) 20 | assert.Equal(t, "private and public does not match", err.Error()) 21 | } 22 | 23 | func TestEdSigner_Sign(t *testing.T) { 24 | ed := NewEdSigner() 25 | m := make([]byte, 4) 26 | rand.Read(m) 27 | sig := ed.Sign(m) 28 | assert.True(t, ed25519.Verify2(ed25519.PublicKey(ed.PublicKey().Bytes()), m, sig)) 29 | } 30 | 31 | func TestNewEdSigner(t *testing.T) { 32 | ed := NewEdSigner() 33 | assert.Equal(t, []byte(ed.pubKey), []byte(ed.privKey[32:])) 34 | log.Info("pub: %v priv: %x", ed.PublicKey().String(), ed.privKey) 35 | } 36 | 37 | func TestEdSigner_ToBuffer(t *testing.T) { 38 | ed := NewEdSigner() 39 | buff := ed.ToBuffer() 40 | ed2, err := NewEdSignerFromBuffer(buff) 41 | assert.Nil(t, err) 42 | assert.Equal(t, ed.privKey, ed2.privKey) 43 | assert.Equal(t, ed.pubKey, ed2.pubKey) 44 | } 45 | 46 | func TestPublicKey_ShortString(t *testing.T) { 47 | pub := NewPublicKey([]byte{1, 2, 3}) 48 | assert.Equal(t, "010203", pub.String()) 49 | assert.Equal(t, "01020", pub.ShortString()) 50 | 51 | pub = NewPublicKey([]byte{1, 2}) 52 | assert.Equal(t, pub.String(), pub.ShortString()) 53 | } 54 | -------------------------------------------------------------------------------- /sim_config.toml: -------------------------------------------------------------------------------- 1 | [main] 2 | 3 | sync-request-timeout = "60000" 4 | 5 | [p2p] 6 | network-id = "1" 7 | max-inbound = "32" 8 | 9 | [p2p.swarm] 10 | bootstrap = true 11 | randcon = "8" 12 | bootnodes = ["libonomy://BqXxyn79TGXGi45oKhUJhVzmkEHrhid4u7WZZuBgJBVK@185.189.48.150:7152"] 13 | -------------------------------------------------------------------------------- /timesync/clock.go: -------------------------------------------------------------------------------- 1 | package timesync 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/libonomy/libonomy-p2p/log" 8 | ) 9 | 10 | // Clock defines the functionality needed from any clock type 11 | type Clock interface { 12 | Now() time.Time 13 | } 14 | 15 | // RealClock is the struct wrapping a local time struct 16 | type RealClock struct{} 17 | 18 | // Now returns the current local time 19 | func (RealClock) Now() time.Time { 20 | return time.Now() 21 | } 22 | 23 | // TimeClock is the struct holding a real clock 24 | type TimeClock struct { 25 | *Ticker 26 | tickInterval time.Duration 27 | startEpoch time.Time 28 | stop chan struct{} 29 | once sync.Once 30 | log log.Log 31 | } 32 | 33 | // NewClock return TimeClock struct that notifies tickInterval has passed 34 | func NewClock(c Clock, tickInterval time.Duration, genesisTime time.Time, logger log.Log) *TimeClock { 35 | if tickInterval == 0 { 36 | logger.Panic("could not create new clock: bad configuration: tick interval is zero") 37 | } 38 | 39 | t := &TimeClock{ 40 | Ticker: NewTicker(c, LayerConv{duration: tickInterval, genesis: genesisTime}), 41 | tickInterval: tickInterval, 42 | startEpoch: genesisTime, 43 | stop: make(chan struct{}), 44 | once: sync.Once{}, 45 | log: logger, 46 | } 47 | go t.startClock() 48 | return t 49 | } 50 | 51 | func (t *TimeClock) startClock() { 52 | t.log.Info("starting global clock now=%v genesis=%v", t.clock.Now(), t.startEpoch) 53 | 54 | for { 55 | currLayer := t.Ticker.TimeToLayer(t.clock.Now()) // get current layer 56 | nextTickTime := t.Ticker.LayerToTime(currLayer + 1) // get next tick time for the next layer 57 | diff := nextTickTime.Sub(t.clock.Now()) 58 | tmr := time.NewTimer(diff) 59 | t.log.With().Info("global clock going to sleep before next layer", log.String("diff", diff.String()), log.Uint64("next_layer", uint64(currLayer))) 60 | select { 61 | case <-tmr.C: 62 | // notify subscribers 63 | if missed, err := t.Notify(); err != nil { 64 | t.log.With().Error("could not notify subscribers", log.Err(err), log.Int("missed", missed)) 65 | } 66 | case <-t.stop: 67 | tmr.Stop() 68 | return 69 | } 70 | } 71 | } 72 | 73 | // GetGenesisTime returns at which time this clock has started (used to calculate current tick) 74 | func (t *TimeClock) GetGenesisTime() time.Time { 75 | return t.startEpoch 76 | } 77 | 78 | // GetInterval returns the time interval between clock ticks 79 | func (t *TimeClock) GetInterval() time.Duration { 80 | return t.tickInterval 81 | } 82 | 83 | // Close closes the clock ticker 84 | func (t *TimeClock) Close() { 85 | t.once.Do(func() { 86 | close(t.stop) 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /timesync/clock_test.go: -------------------------------------------------------------------------------- 1 | package timesync 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/libonomy/libonomy-p2p/common/types" 9 | "github.com/libonomy/libonomy-p2p/log" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | const d50milli = 50 * time.Millisecond 15 | 16 | func TestClock_StartClock(t *testing.T) { 17 | tick := d50milli 18 | c := RealClock{} 19 | ts := NewClock(c, tick, c.Now(), log.NewDefault(t.Name())) 20 | tk := ts.Subscribe() 21 | then := time.Now() 22 | ts.StartNotifying() 23 | 24 | select { 25 | case <-tk: 26 | dur := time.Now().Sub(then) 27 | assert.True(t, tick <= dur) 28 | } 29 | ts.Close() 30 | } 31 | 32 | func TestClock_StartClock_BeforeEpoch(t *testing.T) { 33 | tick := d50milli 34 | tmr := RealClock{} 35 | 36 | waitTime := 2 * d50milli 37 | ts := NewClock(tmr, tick, tmr.Now().Add(2*d50milli), log.NewDefault(t.Name())) 38 | tk := ts.Subscribe() 39 | then := time.Now() 40 | ts.StartNotifying() 41 | 42 | fmt.Println(waitTime) 43 | select { 44 | case <-tk: 45 | dur := time.Now().Sub(then) 46 | fmt.Println(dur) 47 | assert.True(t, waitTime < dur) 48 | } 49 | ts.Close() 50 | } 51 | 52 | func TestClock_TickFutureGenesis(t *testing.T) { 53 | tmr := &RealClock{} 54 | ticker := NewClock(tmr, d50milli, tmr.Now().Add(2*d50milli), log.NewDefault(t.Name())) 55 | assert.Equal(t, types.LayerID(0), ticker.lastTickedLayer) // check assumption that we are on genesis = 0 56 | sub := ticker.Subscribe() 57 | ticker.StartNotifying() 58 | x := <-sub 59 | assert.Equal(t, types.LayerID(1), x) 60 | x = <-sub 61 | assert.Equal(t, types.LayerID(2), x) 62 | } 63 | 64 | func TestClock_TickPastGenesis(t *testing.T) { 65 | tmr := &RealClock{} 66 | ticker := NewClock(tmr, 2*d50milli, tmr.Now().Add(-7*d50milli), log.NewDefault(t.Name())) 67 | expectedTimeToTick := d50milli // tickInterval is 100ms and the genesis tick (layer 1) was 350ms ago 68 | /* 69 | T-350 -> layer 1 70 | T-250 -> layer 2 71 | T-150 -> layer 3 72 | T-50 -> layer 4 73 | T+50 -> layer 5 74 | */ 75 | sub := ticker.Subscribe() 76 | ticker.StartNotifying() 77 | start := time.Now() 78 | x := <-sub 79 | duration := time.Since(start) 80 | assert.Equal(t, types.LayerID(5), x) 81 | assert.True(t, duration >= expectedTimeToTick, "tick happened too soon (%v)", duration) 82 | assert.True(t, duration < expectedTimeToTick+d50milli, "tick happened more than 50ms too late (%v)", duration) 83 | } 84 | 85 | func TestClock_NewClock(t *testing.T) { 86 | r := require.New(t) 87 | tmr := &RealClock{} 88 | ticker := NewClock(tmr, 100*time.Millisecond, tmr.Now().Add(-190*time.Millisecond), log.NewDefault(t.Name())) 89 | r.Equal(types.LayerID(2), ticker.lastTickedLayer) 90 | } 91 | 92 | func TestClock_CloseTwice(t *testing.T) { 93 | ld := d50milli 94 | clock := NewClock(RealClock{}, ld, time.Now(), log.NewDefault(t.Name())) 95 | clock.StartNotifying() 96 | clock.Close() 97 | clock.Close() 98 | } 99 | -------------------------------------------------------------------------------- /timesync/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/libonomy/libonomy-p2p/log" 7 | ) 8 | 9 | // ConfigValues specifies default values for node config params. 10 | var ( 11 | TimeConfigValues = DefaultConfig() 12 | ) 13 | 14 | // TimeConfig specifies the timesync params for ntp. 15 | type TimeConfig struct { 16 | MaxAllowedDrift time.Duration `mapstructure:"max-allowed-time-drift"` 17 | NtpQueries int `mapstructure:"ntp-queries"` 18 | DefaultTimeoutLatency time.Duration `mapstructure:"default-timeout-latency"` 19 | RefreshNtpInterval time.Duration `mapstructure:"refresh-ntp-interval"` 20 | } 21 | 22 | //todo: this is a duplicate function found also in p2p config 23 | func duration(duration string) (dur time.Duration) { 24 | dur, err := time.ParseDuration(duration) 25 | if err != nil { 26 | log.Error("Could not parse duration string returning 0, error:", err) 27 | } 28 | return dur 29 | } 30 | 31 | // DefaultConfig defines the default tymesync configuration 32 | func DefaultConfig() TimeConfig { 33 | 34 | // TimeConfigValues defines default values for all time and ntp related params. 35 | var TimeConfigValues = TimeConfig{ 36 | MaxAllowedDrift: duration("10s"), 37 | NtpQueries: 5, 38 | DefaultTimeoutLatency: duration("10s"), 39 | RefreshNtpInterval: duration("30m"), 40 | } 41 | 42 | return TimeConfigValues 43 | } 44 | -------------------------------------------------------------------------------- /timesync/converter.go: -------------------------------------------------------------------------------- 1 | package timesync 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/libonomy/libonomy-p2p/common/types" 7 | ) 8 | 9 | // LayerConv is a converter between time to layer ID struct 10 | type LayerConv struct { 11 | duration time.Duration // the layer duration, assumed to be > 0 12 | genesis time.Time // the genesis time 13 | } 14 | 15 | // TimeToLayer returns the layer of the provided time 16 | func (lc LayerConv) TimeToLayer(t time.Time) types.LayerID { 17 | if t.Before(lc.genesis) { // the genesis is in the future 18 | return 0 19 | } 20 | return types.LayerID((t.Sub(lc.genesis) / lc.duration) + 1) 21 | } 22 | 23 | // LayerToTime returns the time of the provided layer 24 | func (lc LayerConv) LayerToTime(id types.LayerID) time.Time { 25 | if id == 0 { // layer 1 is genesis, consider 0 also as genesis 26 | return lc.genesis 27 | } 28 | return lc.genesis.Add(time.Duration(id-1) * lc.duration) 29 | } 30 | -------------------------------------------------------------------------------- /timesync/converter_test.go: -------------------------------------------------------------------------------- 1 | package timesync 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/libonomy/libonomy-p2p/common/types" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func getTime() time.Time { 12 | layout := "2006-01-02T15:04:05.000Z" 13 | curr := "2018-11-12T11:45:26.000Z" 14 | tm, _ := time.Parse(layout, curr) 15 | 16 | return tm 17 | } 18 | 19 | func TestLayerConv_LayerToTime(t *testing.T) { 20 | r := require.New(t) 21 | tm := getTime() 22 | lc := LayerConv{5 * time.Second, tm} 23 | r.Equal(tm.Add(10*time.Second), lc.LayerToTime(3)) 24 | r.Equal(lc.genesis, lc.LayerToTime(0)) 25 | } 26 | 27 | func TestLayerConv_TimeToLayer(t *testing.T) { 28 | r := require.New(t) 29 | tm := getTime() 30 | lc := &LayerConv{5 * time.Second, tm} 31 | r.Equal(types.LayerID(2), lc.TimeToLayer(tm.Add(9*time.Second))) 32 | r.Equal(types.LayerID(3), lc.TimeToLayer(tm.Add(10*time.Second))) 33 | r.Equal(types.LayerID(3), lc.TimeToLayer(tm.Add(12*time.Second))) 34 | 35 | lc.genesis = tm.Add(2 * time.Second) 36 | r.Equal(types.LayerID(0), lc.TimeToLayer(tm)) 37 | } 38 | 39 | func TestTicker_pingPong(t *testing.T) { 40 | r := require.New(t) 41 | tm := getTime() 42 | lc := LayerConv{5 * time.Second, tm} 43 | ttl := lc.TimeToLayer(tm.Add(9 * time.Second)) 44 | r.Equal(types.LayerID(2), lc.TimeToLayer(lc.LayerToTime(ttl))) 45 | } 46 | -------------------------------------------------------------------------------- /timesync/ntp_test.go: -------------------------------------------------------------------------------- 1 | package timesync 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/libonomy/libonomy-p2p/crypto" 11 | "github.com/libonomy/libonomy-p2p/timesync/config" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestCheckSystemClockDrift(t *testing.T) { 16 | drift, err := CheckSystemClockDrift() 17 | t.Log("Checking system clock drift from NTP") 18 | if drift < -config.TimeConfigValues.MaxAllowedDrift || drift > config.TimeConfigValues.MaxAllowedDrift { 19 | assert.NotNil(t, err, fmt.Sprintf("Didn't get that drift exceedes. %s", err)) 20 | } else { 21 | assert.Nil(t, err, fmt.Sprintf("Drift is ok")) 22 | } 23 | } 24 | 25 | func TestNtpPacket_Time(t *testing.T) { 26 | ntpDate := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC) 27 | since := time.Since(ntpDate) 28 | sysTime := time.Now().UTC() 29 | p := NtpPacket{ 30 | TxTimeSec: uint32(since.Seconds()), 31 | TxTimeFrac: uint32(since.Nanoseconds()), 32 | } 33 | 34 | // This will fail when our conversion doesn't function right because ntp count 70 years more than unix 35 | assert.Equal(t, p.Time().Year(), sysTime.Year(), "Converted and system time should be the same year") 36 | } 37 | 38 | func generateRandomDurations() (sortableDurations, error) { 39 | sd := sortableDurations{} 40 | rand := 0 41 | for rand < 10 { 42 | rand = int(crypto.GetRandomUInt32(50)) 43 | } 44 | for i := 0; i < rand; i++ { 45 | randomSecs := crypto.GetRandomUInt32(100) 46 | sd = append(sd, time.Duration(time.Second*time.Duration(randomSecs))) 47 | } 48 | return sd, nil 49 | } 50 | 51 | func TestSortableDurations_RemoveExtremes(t *testing.T) { 52 | // Generate slice of len (random) of random durations (with max 100 seconds) 53 | durations, err := generateRandomDurations() 54 | if err != nil { 55 | t.Error("could'nt create durations") 56 | } 57 | baseLen := len(durations) 58 | durations.RemoveExtremes() 59 | assert.True(t, len(durations) == baseLen-2, "Slice length should be reduced by two ") 60 | } 61 | 62 | // TODO : add more tests for rest of the functions 63 | 64 | func TestCheckMessageDrift(t *testing.T) { 65 | reqtime := time.Now() 66 | for i := 0; reqtime.Add(time.Second * time.Duration(i)).Before(reqtime.Add(MaxAllowedMessageDrift)); i++ { 67 | fu := reqtime.Add(time.Second * time.Duration(i)) 68 | pa := reqtime.Add(-(time.Second * time.Duration(i))) 69 | 70 | assert.True(t, CheckMessageDrift(fu.Unix())) 71 | assert.True(t, CheckMessageDrift(pa.Unix())) 72 | } 73 | 74 | msgtime := time.Now().Add(-(MaxAllowedMessageDrift + time.Minute)) 75 | // check message that was sent too far in the past 76 | on := CheckMessageDrift(msgtime.Unix()) 77 | assert.False(t, on) 78 | 79 | msgtime = time.Now().Add(MaxAllowedMessageDrift + time.Minute) 80 | // check message that was sent too far in the future 81 | on = CheckMessageDrift(msgtime.Unix()) 82 | assert.False(t, on) 83 | } 84 | 85 | func MockntpRequest(server string, rq *NtpPacket) (time.Time, time.Duration, *NtpPacket, error) { 86 | return time.Now(), 0, &NtpPacket{}, nil 87 | } 88 | 89 | func Test_queryNtpServerZeroTime(t *testing.T) { 90 | ntpFunc = MockntpRequest 91 | defer func() { 92 | ntpFunc = ntpRequest 93 | }() 94 | 95 | d, err := queryNtpServer("mock") 96 | 97 | require.Error(t, err) 98 | require.Equal(t, d, zeroDuration) 99 | 100 | } 101 | --------------------------------------------------------------------------------