2017-09-25 09:08:07 +01:00
|
|
|
// Copyright 2017 The go-ethereum Authors
|
|
|
|
// This file is part of the go-ethereum library.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package adapters
|
|
|
|
|
|
|
|
import (
|
2018-10-11 20:32:14 +02:00
|
|
|
"bytes"
|
2017-09-25 09:08:07 +01:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-03-22 13:17:59 +01:00
|
|
|
"log/slog"
|
2017-09-25 09:08:07 +01:00
|
|
|
"net"
|
2018-10-11 20:32:14 +02:00
|
|
|
"net/http"
|
2017-09-25 09:08:07 +01:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"os/signal"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
|
2023-10-28 00:14:43 +02:00
|
|
|
"github.com/ethereum/go-ethereum/internal/reexec"
|
2017-09-25 09:08:07 +01:00
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
|
|
"github.com/ethereum/go-ethereum/node"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p"
|
all: new p2p node representation (#17643)
Package p2p/enode provides a generalized representation of p2p nodes
which can contain arbitrary information in key/value pairs. It is also
the new home for the node database. The "v4" identity scheme is also
moved here from p2p/enr to remove the dependency on Ethereum crypto from
that package.
Record signature handling is changed significantly. The identity scheme
registry is removed and acceptable schemes must be passed to any method
that needs identity. This means records must now be validated explicitly
after decoding.
The enode API is designed to make signature handling easy and safe: most
APIs around the codebase work with enode.Node, which is a wrapper around
a valid record. Going from enr.Record to enode.Node requires a valid
signature.
* p2p/discover: port to p2p/enode
This ports the discovery code to the new node representation in
p2p/enode. The wire protocol is unchanged, this can be considered a
refactoring change. The Kademlia table can now deal with nodes using an
arbitrary identity scheme. This requires a few incompatible API changes:
- Table.Lookup is not available anymore. It used to take a public key
as argument because v4 protocol requires one. Its replacement is
LookupRandom.
- Table.Resolve takes *enode.Node instead of NodeID. This is also for
v4 protocol compatibility because nodes cannot be looked up by ID
alone.
- Types Node and NodeID are gone. Further commits in the series will be
fixes all over the the codebase to deal with those removals.
* p2p: port to p2p/enode and discovery changes
This adapts package p2p to the changes in p2p/discover. All uses of
discover.Node and discover.NodeID are replaced by their equivalents from
p2p/enode.
New API is added to retrieve the enode.Node instance of a peer. The
behavior of Server.Self with discovery disabled is improved. It now
tries much harder to report a working IP address, falling back to
127.0.0.1 if no suitable address can be determined through other means.
These changes were needed for tests of other packages later in the
series.
* p2p/simulations, p2p/testing: port to p2p/enode
No surprises here, mostly replacements of discover.Node, discover.NodeID
with their new equivalents. The 'interesting' API changes are:
- testing.ProtocolSession tracks complete nodes, not just their IDs.
- adapters.NodeConfig has a new method to create a complete node.
These changes were needed to make swarm tests work.
Note that the NodeID change makes the code incompatible with old
simulation snapshots.
* whisper/whisperv5, whisper/whisperv6: port to p2p/enode
This port was easy because whisper uses []byte for node IDs and
URL strings in the API.
* eth: port to p2p/enode
Again, easy to port because eth uses strings for node IDs and doesn't
care about node information in any way.
* les: port to p2p/enode
Apart from replacing discover.NodeID with enode.ID, most changes are in
the server pool code. It now deals with complete nodes instead
of (Pubkey, IP, Port) triples. The database format is unchanged for now,
but we should probably change it to use the node database later.
* node: port to p2p/enode
This change simply replaces discover.Node and discover.NodeID with their
new equivalents.
* swarm/network: port to p2p/enode
Swarm has its own node address representation, BzzAddr, containing both
an overlay address (the hash of a secp256k1 public key) and an underlay
address (enode:// URL).
There are no changes to the BzzAddr format in this commit, but certain
operations such as creating a BzzAddr from a node ID are now impossible
because node IDs aren't public keys anymore.
Most swarm-related changes in the series remove uses of
NewAddrFromNodeID, replacing it with NewAddr which takes a complete node
as argument. ToOverlayAddr is removed because we can just use the node
ID directly.
2018-09-25 00:59:00 +02:00
|
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
2017-09-25 09:08:07 +01:00
|
|
|
"github.com/ethereum/go-ethereum/rpc"
|
2019-11-18 09:40:59 +01:00
|
|
|
"github.com/gorilla/websocket"
|
2017-09-25 09:08:07 +01:00
|
|
|
)
|
|
|
|
|
2018-10-11 20:32:14 +02:00
|
|
|
func init() {
|
|
|
|
// Register a reexec function to start a simulation node when the current binary is
|
2019-01-06 11:58:57 +01:00
|
|
|
// executed as "p2p-node" (rather than whatever the main() function would normally do).
|
2018-10-11 20:32:14 +02:00
|
|
|
reexec.Register("p2p-node", execP2PNode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExecAdapter is a NodeAdapter which runs simulation nodes by executing the current binary
|
|
|
|
// as a child process.
|
2017-09-25 09:08:07 +01:00
|
|
|
type ExecAdapter struct {
|
|
|
|
// BaseDir is the directory under which the data directories for each
|
|
|
|
// simulation node are created.
|
|
|
|
BaseDir string
|
|
|
|
|
all: new p2p node representation (#17643)
Package p2p/enode provides a generalized representation of p2p nodes
which can contain arbitrary information in key/value pairs. It is also
the new home for the node database. The "v4" identity scheme is also
moved here from p2p/enr to remove the dependency on Ethereum crypto from
that package.
Record signature handling is changed significantly. The identity scheme
registry is removed and acceptable schemes must be passed to any method
that needs identity. This means records must now be validated explicitly
after decoding.
The enode API is designed to make signature handling easy and safe: most
APIs around the codebase work with enode.Node, which is a wrapper around
a valid record. Going from enr.Record to enode.Node requires a valid
signature.
* p2p/discover: port to p2p/enode
This ports the discovery code to the new node representation in
p2p/enode. The wire protocol is unchanged, this can be considered a
refactoring change. The Kademlia table can now deal with nodes using an
arbitrary identity scheme. This requires a few incompatible API changes:
- Table.Lookup is not available anymore. It used to take a public key
as argument because v4 protocol requires one. Its replacement is
LookupRandom.
- Table.Resolve takes *enode.Node instead of NodeID. This is also for
v4 protocol compatibility because nodes cannot be looked up by ID
alone.
- Types Node and NodeID are gone. Further commits in the series will be
fixes all over the the codebase to deal with those removals.
* p2p: port to p2p/enode and discovery changes
This adapts package p2p to the changes in p2p/discover. All uses of
discover.Node and discover.NodeID are replaced by their equivalents from
p2p/enode.
New API is added to retrieve the enode.Node instance of a peer. The
behavior of Server.Self with discovery disabled is improved. It now
tries much harder to report a working IP address, falling back to
127.0.0.1 if no suitable address can be determined through other means.
These changes were needed for tests of other packages later in the
series.
* p2p/simulations, p2p/testing: port to p2p/enode
No surprises here, mostly replacements of discover.Node, discover.NodeID
with their new equivalents. The 'interesting' API changes are:
- testing.ProtocolSession tracks complete nodes, not just their IDs.
- adapters.NodeConfig has a new method to create a complete node.
These changes were needed to make swarm tests work.
Note that the NodeID change makes the code incompatible with old
simulation snapshots.
* whisper/whisperv5, whisper/whisperv6: port to p2p/enode
This port was easy because whisper uses []byte for node IDs and
URL strings in the API.
* eth: port to p2p/enode
Again, easy to port because eth uses strings for node IDs and doesn't
care about node information in any way.
* les: port to p2p/enode
Apart from replacing discover.NodeID with enode.ID, most changes are in
the server pool code. It now deals with complete nodes instead
of (Pubkey, IP, Port) triples. The database format is unchanged for now,
but we should probably change it to use the node database later.
* node: port to p2p/enode
This change simply replaces discover.Node and discover.NodeID with their
new equivalents.
* swarm/network: port to p2p/enode
Swarm has its own node address representation, BzzAddr, containing both
an overlay address (the hash of a secp256k1 public key) and an underlay
address (enode:// URL).
There are no changes to the BzzAddr format in this commit, but certain
operations such as creating a BzzAddr from a node ID are now impossible
because node IDs aren't public keys anymore.
Most swarm-related changes in the series remove uses of
NewAddrFromNodeID, replacing it with NewAddr which takes a complete node
as argument. ToOverlayAddr is removed because we can just use the node
ID directly.
2018-09-25 00:59:00 +02:00
|
|
|
nodes map[enode.ID]*ExecNode
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewExecAdapter returns an ExecAdapter which stores node data in
|
|
|
|
// subdirectories of the given base directory
|
|
|
|
func NewExecAdapter(baseDir string) *ExecAdapter {
|
|
|
|
return &ExecAdapter{
|
|
|
|
BaseDir: baseDir,
|
all: new p2p node representation (#17643)
Package p2p/enode provides a generalized representation of p2p nodes
which can contain arbitrary information in key/value pairs. It is also
the new home for the node database. The "v4" identity scheme is also
moved here from p2p/enr to remove the dependency on Ethereum crypto from
that package.
Record signature handling is changed significantly. The identity scheme
registry is removed and acceptable schemes must be passed to any method
that needs identity. This means records must now be validated explicitly
after decoding.
The enode API is designed to make signature handling easy and safe: most
APIs around the codebase work with enode.Node, which is a wrapper around
a valid record. Going from enr.Record to enode.Node requires a valid
signature.
* p2p/discover: port to p2p/enode
This ports the discovery code to the new node representation in
p2p/enode. The wire protocol is unchanged, this can be considered a
refactoring change. The Kademlia table can now deal with nodes using an
arbitrary identity scheme. This requires a few incompatible API changes:
- Table.Lookup is not available anymore. It used to take a public key
as argument because v4 protocol requires one. Its replacement is
LookupRandom.
- Table.Resolve takes *enode.Node instead of NodeID. This is also for
v4 protocol compatibility because nodes cannot be looked up by ID
alone.
- Types Node and NodeID are gone. Further commits in the series will be
fixes all over the the codebase to deal with those removals.
* p2p: port to p2p/enode and discovery changes
This adapts package p2p to the changes in p2p/discover. All uses of
discover.Node and discover.NodeID are replaced by their equivalents from
p2p/enode.
New API is added to retrieve the enode.Node instance of a peer. The
behavior of Server.Self with discovery disabled is improved. It now
tries much harder to report a working IP address, falling back to
127.0.0.1 if no suitable address can be determined through other means.
These changes were needed for tests of other packages later in the
series.
* p2p/simulations, p2p/testing: port to p2p/enode
No surprises here, mostly replacements of discover.Node, discover.NodeID
with their new equivalents. The 'interesting' API changes are:
- testing.ProtocolSession tracks complete nodes, not just their IDs.
- adapters.NodeConfig has a new method to create a complete node.
These changes were needed to make swarm tests work.
Note that the NodeID change makes the code incompatible with old
simulation snapshots.
* whisper/whisperv5, whisper/whisperv6: port to p2p/enode
This port was easy because whisper uses []byte for node IDs and
URL strings in the API.
* eth: port to p2p/enode
Again, easy to port because eth uses strings for node IDs and doesn't
care about node information in any way.
* les: port to p2p/enode
Apart from replacing discover.NodeID with enode.ID, most changes are in
the server pool code. It now deals with complete nodes instead
of (Pubkey, IP, Port) triples. The database format is unchanged for now,
but we should probably change it to use the node database later.
* node: port to p2p/enode
This change simply replaces discover.Node and discover.NodeID with their
new equivalents.
* swarm/network: port to p2p/enode
Swarm has its own node address representation, BzzAddr, containing both
an overlay address (the hash of a secp256k1 public key) and an underlay
address (enode:// URL).
There are no changes to the BzzAddr format in this commit, but certain
operations such as creating a BzzAddr from a node ID are now impossible
because node IDs aren't public keys anymore.
Most swarm-related changes in the series remove uses of
NewAddrFromNodeID, replacing it with NewAddr which takes a complete node
as argument. ToOverlayAddr is removed because we can just use the node
ID directly.
2018-09-25 00:59:00 +02:00
|
|
|
nodes: make(map[enode.ID]*ExecNode),
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name returns the name of the adapter for logging purposes
|
|
|
|
func (e *ExecAdapter) Name() string {
|
|
|
|
return "exec-adapter"
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewNode returns a new ExecNode using the given config
|
|
|
|
func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
|
2020-08-03 19:40:46 +02:00
|
|
|
if len(config.Lifecycles) == 0 {
|
|
|
|
return nil, errors.New("node must have at least one service lifecycle")
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
2020-08-03 19:40:46 +02:00
|
|
|
for _, service := range config.Lifecycles {
|
|
|
|
if _, exists := lifecycleConstructorFuncs[service]; !exists {
|
2017-09-25 09:08:07 +01:00
|
|
|
return nil, fmt.Errorf("unknown node service %q", service)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the node directory using the first 12 characters of the ID
|
|
|
|
// as Unix socket paths cannot be longer than 256 characters
|
|
|
|
dir := filepath.Join(e.BaseDir, config.ID.String()[:12])
|
|
|
|
if err := os.Mkdir(dir, 0755); err != nil {
|
|
|
|
return nil, fmt.Errorf("error creating node directory: %s", err)
|
|
|
|
}
|
|
|
|
|
2019-03-22 05:55:47 +01:00
|
|
|
err := config.initDummyEnode()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-06-21 13:45:32 +02:00
|
|
|
|
2017-09-25 09:08:07 +01:00
|
|
|
// generate the config
|
|
|
|
conf := &execNodeConfig{
|
|
|
|
Stack: node.DefaultConfig,
|
|
|
|
Node: config,
|
|
|
|
}
|
2019-02-26 12:32:48 +01:00
|
|
|
if config.DataDir != "" {
|
|
|
|
conf.Stack.DataDir = config.DataDir
|
|
|
|
} else {
|
|
|
|
conf.Stack.DataDir = filepath.Join(dir, "data")
|
|
|
|
}
|
2019-06-21 13:45:32 +02:00
|
|
|
|
|
|
|
// these parameters are crucial for execadapter node to run correctly
|
2017-09-25 09:08:07 +01:00
|
|
|
conf.Stack.WSHost = "127.0.0.1"
|
|
|
|
conf.Stack.WSPort = 0
|
|
|
|
conf.Stack.WSOrigins = []string{"*"}
|
|
|
|
conf.Stack.WSExposeAll = true
|
2019-06-21 13:45:32 +02:00
|
|
|
conf.Stack.P2P.EnableMsgEvents = config.EnableMsgEvents
|
2017-09-25 09:08:07 +01:00
|
|
|
conf.Stack.P2P.NoDiscovery = true
|
|
|
|
conf.Stack.P2P.NAT = nil
|
|
|
|
|
2019-11-18 09:40:59 +01:00
|
|
|
// Listen on a localhost port, which we set when we
|
2018-06-14 11:21:17 +02:00
|
|
|
// initialise NodeConfig (usually a random port)
|
|
|
|
conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port)
|
2017-09-25 09:08:07 +01:00
|
|
|
|
|
|
|
node := &ExecNode{
|
|
|
|
ID: config.ID,
|
|
|
|
Dir: dir,
|
|
|
|
Config: conf,
|
|
|
|
adapter: e,
|
|
|
|
}
|
|
|
|
node.newCmd = node.execCommand
|
|
|
|
e.nodes[node.ID] = node
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExecNode starts a simulation node by exec'ing the current binary and
|
|
|
|
// running the configured services
|
|
|
|
type ExecNode struct {
|
all: new p2p node representation (#17643)
Package p2p/enode provides a generalized representation of p2p nodes
which can contain arbitrary information in key/value pairs. It is also
the new home for the node database. The "v4" identity scheme is also
moved here from p2p/enr to remove the dependency on Ethereum crypto from
that package.
Record signature handling is changed significantly. The identity scheme
registry is removed and acceptable schemes must be passed to any method
that needs identity. This means records must now be validated explicitly
after decoding.
The enode API is designed to make signature handling easy and safe: most
APIs around the codebase work with enode.Node, which is a wrapper around
a valid record. Going from enr.Record to enode.Node requires a valid
signature.
* p2p/discover: port to p2p/enode
This ports the discovery code to the new node representation in
p2p/enode. The wire protocol is unchanged, this can be considered a
refactoring change. The Kademlia table can now deal with nodes using an
arbitrary identity scheme. This requires a few incompatible API changes:
- Table.Lookup is not available anymore. It used to take a public key
as argument because v4 protocol requires one. Its replacement is
LookupRandom.
- Table.Resolve takes *enode.Node instead of NodeID. This is also for
v4 protocol compatibility because nodes cannot be looked up by ID
alone.
- Types Node and NodeID are gone. Further commits in the series will be
fixes all over the the codebase to deal with those removals.
* p2p: port to p2p/enode and discovery changes
This adapts package p2p to the changes in p2p/discover. All uses of
discover.Node and discover.NodeID are replaced by their equivalents from
p2p/enode.
New API is added to retrieve the enode.Node instance of a peer. The
behavior of Server.Self with discovery disabled is improved. It now
tries much harder to report a working IP address, falling back to
127.0.0.1 if no suitable address can be determined through other means.
These changes were needed for tests of other packages later in the
series.
* p2p/simulations, p2p/testing: port to p2p/enode
No surprises here, mostly replacements of discover.Node, discover.NodeID
with their new equivalents. The 'interesting' API changes are:
- testing.ProtocolSession tracks complete nodes, not just their IDs.
- adapters.NodeConfig has a new method to create a complete node.
These changes were needed to make swarm tests work.
Note that the NodeID change makes the code incompatible with old
simulation snapshots.
* whisper/whisperv5, whisper/whisperv6: port to p2p/enode
This port was easy because whisper uses []byte for node IDs and
URL strings in the API.
* eth: port to p2p/enode
Again, easy to port because eth uses strings for node IDs and doesn't
care about node information in any way.
* les: port to p2p/enode
Apart from replacing discover.NodeID with enode.ID, most changes are in
the server pool code. It now deals with complete nodes instead
of (Pubkey, IP, Port) triples. The database format is unchanged for now,
but we should probably change it to use the node database later.
* node: port to p2p/enode
This change simply replaces discover.Node and discover.NodeID with their
new equivalents.
* swarm/network: port to p2p/enode
Swarm has its own node address representation, BzzAddr, containing both
an overlay address (the hash of a secp256k1 public key) and an underlay
address (enode:// URL).
There are no changes to the BzzAddr format in this commit, but certain
operations such as creating a BzzAddr from a node ID are now impossible
because node IDs aren't public keys anymore.
Most swarm-related changes in the series remove uses of
NewAddrFromNodeID, replacing it with NewAddr which takes a complete node
as argument. ToOverlayAddr is removed because we can just use the node
ID directly.
2018-09-25 00:59:00 +02:00
|
|
|
ID enode.ID
|
2017-09-25 09:08:07 +01:00
|
|
|
Dir string
|
|
|
|
Config *execNodeConfig
|
|
|
|
Cmd *exec.Cmd
|
|
|
|
Info *p2p.NodeInfo
|
|
|
|
|
|
|
|
adapter *ExecAdapter
|
|
|
|
client *rpc.Client
|
|
|
|
wsAddr string
|
|
|
|
newCmd func() *exec.Cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
// Addr returns the node's enode URL
|
|
|
|
func (n *ExecNode) Addr() []byte {
|
|
|
|
if n.Info == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return []byte(n.Info.Enode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client returns an rpc.Client which can be used to communicate with the
|
|
|
|
// underlying services (it is set once the node has started)
|
|
|
|
func (n *ExecNode) Client() (*rpc.Client, error) {
|
|
|
|
return n.client, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start exec's the node passing the ID and service as command line arguments
|
2018-10-11 20:32:14 +02:00
|
|
|
// and the node config encoded as JSON in an environment variable.
|
2017-09-25 09:08:07 +01:00
|
|
|
func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
|
|
|
|
if n.Cmd != nil {
|
|
|
|
return errors.New("already started")
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
n.Stop()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// encode a copy of the config containing the snapshot
|
|
|
|
confCopy := *n.Config
|
|
|
|
confCopy.Snapshots = snapshots
|
|
|
|
confCopy.PeerAddrs = make(map[string]string)
|
|
|
|
for id, node := range n.adapter.nodes {
|
|
|
|
confCopy.PeerAddrs[id.String()] = node.wsAddr
|
|
|
|
}
|
|
|
|
confData, err := json.Marshal(confCopy)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error generating node config: %s", err)
|
|
|
|
}
|
2020-11-10 21:19:44 +08:00
|
|
|
// expose the admin namespace via websocket if it's not enabled
|
|
|
|
exposed := confCopy.Stack.WSExposeAll
|
|
|
|
if !exposed {
|
|
|
|
for _, api := range confCopy.Stack.WSModules {
|
|
|
|
if api == "admin" {
|
|
|
|
exposed = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !exposed {
|
|
|
|
confCopy.Stack.WSModules = append(confCopy.Stack.WSModules, "admin")
|
|
|
|
}
|
2018-10-11 20:32:14 +02:00
|
|
|
// start the one-shot server that waits for startup information
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
statusURL, statusC := n.waitForStartupJSON(ctx)
|
2017-09-25 09:08:07 +01:00
|
|
|
|
|
|
|
// start the node
|
|
|
|
cmd := n.newCmd()
|
|
|
|
cmd.Stdout = os.Stdout
|
2018-10-11 20:32:14 +02:00
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
cmd.Env = append(os.Environ(),
|
|
|
|
envStatusURL+"="+statusURL,
|
|
|
|
envNodeConfig+"="+string(confData),
|
|
|
|
)
|
2017-09-25 09:08:07 +01:00
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return fmt.Errorf("error starting node: %s", err)
|
|
|
|
}
|
|
|
|
n.Cmd = cmd
|
|
|
|
|
2019-11-18 09:40:59 +01:00
|
|
|
// Wait for the node to start.
|
2018-10-11 20:32:14 +02:00
|
|
|
status := <-statusC
|
|
|
|
if status.Err != "" {
|
|
|
|
return errors.New(status.Err)
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
2019-11-18 09:40:59 +01:00
|
|
|
client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "")
|
2017-09-25 09:08:07 +01:00
|
|
|
if err != nil {
|
2018-10-11 20:32:14 +02:00
|
|
|
return fmt.Errorf("can't connect to RPC server: %v", err)
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
|
|
|
|
2019-11-18 09:40:59 +01:00
|
|
|
// Node ready :)
|
2018-10-11 20:32:14 +02:00
|
|
|
n.client = client
|
|
|
|
n.wsAddr = status.WSEndpoint
|
|
|
|
n.Info = status.NodeInfo
|
2017-09-25 09:08:07 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-10-11 20:32:14 +02:00
|
|
|
// waitForStartupJSON runs a one-shot HTTP server to receive a startup report.
|
|
|
|
func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeStartupJSON) {
|
|
|
|
var (
|
|
|
|
ch = make(chan nodeStartupJSON, 1)
|
|
|
|
quitOnce sync.Once
|
|
|
|
srv http.Server
|
|
|
|
)
|
|
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
if err != nil {
|
|
|
|
ch <- nodeStartupJSON{Err: err.Error()}
|
|
|
|
return "", ch
|
|
|
|
}
|
|
|
|
quit := func(status nodeStartupJSON) {
|
|
|
|
quitOnce.Do(func() {
|
|
|
|
l.Close()
|
|
|
|
ch <- status
|
|
|
|
})
|
|
|
|
}
|
|
|
|
srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var status nodeStartupJSON
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&status); err != nil {
|
|
|
|
status.Err = fmt.Sprintf("can't decode startup report: %v", err)
|
|
|
|
}
|
|
|
|
quit(status)
|
|
|
|
})
|
|
|
|
// Run the HTTP server, but don't wait forever and shut it down
|
|
|
|
// if the context is canceled.
|
|
|
|
go srv.Serve(l)
|
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
quit(nodeStartupJSON{Err: "didn't get startup report"})
|
|
|
|
}()
|
|
|
|
|
|
|
|
url := "http://" + l.Addr().String()
|
|
|
|
return url, ch
|
|
|
|
}
|
|
|
|
|
2017-09-25 09:08:07 +01:00
|
|
|
// execCommand returns a command which runs the node locally by exec'ing
|
|
|
|
// the current binary but setting argv[0] to "p2p-node" so that the child
|
|
|
|
// runs execP2PNode
|
|
|
|
func (n *ExecNode) execCommand() *exec.Cmd {
|
|
|
|
return &exec.Cmd{
|
|
|
|
Path: reexec.Self(),
|
2020-08-03 19:40:46 +02:00
|
|
|
Args: []string{"p2p-node", strings.Join(n.Config.Node.Lifecycles, ","), n.ID.String()},
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop stops the node by first sending SIGTERM and then SIGKILL if the node
|
|
|
|
// doesn't stop within 5s
|
|
|
|
func (n *ExecNode) Stop() error {
|
|
|
|
if n.Cmd == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
n.Cmd = nil
|
|
|
|
}()
|
|
|
|
|
|
|
|
if n.client != nil {
|
|
|
|
n.client.Close()
|
|
|
|
n.client = nil
|
|
|
|
n.wsAddr = ""
|
|
|
|
n.Info = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
|
|
|
return n.Cmd.Process.Kill()
|
|
|
|
}
|
2020-02-12 22:19:47 +08:00
|
|
|
waitErr := make(chan error, 1)
|
2017-09-25 09:08:07 +01:00
|
|
|
go func() {
|
|
|
|
waitErr <- n.Cmd.Wait()
|
|
|
|
}()
|
2024-04-09 14:51:54 +08:00
|
|
|
timer := time.NewTimer(5 * time.Second)
|
|
|
|
defer timer.Stop()
|
|
|
|
|
2017-09-25 09:08:07 +01:00
|
|
|
select {
|
|
|
|
case err := <-waitErr:
|
|
|
|
return err
|
2024-04-09 14:51:54 +08:00
|
|
|
case <-timer.C:
|
2017-09-25 09:08:07 +01:00
|
|
|
return n.Cmd.Process.Kill()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodeInfo returns information about the node
|
|
|
|
func (n *ExecNode) NodeInfo() *p2p.NodeInfo {
|
|
|
|
info := &p2p.NodeInfo{
|
|
|
|
ID: n.ID.String(),
|
|
|
|
}
|
|
|
|
if n.client != nil {
|
|
|
|
n.client.Call(&info, "admin_nodeInfo")
|
|
|
|
}
|
|
|
|
return info
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServeRPC serves RPC requests over the given connection by dialling the
|
|
|
|
// node's WebSocket address and joining the two connections
|
2019-11-18 09:40:59 +01:00
|
|
|
func (n *ExecNode) ServeRPC(clientConn *websocket.Conn) error {
|
|
|
|
conn, _, err := websocket.DefaultDialer.Dial(n.wsAddr, nil)
|
2017-09-25 09:08:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(2)
|
2019-11-18 09:40:59 +01:00
|
|
|
go wsCopy(&wg, conn, clientConn)
|
|
|
|
go wsCopy(&wg, clientConn, conn)
|
2017-09-25 09:08:07 +01:00
|
|
|
wg.Wait()
|
2019-11-18 09:40:59 +01:00
|
|
|
conn.Close()
|
2017-09-25 09:08:07 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-18 09:40:59 +01:00
|
|
|
func wsCopy(wg *sync.WaitGroup, src, dst *websocket.Conn) {
|
|
|
|
defer wg.Done()
|
|
|
|
for {
|
|
|
|
msgType, r, err := src.NextReader()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w, err := dst.NextWriter(msgType)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, err = io.Copy(w, r); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-25 09:08:07 +01:00
|
|
|
// Snapshots creates snapshots of the services by calling the
|
|
|
|
// simulation_snapshot RPC method
|
|
|
|
func (n *ExecNode) Snapshots() (map[string][]byte, error) {
|
|
|
|
if n.client == nil {
|
|
|
|
return nil, errors.New("RPC not started")
|
|
|
|
}
|
|
|
|
var snapshots map[string][]byte
|
|
|
|
return snapshots, n.client.Call(&snapshots, "simulation_snapshot")
|
|
|
|
}
|
|
|
|
|
|
|
|
// execNodeConfig is used to serialize the node configuration so it can be
|
|
|
|
// passed to the child process as a JSON encoded environment variable
|
|
|
|
type execNodeConfig struct {
|
|
|
|
Stack node.Config `json:"stack"`
|
|
|
|
Node *NodeConfig `json:"node"`
|
|
|
|
Snapshots map[string][]byte `json:"snapshots,omitempty"`
|
|
|
|
PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
|
|
|
|
}
|
|
|
|
|
2020-11-10 21:19:44 +08:00
|
|
|
func initLogging() {
|
|
|
|
// Initialize the logging by default first.
|
2023-11-29 15:33:50 +08:00
|
|
|
var innerHandler slog.Handler
|
|
|
|
innerHandler = slog.NewTextHandler(os.Stderr, nil)
|
|
|
|
glogger := log.NewGlogHandler(innerHandler)
|
|
|
|
glogger.Verbosity(log.LevelInfo)
|
|
|
|
log.SetDefault(log.NewLogger(glogger))
|
2020-11-10 21:19:44 +08:00
|
|
|
|
|
|
|
confEnv := os.Getenv(envNodeConfig)
|
|
|
|
if confEnv == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var conf execNodeConfig
|
|
|
|
if err := json.Unmarshal([]byte(confEnv), &conf); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var writer = os.Stderr
|
|
|
|
if conf.Node.LogFile != "" {
|
|
|
|
logWriter, err := os.Create(conf.Node.LogFile)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
writer = logWriter
|
|
|
|
}
|
2023-11-29 15:33:50 +08:00
|
|
|
var verbosity = log.LevelInfo
|
|
|
|
if conf.Node.LogVerbosity <= log.LevelTrace && conf.Node.LogVerbosity >= log.LevelCrit {
|
|
|
|
verbosity = log.FromLegacyLevel(int(conf.Node.LogVerbosity))
|
2020-11-10 21:19:44 +08:00
|
|
|
}
|
|
|
|
// Reinitialize the logger
|
2023-11-29 15:33:50 +08:00
|
|
|
innerHandler = log.NewTerminalHandler(writer, true)
|
|
|
|
glogger = log.NewGlogHandler(innerHandler)
|
2020-11-10 21:19:44 +08:00
|
|
|
glogger.Verbosity(verbosity)
|
2023-11-29 15:33:50 +08:00
|
|
|
log.SetDefault(log.NewLogger(glogger))
|
2020-11-10 21:19:44 +08:00
|
|
|
}
|
|
|
|
|
2018-10-11 20:32:14 +02:00
|
|
|
// execP2PNode starts a simulation node when the current binary is executed with
|
2017-09-25 09:08:07 +01:00
|
|
|
// argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
|
2018-10-11 20:32:14 +02:00
|
|
|
// and the node config from an environment variable.
|
2017-09-25 09:08:07 +01:00
|
|
|
func execP2PNode() {
|
2020-11-10 21:19:44 +08:00
|
|
|
initLogging()
|
|
|
|
|
2018-10-11 20:32:14 +02:00
|
|
|
statusURL := os.Getenv(envStatusURL)
|
|
|
|
if statusURL == "" {
|
|
|
|
log.Crit("missing " + envStatusURL)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start the node and gather startup report.
|
|
|
|
var status nodeStartupJSON
|
|
|
|
stack, stackErr := startExecNodeStack()
|
|
|
|
if stackErr != nil {
|
|
|
|
status.Err = stackErr.Error()
|
|
|
|
} else {
|
2020-11-10 21:19:44 +08:00
|
|
|
status.WSEndpoint = stack.WSEndpoint()
|
2018-10-11 20:32:14 +02:00
|
|
|
status.NodeInfo = stack.Server().NodeInfo()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send status to the host.
|
|
|
|
statusJSON, _ := json.Marshal(status)
|
2023-03-27 19:44:41 +08:00
|
|
|
resp, err := http.Post(statusURL, "application/json", bytes.NewReader(statusJSON))
|
|
|
|
if err != nil {
|
2018-10-11 20:32:14 +02:00
|
|
|
log.Crit("Can't post startup info", "url", statusURL, "err", err)
|
|
|
|
}
|
2023-03-27 19:44:41 +08:00
|
|
|
resp.Body.Close()
|
2018-10-11 20:32:14 +02:00
|
|
|
if stackErr != nil {
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop the stack if we get a SIGTERM signal.
|
|
|
|
go func() {
|
|
|
|
sigc := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(sigc, syscall.SIGTERM)
|
|
|
|
defer signal.Stop(sigc)
|
|
|
|
<-sigc
|
|
|
|
log.Info("Received SIGTERM, shutting down...")
|
2020-08-03 19:40:46 +02:00
|
|
|
stack.Close()
|
2018-10-11 20:32:14 +02:00
|
|
|
}()
|
|
|
|
stack.Wait() // Wait for the stack to exit.
|
|
|
|
}
|
2017-09-25 09:08:07 +01:00
|
|
|
|
2018-10-11 20:32:14 +02:00
|
|
|
func startExecNodeStack() (*node.Node, error) {
|
2017-09-25 09:08:07 +01:00
|
|
|
// read the services from argv
|
|
|
|
serviceNames := strings.Split(os.Args[1], ",")
|
|
|
|
|
|
|
|
// decode the config
|
2018-10-11 20:32:14 +02:00
|
|
|
confEnv := os.Getenv(envNodeConfig)
|
2017-09-25 09:08:07 +01:00
|
|
|
if confEnv == "" {
|
2024-02-29 17:56:46 +08:00
|
|
|
return nil, errors.New("missing " + envNodeConfig)
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
|
|
|
var conf execNodeConfig
|
|
|
|
if err := json.Unmarshal([]byte(confEnv), &conf); err != nil {
|
2018-10-11 20:32:14 +02:00
|
|
|
return nil, fmt.Errorf("error decoding %s: %v", envNodeConfig, err)
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
2019-03-22 21:37:25 +01:00
|
|
|
|
|
|
|
// create enode record
|
2019-07-22 12:34:41 +02:00
|
|
|
nodeTcpConn, _ := net.ResolveTCPAddr("tcp", conf.Stack.P2P.ListenAddr)
|
2019-03-22 21:37:25 +01:00
|
|
|
if nodeTcpConn.IP == nil {
|
|
|
|
nodeTcpConn.IP = net.IPv4(127, 0, 0, 1)
|
2019-03-22 05:55:47 +01:00
|
|
|
}
|
2019-03-22 21:37:25 +01:00
|
|
|
conf.Node.initEnode(nodeTcpConn.IP, nodeTcpConn.Port, nodeTcpConn.Port)
|
2017-09-25 09:08:07 +01:00
|
|
|
conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey
|
2017-12-01 11:49:04 +00:00
|
|
|
conf.Stack.Logger = log.New("node.id", conf.Node.ID.String())
|
2017-09-25 09:08:07 +01:00
|
|
|
|
|
|
|
// initialize the devp2p stack
|
|
|
|
stack, err := node.New(&conf.Stack)
|
|
|
|
if err != nil {
|
2018-10-11 20:32:14 +02:00
|
|
|
return nil, fmt.Errorf("error creating node stack: %v", err)
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
|
|
|
|
2020-08-03 19:40:46 +02:00
|
|
|
// Register the services, collecting them into a map so they can
|
|
|
|
// be accessed by the snapshot API.
|
|
|
|
services := make(map[string]node.Lifecycle, len(serviceNames))
|
2017-09-25 09:08:07 +01:00
|
|
|
for _, name := range serviceNames {
|
2020-08-03 19:40:46 +02:00
|
|
|
lifecycleFunc, exists := lifecycleConstructorFuncs[name]
|
2017-09-25 09:08:07 +01:00
|
|
|
if !exists {
|
2018-10-11 20:32:14 +02:00
|
|
|
return nil, fmt.Errorf("unknown node service %q", err)
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
2020-08-03 19:40:46 +02:00
|
|
|
ctx := &ServiceContext{
|
|
|
|
RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs},
|
|
|
|
Config: conf.Node,
|
|
|
|
}
|
|
|
|
if conf.Snapshots != nil {
|
|
|
|
ctx.Snapshot = conf.Snapshots[name]
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
2020-08-03 19:40:46 +02:00
|
|
|
service, err := lifecycleFunc(ctx, stack)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
2020-08-03 19:40:46 +02:00
|
|
|
services[name] = service
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
|
|
|
|
2020-08-03 19:40:46 +02:00
|
|
|
// Add the snapshot API.
|
|
|
|
stack.RegisterAPIs([]rpc.API{{
|
|
|
|
Namespace: "simulation",
|
|
|
|
Service: SnapshotAPI{services},
|
|
|
|
}})
|
2017-09-25 09:08:07 +01:00
|
|
|
|
2018-10-11 20:32:14 +02:00
|
|
|
if err = stack.Start(); err != nil {
|
|
|
|
err = fmt.Errorf("error starting stack: %v", err)
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
2018-10-11 20:32:14 +02:00
|
|
|
return stack, err
|
|
|
|
}
|
2017-09-25 09:08:07 +01:00
|
|
|
|
2018-10-11 20:32:14 +02:00
|
|
|
const (
|
|
|
|
envStatusURL = "_P2P_STATUS_URL"
|
|
|
|
envNodeConfig = "_P2P_NODE_CONFIG"
|
|
|
|
)
|
2017-09-25 09:08:07 +01:00
|
|
|
|
2018-10-11 20:32:14 +02:00
|
|
|
// nodeStartupJSON is sent to the simulation host after startup.
|
|
|
|
type nodeStartupJSON struct {
|
|
|
|
Err string
|
|
|
|
WSEndpoint string
|
|
|
|
NodeInfo *p2p.NodeInfo
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// SnapshotAPI provides an RPC method to create snapshots of services
|
|
|
|
type SnapshotAPI struct {
|
2020-08-03 19:40:46 +02:00
|
|
|
services map[string]node.Lifecycle
|
2017-09-25 09:08:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (api SnapshotAPI) Snapshot() (map[string][]byte, error) {
|
|
|
|
snapshots := make(map[string][]byte)
|
|
|
|
for name, service := range api.services {
|
|
|
|
if s, ok := service.(interface {
|
|
|
|
Snapshot() ([]byte, error)
|
|
|
|
}); ok {
|
|
|
|
snap, err := s.Snapshot()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
snapshots[name] = snap
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return snapshots, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type wsRPCDialer struct {
|
|
|
|
addrs map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
// DialRPC implements the RPCDialer interface by creating a WebSocket RPC
|
|
|
|
// client of the given node
|
all: new p2p node representation (#17643)
Package p2p/enode provides a generalized representation of p2p nodes
which can contain arbitrary information in key/value pairs. It is also
the new home for the node database. The "v4" identity scheme is also
moved here from p2p/enr to remove the dependency on Ethereum crypto from
that package.
Record signature handling is changed significantly. The identity scheme
registry is removed and acceptable schemes must be passed to any method
that needs identity. This means records must now be validated explicitly
after decoding.
The enode API is designed to make signature handling easy and safe: most
APIs around the codebase work with enode.Node, which is a wrapper around
a valid record. Going from enr.Record to enode.Node requires a valid
signature.
* p2p/discover: port to p2p/enode
This ports the discovery code to the new node representation in
p2p/enode. The wire protocol is unchanged, this can be considered a
refactoring change. The Kademlia table can now deal with nodes using an
arbitrary identity scheme. This requires a few incompatible API changes:
- Table.Lookup is not available anymore. It used to take a public key
as argument because v4 protocol requires one. Its replacement is
LookupRandom.
- Table.Resolve takes *enode.Node instead of NodeID. This is also for
v4 protocol compatibility because nodes cannot be looked up by ID
alone.
- Types Node and NodeID are gone. Further commits in the series will be
fixes all over the the codebase to deal with those removals.
* p2p: port to p2p/enode and discovery changes
This adapts package p2p to the changes in p2p/discover. All uses of
discover.Node and discover.NodeID are replaced by their equivalents from
p2p/enode.
New API is added to retrieve the enode.Node instance of a peer. The
behavior of Server.Self with discovery disabled is improved. It now
tries much harder to report a working IP address, falling back to
127.0.0.1 if no suitable address can be determined through other means.
These changes were needed for tests of other packages later in the
series.
* p2p/simulations, p2p/testing: port to p2p/enode
No surprises here, mostly replacements of discover.Node, discover.NodeID
with their new equivalents. The 'interesting' API changes are:
- testing.ProtocolSession tracks complete nodes, not just their IDs.
- adapters.NodeConfig has a new method to create a complete node.
These changes were needed to make swarm tests work.
Note that the NodeID change makes the code incompatible with old
simulation snapshots.
* whisper/whisperv5, whisper/whisperv6: port to p2p/enode
This port was easy because whisper uses []byte for node IDs and
URL strings in the API.
* eth: port to p2p/enode
Again, easy to port because eth uses strings for node IDs and doesn't
care about node information in any way.
* les: port to p2p/enode
Apart from replacing discover.NodeID with enode.ID, most changes are in
the server pool code. It now deals with complete nodes instead
of (Pubkey, IP, Port) triples. The database format is unchanged for now,
but we should probably change it to use the node database later.
* node: port to p2p/enode
This change simply replaces discover.Node and discover.NodeID with their
new equivalents.
* swarm/network: port to p2p/enode
Swarm has its own node address representation, BzzAddr, containing both
an overlay address (the hash of a secp256k1 public key) and an underlay
address (enode:// URL).
There are no changes to the BzzAddr format in this commit, but certain
operations such as creating a BzzAddr from a node ID are now impossible
because node IDs aren't public keys anymore.
Most swarm-related changes in the series remove uses of
NewAddrFromNodeID, replacing it with NewAddr which takes a complete node
as argument. ToOverlayAddr is removed because we can just use the node
ID directly.
2018-09-25 00:59:00 +02:00
|
|
|
func (w *wsRPCDialer) DialRPC(id enode.ID) (*rpc.Client, error) {
|
2017-09-25 09:08:07 +01:00
|
|
|
addr, ok := w.addrs[id.String()]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("unknown node: %s", id)
|
|
|
|
}
|
|
|
|
return rpc.DialWebsocket(context.Background(), addr, "http://localhost")
|
|
|
|
}
|