Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 276
P2P bootstrapping#14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
f5ab637504f90fd3d534ea58b55b0702753ca96412a0701dc229368bbb1da59File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package config | ||
| type NodeConfig struct { | ||
| P2P P2PConfig | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package config | ||
| type P2PConfig struct { | ||
| ListenAddress string // Address to listen for incoming connections | ||
| Seeds string // Comma separated list of seed nodes to connect to | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package conv | ||
| import ( | ||
| tmcfg "github.com/lazyledger/lazyledger-core/config" | ||
| "github.com/lazyledger/optimint/config" | ||
| ) | ||
| // GetNodeConfig translates Tendermint's configuration into Optimint configuration. | ||
| // | ||
| // This method only translates configuration, and doesn't verify it. If some option is missing in Tendermint's | ||
| // config, it's skipped during translation. | ||
| func GetNodeConfig(cfg *tmcfg.Config) config.NodeConfig { | ||
| nodeConf := config.NodeConfig{} | ||
| if cfg != nil { | ||
| if cfg.P2P != nil { | ||
| nodeConf.P2P.ListenAddress = cfg.P2P.ListenAddress | ||
| nodeConf.P2P.Seeds = cfg.P2P.Seeds | ||
| } | ||
| } | ||
| return nodeConf | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package conv | ||
| import ( | ||
| "testing" | ||
| "github.com/stretchr/testify/assert" | ||
| tmcfg "github.com/lazyledger/lazyledger-core/config" | ||
| "github.com/lazyledger/optimint/config" | ||
| ) | ||
| func TestGetNodeConfig(t *testing.T) { | ||
| t.Parallel() | ||
| cases := []struct { | ||
| name string | ||
| input *tmcfg.Config | ||
| expected config.NodeConfig | ||
| }{ | ||
| {"empty", nil, config.NodeConfig{}}, | ||
| {"Seeds", &tmcfg.Config{P2P: &tmcfg.P2PConfig{Seeds: "seeds"}}, config.NodeConfig{P2P: config.P2PConfig{Seeds: "seeds"}}}, | ||
| {"ListenAddress", &tmcfg.Config{P2P: &tmcfg.P2PConfig{ListenAddress: "127.0.0.1:7676"}}, config.NodeConfig{P2P: config.P2PConfig{ListenAddress: "127.0.0.1:7676"}}}, | ||
| } | ||
| for _, c := range cases { | ||
| t.Run(c.name, func(t *testing.T) { | ||
| actual := GetNodeConfig(c.input) | ||
| assert.Equal(t, c.expected, actual) | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package conv | ||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "github.com/lazyledger/lazyledger-core/p2p" | ||
| "github.com/libp2p/go-libp2p-core/crypto" | ||
| ) | ||
| var ErrNilKey = errors.New("key can't be nil") | ||
| var ErrUnsupportedKeyType = errors.New("unsupported key type") | ||
| func GetNodeKey(nodeKey *p2p.NodeKey) (crypto.PrivKey, error) { | ||
| if nodeKey == nil || nodeKey.PrivKey == nil { | ||
| return nil, ErrNilKey | ||
| } | ||
| switch nodeKey.PrivKey.Type() { | ||
| case "ed25519": | ||
| privKey, err := crypto.UnmarshalEd25519PrivateKey(nodeKey.PrivKey.Bytes()) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("node private key unmarshaling error: %w", err) | ||
| } | ||
| return privKey, nil | ||
| default: | ||
| return nil, ErrUnsupportedKeyType | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package conv | ||
| import ( | ||
| "testing" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| "github.com/lazyledger/lazyledger-core/crypto/secp256k1" | ||
| "github.com/lazyledger/lazyledger-core/p2p" | ||
| pb "github.com/libp2p/go-libp2p-core/crypto/pb" | ||
| ) | ||
| func TestGetNodeKey(t *testing.T) { | ||
| t.Parallel() | ||
| valid := p2p.GenNodeKey() | ||
| invalid := p2p.NodeKey{ | ||
| PrivKey: secp256k1.GenPrivKey(), | ||
| } | ||
| cases := []struct { | ||
| name string | ||
| input *p2p.NodeKey | ||
| expectedType pb.KeyType | ||
| err error | ||
| }{ | ||
| {"nil", nil, pb.KeyType(-1), ErrNilKey}, | ||
| {"empty", &p2p.NodeKey{}, pb.KeyType(-1), ErrNilKey}, | ||
| {"invalid", &invalid, pb.KeyType(-1), ErrUnsupportedKeyType}, | ||
| {"valid", &valid, pb.KeyType_Ed25519, nil}, | ||
| } | ||
| for _, c := range cases { | ||
| t.Run(c.name, func(t *testing.T) { | ||
| actual, err := GetNodeKey(c.input) | ||
| if c.err != nil { | ||
| assert.Nil(t, actual) | ||
| assert.Error(t, err) | ||
| assert.ErrorIs(t, c.err, err) | ||
| } else { | ||
| require.NotNil(t, actual) | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, c.expectedType, actual.Type()) | ||
| } | ||
| }) | ||
| } | ||
| } |
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package log | ||
| // Logger interface is compatible with Tendermint logger | ||
| type Logger interface { | ||
| Debug(msg string, keyvals ...interface{}) | ||
| Info(msg string, keyvals ...interface{}) | ||
| Error(msg string, keyvals ...interface{}) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| package p2p | ||
| import ( | ||
| "context" | ||
| "strings" | ||
| "time" | ||
| "github.com/libp2p/go-libp2p" | ||
| "github.com/libp2p/go-libp2p-core/crypto" | ||
| "github.com/libp2p/go-libp2p-core/host" | ||
| "github.com/libp2p/go-libp2p-core/peer" | ||
| "github.com/lazyledger/optimint/config" | ||
| "github.com/lazyledger/optimint/log" | ||
| "github.com/multiformats/go-multiaddr" | ||
| ) | ||
| // Client is a P2P client, implemented with libp2p | ||
| type Client struct { | ||
| conf config.P2PConfig | ||
| privKey crypto.PrivKey | ||
| host host.Host | ||
| logger log.Logger | ||
| } | ||
| // NewClient creates new Client object | ||
| // | ||
| // Basic checks on parameters are done, and default parameters are provided for unset-configuration | ||
| func NewClient(conf config.P2PConfig, privKey crypto.PrivKey, logger log.Logger) (*Client, error) { | ||
| if privKey == nil { | ||
| return nil, ErrNoPrivKey | ||
| } | ||
| if conf.ListenAddress == "" { | ||
| // TODO(tzdybal): extract const | ||
| conf.ListenAddress = "0.0.0.0:7676" | ||
| } | ||
| return &Client{ | ||
| conf: conf, | ||
| privKey: privKey, | ||
| logger: logger, | ||
| }, nil | ||
| } | ||
| func (c *Client) Start() error { | ||
| c.logger.Debug("Starting P2P client") | ||
| err := c.listen() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // start bootstrapping connections | ||
| err = c.bootstrap() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return nil | ||
| } | ||
| func (c *Client) listen() error { | ||
| // TODO(tzdybal): consider requiring listen address in multiaddress format | ||
| maddr, err := GetMultiAddr(c.conf.ListenAddress) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| //TODO(tzdybal): think about per-client context | ||
| host, err := libp2p.New(context.Background(), libp2p.ListenAddrs(maddr), libp2p.Identity(c.privKey)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| for _, a := range host.Addrs() { | ||
| c.logger.Info("listening on", "address", a) | ||
| } | ||
| c.host = host | ||
| return nil | ||
| } | ||
| func (c *Client) bootstrap() error { | ||
| if len(strings.TrimSpace(c.conf.Seeds)) == 0 { | ||
| c.logger.Info("no seed nodes - only listening for connections") | ||
| return nil | ||
| } | ||
| seeds := strings.Split(c.conf.Seeds, ",") | ||
| for _, s := range seeds { | ||
| maddr, err := GetMultiAddr(s) | ||
| if err != nil { | ||
| c.logger.Error("error while parsing seed node", "address", s, "error", err) | ||
| continue | ||
| } | ||
| c.logger.Debug("seed", "addr", maddr.String()) | ||
| // TODO(tzdybal): configuration param for connection timeout | ||
| ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) | ||
| defer cancel() | ||
| addrInfo, err := peer.AddrInfoFromP2pAddr(maddr) | ||
| if err != nil { | ||
| c.logger.Error("error while creating address info", "error", err) | ||
| continue | ||
| } | ||
| err = c.host.Connect(ctx, *addrInfo) | ||
| if err != nil { | ||
| c.logger.Error("error while connecting to seed node", "error", err) | ||
| continue | ||
| } | ||
| c.logger.Debug("connected to seed node", "address", s) | ||
| } | ||
| return nil | ||
| } | ||
| func GetMultiAddr(addr string) (multiaddr.Multiaddr, error) { | ||
| var err error | ||
| var p2pId multiaddr.Multiaddr | ||
| if at := strings.IndexRune(addr, '@'); at != -1 { | ||
| p2pId, err = multiaddr.NewMultiaddr("/p2p/" + addr[:at]) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| addr = addr[at+1:] | ||
| } | ||
| parts := strings.Split(addr, ":") | ||
| if len(parts) != 2 { | ||
| return nil, ErrInvalidAddress | ||
| } | ||
| maddr, err := multiaddr.NewMultiaddr("/ip4/" + parts[0] + "/tcp/" + parts[1]) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if p2pId != nil { | ||
| maddr = maddr.Encapsulate(p2pId) | ||
| } | ||
| return maddr, nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/p2p/Is that really a valid MultiAddr?NVM, it's valid. I need to go and read more about multiaddrs myslef.preferred over /ipfsis not a really helpful description of the protocol here: https://github.com/multiformats/multiaddr/blob/master/protocols.csvThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/p2pis something we can use without "registering" protocol, and it supports IDs - it's basically what we need, without reinventing the wheel.