github.com/tuotoo/go-ethereum@v1.7.4-0.20171121184211-049797d40a24/p2p/simulations/network.go (about)

     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 <http://www.gnu.org/licenses/>.
    16  
    17  package simulations
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"sync"
    25  
    26  	"github.com/ethereum/go-ethereum/event"
    27  	"github.com/ethereum/go-ethereum/log"
    28  	"github.com/ethereum/go-ethereum/p2p"
    29  	"github.com/ethereum/go-ethereum/p2p/discover"
    30  	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
    31  )
    32  
    33  // NetworkConfig defines configuration options for starting a Network
    34  type NetworkConfig struct {
    35  	ID             string `json:"id"`
    36  	DefaultService string `json:"default_service,omitempty"`
    37  }
    38  
    39  // Network models a p2p simulation network which consists of a collection of
    40  // simulated nodes and the connections which exist between them.
    41  //
    42  // The Network has a single NodeAdapter which is responsible for actually
    43  // starting nodes and connecting them together.
    44  //
    45  // The Network emits events when nodes are started and stopped, when they are
    46  // connected and disconnected, and also when messages are sent between nodes.
    47  type Network struct {
    48  	NetworkConfig
    49  
    50  	Nodes   []*Node `json:"nodes"`
    51  	nodeMap map[discover.NodeID]int
    52  
    53  	Conns   []*Conn `json:"conns"`
    54  	connMap map[string]int
    55  
    56  	nodeAdapter adapters.NodeAdapter
    57  	events      event.Feed
    58  	lock        sync.RWMutex
    59  	quitc       chan struct{}
    60  }
    61  
    62  // NewNetwork returns a Network which uses the given NodeAdapter and NetworkConfig
    63  func NewNetwork(nodeAdapter adapters.NodeAdapter, conf *NetworkConfig) *Network {
    64  	return &Network{
    65  		NetworkConfig: *conf,
    66  		nodeAdapter:   nodeAdapter,
    67  		nodeMap:       make(map[discover.NodeID]int),
    68  		connMap:       make(map[string]int),
    69  		quitc:         make(chan struct{}),
    70  	}
    71  }
    72  
    73  // Events returns the output event feed of the Network.
    74  func (self *Network) Events() *event.Feed {
    75  	return &self.events
    76  }
    77  
    78  // NewNode adds a new node to the network with a random ID
    79  func (self *Network) NewNode() (*Node, error) {
    80  	conf := adapters.RandomNodeConfig()
    81  	conf.Services = []string{self.DefaultService}
    82  	return self.NewNodeWithConfig(conf)
    83  }
    84  
    85  // NewNodeWithConfig adds a new node to the network with the given config,
    86  // returning an error if a node with the same ID or name already exists
    87  func (self *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) {
    88  	self.lock.Lock()
    89  	defer self.lock.Unlock()
    90  
    91  	// create a random ID and PrivateKey if not set
    92  	if conf.ID == (discover.NodeID{}) {
    93  		c := adapters.RandomNodeConfig()
    94  		conf.ID = c.ID
    95  		conf.PrivateKey = c.PrivateKey
    96  	}
    97  	id := conf.ID
    98  
    99  	// assign a name to the node if not set
   100  	if conf.Name == "" {
   101  		conf.Name = fmt.Sprintf("node%02d", len(self.Nodes)+1)
   102  	}
   103  
   104  	// check the node doesn't already exist
   105  	if node := self.getNode(id); node != nil {
   106  		return nil, fmt.Errorf("node with ID %q already exists", id)
   107  	}
   108  	if node := self.getNodeByName(conf.Name); node != nil {
   109  		return nil, fmt.Errorf("node with name %q already exists", conf.Name)
   110  	}
   111  
   112  	// if no services are configured, use the default service
   113  	if len(conf.Services) == 0 {
   114  		conf.Services = []string{self.DefaultService}
   115  	}
   116  
   117  	// use the NodeAdapter to create the node
   118  	adapterNode, err := self.nodeAdapter.NewNode(conf)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	node := &Node{
   123  		Node:   adapterNode,
   124  		Config: conf,
   125  	}
   126  	log.Trace(fmt.Sprintf("node %v created", id))
   127  	self.nodeMap[id] = len(self.Nodes)
   128  	self.Nodes = append(self.Nodes, node)
   129  
   130  	// emit a "control" event
   131  	self.events.Send(ControlEvent(node))
   132  
   133  	return node, nil
   134  }
   135  
   136  // Config returns the network configuration
   137  func (self *Network) Config() *NetworkConfig {
   138  	return &self.NetworkConfig
   139  }
   140  
   141  // StartAll starts all nodes in the network
   142  func (self *Network) StartAll() error {
   143  	for _, node := range self.Nodes {
   144  		if node.Up {
   145  			continue
   146  		}
   147  		if err := self.Start(node.ID()); err != nil {
   148  			return err
   149  		}
   150  	}
   151  	return nil
   152  }
   153  
   154  // StopAll stops all nodes in the network
   155  func (self *Network) StopAll() error {
   156  	for _, node := range self.Nodes {
   157  		if !node.Up {
   158  			continue
   159  		}
   160  		if err := self.Stop(node.ID()); err != nil {
   161  			return err
   162  		}
   163  	}
   164  	return nil
   165  }
   166  
   167  // Start starts the node with the given ID
   168  func (self *Network) Start(id discover.NodeID) error {
   169  	return self.startWithSnapshots(id, nil)
   170  }
   171  
   172  // startWithSnapshots starts the node with the given ID using the give
   173  // snapshots
   174  func (self *Network) startWithSnapshots(id discover.NodeID, snapshots map[string][]byte) error {
   175  	node := self.GetNode(id)
   176  	if node == nil {
   177  		return fmt.Errorf("node %v does not exist", id)
   178  	}
   179  	if node.Up {
   180  		return fmt.Errorf("node %v already up", id)
   181  	}
   182  	log.Trace(fmt.Sprintf("starting node %v: %v using %v", id, node.Up, self.nodeAdapter.Name()))
   183  	if err := node.Start(snapshots); err != nil {
   184  		log.Warn(fmt.Sprintf("start up failed: %v", err))
   185  		return err
   186  	}
   187  	node.Up = true
   188  	log.Info(fmt.Sprintf("started node %v: %v", id, node.Up))
   189  
   190  	self.events.Send(NewEvent(node))
   191  
   192  	// subscribe to peer events
   193  	client, err := node.Client()
   194  	if err != nil {
   195  		return fmt.Errorf("error getting rpc client  for node %v: %s", id, err)
   196  	}
   197  	events := make(chan *p2p.PeerEvent)
   198  	sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents")
   199  	if err != nil {
   200  		return fmt.Errorf("error getting peer events for node %v: %s", id, err)
   201  	}
   202  	go self.watchPeerEvents(id, events, sub)
   203  	return nil
   204  }
   205  
   206  // watchPeerEvents reads peer events from the given channel and emits
   207  // corresponding network events
   208  func (self *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEvent, sub event.Subscription) {
   209  	defer func() {
   210  		sub.Unsubscribe()
   211  
   212  		// assume the node is now down
   213  		self.lock.Lock()
   214  		node := self.getNode(id)
   215  		node.Up = false
   216  		self.lock.Unlock()
   217  		self.events.Send(NewEvent(node))
   218  	}()
   219  	for {
   220  		select {
   221  		case event, ok := <-events:
   222  			if !ok {
   223  				return
   224  			}
   225  			peer := event.Peer
   226  			switch event.Type {
   227  
   228  			case p2p.PeerEventTypeAdd:
   229  				self.DidConnect(id, peer)
   230  
   231  			case p2p.PeerEventTypeDrop:
   232  				self.DidDisconnect(id, peer)
   233  
   234  			case p2p.PeerEventTypeMsgSend:
   235  				self.DidSend(id, peer, event.Protocol, *event.MsgCode)
   236  
   237  			case p2p.PeerEventTypeMsgRecv:
   238  				self.DidReceive(peer, id, event.Protocol, *event.MsgCode)
   239  
   240  			}
   241  
   242  		case err := <-sub.Err():
   243  			if err != nil {
   244  				log.Error(fmt.Sprintf("error getting peer events for node %v", id), "err", err)
   245  			}
   246  			return
   247  		}
   248  	}
   249  }
   250  
   251  // Stop stops the node with the given ID
   252  func (self *Network) Stop(id discover.NodeID) error {
   253  	node := self.GetNode(id)
   254  	if node == nil {
   255  		return fmt.Errorf("node %v does not exist", id)
   256  	}
   257  	if !node.Up {
   258  		return fmt.Errorf("node %v already down", id)
   259  	}
   260  	if err := node.Stop(); err != nil {
   261  		return err
   262  	}
   263  	node.Up = false
   264  	log.Info(fmt.Sprintf("stop node %v: %v", id, node.Up))
   265  
   266  	self.events.Send(ControlEvent(node))
   267  	return nil
   268  }
   269  
   270  // Connect connects two nodes together by calling the "admin_addPeer" RPC
   271  // method on the "one" node so that it connects to the "other" node
   272  func (self *Network) Connect(oneID, otherID discover.NodeID) error {
   273  	log.Debug(fmt.Sprintf("connecting %s to %s", oneID, otherID))
   274  	conn, err := self.GetOrCreateConn(oneID, otherID)
   275  	if err != nil {
   276  		return err
   277  	}
   278  	if conn.Up {
   279  		return fmt.Errorf("%v and %v already connected", oneID, otherID)
   280  	}
   281  	if err := conn.nodesUp(); err != nil {
   282  		return err
   283  	}
   284  	client, err := conn.one.Client()
   285  	if err != nil {
   286  		return err
   287  	}
   288  	self.events.Send(ControlEvent(conn))
   289  	return client.Call(nil, "admin_addPeer", string(conn.other.Addr()))
   290  }
   291  
   292  // Disconnect disconnects two nodes by calling the "admin_removePeer" RPC
   293  // method on the "one" node so that it disconnects from the "other" node
   294  func (self *Network) Disconnect(oneID, otherID discover.NodeID) error {
   295  	conn := self.GetConn(oneID, otherID)
   296  	if conn == nil {
   297  		return fmt.Errorf("connection between %v and %v does not exist", oneID, otherID)
   298  	}
   299  	if !conn.Up {
   300  		return fmt.Errorf("%v and %v already disconnected", oneID, otherID)
   301  	}
   302  	client, err := conn.one.Client()
   303  	if err != nil {
   304  		return err
   305  	}
   306  	self.events.Send(ControlEvent(conn))
   307  	return client.Call(nil, "admin_removePeer", string(conn.other.Addr()))
   308  }
   309  
   310  // DidConnect tracks the fact that the "one" node connected to the "other" node
   311  func (self *Network) DidConnect(one, other discover.NodeID) error {
   312  	conn, err := self.GetOrCreateConn(one, other)
   313  	if err != nil {
   314  		return fmt.Errorf("connection between %v and %v does not exist", one, other)
   315  	}
   316  	if conn.Up {
   317  		return fmt.Errorf("%v and %v already connected", one, other)
   318  	}
   319  	conn.Up = true
   320  	self.events.Send(NewEvent(conn))
   321  	return nil
   322  }
   323  
   324  // DidDisconnect tracks the fact that the "one" node disconnected from the
   325  // "other" node
   326  func (self *Network) DidDisconnect(one, other discover.NodeID) error {
   327  	conn, err := self.GetOrCreateConn(one, other)
   328  	if err != nil {
   329  		return fmt.Errorf("connection between %v and %v does not exist", one, other)
   330  	}
   331  	if !conn.Up {
   332  		return fmt.Errorf("%v and %v already disconnected", one, other)
   333  	}
   334  	conn.Up = false
   335  	self.events.Send(NewEvent(conn))
   336  	return nil
   337  }
   338  
   339  // DidSend tracks the fact that "sender" sent a message to "receiver"
   340  func (self *Network) DidSend(sender, receiver discover.NodeID, proto string, code uint64) error {
   341  	msg := &Msg{
   342  		One:      sender,
   343  		Other:    receiver,
   344  		Protocol: proto,
   345  		Code:     code,
   346  		Received: false,
   347  	}
   348  	self.events.Send(NewEvent(msg))
   349  	return nil
   350  }
   351  
   352  // DidReceive tracks the fact that "receiver" received a message from "sender"
   353  func (self *Network) DidReceive(sender, receiver discover.NodeID, proto string, code uint64) error {
   354  	msg := &Msg{
   355  		One:      sender,
   356  		Other:    receiver,
   357  		Protocol: proto,
   358  		Code:     code,
   359  		Received: true,
   360  	}
   361  	self.events.Send(NewEvent(msg))
   362  	return nil
   363  }
   364  
   365  // GetNode gets the node with the given ID, returning nil if the node does not
   366  // exist
   367  func (self *Network) GetNode(id discover.NodeID) *Node {
   368  	self.lock.Lock()
   369  	defer self.lock.Unlock()
   370  	return self.getNode(id)
   371  }
   372  
   373  // GetNode gets the node with the given name, returning nil if the node does
   374  // not exist
   375  func (self *Network) GetNodeByName(name string) *Node {
   376  	self.lock.Lock()
   377  	defer self.lock.Unlock()
   378  	return self.getNodeByName(name)
   379  }
   380  
   381  func (self *Network) getNode(id discover.NodeID) *Node {
   382  	i, found := self.nodeMap[id]
   383  	if !found {
   384  		return nil
   385  	}
   386  	return self.Nodes[i]
   387  }
   388  
   389  func (self *Network) getNodeByName(name string) *Node {
   390  	for _, node := range self.Nodes {
   391  		if node.Config.Name == name {
   392  			return node
   393  		}
   394  	}
   395  	return nil
   396  }
   397  
   398  // GetNodes returns the existing nodes
   399  func (self *Network) GetNodes() []*Node {
   400  	self.lock.Lock()
   401  	defer self.lock.Unlock()
   402  	return self.Nodes
   403  }
   404  
   405  // GetConn returns the connection which exists between "one" and "other"
   406  // regardless of which node initiated the connection
   407  func (self *Network) GetConn(oneID, otherID discover.NodeID) *Conn {
   408  	self.lock.Lock()
   409  	defer self.lock.Unlock()
   410  	return self.getConn(oneID, otherID)
   411  }
   412  
   413  // GetOrCreateConn is like GetConn but creates the connection if it doesn't
   414  // already exist
   415  func (self *Network) GetOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) {
   416  	self.lock.Lock()
   417  	defer self.lock.Unlock()
   418  	if conn := self.getConn(oneID, otherID); conn != nil {
   419  		return conn, nil
   420  	}
   421  
   422  	one := self.getNode(oneID)
   423  	if one == nil {
   424  		return nil, fmt.Errorf("node %v does not exist", oneID)
   425  	}
   426  	other := self.getNode(otherID)
   427  	if other == nil {
   428  		return nil, fmt.Errorf("node %v does not exist", otherID)
   429  	}
   430  	conn := &Conn{
   431  		One:   oneID,
   432  		Other: otherID,
   433  		one:   one,
   434  		other: other,
   435  	}
   436  	label := ConnLabel(oneID, otherID)
   437  	self.connMap[label] = len(self.Conns)
   438  	self.Conns = append(self.Conns, conn)
   439  	return conn, nil
   440  }
   441  
   442  func (self *Network) getConn(oneID, otherID discover.NodeID) *Conn {
   443  	label := ConnLabel(oneID, otherID)
   444  	i, found := self.connMap[label]
   445  	if !found {
   446  		return nil
   447  	}
   448  	return self.Conns[i]
   449  }
   450  
   451  // Shutdown stops all nodes in the network and closes the quit channel
   452  func (self *Network) Shutdown() {
   453  	for _, node := range self.Nodes {
   454  		log.Debug(fmt.Sprintf("stopping node %s", node.ID().TerminalString()))
   455  		if err := node.Stop(); err != nil {
   456  			log.Warn(fmt.Sprintf("error stopping node %s", node.ID().TerminalString()), "err", err)
   457  		}
   458  	}
   459  	close(self.quitc)
   460  }
   461  
   462  // Node is a wrapper around adapters.Node which is used to track the status
   463  // of a node in the network
   464  type Node struct {
   465  	adapters.Node `json:"-"`
   466  
   467  	// Config if the config used to created the node
   468  	Config *adapters.NodeConfig `json:"config"`
   469  
   470  	// Up tracks whether or not the node is running
   471  	Up bool `json:"up"`
   472  }
   473  
   474  // ID returns the ID of the node
   475  func (self *Node) ID() discover.NodeID {
   476  	return self.Config.ID
   477  }
   478  
   479  // String returns a log-friendly string
   480  func (self *Node) String() string {
   481  	return fmt.Sprintf("Node %v", self.ID().TerminalString())
   482  }
   483  
   484  // NodeInfo returns information about the node
   485  func (self *Node) NodeInfo() *p2p.NodeInfo {
   486  	// avoid a panic if the node is not started yet
   487  	if self.Node == nil {
   488  		return nil
   489  	}
   490  	info := self.Node.NodeInfo()
   491  	info.Name = self.Config.Name
   492  	return info
   493  }
   494  
   495  // MarshalJSON implements the json.Marshaler interface so that the encoded
   496  // JSON includes the NodeInfo
   497  func (self *Node) MarshalJSON() ([]byte, error) {
   498  	return json.Marshal(struct {
   499  		Info   *p2p.NodeInfo        `json:"info,omitempty"`
   500  		Config *adapters.NodeConfig `json:"config,omitempty"`
   501  		Up     bool                 `json:"up"`
   502  	}{
   503  		Info:   self.NodeInfo(),
   504  		Config: self.Config,
   505  		Up:     self.Up,
   506  	})
   507  }
   508  
   509  // Conn represents a connection between two nodes in the network
   510  type Conn struct {
   511  	// One is the node which initiated the connection
   512  	One discover.NodeID `json:"one"`
   513  
   514  	// Other is the node which the connection was made to
   515  	Other discover.NodeID `json:"other"`
   516  
   517  	// Up tracks whether or not the connection is active
   518  	Up bool `json:"up"`
   519  
   520  	one   *Node
   521  	other *Node
   522  }
   523  
   524  // nodesUp returns whether both nodes are currently up
   525  func (self *Conn) nodesUp() error {
   526  	if !self.one.Up {
   527  		return fmt.Errorf("one %v is not up", self.One)
   528  	}
   529  	if !self.other.Up {
   530  		return fmt.Errorf("other %v is not up", self.Other)
   531  	}
   532  	return nil
   533  }
   534  
   535  // String returns a log-friendly string
   536  func (self *Conn) String() string {
   537  	return fmt.Sprintf("Conn %v->%v", self.One.TerminalString(), self.Other.TerminalString())
   538  }
   539  
   540  // Msg represents a p2p message sent between two nodes in the network
   541  type Msg struct {
   542  	One      discover.NodeID `json:"one"`
   543  	Other    discover.NodeID `json:"other"`
   544  	Protocol string          `json:"protocol"`
   545  	Code     uint64          `json:"code"`
   546  	Received bool            `json:"received"`
   547  }
   548  
   549  // String returns a log-friendly string
   550  func (self *Msg) String() string {
   551  	return fmt.Sprintf("Msg(%d) %v->%v", self.Code, self.One.TerminalString(), self.Other.TerminalString())
   552  }
   553  
   554  // ConnLabel generates a deterministic string which represents a connection
   555  // between two nodes, used to compare if two connections are between the same
   556  // nodes
   557  func ConnLabel(source, target discover.NodeID) string {
   558  	var first, second discover.NodeID
   559  	if bytes.Compare(source.Bytes(), target.Bytes()) > 0 {
   560  		first = target
   561  		second = source
   562  	} else {
   563  		first = source
   564  		second = target
   565  	}
   566  	return fmt.Sprintf("%v-%v", first, second)
   567  }
   568  
   569  // Snapshot represents the state of a network at a single point in time and can
   570  // be used to restore the state of a network
   571  type Snapshot struct {
   572  	Nodes []NodeSnapshot `json:"nodes,omitempty"`
   573  	Conns []Conn         `json:"conns,omitempty"`
   574  }
   575  
   576  // NodeSnapshot represents the state of a node in the network
   577  type NodeSnapshot struct {
   578  	Node Node `json:"node,omitempty"`
   579  
   580  	// Snapshots is arbitrary data gathered from calling node.Snapshots()
   581  	Snapshots map[string][]byte `json:"snapshots,omitempty"`
   582  }
   583  
   584  // Snapshot creates a network snapshot
   585  func (self *Network) Snapshot() (*Snapshot, error) {
   586  	self.lock.Lock()
   587  	defer self.lock.Unlock()
   588  	snap := &Snapshot{
   589  		Nodes: make([]NodeSnapshot, len(self.Nodes)),
   590  		Conns: make([]Conn, len(self.Conns)),
   591  	}
   592  	for i, node := range self.Nodes {
   593  		snap.Nodes[i] = NodeSnapshot{Node: *node}
   594  		if !node.Up {
   595  			continue
   596  		}
   597  		snapshots, err := node.Snapshots()
   598  		if err != nil {
   599  			return nil, err
   600  		}
   601  		snap.Nodes[i].Snapshots = snapshots
   602  	}
   603  	for i, conn := range self.Conns {
   604  		snap.Conns[i] = *conn
   605  	}
   606  	return snap, nil
   607  }
   608  
   609  // Load loads a network snapshot
   610  func (self *Network) Load(snap *Snapshot) error {
   611  	for _, n := range snap.Nodes {
   612  		if _, err := self.NewNodeWithConfig(n.Node.Config); err != nil {
   613  			return err
   614  		}
   615  		if !n.Node.Up {
   616  			continue
   617  		}
   618  		if err := self.startWithSnapshots(n.Node.Config.ID, n.Snapshots); err != nil {
   619  			return err
   620  		}
   621  	}
   622  	for _, conn := range snap.Conns {
   623  		if err := self.Connect(conn.One, conn.Other); err != nil {
   624  			return err
   625  		}
   626  	}
   627  	return nil
   628  }
   629  
   630  // Subscribe reads control events from a channel and executes them
   631  func (self *Network) Subscribe(events chan *Event) {
   632  	for {
   633  		select {
   634  		case event, ok := <-events:
   635  			if !ok {
   636  				return
   637  			}
   638  			if event.Control {
   639  				self.executeControlEvent(event)
   640  			}
   641  		case <-self.quitc:
   642  			return
   643  		}
   644  	}
   645  }
   646  
   647  func (self *Network) executeControlEvent(event *Event) {
   648  	log.Trace("execute control event", "type", event.Type, "event", event)
   649  	switch event.Type {
   650  	case EventTypeNode:
   651  		if err := self.executeNodeEvent(event); err != nil {
   652  			log.Error("error executing node event", "event", event, "err", err)
   653  		}
   654  	case EventTypeConn:
   655  		if err := self.executeConnEvent(event); err != nil {
   656  			log.Error("error executing conn event", "event", event, "err", err)
   657  		}
   658  	case EventTypeMsg:
   659  		log.Warn("ignoring control msg event")
   660  	}
   661  }
   662  
   663  func (self *Network) executeNodeEvent(e *Event) error {
   664  	if !e.Node.Up {
   665  		return self.Stop(e.Node.ID())
   666  	}
   667  
   668  	if _, err := self.NewNodeWithConfig(e.Node.Config); err != nil {
   669  		return err
   670  	}
   671  	return self.Start(e.Node.ID())
   672  }
   673  
   674  func (self *Network) executeConnEvent(e *Event) error {
   675  	if e.Conn.Up {
   676  		return self.Connect(e.Conn.One, e.Conn.Other)
   677  	} else {
   678  		return self.Disconnect(e.Conn.One, e.Conn.Other)
   679  	}
   680  }