Setup Local Node

Setup Local Node

setupLocalNode function in the p2p/server.go file is responsible for initializing the local node configuration. This includes creating the devp2p handshake, setting up the local node record, and preparing the local node for network operations.
Steps:
  1. Create the devp2p Handshake:
      • Extracts the public key from the server's private key in byte array form.
      • Initializes the protoHandshake struct with the version, node name, and public key.
      • Loops through the server's supported protocols and adds their capabilities to the handshake.
      • Sorts the capabilities for consistency.
  1. Create the Local Node:
      • Opens the database where information about known nodes is stored.
      • Stores the database reference in the server's nodedb field.
      • Creates a new LocalNode object with the node database and the server's private key.
      • Sets a fallback IP address for the local node to 127.0.0.1
  1. Set Protocol Attributes:
      • Iterates over the server's protocols. For each protocol, it sets the corresponding attributes in the local node record.
/// ---p2p/server.go--- func (srv *Server) setupLocalNode() error { // Create the devp2p handshake. pubkey := crypto.FromECDSAPub(&srv.PrivateKey.PublicKey) srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: pubkey[1:]} // iterate protocols supported by the server, record the capability of those // protocols in srv.ourHandshake.Caps for _, p := range srv.Protocols { srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap()) } slices.SortFunc(srv.ourHandshake.Caps, Cap.Cmp) // Create the local node. db, err := enode.OpenDB(srv.NodeDatabase) if err != nil { return err } srv.nodedb = db srv.localnode = enode.NewLocalNode(db, srv.PrivateKey) srv.localnode.SetFallbackIP(net.IP{127, 0, 0, 1}) // TODO: check conflicts for _, p := range srv.Protocols { for _, e := range p.Attributes { srv.localnode.Set(e) } } return nil } /// ---p2p/peer.go--- const ( baseProtocolVersion = 5 ) /// ---p2p/protocol.go--- // Cmp defines the canonical sorting order of capabilities. func (cap Cap) Cmp(other Cap) int { if cap.Name == other.Name { if cap.Version < other.Version { return -1 } if cap.Version > other.Version { return 1 } return 0 } return strings.Compare(cap.Name, other.Name) }
 
protoHandshake
server.protoHandshake stores information should be exchaned by nodes during handshake process. Handshare process ensures that two nodes can establish a secure and authenticated connection before they start exchanging Ethereum protocol messages. Nodes exchange their capabilities, which include the protocols they support and the versions of these protocols. This allows the nodes to determine a common set of protocols to use for communication.
/// ---p2p/peer.go--- // protoHandshake is the RLP structure of the protocol handshake. type protoHandshake struct { Version uint64 Name string Caps []Cap ListenPort uint64 ID []byte // secp256k1 public key // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` } /// ---p2p/protocol.go--- // Cap is the structure of a peer capability. type Cap struct { Name string Version uint }
 

New Local Node

NewLocalNode function initializes a local node with its ID, database, private key, and other relevant fields. It sets up the ENR entries, endpoint trackers, sequence number, and update timestamp. The function ensures that the local node is ready to participate in the Ethereum network, with mechanisms in place for predicting its IP address, managing updates to its ENR, and maintaining thread-safe access to its current node record.
Fields
  1. id
    1. Represents the unique identifier of the node, derived from the public key.
  1. db
    1. A reference to the node database (DB), which stores information about known nodes.
  1. key
      • The private key (ecdsa.PrivateKey) of the local node.
      • This key is used for signing node records and encrypting communications.
  1. entries
      • A map (map[string]enr.Entry) that holds the ENR (Ethereum Node Record) entries of the local node.
      • ENR entries are key-value pairs that store information about the node, such as its IP address, port, and other metadata.
  1. endpoint4 and endpoint6:
      • These fields store information about the node's endpoints for IPv4 and IPv6.
      • Each endpoint contains an IPTracker (netutil.NewIPTracker), which is used to track and predict the node's IP address based on network statements and contacts.
      • The staticIP, fallbackIP, and fallbackUDP fields are used during IP prediction process as the fallback prediction option.
  1. seq:
    1. The sequence number is used to ensure that updates to the ENR are applied in the correct order.
  1. update:
    1. A timestamp (time.Time) that records when the ENR was last updated. It is initialized to the current time using time.Now().
  1. cur
    1. An atomic value (atomic.Value) that holds a pointer to the current node record which contains a signed node record (enr.Record) that contains various entries (such as IP addresses and ports). The signed node record is used in the peer-to-peer (P2P) networking layer to identify and verify nodes in the network. When nodes communicate with each other, they exchange their signed node records to establish trust.
/// ---p2p/enode/localnode.go--- // LocalNode produces the signed node record of a local node, i.e. a node run in the // current process. Setting ENR entries via the Set method updates the record. A new version // of the record is signed on demand when the Node method is called. type LocalNode struct { cur atomic.Value // holds a non-nil node pointer while the record is up-to-date id ID key *ecdsa.PrivateKey db *DB // everything below is protected by a lock mu sync.RWMutex seq uint64 update time.Time // timestamp when the record was last updated entries map[string]enr.Entry endpoint4 lnEndpoint endpoint6 lnEndpoint } type lnEndpoint struct { track *netutil.IPTracker staticIP, fallbackIP net.IP fallbackUDP uint16 // port } // NewLocalNode creates a local node. func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode { ln := &LocalNode{ id: PubkeyToIDV4(&key.PublicKey), db: db, key: key, entries: make(map[string]enr.Entry), endpoint4: lnEndpoint{ track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements), }, endpoint6: lnEndpoint{ track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements), }, } ln.seq = db.localSeq(ln.id) ln.update = time.Now() ln.cur.Store((*Node)(nil)) return ln } /// ---p2p/enode/urlv4.go--- // PubkeyToIDV4 derives the v4 node address from the given public key. func PubkeyToIDV4(key *ecdsa.PublicKey) ID { e := make([]byte, 64) math.ReadBits(key.X, e[:len(e)/2]) math.ReadBits(key.Y, e[len(e)/2:]) return ID(crypto.Keccak256Hash(e)) }
 
