Hiểu sâu về thiết kế mạng Ethereum P2P từ lớp mã
lời tựa
lời tựa
Bài viết này sẽ giới thiệu sơ lược về sự phát triển của mạng P2P, đồng thời giới thiệu sơ lược về cơ chế khám phá nút, bảng băm phân tán, tra cứu nút, thêm nút và loại bỏ nút trong Ethereum dưới góc độ mã nguồn. Một phân tích ngắn gọn về thiết kế bảo mật mạng P2P.
kiến thức cơ bản
kiến thức cơ bản
mạng ngang hàng
Mạng P2P khác với cấu trúc CS truyền thống, trong mạng P2P, mỗi nút có thể là máy khách hoặc máy chủ, giao thức liên lạc giữa các nút thường được thực hiện trực tiếp thông qua Socket.
Sự phát triển của công nghệ P2P đã trải qua 4 giai đoạn phát triển sau:
1. Tập trung:
Đây là phương thức định tuyến đơn giản nhất trong chế độ mạng P2P, tức là có một nút trung tâm, nút này lưu trữ thông tin chỉ mục của tất cả các nút khác, thông tin chỉ mục thường bao gồm IP nút, cổng, tài nguyên nút, v.v. định tuyến là nó có cấu trúc đơn giản và dễ thực hiện. Dễ dàng, nhưng nhược điểm cũng rõ ràng. Vì nút trung tâm cần lưu trữ thông tin định tuyến của tất cả các nút, nên khi quy mô nút tiếp tục mở rộng, tắc nghẽn hiệu suất dễ xảy ra và cũng có một vấn đề lỗi nút đơn
2. Phân bố:
Có nghĩa là nút trung tâm bị loại bỏ, mạng ngẫu nhiên được thiết lập giữa các nút P2P và kênh kết nối được thiết lập ngẫu nhiên giữa nút mới được thêm vào và nút trong mạng P2P, từ đó hình thành cấu trúc cấu trúc liên kết ngẫu nhiên khi một nút mới tham gia mạng, nó chọn ngẫu nhiên một nút hiện có và thiết lập mối quan hệ hàng xóm, sau khi nút thiết lập kết nối với nút hàng xóm, nó cần phát toàn bộ mạng để toàn mạng biết sự tồn tại của nút."Các bước phát cụ thể là: nút đầu tiên phát đến các nút hàng xóm và sau khi nút hàng xóm nhận được thông báo phát, nó tiếp tục phát đến các nút hàng xóm của chính nó, v.v., phương thức phát này còn được gọi là"cơ chế lũ lụt
, và vấn đề của cơ chế tràn ngập là khả năng kiểm soát kém, chủ yếu bao gồm hai vấn đề lớn: một là dễ hình thành chu kỳ tràn ngập, ví dụ, nút A gửi tin nhắn đến nút B đến nút C và nút C phát sóng đến nút C A, điều này tạo thành một chu kỳ. Một vấn đề khác là hợp đồng phụ trả lời tin nhắn. Ví dụ: nếu tài nguyên mà nút A muốn yêu cầu thuộc sở hữu của nhiều nút, thì trong một khoảng thời gian ngắn, một số lượng lớn các nút sẽ gửi thông báo phản hồi đến nút A cùng một lúc. Điều này có khả năng khiến nút A bị sập ngay lập tức
Phương pháp loại bỏ vòng lặp tràn ngập có thể học hỏi từ việc kiểm soát phát sóng tràn ngập trong các giao thức định tuyến mạng IP. Một phương pháp là đặt giá trị TTL cho mỗi thông báo truy vấn. Mỗi khi chuyển tiếp thông báo tràn ngập, giá trị TTL sẽ giảm đi 1. Khi nút chấp nhận Khi TTL bằng 0, tin nhắn sẽ không được chuyển tiếp, điều này có thể tránh vòng lặp vô tận của tin nhắn truy vấn trong mạng và cũng có thể đặt cờ duy nhất cho tin nhắn lũ lụt và sẽ không chuyển tiếp bản sao nhận được tin nhắn để tránh vòng lặp vô tận.Phương pháp ứng phó với cơn bão có thể thực hiện phân đoạn mạng ở lớp liên kết dữ liệu để giảm phân đoạn truyền phát tin nhắn chéo
3. Lai ghép:
Loại kết hợp thực sự là một cấu trúc tập trung và phân tán hỗn hợp, có nhiều mạng phân tán bao gồm các siêu nút trong mạng và mỗi siêu nút có nhiều nút thông thường để tạo thành một mạng tập trung cục bộ với nó. Có thể thêm một nút thông thường mới vào trước . Chọn một siêu nút để liên lạc và siêu nút sẽ đẩy các danh sách siêu nút khác sang nút tham gia mới. Nút tham gia quyết định chọn siêu nút cụ thể làm nút cha theo trạng thái của siêu nút trong danh sách. Việc phát sóng lũ của cấu trúc này chỉ xảy ra giữa các siêu nút, có thể tránh được lũ quy mô lớn. dựa trên cấu trúc lai. Phát triển và thực hiện
4. Cơ cấu:
Mạng P2P có cấu trúc là cấu trúc mạng phân tán. Khác với cấu trúc phân tán đã đề cập ở trên, mạng phân tán là mạng ngẫu nhiên, trong khi mạng có cấu trúc tổ chức tất cả các nút một cách có trật tự theo một cấu trúc nhất định, chẳng hạn như tạo thành mạng Vòng hoặc mạng cây, mạng có cấu trúc thường dựa trên thuật toán bảng băm phân tán (Bảng băm phân tán, DHI) trong triển khai cụ thể, các sơ đồ triển khai cụ thể bao gồm Chord, Pasty, CAN, Kademlia và các thuật toán khác
Bốn cấu trúc mạng được so sánh như sau:
khám phá nút
Phát hiện nút là bước đầu tiên để bất kỳ nút nào truy cập vào mạng P2P. Phát hiện nút có thể được chia thành hai loại:
- Phát hiện nút ban đầu:
Đề cập đến nút là một nút hoàn toàn mới chưa từng hoạt động. Nút này không có bất kỳ dữ liệu nào của các nút khác trong mạng, tại thời điểm này, nút nhận thấy rằng nó chỉ có thể dựa vào các nút gốc được mã hóa cứng trong nút để lấy thông tin của mạng P2P.
- Phát hiện nút đã biết:
Nút đã chạy trước đó và cơ sở dữ liệu nút lưu thông tin của các nút khác trong mạng, lúc này nút thấy rằng nó có thể dựa vào các nút được tóm tắt trong cơ sở dữ liệu nút để lấy thông tin của mạng P2P, vì vậy để xây dựng cấu trúc liên kết mạng riêng của mình
nút hạt giống"Trong mạng P2P, nút ban đầu sẽ nhanh chóng khám phá các nút khác trong mạng thông qua một số nút ổn định lâu dài khi khởi động, các nút này được gọi là"nút hạt giống
DNS-Seed:
(Thông tin về nút gốc sẽ được mã hóa cứng trong mã) Nhìn chung, nút gốc có thể được chia thành hai loại:"còn được biết là"Nút hạt DNS
IP-Seed:
, DNS là một dịch vụ truy vấn tên miền do Internet cung cấp. Nó ánh xạ tên miền và địa chỉ IP và lưu trữ chúng trong cơ sở dữ liệu phân tán. Khi chúng ta truy cập máy chủ DNS, hãy cung cấp cho nó một tên miền và máy chủ DNS sẽ tương ứng với tên miền địa chỉ IP trả lại
Tức là địa chỉ IP của nút gốc được mã hóa cứng thành mã và địa chỉ của các nút mã hóa cứng này được gọi là nút hạt giống
thuật toán KDA
HashTable
Kademlia là công nghệ bảng băm phân tán (DHT). So với các công nghệ DHT khác, thuật toán KDA sử dụng thuật toán XOR để tính toán khoảng cách giữa các nút, sau đó thiết lập cấu trúc liên kết DHT mới. Thuật toán này có thể cải thiện đáng kể tốc độ truy vấn của tuyến đường .
Bảng băm là một vùng chứa để lưu trữ các cặp khóa-giá trị. Các cặp khóa-giá trị được gọi là cặp Khóa/Giá trị. Cấu trúc dữ liệu bảng băm chứa N nhóm. Đối với một bảng băm cụ thể, N (số lượng nhóm) thường cố định, vì vậy bạn có thể đánh số thứ tự cho mỗi nhóm 0~N-1 và nhóm này được dùng để lưu trữ các cặp key-value, có thể hiểu đơn giản là một mảng động, lưu trữ Nhiều cặp key-value.
HashCollision
Hình dưới đây cho thấy nguyên tắc tìm kiếm của bảng băm. Chúng ta có thể dễ dàng và nhanh chóng lấy được giá trị thông qua Khóa. Khi sử dụng một khóa nào đó để tìm kiếm, trước tiên hãy sử dụng một hàm băm nhất định để tính toán giá trị băm của khóa và giá trị thu được hash Giá trị thường là một số nguyên, sau đó giá trị hash được sử dụng để thực hiện thao tác modulo trên N (số lượng nhóm) (phần dư của phép chia) và có thể tính toán số lượng nhóm tương ứng
Khi nói đến bảng băm, chúng ta phải đề cập đến xung đột bảng băm, khi hai Khóa khác nhau thực hiện các phép tính băm để thu được giá trị băm giống nhau, đó được gọi là xung đột hàm băm, một khi điều này xảy ra, hai khóa sẽ tương ứng với hai khóa Các cặp -value sẽ được lưu trữ trong cùng một nhóm (bucket) Một xung đột băm khác là mặc dù các giá trị băm được tính toán là khác nhau nhưng cùng một số nhóm thu được sau thao tác modulo. Đôi khi, hai cặp khóa-giá trị sẽ được lưu trữ trong một thùng Nguyên tắc xung đột băm được thể hiện trong hình bên dưới:
Nếu một hash table hoàn toàn không có xung đột khi lưu dữ liệu thì trong mỗi bucket chỉ có 0 hoặc 1 cặp key-value nên tra cứu rất nhanh, ngược lại nếu hash table xuất hiện khi lưu dữ liệu thì nghiêm trọng. xung đột sẽ khiến nhiều cặp khóa-giá trị được lưu trữ trong một số nhóm nhất định. Khi tìm kiếm khóa, cần phải so sánh xem từng khóa trong nhóm này có giống nhau hay không, hiệu quả tìm kiếm sẽ trở nên rất thấp ~
bảng băm phân tán
Bảng băm phân tán về mặt khái niệm tương tự như bảng băm truyền thống, điểm khác biệt là bảng băm truyền thống chủ yếu được sử dụng trong một phần mềm nhất định trên một máy và bảng băm phân tán chủ yếu được sử dụng trong hệ thống phân tán (tại thời điểm này, phân tán Các nút của hệ thống có thể được hiểu một cách phổ biến là các thùng trong bảng băm). :
phân tích mã nguồn
P2PServer cơ bản của Ethereum có thể được chia thành ba lớp sau:
- lớp trên cùng:
Việc triển khai cụ thể từng giao thức trong Ethereum
- lớp trung lưu:
Lớp liên kết giao tiếp p2p trong Ethereum chịu trách nhiệm bắt đầu theo dõi, xử lý các kết nối mới hoặc duy trì kết nối
- Lớp đáy:
Lớp IO của mạng truyền thông dữ liệu trong Ethereum chịu trách nhiệm chính cho việc quản lý bảng định tuyến và các hoạt động đọc và ghi của cơ sở dữ liệu
cấu trúc bảng
// filedir:go-ethereum-1.10.2\p2p\discover\table.go L40
const (
alpha = 3 // Kademlia concurrency factor
bucketSize = 16 // Kademlia bucket size
maxReplacements = 10 // Size of per-bucket replacement list
// We keep buckets for the upper 1/15 of distances because
// it's very unlikely we'll ever encounter a node that's closer.
hashBits = len(common.Hash{}) * 8
nBuckets = hashBits / 15 // Number of buckets
bucketMinDistance = hashBits - nBuckets // Log distance of closest bucket
// IP address limits.
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
tableIPLimit, tableSubnet = 10, 24
refreshInterval = 30 * time.Minute
revalidateInterval = 10 * time.Second
copyNodesInterval = 30 * time.Second
seedMinTableTime = 5 * time.Minute
seedCount = 30
seedMaxAge = 5 * 24 * time.Hour
)
type Table struct {
mutex sync.Mutex // protects buckets, bucket content, nursery, rand
buckets [nBuckets]*bucket // index of known nodes by distance
nursery []*node // bootstrap nodes
rand *mrand.Rand // source of randomness, periodically reseeded
ips netutil.DistinctNetSet
log log.Logger
db *enode.DB // database of known nodes
net transport
refreshReq chan chan struct{}
initDone chan struct{}
closeReq chan struct{}
closed chan struct{}
nodeAddedHook func(*node) // for testing
}
type bucket struct {
entries []*node // live entries, sorted by time of last contact
replacements []*node // recently seen nodes to be used if revalidation fails
ips netutil.DistinctNetSet
}
Cấu trúc dữ liệu bảng trông như thế này:
Một vài biến chính:
buckets: K buckets, mỗi K bucket chứa các nút (sắp xếp giảm dần theo hoạt động gần đây), dùng để liệt kê các chỉ mục nút đã biết theo khoảng cách
Vườn ươm: nút hạt giống, khi một nút được khởi động, nó có thể liên kết tối đa 35 nút hạt giống, năm trong số đó được Ethereum chỉ định chính thức và 30 nút còn lại được trích xuất từ cơ sở dữ liệu
db: cơ sở dữ liệu được sử dụng để lưu trữ các nút P2P (có hai nút trong Ethereum và nút còn lại được sử dụng để lưu trữ dữ liệu trên chuỗi)
- refreshReq: đường dẫn để làm mới các sự kiện K-bucket
tạo bảng
// filedir:go-ethereum-1.10.2\p2p\discover\table.go L102
func newTable(t transport, db *enode.DB, bootnodes []*enode.Node, log log.Logger) (*Table, error) {
tab := &Table{
net: t,
db: db,
refreshReq: make(chan chan struct{}),
initDone: make(chan struct{}),
closeReq: make(chan struct{}),
closed: make(chan struct{}),
rand: mrand.New(mrand.NewSource(0)),
ips: netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit},
log: log,
}
if err := tab.setFallbackNodes(bootnodes); err != nil {
return nil, err
}
for i := range tab.buckets {
tab.buckets[i] = &bucket{
ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
}
}
tab.seedRand()
tab.loadSeedNodes()
return tab, nil
}
Hàm newTable được sử dụng để tạo một bảng mới:
Trong đoạn mã trên, tab đối tượng Bảng trước tiên được khởi tạo với các tham số đến và hàm setFallbackNodes được gọi để đặt các nút liên kết ban đầu (nghĩa là để có được 5 nút y tá, sẽ được sử dụng sau này nếu bảng trống hoặc không có thông tin về nút trong cơ sở dữ liệu. để liên kết mạng), sau đó sử dụng vòng lặp for kết hợp với hàm ValidateComplete để xác minh xem nút có hợp lệ hay không
for i := range tab.buckets {
tab.buckets[i] = &bucket{
ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
}
}
Sau đó khởi tạo thùng K:
tab.seedRand()
tab.loadSeedNodes()
return tab, nil
Sau đó, chọn ngẫu nhiên 30 nút từ table.buckets và tải nút gốc vào nhóm tương ứng:
// filedir: go-ethereum-1.10.2\p2p\discover\table.go L302
func (tab *Table) loadSeedNodes() {
seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge))
seeds = append(seeds, tab.nursery...)
for i := range seeds {
seed := seeds[i]
age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) }}
tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
tab.addSeenNode(seed)
}
}
Cách triển khai cụ thể của hàm loadSeedNodes như sau (seedCount ở đây là biến toàn cục được xác định ở đầu table.go, với giá trị là 30):
// filedir: go-ethereum-1.10.2\p2p\discover\table.go L458
func (tab *Table) addSeenNode(n *node) {
if n.ID() == tab.self().ID() {
return
}
tab.mutex.Lock()
defer tab.mutex.Unlock()
b := tab.bucket(n.ID())
if contains(b.entries, n.ID()) {
// Already in bucket, don't add.
return
}
if len(b.entries) >= bucketSize {
// Bucket full, maybe add as replacement.
tab.addReplacement(b, n)
return
}
if !tab.addIP(b, n.IP()) {
// Can't add: IP limit reached.
return
}
// Add to end of bucket:
b.entries = append(b.entries, n)
b.replacements = deleteNode(b.replacements, n)
n.addedAt = time.Now()
if tab.nodeAddedHook != nil {
tab.nodeAddedHook(n)
}
}
AddSeenNode ở đây dùng để thêm các nút vào nhóm, tại đây nó sẽ kiểm tra xem nút được thêm vào đã tồn tại chưa và nhóm đã đầy chưa, nếu đầy thì gọi tab.addReplacement(b, n) để thêm nút vào danh sách thay thế. Sau đó thêm IP, rồi cập nhật nhóm:
người nghe sự kiện
// filedir: go-ethereum-1.10.2\p2p\discover\table.go L55
refreshInterval = 30 * time.Minute
revalidateInterval = 10 * time.Second
copyNodesInterval = 30 * time.Second
// filedir: go-ethereum-1.10.2\p2p\discover\table.go L218
// loop schedules runs of doRefresh, doRevalidate and copyLiveNodes.
func (tab *Table) loop() {
var (
revalidate = time.NewTimer(tab.nextRevalidateTime())
refresh = time.NewTicker(refreshInterval)
copyNodes = time.NewTicker(copyNodesInterval)
refreshDone = make(chan struct{}) // where doRefresh reports completion
revalidateDone chan struct{} // where doRevalidate reports completion
waiting = []chan struct{}{tab.initDone} // holds waiting callers while doRefresh runs
)
defer refresh.Stop()
defer revalidate.Stop()
defer copyNodes.Stop()
// Start initial refresh.
go tab.doRefresh(refreshDone)
loop:
for {
select {
Hàm vòng lặp là vòng lặp chính trong table.go. Các biến cục bộ sẽ được sử dụng sau này được xác định ở đầu hàm, sau đó nhóm được làm mới thông qua deRefresh. Vòng lặp ở đây sẽ tự động làm mới nhóm K sau mỗi 30 phút. Xác minh xem các nút K-bucket có thể được ping sau mỗi 10 giây hay không và lưu trữ các nút trong K-bucket trong hơn 5 phút trong cơ sở dữ liệu cục bộ cứ sau 30 giây dưới dạng các nút ổn định:
tab.seedRand()
if refreshDone == nil {
refreshDone = make(chan struct{})
go tab.doRefresh(refreshDone)
}
case <-refresh.C: //Làm mới k sự kiện xô thường xuyên, refreshInterval=30 phút
waiting = append(waiting, req)
if refreshDone == nil {
refreshDone = make(chan struct{})
go tab.doRefresh(refreshDone)
}
case req := <-tab.refreshReq: //Làm mới sự kiện yêu cầu của k bucket
for _, ch := range waiting {
close(ch)
}
waiting, refreshDone = nil, nil
trường hợp <-refreshDone: // doRefresh đã hoàn thành
revalidateDone = make(chan struct{})
go tab.doRevalidate(revalidateDone)
case <-revalidate.C: // Xác minh tính hợp lệ của các nút k-bucket, 10 giây
revalidate.Reset(tab.nextRevalidateTime())
revalidateDone = nil
case <-revalidateDone: // Xác minh tính hợp lệ của K bucket node đã hoàn tất
go tab.copyLiveNodes()
case <-tab.closeReq:
break loop
}
}
if refreshDone != nil {
<-refreshDone
}
for _, ch := range waiting {
close(ch)
}
if revalidateDone != nil {
<-revalidateDone
}
close(tab.closed)
case <-copyNodes.C: // Lưu node trong cơ sở dữ liệu thường xuyên (30 giây) Nếu node tồn tại trong k-bucket hơn 5 phút được coi là node ổn định
} tra cứu nút
// getNode returns the node with the given ID or nil if it isn't in the table.
func (tab *Table) getNode(id enode.ID) *enode.Node {
tab.mutex.Lock()
defer tab.mutex.Unlock()
b := tab.bucket(id)
for _, e := range b.entries {
if e.ID() == id {
return unwrapNode(e)
}
}
return nil
}
Hàm getNode được sử dụng để tìm một nút theo ID và trả về nil nếu nó không tồn tại:
khám phá nút
// filedir:go-ethereum-1.10.2\p2p\discover\table.go L278
func (tab *Table) doRefresh(done chan struct{}) {
defer close(done)
tab.loadSeedNodes()
// Run self lookup to discover new neighbor nodes.
tab.net.lookupSelf()
for i := 0; i < 3; i++ {
tab.net.lookupRandom()
}
}
Mạng phân tán Ethereum áp dụng mô hình mạng có cấu trúc và sơ đồ triển khai của nó sử dụng giao thức Kademlia. Dưới đây chúng tôi giới thiệu ngắn gọn về khám phá nút. Trong Ethereum, giá trị của k là 16, có nghĩa là mỗi nhóm k chứa 16 nút. 256 nhóm k , K-buckets ghi lại NodeId, Khoảng cách, Điểm cuối, IP và các thông tin khác của nút, đồng thời sắp xếp chúng theo khoảng cách từ nút Đích. Việc tìm kiếm nút được thực hiện bởi doRefresh():
// filedir: go-ethereum-1.10.2\p2p\discover\table.go L302
func (tab *Table) loadSeedNodes() {
seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge))
seeds = append(seeds, tab.nursery...)
for i := range seeds {
seed := seeds[i]
age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) }}
tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
tab.addSeenNode(seed)
}
}
// filedir:go-ethereum-1.10.2\p2p\enode\nodedb.go L440
// QuerySeeds retrieves random nodes to be used as potential seed nodes
// for bootstrapping.
func (db *DB) QuerySeeds(n int, maxAge time.Duration) []*Node {
var (
now = time.Now()
nodes = make([]*Node, 0, n)
it = db.lvl.NewIterator(nil, nil)
id ID
)
defer it.Release()
seek:
for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ {
ctr := id[0]
rand.Read(id[:])
id[0] = ctr + id[0]%16
it.Seek(nodeKey(id))
n := nextNode(it)
if n == nil {
id[0] = 0
continue seek // iterator exhausted
}
if now.Sub(db.LastPongReceived(n.ID(), n.IP())) > maxAge {
continue seek
}
for i := range nodes {
if nodes[i].ID() == n.ID() {
continue seek // duplicate
}
}
nodes = append(nodes, n)
}
return nodes
}
Như bạn có thể thấy từ đoạn mã trên, trước tiên hãy gọi tab.loadSeedNodes() để tải các nút từ cơ sở dữ liệu và chèn chúng vào bảng:
// filedir: go-ethereum-1.10.2\p2p\discover\v5_udp.go L281
// lookupSelf looks up our own node ID.
// This is needed to satisfy the transport interface.
func (t *UDPv5) lookupSelf() []*enode.Node {
return t.newLookup(t.closeCtx, t.Self().ID()).run()
}
// filedir: go-ethereum-1.10.2\p2p\discover\v5_udp.go L292
func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
return t.lookupWorker(n, target)
})
}
func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup {
it := &lookup{
tab: tab,
queryfunc: q,
asked: make(map[enode.ID]bool),
seen: make(map[enode.ID]bool),
result: nodesByDistance{target: target},
replyCh: make(chan []*node, alpha),
cancelCh: ctx.Done(),
queries: -1,
}
// Don't query further if we hit ourself.
// Unlikely to happen often in practice.
it.asked[tab.self().ID()] = true
return it
}
Sau đó, sử dụng lookupSelf để khám phá các nút mới. Ở đây, ID của nút hiện tại sẽ được sử dụng trước để chạy newLookup khám phá các nút lân cận:
// filedir: go-ethereum-1.10.2\p2p\discover\v5_udp.go L275
func (t *UDPv5) lookupRandom() []*enode.Node {
return t.newRandomLookup(t.closeCtx).run()
}
// filedir: go-ethereum-1.10.2\p2p\discover\v5_udp.go L287
func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup {
var target enode.ID
crand.Read(target[:])
return t.newLookup(ctx, target)
}
func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
return t.lookupWorker(n, target)
})
}
// lookupWorker performs FINDNODE calls against a single node during lookup.
func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
var (
dists = lookupDistances(target, destNode.ID())
nodes = nodesByDistance{target: target}
err error
)
var r []*enode.Node
r, err = t.findnode(unwrapNode(destNode), dists)
if err == errClosed {
return nil, err
}
for _, n := range r {
if n.ID() != t.Self().ID() {
nodes.push(wrapNode(n), findnodeResultLimit)
}
}
return nodes.entries, err
}
// filedir:go-ethereum-1.10.2\p2p\discover\v5_udp.go L362
// findnode calls FINDNODE on a node and waits for responses.
func (t *UDPv5) findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) {
resp := t.call(n, v5wire.NodesMsg, &v5wire.Findnode{Distances: distances})
return t.waitForNodes(resp, distances)
}
func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) *callV5 {
c := &callV5{
node: node,
packet: packet,
responseType: responseType,
reqid: make([]byte, 8),
ch: make(chan v5wire.Packet, 1),
err: make(chan error, 1),
}
// Assign request ID.
crand.Read(c.reqid)
packet.SetRequestID(c.reqid)
// Send call to dispatch.
select {
case t.callCh <- c:
case <-t.closeCtx.Done():
c.err <- errClosed
}
return c
}
Cuối cùng, một mục tiêu ngẫu nhiên được thực hiện để tra cứu:
cấu trúc dịch vụ
// filedir:go-ethereum-1.10.2\p2p\server.go L160
// Server manages all peer connections.
type Server struct {
// Config fields may not be modified while the server is running.
Config
// Hooks for testing. These are useful because we can inhibit
// the whole protocol stack.
newTransport func(net.Conn, *ecdsa.PublicKey) transport
newPeerHook func(*Peer)
listenFunc func(network, addr string) (net.Listener, error)
lock sync.Mutex // protects running
running bool
listener net.Listener
ourHandshake *protoHandshake
loopWG sync.WaitGroup // loop, listenLoop
peerFeed event.Feed
log log.Logger
nodedb *enode.DB
localnode *enode.LocalNode
ntab *discover.UDPv4
DiscV5 *discover.UDPv5
discmix *enode.FairMix
dialsched *dialScheduler
// Channels into the run loop.
quit chan struct{}
addtrusted chan *enode.Node
removetrusted chan *enode.Node
peerOp chan peerOpFunc
peerOpDone chan struct{}
delpeer chan peerDrop
checkpointPostHandshake chan *conn
checkpointAddPeer chan *conn
// State of run loop and listenLoop.
inboundHistory expHeap
}
Cấu trúc dữ liệu phía máy chủ như sau:
// Config holds Server options.
type Config struct {
PrivateKey *ecdsa.PrivateKey `toml:"-"`
MaxPeers int
MaxPendingPeers int `toml:",omitempty"`
DialRatio int `toml:",omitempty"`
NoDiscovery bool
DiscoveryV5 bool `toml:",omitempty"`
Name string `toml:"-"`
BootstrapNodes []*enode.Node
BootstrapNodesV5 []*enode.Node `toml:",omitempty"`
StaticNodes []*enode.Node
TrustedNodes []*enode.Node
NetRestrict *netutil.Netlist `toml:",omitempty"`
NodeDatabase string `toml:",omitempty"`
Protocols []Protocol `toml:"-"`
ListenAddr string
NAT nat.Interface `toml:",omitempty"`
Dialer NodeDialer `toml:"-"`
NoDial bool `toml:",omitempty"`
EnableMsgEvents bool
Logger log.Logger `toml:",omitempty"`
clock mclock.Clock
}
#####
Cấu hình máy chủ (khóa bí mật của nút cục bộ, tỷ lệ quay số, số lượng kết nối nút tối đa, tỷ lệ quay số, bản ghi sự kiện, v.v.):
nút mới
// filedir:go-ethereum-1.10.2\p2p\server.go L318
func (srv *Server) AddPeer(node *enode.Node) {
srv.dialsched.addStatic(node)
}
// filedir:go-ethereum-1.10.2\p2p\dial.go L190
// addStatic adds a static dial candidate.
func (d *dialScheduler) addStatic(n *enode.Node) {
select {
case d.addStaticCh <- n:
case <-d.ctx.Done():
}
}
Hàm AddPeer được sử dụng để thêm một nút nhất định và mã triển khai của nó như sau:
// AddTrustedPeer adds the given node to a reserved whitelist which allows the
// node to always connect, even if the slot are full.
func (srv *Server) AddTrustedPeer(node *enode.Node) {
select {
case srv.addtrusted <- node:
case <-srv.quit:
}
}
Hàm AddTrustedPeer được sử dụng để thêm một nút đáng tin cậy:
loại bỏ nút
// filedir:go-ethereum-1.10.2\p2p\server.go L325
func (srv *Server) RemovePeer(node *enode.Node) {
var (
ch chan *PeerEvent
sub event.Subscription
)
// Disconnect the peer on the main loop.
srv.doPeerOp(func(peers map[enode.ID]*Peer) {
srv.dialsched.removeStatic(node)
if peer := peers[node.ID()]; peer != nil {
ch = make(chan *PeerEvent, 1)
sub = srv.peerFeed.Subscribe(ch)
peer.Disconnect(DiscRequested)
}
})
// Wait for the peer connection to end.
if ch != nil {
defer sub.Unsubscribe()
for ev := range ch {
if ev.Peer == node.ID() && ev.Type == PeerEventTypeDrop {
return
}
}
}
}
// filedir:go-ethereum-1.10.2\p2p\dial.go L198
// removeStatic removes a static dial candidate.
func (d *dialScheduler) removeStatic(n *enode.Node) {
select {
case d.remStaticCh <- n:
case <-d.ctx.Done():
}
}
Hàm RemovePeer được sử dụng để xóa một nút và ngắt kết nối khỏi nút đó:
// RemoveTrustedPeer removes the given node from the trusted peer set.
func (srv *Server) RemoveTrustedPeer(node *enode.Node) {
select {
case srv.removetrusted <- node:
case <-srv.quit:
}
}
Hàm RemoveTrustedPeer được sử dụng để xóa một đồng nghiệp đáng tin cậy:
chấm dứt dịch vụ
func (srv *Server) Stop() {
srv.lock.Lock()
if !srv.running {
srv.lock.Unlock()
return
}
srv.running = false
if srv.listener != nil {
// this unblocks listener Accept
srv.listener.Close()
}
close(srv.quit)
srv.lock.Unlock()
srv.loopWG.Wait()
}
Hàm Stop dùng để chấm dứt hoạt động của nút, mã lệnh cụ thể như sau:
bắt đầu dịch vụ
// filedir:go-ethereum-1.10.2\p2p\server.go L433
func (srv *Server) Start() (err error) {
srv.lock.Lock()
defer srv.lock.Unlock()
if srv.running {
return errors.New("server already running")
}
srv.running = true
srv.log = srv.Config.Logger
if srv.log == nil {
srv.log = log.Root()
}
if srv.clock == nil {
srv.clock = mclock.System{}
}
if srv.NoDial && srv.ListenAddr == "" {
srv.log.Warn("P2P server will be useless, neither dialing nor listening")
}
// static fields
if srv.PrivateKey == nil {
return errors.New("Server.PrivateKey must be set to a non-nil key")
}
if srv.newTransport == nil {
srv.newTransport = newRLPX
}
if srv.listenFunc == nil {
srv.listenFunc = net.Listen
}
srv.quit = make(chan struct{})
srv.delpeer = make(chan peerDrop)
srv.checkpointPostHandshake = make(chan *conn)
srv.checkpointAddPeer = make(chan *conn)
srv.addtrusted = make(chan *enode.Node)
srv.removetrusted = make(chan *enode.Node)
srv.peerOp = make(chan peerOpFunc)
srv.peerOpDone = make(chan struct{})
if err := srv.setupLocalNode(); err != nil {
return err
}
if srv.ListenAddr != "" {
if err := srv.setupListening(); err != nil {
return err
}
}
if err := srv.setupDiscovery(); err != nil {
return err
}
srv.setupDialScheduler()
srv.loopWG.Add(1)
go srv.run()
return nil
}
Hàm bắt đầu nằm trong go-ethereum-1.10.2\p2p\server.go được sử dụng để bắt đầu một nút P2P:
// Start starts running the server.
// Servers can not be re-used after stopping.
func (srv *Server) Start() (err error) {
srv.lock.Lock()
defer srv.lock.Unlock()
if srv.running {
return errors.New("server already running")
}
srv.running = true
srv.log = srv.Config.Logger
if srv.log == nil {
srv.log = log.Root()
}
if srv.clock == nil {
srv.clock = mclock.System{}
}
if srv.NoDial && srv.ListenAddr == "" {
srv.log.Warn("P2P server will be useless, neither dialing nor listening")
}
// static fields
if srv.PrivateKey == nil {
return errors.New("Server.PrivateKey must be set to a non-nil key")
}
if srv.newTransport == nil {
srv.newTransport = newRLPX
}
if srv.listenFunc == nil {
srv.listenFunc = net.Listen
}
srv.quit = make(chan struct{})
srv.delpeer = make(chan peerDrop)
srv.checkpointPostHandshake = make(chan *conn)
srv.checkpointAddPeer = make(chan *conn)
srv.addtrusted = make(chan *enode.Node)
srv.removetrusted = make(chan *enode.Node)
srv.peerOp = make(chan peerOpFunc)
srv.peerOpDone = make(chan struct{})
Tại đây, trước tiên hãy kiểm tra xem nút hiện tại có ở trạng thái đang chạy hay không, nếu có, hãy quay lại trực tiếp và đưa ra thông báo lỗi, nếu không, hãy đặt srv.running thành true, sau đó vào quy trình khởi động dịch vụ, sau đó kiểm tra xem nhật ký đã được bật hay chưa , v.v., sau đó khởi tạo cấu hình thông tin dịch vụ P2P:
Sau đó, gọi setupLocalNode để bắt đầu một nút cục bộ và thiết lập một màn hình cục bộ, sau đó định cấu hình giao thức mạng DiscoveryV5 để tạo bảng định tuyến nút.
srv.setupDialScheduler()
srv.loopWG.Add(1)
go srv.run()
return nil
}
Sau đó, gọi setupDialScheduler để bắt đầu quá trình kết nối quay số đang hoạt động, sau đó mở một coroutine để duy trì mạng ngang hàng:
func (srv *Server) setupDialScheduler() {
config := dialConfig{
self: srv.localnode.ID(),
maxDialPeers: srv.maxDialedConns(),
maxActiveDials: srv.MaxPendingPeers,
log: srv.Logger,
netRestrict: srv.NetRestrict,
dialer: srv.Dialer,
clock: srv.clock,
}
if srv.ntab != nil {
config.resolver = srv.ntab
}
if config.dialer == nil {
config.dialer = tcpDialer{&net.Dialer{Timeout: defaultDialTimeout}}
}
srv.dialsched = newDialScheduler(config, srv.discmix, srv.SetupConn)
for _, n := range srv.StaticNodes {
srv.dialsched.addStatic(n)
}
}
Mã của setupDialScheduler như sau. Ở đây, kết nối được thiết lập thông qua newDialScheduler. Tham số discmix xác định tập hợp nút khi chủ động thiết lập kết nối. Nó là một trình vòng lặp và chức năng thiết lập kết nối setupConn được truyền vào cùng một lúc:
// filedir:go-ethereum-1.10.2\p2p\dial.go L162
func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler {
d := &dialScheduler{
dialConfig: config.withDefaults(),
setupFunc: setupFunc,
dialing: make(map[enode.ID]*dialTask),
static: make(map[enode.ID]*dialTask),
peers: make(map[enode.ID]connFlag),
doneCh: make(chan *dialTask),
nodesIn: make(chan *enode.Node),
addStaticCh: make(chan *enode.Node),
remStaticCh: make(chan *enode.Node),
addPeerCh: make(chan *conn),
remPeerCh: make(chan *conn),
}
d.lastStatsLog = d.clock.Now()
d.ctx, d.cancel = context.WithCancel(context.Background())
d.wg.Add(2)
go d.readNodes(it)
go d.loop(it)
return d
}
Hàm newDialScheduler như sau, trong đó các nút được lấy từ trình vòng lặp thông qua d.readNodes(it), sau đó được chuyển đến d.loop(it) qua kênh để kết nối:
giám sát dịch vụ
func (srv *Server) setupListening() error {
// Launch the listener.
listener, err := srv.listenFunc("tcp", srv.ListenAddr)
if err != nil {
return err
}
srv.listener = listener
srv.ListenAddr = listener.Addr().String()
// Update the local node record and map the TCP listening port if NAT is configured.
if tcp, ok := listener.Addr().(*net.TCPAddr); ok {
srv.localnode.Set(enr.TCP(tcp.Port))
if !tcp.IP.IsLoopback() && srv.NAT != nil {
srv.loopWG.Add(1)
go func() {
nat.Map(srv.NAT, srv.quit, "tcp", tcp.Port, tcp.Port, "ethereum p2p")
srv.loopWG.Done()
}()
}
}
srv.loopWG.Add(1)
go srv.listenLoop()
return nil
}
Trong quá trình khởi động dịch vụ trên có một hàm setupListening dùng để lắng nghe các sự kiện, mã cụ thể như sau:
// listenLoop runs in its own goroutine and accepts
// inbound connections.
func (srv *Server) listenLoop() {
srv.log.Debug("TCP listener up", "addr", srv.listener.Addr())
// The slots channel limits accepts of new connections.
tokens := defaultMaxPendingPeers
if srv.MaxPendingPeers > 0 {
tokens = srv.MaxPendingPeers
}
slots := make(chan struct{}, tokens)
for i := 0; i < tokens; i++ {
slots <- struct{}{}
}
// Wait for slots to be returned on exit. This ensures all connection goroutines
// are down before listenLoop returns.
defer srv.loopWG.Done()
defer func() {
for i := 0; i < cap(slots); i++ {
<-slots
}
}()
for {
// Wait for a free slot before accepting.
<-slots
var (
fd net.Conn
err error
lastLog time.Time
)
for {
fd, err = srv.listener.Accept()
if netutil.IsTemporaryError(err) {
if time.Since(lastLog) > 1*time.Second {
srv.log.Debug("Temporary read error", "err", err)
lastLog = time.Now()
}
time.Sleep(time.Millisecond * 200)
continue
} else if err != nil {
srv.log.Debug("Read error", "err", err)
slots <- struct{}{}
return
}
break
}
remoteIP := netutil.AddrIP(fd.RemoteAddr())
if err := srv.checkInboundConn(remoteIP); err != nil {
srv.log.Debug("Rejected inbound connection", "addr", fd.RemoteAddr(), "err", err)
fd.Close()
slots <- struct{}{}
continue
}
if remoteIP != nil {
var addr *net.TCPAddr
if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok {
addr = tcp
}
fd = newMeteredConn(fd, true, addr)
srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr())
}
go func() {
srv.SetupConn(fd, inboundConn, nil)
slots <- struct{}{}
}()
}
}
Trong đoạn mã trên, một srv.listenLoop() khác được gọi, là một goroutine vòng lặp vô hạn, nó sẽ lắng nghe cổng và nhận các yêu cầu bên ngoài:
// SetupConn runs the handshakes and attempts to add the connection
// as a peer. It returns when the connection has been added as a peer
// or the handshakes have failed.
func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error {
c := &conn{fd: fd, flags: flags, cont: make(chan error)}
if dialDest == nil {
c.transport = srv.newTransport(fd, nil)
} else {
c.transport = srv.newTransport(fd, dialDest.Pubkey())
}
err := srv.setupConn(c, flags, dialDest)
if err != nil {
c.close(err)
}
return err
}
SetupConn ở đây chủ yếu thực thi giao thức bắt tay và cố gắng tạo liên kết dưới dạng đối tượng ngang hàng:
func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) error {
// Prevent leftover pending conns from entering the handshake.
srv.lock.Lock()
running := srv.running
srv.lock.Unlock()
if !running {
return errServerStopped
}
// If dialing, figure out the remote public key.
var dialPubkey *ecdsa.PublicKey
Trong đoạn mã trên, hàm srv.setupConn(c, flags, dialDest) được gọi lại, được sử dụng để thực thi giao thức bắt tay:
dialPubkey = new(ecdsa.PublicKey)
if err := dialDest.Load((*enode.Secp256k1)(dialPubkey)); err != nil {
err = errors.New("dial destination doesn't have a secp256k1 public key")
srv.log.Trace("Setting up connection failed", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
return err
}
}
// Run the RLPx handshake.
remotePubkey,if dialDest != nil { // dest=nil kết nối thụ động, dest != nil kết nối tích cực
if err != nil {
srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
return err
}
if dialDest != nil {
c.node = dialDest
} else {
c.node = nodeFromConn(remotePubkey, c.fd)
}
clog := srv.log.New("id", c.node.ID(), "addr", c.fd.RemoteAddr(), "conn", c.flags)
err = srv.checkpoint(c, srv.checkpointPostHandshake)
if err != nil {
clog.Trace("Rejected peer", "err", err)
return err
}
// Run the capability negotiation handshake.
phs,err := c.doEncHandshake(srv.PrivateKey) // trao đổi khóa công khai, xác định bắt tay ở cấp RLPx của khóa bí mật dùng chung
if err != nil {
clog.Trace("Failed p2p handshake", "err", err)
return err
}
if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) {
clog.Trace("Wrong devp2p handshake identity", "phsid", hex.EncodeToString(phs.ID))
return DiscUnexpectedIdentity
}
c.caps, c.name = phs.Caps, phs.Name
err = srv.checkpoint(c,err := c.doProtoHandshake(srv.ourHandshake) // Bắt tay ở cấp độ giao thức, nghĩa là bắt tay p2p, đến và đi
if err != nil {
clog.Trace("Rejected peer", "err", err)
return err
}
return nil
}
srv.checkpointAddPeer) // xác minh trạng thái
// filedir:go-ethereum-1.10.2\p2p\transport.go L123
func (t *rlpxTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
t.conn.SetDeadline(time.Now().Add(handshakeTimeout))
return t.conn.Handshake(prv)
}
Key handshake được triển khai thông qua hàm deEncHandshake và hàm Handshake() được gọi trong hàm:
// filedir:go-ethereum-1.10.2\p2p\rlpx\rlpx.go L253
func (c *Conn) Handshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
var (
sec Secrets
err error
)
Mã bắt tay như sau, trong đó logic bắt tay tương ứng sẽ được thực thi tùy theo đó là bắt tay chủ động hay bắt tay thụ động:
sec, err = initiatorEncHandshake(c.conn, prv,if c.dialDest != nil {// Bắt tay tích cực
c.dialDest) // Chủ động bắt đầu bắt tay xác minh khóa kết thúc, xác nhận khóa dùng chung
sec, err = receiverEncHandshake(c.conn, prv)
}
if err != nil {
return nil, err
}
c.InitWithSecrets(sec)
return sec.remote, err
}
} other { // bắt tay thụ động
// filedir:go-ethereum-1.10.2\p2p\rlpx\rlpx.go L477
func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s Secrets, err error) {
h := &encHandshake{initiator: true, remote: ecies.ImportECDSAPublic(remote)}
authMsg, err := h.makeAuthMsg(prv)
if err != nil {
return s, err
}
authPacket, err := sealEIP8(authMsg, h)
if err != nil {
return s, err
}
if _, err = conn.Write(authPacket); err != nil {
return s, err
}
authRespMsg := new(authRespV4)
authRespPacket, err := readHandshakeMsg(authRespMsg, encAuthRespLen, prv, conn)
if err != nil {
return s, err
}
if err := h.handleAuthResp(authRespMsg); err != nil {
return s, err
}
return h.secrets(authPacket, authRespPacket)
}
Quá trình chủ động bắt đầu bắt tay như sau. Ở đây, makeAuthMsg được gọi để tạo thông tin nhận dạng Auth, bao gồm chữ ký, khóa chung và số phiên bản tương ứng với chữ ký được tạo bởi một nonce ngẫu nhiên, sau đó phương thức sealEIP8 được gọi để thực hiện mã hóa rlpx, sau đó bắt đầu bắt tay được mã hóa và sau đó nhận được Trả lại thông báo authResp, xác minh và giải mã nó, lấy khóa chung của bên kia, sau đó tạo AES, MAC:
func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, err error) {
authMsg := new(authMsgV4)
authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn)
if err != nil {
return s, err
}
h := new(encHandshake)
if err := h.handleAuthMsg(authMsg, prv); err != nil {
return s, err
}
authRespMsg, err := h.makeAuthResp()
if err != nil {
return s, err
}
var authRespPacket []byte
if authMsg.gotPlain {
authRespPacket, err = authRespMsg.sealPlain(h)
} else {
authRespPacket, err = sealEIP8(authRespMsg, h)
}
if err != nil {
return s, err
}
if _, err = conn.Write(authRespPacket); err != nil {
return s, err
}
return h.secrets(authPacket, authRespPacket)
}
receiverEncHandshake như sau, gần giống với initiatorEncHandshake:
func (t *rlpxTransport) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) {
werr := make(chan error, 1)
go func() { werr <- Send(t, handshakeMsg, our) }()
if their, err = readProtocolHandshake(t); err != nil {
<-werr // make sure the write terminates too
return nil, err
}
if err := <-werr; err != nil {
return nil, fmt.Errorf("write error: %v", err)
}
// If the protocol version supports Snappy encoding, upgrade immediately
t.conn.SetSnappy(their.Version >= snappyProtocolVersion)
return their, nil
}
Sau đó, hoàn thành thao tác bắt tay giao thức thông qua doProtoHandshake, gọi gửi tại đây để gửi thao tác bắt tay, sau đó đọc thông tin được trả về thông qua readProtocolHandshake, sau đó kiểm tra:
chu kỳ dịch vụ
func (srv *Server) run() {
srv.log.Info("Started P2P networking", "self", srv.localnode.Node().URLv4())
defer srv.loopWG.Done()
defer srv.nodedb.Close()
defer srv.discmix.Close()
defer srv.dialsched.stop()
var (
peers = make(map[enode.ID]*Peer)
inboundCount = 0
trusted = make(map[enode.ID]bool, len(srv.TrustedNodes))
)
for _, n := range srv.TrustedNodes {
trusted[n.ID()] = true
}
running:
for {
select {
case <-srv.quit:
break running
case n := <-srv.addtrusted:
srv.log.Trace("Adding trusted node", "node", n)
trusted[n.ID()] = true
if p, ok := peers[n.ID()]; ok {
p.rw.set(trustedConn, true)
}
case n := <-srv.removetrusted:
srv.log.Trace("Removing trusted node", "node", n)
delete(trusted, n.ID())
if p, ok := peers[n.ID()]; ok {
p.rw.set(trustedConn, false)
}
case op := <-srv.peerOp:
op(peers)
srv.peerOpDone <- struct{}{}
case c := <-srv.checkpointPostHandshake:
if trusted[c.node.ID()] {
c.flags |= trustedConn
}
c.cont <- srv.postHandshakeChecks(peers, inboundCount, c)
case c := <-srv.checkpointAddPeer:
err := srv.addPeerChecks(peers, inboundCount, c)
if err == nil {
p := srv.launchPeer(c)
peers[c.node.ID()] = p
srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", p.Name())
srv.dialsched.peerAdded(c)
if p.Inbound() {
inboundCount++
}
}
c.cont <- err
case pd := <-srv.delpeer:
// A peer disconnected.
d := common.PrettyDuration(mclock.Now() - pd.created)
delete(peers, pd.ID())
srv.log.Debug("Removing p2p peer", "peercount", len(peers), "id", pd.ID(), "duration", d, "req", pd.requested, "err", pd.err)
srv.dialsched.peerRemoved(pd.rw)
if pd.Inbound() {
inboundCount--
}
}
}
srv.log.Trace("P2P networking is spinning down")
if srv.ntab != nil {
srv.ntab.Close()
}
if srv.DiscV5 != nil {
srv.DiscV5.Close()
}
for _, p := range peers {
p.Disconnect(DiscQuitting)
}
for len(peers) > 0 {
p := <-srv.delpeer
p.log.Trace("<-delpeer (spindown)")
delete(peers, p.ID())
}
}
Chức năng chạy là vòng lặp chính của dịch vụ, giám sát việc chấm dứt máy chủ, thêm nút tin cậy, xóa nút tin cậy, thêm nút kiểm tra, v.v.:
thông tin nút
// NodeInfo gathers and returns a collection of metadata known about the host.
func (srv *Server) NodeInfo() *NodeInfo {
node := srv.Self()
info := &NodeInfo{
Name: srv.Name,
Enode: node.URLv4(),
ID: node.ID().String(),
IP: &nb

