NAME

Algorithm::Kademlia - Pure Perl implementation of the Kademlia DHT algorithm

SYNOPSIS

use Algorithm::Kademlia qw[xor_distance xor_bucket_index];

my $local_id = pack( 'H*', '00' x 20 ); # 160-bit ID
my $rt = Algorithm::Kademlia::RoutingTable->new( local_id_bin => $local_id );

# Add a peer (handles Least-Recently-Seen eviction policy)
my $stale = $rt->add_peer( $peer_id, { addr => '127.0.0.1:4001' } );
if ($stale) {
    # Bucket is full. The protocol requires you to ping the $stale node.
    # If the ping fails:
    #   $rt->evict_peer($stale->{id});
    #   $rt->add_peer($peer_id, ...);
    # If the ping succeeds:
    #   The new $peer_id is discarded (as $stale was moved to the tail).
}

# Storage for the DHT
my $storage = Algorithm::Kademlia::Storage->new( ttl => 3600 );
$storage->put($key_bin, $value_bin);

# State management for iterative lookups
my $search = Algorithm::Kademlia::Search->new(
    target_id_bin => $target_key,
    alpha         => 3
);
$search->add_candidates($rt->find_closest($target_key));

while (!$search->is_finished) {
    my @to_query = $search->next_to_query();
    for my $node (@to_query) {
        # ... Send FIND_NODE RPC to $node ...
        # If response: $search->mark_responded($node->{id}, @found_peers);
        # If failure:  $search->mark_failed($node->{id});
    }
}
my @results = $search->best_results();

DESCRIPTION

Algorithm::Kademlia provides the mathematical and structural foundations for a Kademlia Distributed Hash Table (DHT). It is designed to be protocol-agnostic, meaning it only handles the XOR-metric distance calculations, the k-bucket routing table logic, and the search state management.

This module is suitable for building BitTorrent-compatible DHTs, libp2p Kademlia implementations, or custom peer-to-peer storage systems.

FUNCTIONS

These are bitwise logic utilities for the Kademlia XOR metrics.

xor_distance( $id1, $id2 )

Returns the bitwise XOR of two binary strings.

my $id1  = pack('H*', '00' x 20);
my $id2  = pack('H*', 'ff' x 20);
my $dist = xor_distance($id1, $id2);
say unpack('H*', $dist); # ffffffffffffffffffffffffffffffffffffffff

xor_bucket_index( $id1, $id2 )

Calculates the index of the k-bucket that $id2 would fall into relative to $id1. Returns undef if the IDs are identical.

The index corresponds to the bit position of the most significant bit that differs between the two IDs. For 160-bit IDs, the result is between 0 and 159.

my $local = pack('H*', '00' x 20);
my $peer  = pack('H*', '80' . ('00' x 19)); # 160th bit differs
my $idx   = xor_bucket_index($local, $peer);
say $idx; # 159

Algorithm::Kademlia::RoutingTable

This class implements the 160 k-bucket structure (or appropriate size for the ID length provided). Peers are bucketed by their XOR distance from the local node ID.

new( local_id_bin => ..., [ k => 20 ] )

Constructor. local_id_bin is the binary string of the local node's ID.

my $rt = Algorithm::Kademlia::RoutingTable->new(
    local_id_bin => $my_id_bin,
    k            => 20
);

add_peer( $id_bin, $data )

Adds a peer to the appropriate bucket.

my $stale = $rt->add_peer($id, { ip => '1.2.3.4', port => 4444 });
if ($stale) {
    # Bucket full! Ping $stale->{data}{ip}
}

evict_peer( $id_bin )

Removes a peer from the routing table. Usually called after a stale peer fails a ping check.

$rt->evict_peer($stale_id);

find_closest( $target_id_bin, [ $count ] )

Returns a list of up to $count (defaults to k) peers closest to the target ID according to the XOR metric.

my @peers = $rt->find_closest($target_key, 10);
for my $peer (@peers) {
    say 'Node ' . unpack('H*', $peer->{id}) . ' is at ' . $peer->{data}{ip};
}

size( )

Returns the total number of peers across all buckets.

say 'Routing table has ' . $rt->size . ' peers';

Algorithm::Kademlia::Storage

A simple in-memory key-value store intended to hold the DHT data.

new( [ ttl => 86400 ] )

Constructor. ttl is the time-to-live for entries in seconds.

my $storage = Algorithm::Kademlia::Storage->new( ttl => 3600 );

put( $key_bin, $value_bin, [ $publisher_id_bin ] )

Stores a value.

$storage->put($cid_bin, $data_bin, $provider_id);

get( $key_bin )

Retrieves a value. Returns undef if the key is missing or has expired.

my $data = $storage->get($cid_bin);
die 'Expired or not found' unless defined $data;

entries( )

Returns a hash reference of all non-expired entries in the store.

my %all = $storage->entries();
for my ($key, $info) (%all) {
    say 'Key: ' . unpack('H*', $key) . ' Value: ' . $info->{value};
}

Algorithm::Kademlia::Search

A state manager for the iterative Kademlia lookup algorithm. It tracks which nodes have been queried, which have responded, and which have failed.

new( target_id_bin => ..., [ k => 20, alpha => 3 ] )

Constructor. alpha is the concurrency parameter (how many parallel queries to allow).

my $search = Algorithm::Kademlia::Search->new(
    target_id_bin => $target_key,
    alpha         => 3
);

add_candidates( @peers )

Adds new potential nodes to the search shortlist.

$search->add_candidates( $rt->find_closest($target_key) );

pending_queries( )

Returns a list of nodes that have been queried but have not yet responded or failed.

my @waiting = $search->pending_queries();
say 'Waiting for ' . scalar(@waiting) . ' nodes...';

next_to_query( )

Returns a list of up to alpha nodes that have not yet been queried, sorted by proximity to the target.

my @to_query = $search->next_to_query();
# Now send your RPCs to these nodes...

mark_responded( $id_bin, @new_peers )

Marks a node as having responded and adds any new peers it returned to the shortlist.

# After getting a response from a FIND_NODE RPC:
$search->mark_responded($peer_id, @peers_from_rpc);

mark_failed( $id_bin )

Marks a node as failed (e.g., RPC timeout).

# After a timeout or connection error:
$search->mark_failed($peer_id);

is_finished()

Returns true if the search has reached a termination condition (either k nodes have responded, or there are no more nodes to query and no pending requests).

while (!$search->is_finished) {
    # ... keep querying ...
}

best_results()

Returns a list of the k closest nodes that successfully responded.

my @k_closest = $search->best_results();

SEE ALSO

InterPlanetary::Kademlia (for the libp2p implementation)

Net::BitTorrent::DHT (for the BitTorrent implementation)

https://xlattice.sourceforge.net/components/protocol/kademlia/specs.html

AUTHOR

Sanko Robinson sanko@cpan.org

COPYRIGHT

Copyright (C) 2023-2026 by Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.