├── .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 | [](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 |
--------------------------------------------------------------------------------