I. Mở đầu

Xin chào mọi người, đây là T.N từ Vietnamlab Center .

Dạo gần đây mình thật sự rất hứng thú với Blockchain, không chỉ bởi vì những thành tựu mà Blockchain đóng góp cho nền công nghiệp Internet hiện tại hay giá trị của các đồng crypto tăng lên hằng ngày mà những ý tưởng công nghệ xây dựng nên các nền tảng Blockchain cũng vô cùng hấp dẫn.

Ở blog này mình muốn giới thiệu đến các bạn tập thư viện libP2P, đây là giải pháp triển khai mô hình mạng ngang hàng mà nhiều blockchain hiện nay đang sử dụng như: Solana, Polkadot...

Nội dung Blog sẽ bao gồm các phần như sau:

  • Giới thiệu về mạng P2P
    Ở phần này, mình sẽ phân biệt 2 mô hình mạng P2P và mạng Client-server một cách đơn giản nhất
  • Giới thiệu về libP2P
    Phần này sẽ nói về cách hoạt động của LibP2P. Để có thể hiểu về nó hy vọng các bạn có thể đọc qua về các Yếu tố
  • Demo một ứng dụng chat được xây dựng bằng thư viện libP2P trên ngôn ngữ Rust
    Phần này chúng ta sẽ clone một phần mềm sử dụng libp2p và phân tích nó, thông qua đó có thể nắm được những công việc cần làm khi xây dựng ứng dụng sử dụng libp2p

II. Kết nối  P2P

1. So sánh với mạng Client-server

Mạng P2P hay còn được biết đến tên gọi là mạng ngang hàng, hay là mạng đồng đẳng. Các máy tính trong mạng này kết nối trực tiếp với nhau mà không có server trung gian nào.

Để dễ hình dung mình có 2 sơ đồ như sau

Client-server P2P
Trong mô hình mạng Server-Client, các thông tin được trao đổi thông qua một máy chủ trung tâm quản lý cơ sở dữ liệu Đối với mạng P2P, mỗi một node tham gia vào mạng đóng vai trò như là một server, chia sẻ thông tin dữ liệu với nhau

2. Xây dựng kết nối P2P

Trong môi trường lý tưởng (các thiết bị ở trong cùng một văn phòng, hoặc các thiết bị tương đối ở gần nhau) kết nối P2P dễ dàng thực hiện bằng kết nối dây giữa các thiết bị với nhau.

Tuy nhiên, trên một mạng diện rộng (WAN) bạn cần có một đường dây riêng, vì vậy bạn cần thuê 1 đường dây từ nhà cung cấp dịch vụ network ở quốc gia, hoặc khu vực mà bạn đặt các server. Điều này khá là tốn kém

Trong trường hợp bạn cần một hệ thống P2P  mang tính toàn cầu thì điều này khá khó khăn. Protocol Labs đã đề xuất 1 giải pháp sử dụng LibP2P để có thể xây dựng một hệ thống mang tính P2P (Hệ thống có tính chất trao đổi thông tin như một mạng P2P, không có server quản lý dữ liệu tập trung)

III. Giới thiệu libP2P (rust-libp2p)

LibP2P là một thư viện xây dựng cho việc tạo ra các mạng ngang hàng phi tập trung.

Để dễ hình dung về chức năng của thư viện này, mình xin đơn cử một số hệ thống sử dụng mạng ngang hàng: BitTorrent, Bitcoin, I2P, IPFS.

Hệ thống ngang hàng thường bao gồm 3 hoặc 4 yếu tố sau:

  • Mỗi nút trên mạng được xác định bởi một khóa công khai (public_key)
  • Hệ thống tìm kiếm các node ngang hàng (Cách thức để tham gia vào mạng)
  • (Tùy chọn) Hệ thống phát và theo dõi (publish-subscribe): Là hệ thống thông báo sự kiện cho toàn mạng
  • (Tùy chọn) Một bảng lưu trữ các mã băm phân tán (Distributed hash table - DHT): Nơi lưu trữ các dữ liệu

Và LibP2P sẽ giúp chúng ta thực hiện được những yếu tố trên

