Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
52c188d
feat(bitswap): implement batch fetching of blocks and enhance file re…
sumanjeet0012 Apr 14, 2026
c7d74b0
feat: enhance DHT record handling with signing and verification for i…
sumanjeet0012 Apr 18, 2026
ea01b07
Merge remote-tracking branch 'origin/main' into improvement/bitswap
sumanjeet0012 May 2, 2026
5637cc3
feat: add FilesystemBlockStore for persistent block storage and enhan…
sumanjeet0012 May 3, 2026
c5982e7
feat: enhance Merkle DAG handling with DAG-PB leaf nodes and balanced…
sumanjeet0012 May 3, 2026
5f4e18a
feat: introduce BlockService for enhanced block retrieval and caching…
sumanjeet0012 May 3, 2026
796b5d5
feat: add chunk_stream function for efficient streaming of file chunk…
sumanjeet0012 May 3, 2026
3e8b881
feat: enhance BlockService and FilesystemBlockStore with type hints, …
sumanjeet0012 May 3, 2026
5df7ca8
Add comprehensive tests for Bitswap functionality
sumanjeet0012 May 3, 2026
58719a7
refactor: clean up imports and improve code formatting across multipl…
sumanjeet0012 May 3, 2026
ead47b0
Refactor type hints and add assertions in tests
sumanjeet0012 May 3, 2026
6acceb2
removed logs file
sumanjeet0012 May 3, 2026
a1456f5
fix: adjust DEFAULT_CHUNK_SIZE for DAG-PB overhead, enhance wantType …
sumanjeet0012 May 3, 2026
f7d27b6
feat: implement batch sending for Bitswap blocks and enhance error ha…
sumanjeet0012 May 3, 2026
54d7ebf
refactor: clean up whitespace and improve code readability in Bitswap…
sumanjeet0012 May 3, 2026
fe1a152
refactor: enhance type hints for batch processing in Bitswap and Merk…
sumanjeet0012 May 3, 2026
49ad3ef
feat: add ProviderQueryManager for DHT-based provider discovery and c…
sumanjeet0012 May 4, 2026
e88f3dc
refactor: clean up type hints and remove unnecessary whitespace in te…
sumanjeet0012 May 4, 2026
f65ca73
refactor: remove unnecessary whitespace in Gossipsub test files
sumanjeet0012 May 4, 2026
55a91e0
newsfragment added
sumanjeet0012 May 4, 2026
21fb316
refactor: improve formatting of docstring in add_block method
sumanjeet0012 May 4, 2026
0cbec92
refactor: improve docstring clarity for add_block method parameters
sumanjeet0012 May 4, 2026
0f0b6bb
refactor: update provider_query_manager to use find_providers for DHT…
sumanjeet0012 May 6, 2026
4d1137a
refactor: enhance verify_record to support multiple key types and imp…
sumanjeet0012 May 6, 2026
9b359fd
feat: Implement Bitswap 1.3.0 with payment gating
sumanjeet0012 May 13, 2026
f4a6965
feat: Add ledger support to BitswapPaymentClient for tracking spent p…
sumanjeet0012 May 16, 2026
92f5718
refactor: remove unused multihash subprojects
sumanjeet0012 May 16, 2026
8715255
refactor: improve CID handling and formatting in Bitswap components
sumanjeet0012 May 17, 2026
350d3b0
feat(bitswap): Implement Bitswap 1.3.0 Payment Client and Ledger
sumanjeet0012 May 19, 2026
b73b9f3
Merge branch 'main' into kubo_com
sumanjeet0012 May 29, 2026
757b30a
refactor: matched the root CID with kubo.
sumanjeet0012 May 30, 2026
caa4267
feat: implement Bitswap interop with kubo completely
sumanjeet0012 May 31, 2026
be24050
feat: Implement Bitswap 1.3.0 Payment Extension
sumanjeet0012 Jun 14, 2026
bedc00a
feat: Enhance Bitswap payment extension and fix lint issues
sumanjeet0012 Jun 14, 2026
fac2614
fix: Correct logging format in QUICTransport for better clarity
sumanjeet0012 Jun 14, 2026
5fca4d1
Merge branch 'main' into improvement/bitswap
sumanjeet0012 Jun 14, 2026
95a03d1
added newsfragment file
sumanjeet0012 Jun 14, 2026
29f8453
fix: Improve formatting of docstring in verify_record function
sumanjeet0012 Jun 14, 2026
50d3c26
fix: lint issues
sumanjeet0012 Jun 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 76 additions & 25 deletions examples/bitswap/bitswap.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3

