Đầu tiên để chạy fullnode bạn cần có 1 server trước đã. Mình sử dụng dịch vụ EC2 của Amazon để tạo.
Nếu bạn muốn sửa code thì xem hướng dẫn: How to build Nitro locally
Mục lục
Cài đặt fullnode trên Arbitrum
Có hai cách để chạy fullnode tương ứng 2 hướng dẫn mà mình tìm thấy trên mạng. Nhưng thực sự hơi lại cả hai cách này, lần đầu theo hướng dẫn của họ đều báo cùng lỗi:
arbitrum-node-nitro-node-1 | Version: v2.0.11-8e786ec, time: 2023-02-07T11:11:46-06:00
arbitrum-node-nitro-node-1 | Sample usage: /usr/local/bin/nitro --help
arbitrum-node-nitro-node-1 |
arbitrum-node-nitro-node-1 |
arbitrum-node-nitro-node-1 | Fatal configuration error: unable to create chain directory: mkdir /home/user/.arbitrum/arb1: permission denied
arbitrum-node-nitro-node-1 | Version: v2.0.11-8e786ec, time: 2023-02-07T11:11:46-06:00
arbitrum-node-nitro-node-1 | Sample usage: /usr/local/bin/nitro --help
arbitrum-node-nitro-node-1 |
arbitrum-node-nitro-node-1 |
arbitrum-node-nitro-node-1 | Fatal configuration error: unable to create chain directory: mkdir /home/user/.arbitrum/arb1: permission denied
arbitrum-node-nitro-node-1 | Version: v2.0.11-8e786ec, time: 2023-02-07T11:11:46-06:00
arbitrum-node-nitro-node-1 | Sample usage: /usr/local/bin/nitro --help
arbitrum-node-nitro-node-1 |
arbitrum-node-nitro-node-1 |
arbitrum-node-nitro-node-1 | Fatal configuration error: unable to create chain directory: mkdir /home/user/.arbitrum/arb1: permission denied
arbitrum-node-nitro-node-1 | Version: v2.0.11-8e786ec, time: 2023-02-07T11:11:46-06:00
arbitrum-node-nitro-node-1 | Sample usage: /usr/local/bin/nitro --help
arbitrum-node-nitro-node-1 |
arbitrum-node-nitro-node-1 |
arbitrum-node-nitro-node-1 | Fatal configuration error: unable to create chain directory: mkdir /home/user/.arbitrum/arb1: permission denied
arbitrum-node-nitro-node-1 | Version: v2.0.11-8e786ec, time: 2023-02-07T11:11:46-06:00
arbitrum-node-nitro-node-1 | Sample usage: /usr/local/bin/nitro --help
Đúng ra dữ liệu phải để ở thư mục của user hiện tại là /home/ubuntu, nhưng thực tế dữ liệu lại để ở thư mục /home/user/.arbitrum/arb1, có vẻ như Dev đang fix cứng thư mục này hoặc nhầm biến $user và xâu “user“. Vì vậy để quá trình diễn ra suôn sẻ bạn cần làm bước CHUẨN BỊ này trước:
// Chuyển sang tài khoản root
sudo -i
// Tạo thư mục
mkdir /home/user
mkdir /home/user/.arbitrum
mkdir /home/user/.arbitrum/arb1
chmod -fR 777 /home/user/.arbitrum
// Chuyển sang user thường
exit
Thêm nữa bạn cần link RPC của Ethereum nhé. Bạn có thể dùng node public hoặc từ dịch vụ từ các bên như Infura, Alchemy,… Ở đây để tiện chia sẻ tôi sử dụng public node, bạn có thể đổi sang RPC khác không ảnh hưởng gì nhé, chọn cái nào càng nhanh càng tốt:
https://ethereum.publicnode.com
Trong hai cách chạy node như ở dưới, bạn nên sử dụng C2 thì hợp lý hơn và dễ quản lý hơn sau này.
C1: Theo hướng dẫn sử dụng docker từ bên Arbitrum
Chi tiết hướng dẫn bạn xem tại: How to run a full node (Nitro). Nhưng thực sự nó không có cách lệnh theo từng bước nên cũng hơi khó dùng và phải mò mẫm thêm.
Các bạn nhớ làm bước CHUẨN BỊ ở trên trước nhé. Các lệnh để chạy node như sau:
// B1: Cài đặt docker
sudo apt update && sudo apt upgrade -y
sudo apt-get remove docker docker-engine docker.io containerd runc
sudo apt install curl -y
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
// B2: Chuyển sang account root
sudo -i
// B2: Pull snapshot về
docker pull offchainlabs/arb-node:v1.4.0-f4bbe91
// B3: chạy
docker run --rm -it -v /some/local/dir/arbitrum-mainnet/:/home/ubuntu/.arbitrum/mainnet -p 0.0.0.0:8547:8547 offchainlabs/arb-node:v1.4.0-f4bbe91 --l1.url https://ethereum.publicnode.com
C2: Theo hướng dẫn sử dụng docker-compose
Đây là hướng dẫn đầy đủ hơn từ Arbitrum One Node. Và tất nhiên bạn vẫn phải làm bước chuẩn bị ở trên.
// Cài đặt docker và docker-compose
sudo apt update && sudo apt upgrade -y
sudo apt-get remove docker docker-engine docker.io containerd runc
sudo apt install curl -y
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
// Xóa tệp cài đặt và cấp quyền
sudo rm -r get-docker.sh
sudo usermod -aG docker ubuntu
// Kiểm tra cài đặt docker
docker --version
docker compose version
// Tạo thư mục
sudo mkdir /home/user
sudo mkdir /home/user/.arbitrum
sudo mkdir /home/user/.arbitrum/arb1
sudo chmod -fR 777 /home/user/.arbitrum
sudo chmod -fR 777 /home/user/.arbitrum/arb1
sudo mkdir -p /home/ubuntu/arbitrum-node/data
sudo chmod -fR 777 /home/ubuntu/arbitrum-node/data
// Tạo cấu hình cho docker-compose
// Nội dung file xem phần dưới
cd arbitrum-node
sudo nano docker-compose.yml
// Chuyển sang tài khoản root
sudo -i
// Chạy node
cd /home/ubuntu/arbitrum-node
docker compose up -d
// Check log
cd /home/ubuntu/arbitrum-node
docker compose logs -f nitro-node
docker compose logs -f nitro-node -n 100
// Tắt node
docker compose stop
// Bật lại node
docker compose start
// Xóa node
docker compose down
// Check dung lượng
du -sh /home/ubuntu/arbitrum-node/data
du -sh /home/user/.arbitrum
Nội dung tệp docker-compose.yml:
version: '3.3'
services:
nitro-node:
network_mode: host
image: 'offchainlabs/nitro-node:v2.0.11-8e786ec'
user: 1000:1000
restart: always
stop_grace_period: 30s
volumes:
- '/home/ubuntu/arbitrum-node/data/:/home/user/.arbitrum'
ports:
- '0.0.0.0:8547:8547'
- '0.0.0.0:8548:8548'
command:
- --init.url=https://snapshot.arbitrum.foundation/arb1/nitro-pruned.tar
- --l1.url=https://rpc.ankr.com/eth
- --l2.chain-id=42161
- --http.api=net,web3,eth,debug
- --http.corsdomain=*
- --http.addr=0.0.0.0
- --http.vhosts=*
logging:
driver: json-file
options:
max-size: 10m
max-file: "10"
Cấu hình này chỉ chạy được RPC, không có WebSocket. Sau đó cấu hình lại như dưới để hỗ trợ cả WebSocket và Feeder:
version: '3.3'
services:
nitro-node:
network_mode: host
image: 'offchainlabs/nitro-node:v2.0.14-2baa834'
user: 1000:1000
restart: always
stop_grace_period: 30s
volumes:
- '/home/ubuntu/arbitrum-node/data/:/home/user/.arbitrum'
ports:
- '0.0.0.0:8547:8547'
- '0.0.0.0:8548:8548'
- '0.0.0.0:9642:9642'
command:
- --init.url=https://snapshot.arbitrum.foundation/arb1/nitro-pruned.tar
- --l1.url=https://rpc.ankr.com/eth
- --l2.chain-id=42161
- --http.api=net,web3,eth,debug
- --http.corsdomain=*
- --http.addr=0.0.0.0
- --http.vhosts=*
- --node.feed.output.addr=0.0.0.0
- --ws.api=net,web3,eth,arb,debug
- --ws.port=8548
- --ws.addr=0.0.0.0
- --ws.origins=*
- --node.staker.enable=false
- --node.transaction-streamer.execute-message-loop-delay=10ms
logging:
driver: json-file
options:
max-size: 10m
max-file: "10"
Sau đó chạy lại bằng lệnh sau:
// Chuyển đến thư mục chứa tệp cấu hình
cd /home/ubuntu/arbitrum-node
// Xóa node cũ
docker compose down
// Chạy lại node
docker compose up -d
Đến đây thì RPC và Websocket thì đã OK nhưng Feeder vẫn không được. Cuối cùng với Feeder đành chạy lệnh dưới để tạo Feeder Relay riêng:
docker run --rm -it -p 0.0.0.0:9642:9642 --entrypoint relay offchainlabs/nitro-node:v2.0.14-2baa834 --node.feed.output.addr=0.0.0.0 --node.feed.input.url=wss://arb1.arbitrum.io/feed --l2.chain-id=42161
Sau khi chạy xong bạn sẽ có:
// RPC
http://localhost:8547/
http://<IP>:8547/
// Websocket
ws://localhost:8548/
ws://<IP>:8548/
// Feeder
ws://localhost:9642/
ws://<IP>:9642/
Sử dụng fullnode
Một số thông tin khi chạy fullnode
Một số thông tin khi chạy node ngày 2023-07-25 mà tôi thống kê được:
- Về chi phí: Tôi sử dụng EC2 của AWS rơi đâu đó tầm 227$, bạn chỉ cần sử dụng cấu hình 1 là được, tất nhiên quá trình khởi tạo ban đầu sẽ lâu hơn:
- Cấu hình 1: Chi phí khoảng 227$ / tháng
- t2.large (2 vCPU + 8G RAM)
- SSD: 1600G
- Server tại zone US East (N. Virginia) – us-east-1
- Sử dụng thực tế:
- Quá trình đồng bộ CPU luôn ở trạng thái 90% đến 95%
- Cấu hình 2: Chi phí khoảng 280$ / tháng
- t3.xlarge: 4 vCPU + 16G RAM
- SSD: 1600G
- Server tại zone US East (N. Virginia) – us-east-1
- Sử dụng thực tế:
- Quá trình đồng bộ CPU khoảng 15% đến 50%
- Sau khi đồng bộ xong thì CPU dao động tầm 1% đến 3%,
- Cấu hình 1: Chi phí khoảng 227$ / tháng
- Thông tin khởi tạo (Cấu hình 2):
- Tải tệp https://snapshot.arbitrum.foundation/arb1/nitro-pruned.tar (Khoảng 272G) => Mất 1h12m
Các tệp snapshot có thể xem tại: https://snapshot.arbitrum.foundation/index.html - Giải nén và khởi tạo dữ liệu: 39 phút
- Đồng bộ các block còn lại (3.4 triệu block): Mất gần 20h
- CPU từ 20% đến 50%
- Response time truy vấn RPC từ VN: 790 ms
- Response time truy vấn RPC từ server khác cùng zone: 85 ms
- Tải tệp https://snapshot.arbitrum.foundation/arb1/nitro-pruned.tar (Khoảng 272G) => Mất 1h12m
- Thông tin khi ở chế độ bình thường (Cấu hình 2):
- CPU dao động từ 1% đến 3%, RAM sử dụng khoảng 6G, SSD 986G
- Đo dữ liệu RPC từ server:
- Response time:
- 73.5 mili seconds (15s/request)
- 40 mili seconds (Request liên tục) => Có thể do cơ chế cache
- Block: 2.442308
- Response time:
Sử dụng RPC
Sau khi node chạy okie xong thì rpc của nó có dạng:
http://localhost:8547
http://<IP>:8547
Lệnh sau để kiểm tra RPC node:
// Kiểm tra thử 1 public rpc
curl --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST https://arbitrum-one.publicnode.com
// Lấy blocknumber mới nhất qua RPC
curl --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST http://localhost:8547
// Xem fullnode có phải vẫn đang đồng bộ không
curl --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' -H "Content-Type: application/json" -X POST http://localhost:8547
// Lấy thông tin block mới nhất
curl --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", false],"id":1}' -H "Content-Type: application/json" -X POST http://localhost:8547
// Lấy chainId
curl --data '{"method":"eth_chainId","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST http://localhost:8547
// Lấy balance ETH
curl --data '{"method":"eth_getBalance","params":["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "latest"],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST http://localhost:8547
Chi tiết về các lệnh RPC, bạn có thể xem tại: Ethereum JSON-RPC-API
Hướng dẫn build Nitro từ mã nguồn
Nếu bạn muốn can thiệp sau hơn vào Nitro node thì bạn cần phải sửa mã nguồn, trước tiên bạn cần build để có thể chạy được trước đã.
Build trực tiếp theo hướng dẫn từ dự án
Hướng dẫn build xem: How to build Nitro locally (Docker, Debian). Tài liệu hướng dẫn này áp dụng cho Debian 11.7 (arm64) và Ubuntu 22.04 (amd64)
Phải đúng môi trường mới build được, các bước khá phức tạp nên mình chưa thực hiện theo cách này.
Build sử dụng docker
// Tải mã nguồn trực tiếp từ tệp zip
// Nếu dùng git clone thì bạn nhớ git clone cả sub project về
wget https://github.com/OffchainLabs/nitro/archive/refs/tags/v2.0.14.zip
unzip v2.0.14.zip
cd nitro-2.0.14
// Cập nhật một số file thiếu, do sub-module nên ko có trong dữ liệu tải về
// Lấy từ https://github.com/OffchainLabs/nitro/tree/v2.0.14
// Lấy toàn bộ tệp trong thư mục blockscout, brotli, fastcache, go-ethereum về thư mục tương ứng
// Build sử dụng docker
sudo docker build -t nitro-node-arbi:1.0 .
Khi build mất tầm 15 phút đến 30 phút, sau khi build xong bạn được tệp image như hình:
Tìm hiểu sâu hơn mã nguồn của fullnode nitro
Mã nguồn fullnode nitro tại: https://github.com/OffchainLabs/nitro/tree/v2.0.14
Cơ chế đồng bộ của Fullnode
Arbitrum không có cơ chế đồng thuận, vì vậy “p2p mode” không sử dụng. Để các nút đồng bộ hóa với trạng thái chuỗi mới nhất, chúng kết nối với nút L1 để đồng bộ hóa lịch sử của chuỗi đã được đăng trong calldata và kết nối với nguồn cấp Sequencer cho các giao dịch chưa được đăng theo lô. Chi tiết xem: Can I run an Arbitrum node in p2p mode? và The Sequencer and Censorship Resistance
Như vậy các node đồng bộ hóa thông qua L1 RPC và Sequencer. Về dữ liệu L1 vài phút mới cần cập nhật 1 lần, vì vậy xử lý dữ liệu từ Sequencer quan trọng, ảnh hưởng tới tốc độ đồng bộ dữ liệu từ server. Để lấy dữ liệu từ Sequencer chúng ta cần kết nối tới một Sequencer Feeder và cách đọc xem bài: How to read the Sequencer feed. Sequencer Feeder:
- wss://arb1.arbitrum.io/feed => Nguồn dữ liệu gốc, fullnode khi chạy cũng kết nối tới feeder này
- Kết nối tới một Feed Relay. Tạo Feed Relay theo hướng dẫn: How to run a feed relay
Sequencer là tập trung, làm nhiệm vụ sắp xếp các giao dịch, thông thường giao dịch nào đến trước sẽ được xử lý trước. Các giao dịch này đồng thời public tới các node để các node đồng bộ và tính toán trạng thái mới nhất của blockchain. Sau một khoảng thời gian (Khoảng vài phút), Sequencer cũng sẽ đẩy 1 lô các giao dịch L2 sang L1. Các node làm Staker sẽ có nhiệm vụ xác thực các giao dịch trên L1.
Vòng đời một giao dịch trên Arbitrum
Tổng quan vòng đời giao dịch trên Arbitrum xem: Overview: The Lifecycle of an Arbitrum Transaction
Tìm hiểu về luồng nhận message từ feeder và cập nhật trạng thái mới nhất
Theo như tìm hiểu được, Fullnode sẽ kết nối tới Sequencer (wss://arb1.arbitrum.io/feed) để lấy thông tin các giao dịch trên L2, từ đó Fullnode sẽ tính toán để ra trạng thái mới nhất của blockchain.
Chi tiết code:
- Bắt đầu từ hàm Start() in arbnode\node.go => Gọi tới hàm n.TxStreamer.Start(ctx)
- Hàm Start() in arc node\transaction_streamer.go => Gọi hàm executeMessages()
- Hàm executeMessages() in arc node\transaction_streamer.go => Call executeNextMsg()
Trả về ExecuteMessageLoopDelay, mặc định tham số này 100ms. Tôi tìm thấy cấu hình này trong Command-line options:
–node.transaction-streamer.execute-message-loop-delay duration delay when polling calls to execute messages (default 100ms) - Hàm executeNextMsg() in arbnode\transaction_streamer.go => Gọi hàm s.exec.DigestMessage(pos, msg)
- Hàm DigestMessage() in arbnode/execution/executionengine.go => Gọi hàm s.digestMessageWithBlockMutex(num, msg)
- Hàm digestMessageWithBlockMutex() in arbnode/execution/executionengine.go => Hàm xử lý chính để tạo block.
Như vậy theo như luồng code thì Fullnode sẽ nhận liên tục các message từ Sequencer, với mỗi message nhận được, fullnode sẽ xử lý để tạo ra block mới. Vòng lặp này thực hiện 100ms một lần, muốn đẩy nhanh ta giảm thời gian vòng lặp này xuống bằng cách sửa tham số:
--node.transaction-streamer.execute-message-loop-delay=10ms
Thêm log vào source code
Do yêu cầu cần đo thời gian một số sự kiện trên Fullnode, cụ thể:
- (1) Thời gian fullnode nhận data từ Feeder
- (2) Thời gian fullnode hoàn thành xử lý block mới
Có hỏi bên OffchainLabs nó báo, cái (1) có thể thêm log ở broadcastclient.go#L376, còn cái (2) nó đã có sẵn ở executionengine.go#L603. Nhưng xem code cái (2) thì thấy nó là thread khác để ghi log, ghi log 1s 1 lần, chứ không phải đúng là thời gian hoàn thành xử lý block mới.
Theo như code thì bắt đầu xử lý block ở executionengine.go#L516 và kết thúc xử lý block ở executionengine.go#L529 => Đây là chỗ cần thêm log cho thằng (2)
Luồng bắn các log events cho client
Phần xử lý sinh ra các events nằm blockchain.go#L1445
Trả lời