1. Yếu tố 1: Public_key

Danh tính của một node trên mạng được xác định bởi khóa công khai (Public_key)

Ví dụ một đoạn hội thoại như:

  • Hey! I’m fKcneuV8aEq7RRMcvyjz. Who are you?
  • I’m UdJP64yER3tUdC7Z7GZJ. Nice to meet you

Định dạng của một public_key là một chuỗi hash của một dữ liệu nhỏ, chũôi này có thể đươc mã hóa ở định dạng RSA, ed25519, secp256k1 hoặc bất kỳ các hệ thống mã hóa đối xứng nào

public_key của 1 node mã hóa những thông tin giao tiếp với nó. Vì vậy việc đánh cấp thông tin của một node là điều bất khả thi.

2. Yếu tố 2: Hệ thống tìm kiếm các node trong mạng

Để tham gia vào một mạng phi tập trung, cần phải có một máy có khả năng phát hiện các máy khác, các phần khác ở trong mạng và cách để kết nối với chúng.

 Trong một môi trường mạng lý tưởng như mạng cục bộ, các node có thể được phát hiện thông qua các giao thức mDNS, DNS-SD. Và trong mạng LAN, bạn có thể ping đến các node trong cùng mạng một cách dễ dàng, các giao thức này đều đã được sẵn sàng.

Tuy nhiên, trên một môi trường rộng lớn như Internet, chúng ta cần những nodes gọi là bootstrap. ( Đây là một địa chỉ mà chúng ta sẽ phải fix cứng ở trong các program của mình, và program này sẽ phải kết nối tới những bootstrap đó).Khi 2 node được kết nối với nhau, chúng có thể share cho nhau những phần còn lại của mạng

3. Yếu tố 3: Hệ thống Public-subcribe

Broadcast các event qua network để tất cả các node quan tâm đến sự kiện này nhận được chúng

Ví dụ:

  • Trong một ứng dụng Chat, các messages được Broadcast trên mạng
  • Trong công nghệ blockchain, khi một transaction hoặc một block được tạo ra
  • Truyên tải các thông tin về cấu trúc mạng để tăng khả năng kết nối

Các sự kiện thường bao gồm một chủ đề. Chỉ những node quan tâm đến chủ đề mới cần được thông báo

4. Yếu tố 4: Distributed hash table

Thuật toán phổ biến nhất của DHT là: Kademlia

Bảng băm này chứa những dữ liệu để liên lạc với những node mà có địa chỉ gần nhất với một giá trị cụ thể nào đó.

Vd: Tìm kiếm 20 các giá trị mà có PeerId cách nó 1 bit

IV. Ứng dụng chat qua mạng P2P (examples/chat.rs)

Để hiểu rõ hơn về LibP2P các bạn có thể đọc thêm tại https://libp2p.io/

Bây giờ chúng ta sẽ clone một ứng dụng chat đơn giản bằng Rust và sử dụng LibP2P để kiểm tra 1 vài yếu tố

Đầu tiên để biên dịch được một chương trình viết bằng Rust thì cũng ta phải cài rust trên máy, các bạn có thể tham khảo ở đường dẫn sau https://www.rust-lang.org/tools/install

Tiếp theo các bạn có thể clone repository libp2p và các example tại https://github.com/libp2p/rust-libp2p .

Ứng dụng mà chúng ta dùng để nghiên cứu là examples/chat.rs

1. Tạo bootstrap node

 // Create a Swarm to manage peers and events
    let mut swarm = {
        let mdns = task::block_on(Mdns::new(MdnsConfig::default()))?;
        let mut behaviour = MyBehaviour {
            floodsub: Floodsub::new(local_peer_id),
            mdns,
            ignored_member: false,
        };

        behaviour.floodsub.subscribe(floodsub_topic.clone());
        Swarm::new(transport, behaviour, local_peer_id)
    };

Các bạn có thể thấy đoạn code trên tại dòng 116 trong source,  đây là đoạn khởi tạo 1 bootstrap có sử dụng giao thức mDNS để phục vụ yếu tố Public-subcribe

Sau đó bootstrap được khởi động như sau:

// Listen on all interfaces and whatever port the OS assigns
    swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;

Đoạn code này sẽ tạo 1 service lắng nghe ở 1 cổng bất kỳ nào đó trên node bootstrap. Ta có thể sửa nó thành 1 địa chỉ cố định như sau

 swarm.listen_on("/ip4/0.0.0.0/tcp/6969".parse()?)?;

sau khi start chương trình cargo run --example chat

Chúng ta sẽ thấy một thông báo như sau

Local peer id: PeerId("12D3KooWSzUj3UHLCvdrNgWibfXvwAyjX9B5KnSzJ6HuJT2GpPYJ")
Listening on "/ip4/127.0.0.1/tcp/6969"
Listening on "/ip4/192.168.72.219/tcp/6969"
Listening on "/ip4/172.18.0.1/tcp/6969"
Listening on "/ip4/172.17.0.1/tcp/6969"
Listening on "/ip4/10.2.20.12/tcp/6969"
Listening on "/ip4/10.2.22.22/tcp/6969"
Listening on "/ip4/172.19.0.1/tcp/6969"

PeerID: 12D3KooWSzUj3UHLCvdrNgWibfXvwAyjX9B5KnSzJ6HuJT2GpPYJ

Chính là public_key đươc đề cập đến ở Yếu tố 1 dùng để xác định các node tham gia vào mạng

2. Tạo các node tham gia vào mạng

Tại 2 node khác nhau (Node được bootstrap cho phép truy cập vào cồng 6969)

Chúng ta cùng khởi động chương trình

Node 1: cargo run --example chat -- /ip4/192.168.72.219/tcp/6969

Local peer id: PeerId("12D3KooWSEnWXnBYzB1VJcnS3EiqSeayfytGyNfkt9jRXh9xtGRC")
Dialed "/ip4/192.168.72.219/tcp/6969"
Listening on "/ip4/127.0.0.1/tcp/46321"
Listening on "/ip4/192.168.72.224/tcp/46321"

Node 2: cargo run --example chat -- /ip4/192.168.72.219/tcp/6969

Local peer id: PeerId("12D3KooWSEnWXnBYzB1VJcnS3EiqSeayfytGyNfkt9jRXh9xtGRC")
Dialed "/ip4/192.168.72.219/tcp/6969"
Listening on "/ip4/127.0.0.1/tcp/35355"
Listening on "/ip4/192.168.72.225/tcp/35355"

Sau khi gửi thông điệp từ 2 node ta có thể thấy các event xuất hiện trên màn hình

Node 1:

bbbbbbbb
Received: '"aaaaaaa"' from PeerId("12D3KooWJofCsqFXX5cS5N1d63xZyG4zkGKYQTfqkJ4dDrKXBhdQ")

Node 2:

aaaaaaa
Received: '"bbbbbbbb"' from PeerId("12D3KooWSEnWXnBYzB1VJcnS3EiqSeayfytGyNfkt9jRXh9xtGRC")

Broadcast node:

Received: '"aaaaaaa"' from PeerId("12D3KooWJofCsqFXX5cS5N1d63xZyG4zkGKYQTfqkJ4dDrKXBhdQ")
Received: '"bbbbbbbb"' from PeerId("12D3KooWSEnWXnBYzB1VJcnS3EiqSeayfytGyNfkt9jRXh9xtGRC")

Như vậy ta có thể thấy Broadcast node đã tìm kiếm các thông tin các node kết nối với nó và broadcast event đến toàn mạng như đã đề cập đến ở Yếu tố 2, và Yếu tố 3

Ngoài ra ta cũng có thể thấy được phần đầu tiên của Hash PeerId 12D3KooW giống nhau, đây là tiền đề để Yếu tố 4 phát huy khả năng tìm kiếm của mình, tuy nhiên trong demo này chưa có cơ hội để thể hiện rõ điều đó

V. Tài liệu tham khảo

1. https://docs.libp2p.io/

2. https://www.youtube.com/watch?v=HqSXFlCwgMY