import argparse
import hashlib
import logging
from pathlib import Path
import sys
Expand All @@ -16,6 +17,7 @@
from libp2p.bitswap import BitswapClient
from libp2p.bitswap.cid import cid_to_bytes, format_cid_for_display
from libp2p.bitswap.dag import MerkleDag
from libp2p.crypto.ed25519 import create_new_key_pair
from libp2p.peer.peerinfo import info_from_p2p_addr
from libp2p.utils.address_validation import (
find_free_port,
Expand All @@ -35,6 +37,23 @@

logger = logging.getLogger(__name__)

DEFAULT_LISTEN_PORT = 4013


def select_preferred_listen_addr(addrs: list[Multiaddr], port: int) -> Multiaddr:
"""Pick a stable, local-friendly address for copy/paste commands."""
preferred_v4 = f"/ip4/127.0.0.1/tcp/{port}"
for addr in addrs:
if str(addr) == preferred_v4:
return addr

preferred_v6 = f"/ip6/::1/tcp/{port}"
for addr in addrs:
if str(addr) == preferred_v6:
return addr

return addrs[0]


def format_size(size_bytes: int) -> str:
"""Format size in human-readable form."""
Expand All @@ -46,13 +65,14 @@ def format_size(size_bytes: int) -> str:
return f"{size:.1f} TB"


async def run_provider(file_path: str, port: int = 0):
async def run_provider(file_path: str, port: int = 0, seed: str | None = None):
"""
Run the provider node to share a file.

Args:
file_path: Path to the file to share
port: TCP port to listen on (0 for auto)
seed: Optional seed string for deterministic peer ID generation

"""
file_path_obj = Path(file_path)
Expand All @@ -73,12 +93,19 @@ async def run_provider(file_path: str, port: int = 0):
if port <= 0:
port = find_free_port()
listen_addrs = get_available_interfaces(port)
# Create host
host = new_host()

async with host.run(listen_addrs=listen_addrs):
peer_id = host.get_id()
logger.info(f"Peer ID: {peer_id}")
# Create host with optional seed for deterministic peer ID
key_pair = None
if seed:
# Convert seed string to bytes (must be 32 bytes for Ed25519)
seed_bytes = hashlib.sha256(seed.encode()).digest()
key_pair = create_new_key_pair(seed=seed_bytes)
logger.info("Using deterministic peer ID from seed")

host = new_host(key_pair=key_pair)

async with host.run(listen_addrs=listen_addrs), trio.open_nursery() as nursery:
logger.info(f"Peer ID: {host.get_id()}")

# Get actual listening addresses
addrs = host.get_addrs()
Expand All @@ -91,7 +118,8 @@ async def run_provider(file_path: str, port: int = 0):
await bitswap.start()
logger.info("✓ Bitswap started")

# Create Merkle DAG
# Set nursery so bitswap can spawn background tasks
bitswap.set_nursery(nursery)
dag = MerkleDag(bitswap)

logger.info("")
Expand All @@ -109,7 +137,7 @@ def progress_callback(current: int, total: int, status: str):
# Add file with directory wrapper for filename preservation
# Always uses Merkle DAG regardless of file size
root_cid = await dag.add_file(
file_path, progress_callback=progress_callback, wrap_with_directory=True
file_path, progress_callback=progress_callback, wrap_with_directory=False
)

# Get all blocks that were stored
Expand All @@ -131,8 +159,10 @@ def progress_callback(current: int, total: int, status: str):
logger.info("FILE READY TO SHARE!")
logger.info("=" * 70)

# Get the first address (clean multiaddr without duplicate /p2p/)
provider_addr = host.get_addrs()[0]
# Prefer a deterministic local address for copy/paste commands.
transport_addrs = host.get_transport_addrs()
provider_addr = select_preferred_listen_addr(transport_addrs, port)
provider_addr = provider_addr.encapsulate(Multiaddr(f"/p2p/{host.get_id()}"))
root_cid_text = format_cid_for_display(root_cid)
logger.info(f"Root CID: {root_cid_text}")
logger.info("")
Expand Down Expand Up @@ -161,6 +191,7 @@ async def run_client(
root_cid_input: str,
output_dir: str = "/tmp",
port: int = 0,
seed: str | None = None,
):
"""
Run the client node to fetch a file.
Expand All @@ -170,6 +201,7 @@ async def run_client(
root_cid_input: Root CID (canonical text, /ipfs/... path, or hex string)
output_dir: Directory to save the file
port: TCP port to listen on (0 for auto)
seed: Optional seed string for deterministic peer ID generation

"""
output_path = Path(output_dir)
Expand All @@ -195,16 +227,24 @@ async def run_client(
port = find_free_port()
listen_addrs = get_available_interfaces(port)

# Create host
host = new_host()
# Create host with optional seed for deterministic peer ID
key_pair = None
if seed:
# Convert seed string to bytes (must be 32 bytes for Ed25519)
seed_bytes = hashlib.sha256(seed.encode()).digest()
key_pair = create_new_key_pair(seed=seed_bytes)
logger.info("Using deterministic peer ID from seed")

async with host.run(listen_addrs=listen_addrs):
host = new_host(key_pair=key_pair)

async with host.run(listen_addrs=listen_addrs), trio.open_nursery() as nursery:
logger.info(f"Client Peer ID: {host.get_id()}")

# Start Bitswap
bitswap = BitswapClient(host)
await bitswap.start()
logger.info("✓ Bitswap started")
bitswap.set_nursery(nursery)

try:
# Connect to provider
Expand All @@ -214,7 +254,6 @@ async def run_client(
await host.connect(peer_info)
logger.info("✓ Connected")

# Create Merkle DAG
dag = MerkleDag(bitswap)

logger.info("")
Expand All @@ -232,7 +271,7 @@ def progress_callback(current: int, total: int, status: str):
# Fetch file with automatic filename extraction
try:
file_data, filename = await dag.fetch_file(
root_cid, progress_callback=progress_callback
root_cid, progress_callback=progress_callback, timeout=120.0
)

# Show fetch statistics
Expand Down Expand Up @@ -284,18 +323,18 @@ def progress_callback(current: int, total: int, status: str):
logger.info("=" * 70)
logger.info(f"Size: {format_size(len(file_data))}")

# Determine output filename
# Determine output filename (priority: metadata > generated)
if filename:
output_filename = filename
logger.info(f"Filename: {filename} (from metadata)")
final_filename = filename
logger.info(f"Filename: {final_filename} (from metadata)")
else:
output_filename = (
final_filename = (
f"file_{format_cid_for_display(root_cid, max_len=16)}.bin"
)
logger.info(f"Filename: {output_filename} (no metadata)")
logger.info(f"Filename: {final_filename} (generated from CID)")

# Handle filename conflicts
output_file = output_path / output_filename
output_file = output_path / final_filename
if output_file.exists():
stem = output_file.stem
suffix = output_file.suffix
Expand All @@ -315,7 +354,9 @@ def progress_callback(current: int, total: int, status: str):
except Exception as e:
logger.error(f"Failed: {e}")
logger.exception("Full traceback:")
raise
finally:
pass # Nursery will cleanup background tasks
await bitswap.stop()


Expand All @@ -333,8 +374,8 @@ def parse_args():
parser.add_argument(
"--port",
type=int,
default=0,
help="Port to listen on (0 for random, provider mode only)",
default=DEFAULT_LISTEN_PORT,
help=("Port to listen on (default: 4012). Use 0 to auto-select a random port."),
)
parser.add_argument(
"--file",
Expand Down Expand Up @@ -365,6 +406,14 @@ def parse_args():
action="store_true",
help="Enable verbose logging",
)
parser.add_argument(
"--seed",
type=str,
help=(
"Seed string for deterministic peer ID generation "
"(same seed = same peer ID)"
),
)

args = parser.parse_args()

Expand Down Expand Up @@ -395,9 +444,11 @@ def main():
)

if args.mode == "provider":
trio.run(run_provider, args.file, args.port)
trio.run(run_provider, args.file, args.port, args.seed)
elif args.mode == "client":
trio.run(run_client, args.provider, args.cid, args.output, args.port)
trio.run(
run_client, args.provider, args.cid, args.output, args.port, args.seed
)
except Exception as e:
logger.critical(f"Script failed: {e}", exc_info=True)
sys.exit(1)
Expand Down
28 changes: 27 additions & 1 deletion libp2p/bitswap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@
New code should prefer the object-returning variants above.
"""

from .block_store import BlockStore, MemoryBlockStore
from .block_service import BlockService
from .block_store import BlockStore, FilesystemBlockStore, MemoryBlockStore
from .gated_decision_engine import PaymentGatedDecisionEngine
from .payment_ledger import PaymentLedger
from .pricing_engine import BlockPricingEngine
from .payment_client_1_3 import BitswapPaymentClient_1_3
from .cid import (
CID_V0,
CID_V1,
Expand Down Expand Up @@ -65,12 +70,33 @@
MessageTooLargeError,
TimeoutError,
)
from .wantlist import (
BitswapMessage,
BlockPresence,
BlockPresenceType,
Wantlist,
WantlistEntry,
WantType,
)

__all__ = [
# Core
"BitswapClient",
"BitswapPaymentClient_1_3",
"PaymentGatedDecisionEngine",
"PaymentLedger",
"BlockPricingEngine",
"BlockService",
"BlockStore",
"MemoryBlockStore",
"FilesystemBlockStore",
# Messages
"BitswapMessage",
"BlockPresence",
"BlockPresenceType",
"Wantlist",
"WantlistEntry",
"WantType",
# CID types
"CIDInput",
"CIDObject",
Expand Down
Loading
Loading