ENR (Ethereum Node Record)
  • The ENR is a signed record that contains metadata about a node, such as its IP address, port, public key, and other attributes.
  • It is used for node discovery and identification in the Ethereum network.
  • ENR entries are stored in the entries field as key-value pairs, where the key is a string (the entry's name) and the value is an enr.Entry.
 

Set Fallback IP

SetFallbackIP function of the LocalNode struct sets a fallback IP address for the local node. This fallback IP address is used when no endpoint prediction can be made about the It also calls ln.updateEndpoints to update the IP prediction based on IP statements from other nodes. Detailed analysis can be seen here.
/// ---p2p/enode/localnode.go--- // SetFallbackIP sets the last-resort IP address. This address is used // if no endpoint prediction can be made and no static IP is set. func (ln *LocalNode) SetFallbackIP(ip net.IP) { ln.mu.Lock() defer ln.mu.Unlock() ln.endpointForIP(ip).fallbackIP = ip ln.updateEndpoints() } type lnEndpoint struct { track *netutil.IPTracker staticIP, fallbackIP net.IP fallbackUDP uint16 // port } func (ln *LocalNode) endpointForIP(ip net.IP) *lnEndpoint { // check whether the IP address is of type IPV4 if ip.To4() != nil { return &ln.endpoint4 } return &ln.endpoint6 }
 

Set Protocol Attributes

In local node set up process, it records node’s protocols’ attributes into entries.
Each protocol in geth can define a set of attributes that describe protocol-specific information about the node. These attributes are stored in the Attributes field of the Protocol struct. Examples of such attributes could be the supported protocol version, network capabilities, and other protocol-specific metadata.
 
Reason To Add Protocol Attribtues Into Node Entries
Signed node entries in the Ethereum Node Record (ENR) are designed to be broadcast to and shared with other nodes in the network. By adding protocol attributes to the local node's entries, geth ensures that the node record contains a comprehensive set of information about the node.
When other nodes discover this node, they receive the signed node record which not only includes node’s IP address and port but also additional protocol-specific information. This helps in determining compatibility and understanding the capabilities of the node without needing further queries.
 
Difference Between srv.ourHandshake and LocalNode.entries
srv.ourHandshake also stores the node's protocol capabilities, but it serves a different purpose compared to the LocalNode's entries.
  • The srv.ourHandshake field is part of the peer-to-peer (P2P) server setup and is used during the handshake process when establishing a connection with another peer. This field contains information about the protocols and capabilities that the local node supports, which is shared with peers during the handshake. The purpose of this is to negotiate the protocols and capabilities that will be used for communication between the peers.
  • The LocalNode entries, on the other hand, are part of the node's ENR (Ethereum Node Record). This record is used for node discovery and includes a broader set of information about the node that can be shared with the entire network. The LocalNode entries include basic network information like IP addresses and ports used by the node, and protocol attributes like information about the protocols supported by the node, which can be accessed by any node in the network.
/// ---p2p/server.go--- func (srv *Server) setupLocalNode() error { // ... for _, p := range srv.Protocols { for _, e := range p.Attributes { srv.localnode.Set(e) } } return nil } /// ---p2p/protocol.go--- // Protocol represents a P2P subprotocol implementation. type Protocol struct { // Name should contain the official protocol name, // often a three-letter word. Name string // Version should contain the version number of the protocol. Version uint // Length should contain the number of message codes used // by the protocol. Length uint64 // Run is called in a new goroutine when the protocol has been // negotiated with a peer. It should read and write messages from // rw. The Payload for each message must be fully consumed. // // The peer connection is closed when Start returns. It should return // any protocol-level error (such as an I/O error) that is // encountered. Run func(peer *Peer, rw MsgReadWriter) error // NodeInfo is an optional helper method to retrieve protocol specific metadata // about the host node. NodeInfo func() interface{} // PeerInfo is an optional helper method to retrieve protocol specific metadata // about a certain peer in the network. If an info retrieval function is set, // but returns nil, it is assumed that the protocol handshake is still running. PeerInfo func(id enode.ID) interface{} // DialCandidates, if non-nil, is a way to tell Server about protocol-specific nodes // that should be dialed. The server continuously reads nodes from the iterator and // attempts to create connections to them. DialCandidates enode.Iterator // Attributes contains protocol specific information for the node record. Attributes []enr.Entry }
/// ---p2p/enode/localnode.go--- func (ln *LocalNode) Set(e enr.Entry) { ln.mu.Lock() defer ln.mu.Unlock() ln.set(e) } func (ln *LocalNode) set(e enr.Entry) { val, exists := ln.entries[e.ENRKey()] if !exists || !reflect.DeepEqual(val, e) { ln.entries[e.ENRKey()] = e ln.invalidate() } }