How to Use MemoryLookup in Iroh for In-Memory Peer Addressing
MemoryLookup in Iroh provides a thread-safe, in-memory store for endpoint addressing data, enabling applications to manually insert peer addresses via add_endpoint_info() and resolve them during connection attempts through the AddressLookup trait.
MemoryLookup is the in-memory implementation of Iroh's address resolution system. According to the n0-computer/iroh source code, this struct maintains a shared BTreeMap wrapped in Arc<RwLock<>> that maps EndpointId values to StoredEndpointInfo, allowing you to programmatically manage addressing data received out-of-band—such as from EndpointTicket objects, configuration files, or custom discovery protocols.
What Is MemoryLookup in Iroh?
The MemoryLookup struct serves as the in-memory address-lookup implementation used by Iroh Endpoint instances to discover how to reach remote peers. Unlike DNS-based or DHT-based discovery, this store exists only in process memory and is populated explicitly by your application.
In iroh/src/address_lookup/memory.rs:75-78, the struct is defined as:
- A
BTreeMap<EndpointId, StoredEndpointInfo>containing the actual addressing data - Wrapped in
Arc<RwLock<>>for thread-safe concurrent access across multiple tasks
The StoredEndpointInfo struct (lines 89-94) holds the EndpointData—including direct addresses and relay URLs—alongside a last_updated timestamp. When the Endpoint queries the lookup, this timestamp is emitted as a microsecond tag in the returned Item, helping your application track address freshness.
Creating and Configuring a MemoryLookup
Basic Initialization
Create a new lookup using the default provenance string "memory_lookup":
use iroh::address_lookup::memory::MemoryLookup;
let address_lookup = MemoryLookup::new();
As implemented in iroh/src/address_lookup/memory.rs:104-107, this constructor initializes the internal BTreeMap and sets the provenance identifier used for debugging address origins.
Custom Provenance for Debugging
To trace which component provided an address, use with_provenance():
let lookup = MemoryLookup::with_provenance("my_app_config");
This method (lines 117-122) sets a custom string that appears in every Item returned by resolve(), allowing you to distinguish between addresses loaded from different sources during connection diagnostics.
Managing Endpoint Address Data
Adding and Updating Addresses
MemoryLookup provides two methods for inserting data, depending on whether you want to merge or overwrite:
add_endpoint_info merges new addressing data with existing entries. It adds new direct addresses to the set while overwriting the relay URL. Use this when receiving incremental updates from a discovery protocol:
use iroh::{EndpointAddr, TransportAddr};
address_lookup.add_endpoint_info(EndpointAddr {
id: remote_id,
addrs: [TransportAddr::Relay("https://relay.example.com".parse()?)]
.into_iter()
.collect(),
});
This corresponds to the implementation in iroh/src/address_lookup/memory.rs:84-99.
set_endpoint_info replaces the entire entry for an endpoint, returning the previous StoredEndpointInfo if present. Use this when you have complete, authoritative addressing data:
use iroh::{EndpointInfo, TransportAddr};
let new_info = EndpointInfo::from_parts(
remote_id,
[TransportAddr::Direct("127.0.0.1:4000".parse()?)]
.into_iter()
.collect(),
);
let previous = address_lookup.set_endpoint_info(new_info);
See lines 68-77 for the implementation details.
Retrieving and Removing Entries
Query the current state without modifying it using get_endpoint_info():
if let Some(info) = address_lookup.get_endpoint_info(remote_id) {
println!("Addresses: {:?}", info.addrs());
}
To delete an entry and return the removed data, use remove_endpoint_info():
let removed = address_lookup.remove_endpoint_info(remote_id);
These operations are found at lines 101-106 and 108-115 respectively.
Integrating MemoryLookup with an Endpoint
The primary integration point occurs when constructing your Endpoint. Pass the lookup to the builder via address_lookup():
use iroh::{Endpoint, endpoint::presets};
let ep = Endpoint::builder(presets::N0)
.address_lookup(address_lookup.clone())
.bind()
.await?;
When the Endpoint attempts to connect to a peer, it calls resolve() on the lookup. As implemented in iroh/src/address_lookup/memory.rs:218-240, this method:
- Acquires a read lock on the internal map
- Constructs an
Itemcontaining theEndpointInfo, the provenance string, and thelast_updatedtimestamp - Returns it as a one-item async stream
The publish() method is a no-op because data persists only in memory and is never broadcast.
Loading Data from Existing Sources
Populate a lookup from a vector of EndpointInfo objects—such as those deserialized from tickets or configuration files—using from_endpoint_info():
let infos: Vec<EndpointInfo> = load_from_config();
let lookup = MemoryLookup::from_endpoint_info(infos);
This constructor (lines 154-160) efficiently inserts all entries into a new lookup instance, preserving the last_updated timestamps from the source data.
Summary
- MemoryLookup maintains a thread-safe
BTreeMap<EndpointId, StoredEndpointInfo>iniroh/src/address_lookup/memory.rsfor runtime address management. - Use
add_endpoint_info()to merge updates andset_endpoint_info()to replace entire entries when managing addressing data. - Integrate with
Endpoint::builder().address_lookup()to enable the endpoint to resolve peers using your in-memory store. - The
resolve()method returns a single-item stream containing theEndpointInfo, provenance tag, and timestamp for consumption by the QUIC connection logic. - All operations are thread-safe via
Arc<RwLock<>>and operate entirely in memory without network side effects.
Frequently Asked Questions
How does MemoryLookup differ from other address lookup implementations in Iroh?
MemoryLookup stores data only in process memory and requires manual population via add_endpoint_info(), whereas other implementations might query DNS, a DHT, or a central discovery service automatically. The publish() method is intentionally a no-op because the lookup does not propagate data to the network—it merely serves as a local cache for out-of-band addressing information.
Is MemoryLookup thread-safe for concurrent access?
Yes. The struct wraps its internal BTreeMap in Arc<RwLock<>> as shown in iroh/src/address_lookup/memory.rs:75-78. This allows multiple Endpoint instances or async tasks to share the same lookup via clone(), with read operations occurring concurrently and write operations serialized through the write lock.
What happens when I call resolve() on a MemoryLookup?
The resolve() method—implemented in iroh/src/address_lookup/memory.rs:218-240—looks up the EndpointId in the internal map and returns a one-item async stream. If the ID exists, the stream yields an Item containing the EndpointInfo, the provenance string (e.g., "memory_lookup"), and a microsecond timestamp indicating when the data was last updated. If the ID is missing, the stream resolves to empty.
Should I use add_endpoint_info or set_endpoint_info for updating addresses?
Use add_endpoint_info when receiving incremental updates from discovery protocols, as it merges new direct addresses with existing ones while overwriting only the relay URL. Use set_endpoint_info when you have complete, authoritative addressing data and want to replace any existing entry entirely, which returns the previous value for comparison or logging purposes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →