This PR significantly changes the APIs for instantiating Ethereum nodes in a Go program. The new APIs are not backwards-compatible, but we feel that this is made up for by the much simpler way of registering services on node.Node. You can find more information and rationale in the design document: https://gist.github.com/renaynay/5bec2de19fde66f4d04c535fd24f0775. There is also a new feature in Node's Go API: it is now possible to register arbitrary handlers on the user-facing HTTP server. In geth, this facility is used to enable GraphQL. There is a single minor change relevant for geth users in this PR: The GraphQL API is no longer available separately from the JSON-RPC HTTP server. If you want GraphQL, you need to enable it using the ./geth --http --graphql flag combination. The --graphql.port and --graphql.addr flags are no longer available.
		
			
				
	
	
		
			174 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			174 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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 main
 | |
| 
 | |
| import (
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"sync/atomic"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/ethereum/go-ethereum/log"
 | |
| 	"github.com/ethereum/go-ethereum/node"
 | |
| 	"github.com/ethereum/go-ethereum/p2p"
 | |
| 	"github.com/ethereum/go-ethereum/p2p/enode"
 | |
| 	"github.com/ethereum/go-ethereum/p2p/simulations"
 | |
| 	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
 | |
| )
 | |
| 
 | |
| var adapterType = flag.String("adapter", "sim", `node adapter to use (one of "sim", "exec" or "docker")`)
 | |
| 
 | |
| // main() starts a simulation network which contains nodes running a simple
 | |
| // ping-pong protocol
 | |
| func main() {
 | |
| 	flag.Parse()
 | |
| 
 | |
| 	// set the log level to Trace
 | |
| 	log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
 | |
| 
 | |
| 	// register a single ping-pong service
 | |
| 	services := map[string]adapters.LifecycleConstructor{
 | |
| 		"ping-pong": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
 | |
| 			pps := newPingPongService(ctx.Config.ID)
 | |
| 			stack.RegisterProtocols(pps.Protocols())
 | |
| 			return pps, nil
 | |
| 		},
 | |
| 	}
 | |
| 	adapters.RegisterLifecycles(services)
 | |
| 
 | |
| 	// create the NodeAdapter
 | |
| 	var adapter adapters.NodeAdapter
 | |
| 
 | |
| 	switch *adapterType {
 | |
| 
 | |
| 	case "sim":
 | |
| 		log.Info("using sim adapter")
 | |
| 		adapter = adapters.NewSimAdapter(services)
 | |
| 
 | |
| 	case "exec":
 | |
| 		tmpdir, err := ioutil.TempDir("", "p2p-example")
 | |
| 		if err != nil {
 | |
| 			log.Crit("error creating temp dir", "err", err)
 | |
| 		}
 | |
| 		defer os.RemoveAll(tmpdir)
 | |
| 		log.Info("using exec adapter", "tmpdir", tmpdir)
 | |
| 		adapter = adapters.NewExecAdapter(tmpdir)
 | |
| 
 | |
| 	default:
 | |
| 		log.Crit(fmt.Sprintf("unknown node adapter %q", *adapterType))
 | |
| 	}
 | |
| 
 | |
| 	// start the HTTP API
 | |
| 	log.Info("starting simulation server on 0.0.0.0:8888...")
 | |
| 	network := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
 | |
| 		DefaultService: "ping-pong",
 | |
| 	})
 | |
| 	if err := http.ListenAndServe(":8888", simulations.NewServer(network)); err != nil {
 | |
| 		log.Crit("error starting simulation server", "err", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // pingPongService runs a ping-pong protocol between nodes where each node
 | |
| // sends a ping to all its connected peers every 10s and receives a pong in
 | |
| // return
 | |
| type pingPongService struct {
 | |
| 	id       enode.ID
 | |
| 	log      log.Logger
 | |
| 	received int64
 | |
| }
 | |
| 
 | |
| func newPingPongService(id enode.ID) *pingPongService {
 | |
| 	return &pingPongService{
 | |
| 		id:  id,
 | |
| 		log: log.New("node.id", id),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (p *pingPongService) Protocols() []p2p.Protocol {
 | |
| 	return []p2p.Protocol{{
 | |
| 		Name:     "ping-pong",
 | |
| 		Version:  1,
 | |
| 		Length:   2,
 | |
| 		Run:      p.Run,
 | |
| 		NodeInfo: p.Info,
 | |
| 	}}
 | |
| }
 | |
| 
 | |
| func (p *pingPongService) Start() error {
 | |
| 	p.log.Info("ping-pong service starting")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (p *pingPongService) Stop() error {
 | |
| 	p.log.Info("ping-pong service stopping")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (p *pingPongService) Info() interface{} {
 | |
| 	return struct {
 | |
| 		Received int64 `json:"received"`
 | |
| 	}{
 | |
| 		atomic.LoadInt64(&p.received),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	pingMsgCode = iota
 | |
| 	pongMsgCode
 | |
| )
 | |
| 
 | |
| // Run implements the ping-pong protocol which sends ping messages to the peer
 | |
| // at 10s intervals, and responds to pings with pong messages.
 | |
| func (p *pingPongService) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
 | |
| 	log := p.log.New("peer.id", peer.ID())
 | |
| 
 | |
| 	errC := make(chan error)
 | |
| 	go func() {
 | |
| 		for range time.Tick(10 * time.Second) {
 | |
| 			log.Info("sending ping")
 | |
| 			if err := p2p.Send(rw, pingMsgCode, "PING"); err != nil {
 | |
| 				errC <- err
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 	go func() {
 | |
| 		for {
 | |
| 			msg, err := rw.ReadMsg()
 | |
| 			if err != nil {
 | |
| 				errC <- err
 | |
| 				return
 | |
| 			}
 | |
| 			payload, err := ioutil.ReadAll(msg.Payload)
 | |
| 			if err != nil {
 | |
| 				errC <- err
 | |
| 				return
 | |
| 			}
 | |
| 			log.Info("received message", "msg.code", msg.Code, "msg.payload", string(payload))
 | |
| 			atomic.AddInt64(&p.received, 1)
 | |
| 			if msg.Code == pingMsgCode {
 | |
| 				log.Info("sending pong")
 | |
| 				go p2p.Send(rw, pongMsgCode, "PONG")
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 	return <-errC
 | |
| }
 |