specifications/network/network-address.md
NetworkAddress is a compact, efficient, self-describing and future-proof network address represented as a stack of
protocols, inspired by libp2p's multiaddr format. The primary differences include
using [BCS] to describe the binary format and reducing the set of supported protocols.
In particular, a NetworkAddress is intended to be a fully self-contained description of how to dial a
DiemNet peer, describing both the base transport protocol and all subsequent connection upgrade protocols.
/// A `NetworkAddress` is a sequence of `Protocol`s describing how to dial a peer.
///
/// Note: `NetworkAddress` must not be empty.
pub struct NetworkAddress(Vec<Protocol>);
/// A single protocol in the [`NetworkAddress`] protocol stack.
pub enum Protocol {
Ip4(Ipv4Addr), // effectively [u8; 4]
Ip6(Ipv6Addr), // effectively [u8; 16]
Dns(DnsName),
Dns4(DnsName),
Dns6(DnsName),
Tcp(u16),
Memory(u16),
// human-readable x25519::PublicKey is lower-case hex encoded
NoiseIK(x25519::PublicKey),
Handshake(u8),
}
/// A minimally parsed DNS name. We don't really do any checking other than
/// enforcing:
///
/// 1\. it is not an empty string
/// 2\. it is not larger than 255 bytes
/// 3\. it does not contain any forward slash '/' characters
/// 4\. it is a valid unicode string
///
/// From the [DNS name syntax RFC](https://tools.ietf.org/html/rfc2181#page-13),
/// the standard rules are:
///
/// 1\. the total size <= 255 bytes
/// 2\. each label <= 63 bytes
/// 3\. any binary string is valid
///
/// So the restrictions we're adding are (3) no '/' characters and (4) the name
/// is a valid unicode string. We do this because '/' characters are already our
/// protocol delimiter and Rust's [`::std::net::ToSocketAddr`] API requires a
/// `&str`.
pub struct DnsName(String);
All NetworkAddress sent and received over-the-wire or stored on-chain are in their compact [u8] [bcs]-serialized
format. However, implementations may also wish to implement the optional human-readable format to aid reading
NetworkAddresses from configuration or printing NetworkAddress in logs.
Each Protocol segment is formatted of the form /<Protocol>/<ProtocolConfig>, and applies to the following rules:
Protocol and ProtocolConfig must not contain any forward slash /Examples:
Ip4([127, 0, 0, 1]) => "/ip4/127.0.0.1",
Ip6([0x2601, 0, .., 0, 0xfebc]) => "/ip6/2601::febc",
Dns("example.com") => "/dns/example.com",
Dns4("example.com") => "/dns4/example.com",
Dns6("example.com") => "/dns6/example.com",
Tcp(6080) => "/tcp/6080",
Memory(1234) => "/memory/1234",
NoiseIK(b"080e287879c918794170e258bfaddd75acac5b3e350419044655e4983a487120") =>
"/ln-noise-ik/080e287879c918794170e258bfaddd75acac5b3e350419044655e4983a487120",
Handshake(0) => "/ln-handshake/0",
A NetworkAddress is then just a concatenation of these individually formatted Protocol segments:
NetworkAddress([
Ip4([127, 0, 0, 1]),
Tcp(6080),
NoiseIK(b"080e287879c918794170e258bfaddd75acac5b3e350419044655e4983a487120"),
Handshake(0),
]) => "/ip4/127.0.0.1/tcp/6080/ln-noise-ik/080e287879c918794170e258bfaddd75acac5b3e350419044655e4983a487120/ln-handshake/0"
A NetworkAddress as a concatenation of Protocol segments must have the following characteristics:
Ip4 or Dns) andTcp).Memory is a special protocol that is both Layer3 and Layer4./Example possible combinations:
/ip4/127.0.0.1/tcp/6080/memory/6080/dns/novi.com/tcp/80/ln-noise-ik/080e287879c918794170e258bfaddd75acac5b3e350419044655e4983a487120/ln-handshake/0It is important for Diem to support upgrading its network protocols. In particular, providing a process for doing backwards-incompatible upgrades allows Diem to evolve quickly and prevents protocol ossification.
Each node advertises the full set of protocols needed to speak with a it using
onchain discovery. In other words, a node that sees
"/dns6/example.com/tcp/1234/ln-noise-ik/<pubkey>/ln-handshake/0" knows unambiguously the precise set of protocols it
must speak and in which order for the listening node to understand them.
By allowing several NetworkAddresses to be advertised at once, nodes can also facilitate backwards-incompatible
network protocol upgrades. For instance, suppose Diem wants to modify the Noise secure transport to a different
handshake pattern or crypto primitives (e.g. move from "Noise_IK_25519_AESGCM_SHA256" to
"Noise_IX_25519_ChaChaPoly_BLAKE2s"). A new binary version of Diem could be released that supports the new Noise
handshake. Nodes that update to the new binary version would then advertise both the old handshake protocol and the new
handshake protocol:
addrs := [
// new protocol
"/ip4/1.2.3.4/tcp/6181/ln-noise-ix/<pubkey>/ln-handshake/0",
// old protocol
"/ip4/1.2.3.4/tcp/6180/ln-noise-ik/<pubkey>/ln-handshake/0",
]
Nodes running the old binary (a binary that doesn't understand the new handshake protocol) will simply dial updated nodes using the advertised address containing the old protocol. Meanwhile, new nodes can use the updated protocol to speak with each other. By advertising old and new simultaneously, connectivity can be maintained during the upgrade rollout.
Finally, when all nodes have updated, each node can rotate out the old protocol. A subsequent release can then fully remove the old handshake logic from the codebase. The final set of advertised addresses for each node will then look like:
// new protocol only
addrs := [
"/ip4/1.2.3.4/tcp/6181/ln-noise-ix/<pubkey>/ln-handshake/0",
]