github.com/jimmyx0x/go-ethereum@v1.10.28/eth/protocols/snap/handler.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package snap 18 19 import ( 20 "bytes" 21 "fmt" 22 "time" 23 24 "github.com/ethereum/go-ethereum/common" 25 "github.com/ethereum/go-ethereum/core" 26 "github.com/ethereum/go-ethereum/light" 27 "github.com/ethereum/go-ethereum/log" 28 "github.com/ethereum/go-ethereum/metrics" 29 "github.com/ethereum/go-ethereum/p2p" 30 "github.com/ethereum/go-ethereum/p2p/enode" 31 "github.com/ethereum/go-ethereum/p2p/enr" 32 "github.com/ethereum/go-ethereum/trie" 33 ) 34 35 const ( 36 // softResponseLimit is the target maximum size of replies to data retrievals. 37 softResponseLimit = 2 * 1024 * 1024 38 39 // maxCodeLookups is the maximum number of bytecodes to serve. This number is 40 // there to limit the number of disk lookups. 41 maxCodeLookups = 1024 42 43 // stateLookupSlack defines the ratio by how much a state response can exceed 44 // the requested limit in order to try and avoid breaking up contracts into 45 // multiple packages and proving them. 46 stateLookupSlack = 0.1 47 48 // maxTrieNodeLookups is the maximum number of state trie nodes to serve. This 49 // number is there to limit the number of disk lookups. 50 maxTrieNodeLookups = 1024 51 52 // maxTrieNodeTimeSpent is the maximum time we should spend on looking up trie nodes. 53 // If we spend too much time, then it's a fairly high chance of timing out 54 // at the remote side, which means all the work is in vain. 55 maxTrieNodeTimeSpent = 5 * time.Second 56 ) 57 58 // Handler is a callback to invoke from an outside runner after the boilerplate 59 // exchanges have passed. 60 type Handler func(peer *Peer) error 61 62 // Backend defines the data retrieval methods to serve remote requests and the 63 // callback methods to invoke on remote deliveries. 64 type Backend interface { 65 // Chain retrieves the blockchain object to serve data. 66 Chain() *core.BlockChain 67 68 // RunPeer is invoked when a peer joins on the `eth` protocol. The handler 69 // should do any peer maintenance work, handshakes and validations. If all 70 // is passed, control should be given back to the `handler` to process the 71 // inbound messages going forward. 72 RunPeer(peer *Peer, handler Handler) error 73 74 // PeerInfo retrieves all known `snap` information about a peer. 75 PeerInfo(id enode.ID) interface{} 76 77 // Handle is a callback to be invoked when a data packet is received from 78 // the remote peer. Only packets not consumed by the protocol handler will 79 // be forwarded to the backend. 80 Handle(peer *Peer, packet Packet) error 81 } 82 83 // MakeProtocols constructs the P2P protocol definitions for `snap`. 84 func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { 85 // Filter the discovery iterator for nodes advertising snap support. 86 dnsdisc = enode.Filter(dnsdisc, func(n *enode.Node) bool { 87 var snap enrEntry 88 return n.Load(&snap) == nil 89 }) 90 91 protocols := make([]p2p.Protocol, len(ProtocolVersions)) 92 for i, version := range ProtocolVersions { 93 version := version // Closure 94 95 protocols[i] = p2p.Protocol{ 96 Name: ProtocolName, 97 Version: version, 98 Length: protocolLengths[version], 99 Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { 100 return backend.RunPeer(NewPeer(version, p, rw), func(peer *Peer) error { 101 return Handle(backend, peer) 102 }) 103 }, 104 NodeInfo: func() interface{} { 105 return nodeInfo(backend.Chain()) 106 }, 107 PeerInfo: func(id enode.ID) interface{} { 108 return backend.PeerInfo(id) 109 }, 110 Attributes: []enr.Entry{&enrEntry{}}, 111 DialCandidates: dnsdisc, 112 } 113 } 114 return protocols 115 } 116 117 // Handle is the callback invoked to manage the life cycle of a `snap` peer. 118 // When this function terminates, the peer is disconnected. 119 func Handle(backend Backend, peer *Peer) error { 120 for { 121 if err := HandleMessage(backend, peer); err != nil { 122 peer.Log().Debug("Message handling failed in `snap`", "err", err) 123 return err 124 } 125 } 126 } 127 128 // HandleMessage is invoked whenever an inbound message is received from a 129 // remote peer on the `snap` protocol. The remote connection is torn down upon 130 // returning any error. 131 func HandleMessage(backend Backend, peer *Peer) error { 132 // Read the next message from the remote peer, and ensure it's fully consumed 133 msg, err := peer.rw.ReadMsg() 134 if err != nil { 135 return err 136 } 137 if msg.Size > maxMessageSize { 138 return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) 139 } 140 defer msg.Discard() 141 start := time.Now() 142 // Track the amount of time it takes to serve the request and run the handler 143 if metrics.Enabled { 144 h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code) 145 defer func(start time.Time) { 146 sampler := func() metrics.Sample { 147 return metrics.ResettingSample( 148 metrics.NewExpDecaySample(1028, 0.015), 149 ) 150 } 151 metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) 152 }(start) 153 } 154 // Handle the message depending on its contents 155 switch { 156 case msg.Code == GetAccountRangeMsg: 157 // Decode the account retrieval request 158 var req GetAccountRangePacket 159 if err := msg.Decode(&req); err != nil { 160 return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) 161 } 162 // Service the request, potentially returning nothing in case of errors 163 accounts, proofs := ServiceGetAccountRangeQuery(backend.Chain(), &req) 164 165 // Send back anything accumulated (or empty in case of errors) 166 return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ 167 ID: req.ID, 168 Accounts: accounts, 169 Proof: proofs, 170 }) 171 172 case msg.Code == AccountRangeMsg: 173 // A range of accounts arrived to one of our previous requests 174 res := new(AccountRangePacket) 175 if err := msg.Decode(res); err != nil { 176 return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) 177 } 178 // Ensure the range is monotonically increasing 179 for i := 1; i < len(res.Accounts); i++ { 180 if bytes.Compare(res.Accounts[i-1].Hash[:], res.Accounts[i].Hash[:]) >= 0 { 181 return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:]) 182 } 183 } 184 requestTracker.Fulfil(peer.id, peer.version, AccountRangeMsg, res.ID) 185 186 return backend.Handle(peer, res) 187 188 case msg.Code == GetStorageRangesMsg: 189 // Decode the storage retrieval request 190 var req GetStorageRangesPacket 191 if err := msg.Decode(&req); err != nil { 192 return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) 193 } 194 // Service the request, potentially returning nothing in case of errors 195 slots, proofs := ServiceGetStorageRangesQuery(backend.Chain(), &req) 196 197 // Send back anything accumulated (or empty in case of errors) 198 return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ 199 ID: req.ID, 200 Slots: slots, 201 Proof: proofs, 202 }) 203 204 case msg.Code == StorageRangesMsg: 205 // A range of storage slots arrived to one of our previous requests 206 res := new(StorageRangesPacket) 207 if err := msg.Decode(res); err != nil { 208 return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) 209 } 210 // Ensure the ranges are monotonically increasing 211 for i, slots := range res.Slots { 212 for j := 1; j < len(slots); j++ { 213 if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 { 214 return fmt.Errorf("storage slots not monotonically increasing for account #%d: #%d [%x] vs #%d [%x]", i, j-1, slots[j-1].Hash[:], j, slots[j].Hash[:]) 215 } 216 } 217 } 218 requestTracker.Fulfil(peer.id, peer.version, StorageRangesMsg, res.ID) 219 220 return backend.Handle(peer, res) 221 222 case msg.Code == GetByteCodesMsg: 223 // Decode bytecode retrieval request 224 var req GetByteCodesPacket 225 if err := msg.Decode(&req); err != nil { 226 return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) 227 } 228 // Service the request, potentially returning nothing in case of errors 229 codes := ServiceGetByteCodesQuery(backend.Chain(), &req) 230 231 // Send back anything accumulated (or empty in case of errors) 232 return p2p.Send(peer.rw, ByteCodesMsg, &ByteCodesPacket{ 233 ID: req.ID, 234 Codes: codes, 235 }) 236 237 case msg.Code == ByteCodesMsg: 238 // A batch of byte codes arrived to one of our previous requests 239 res := new(ByteCodesPacket) 240 if err := msg.Decode(res); err != nil { 241 return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) 242 } 243 requestTracker.Fulfil(peer.id, peer.version, ByteCodesMsg, res.ID) 244 245 return backend.Handle(peer, res) 246 247 case msg.Code == GetTrieNodesMsg: 248 // Decode trie node retrieval request 249 var req GetTrieNodesPacket 250 if err := msg.Decode(&req); err != nil { 251 return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) 252 } 253 // Service the request, potentially returning nothing in case of errors 254 nodes, err := ServiceGetTrieNodesQuery(backend.Chain(), &req, start) 255 if err != nil { 256 return err 257 } 258 // Send back anything accumulated (or empty in case of errors) 259 return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ 260 ID: req.ID, 261 Nodes: nodes, 262 }) 263 264 case msg.Code == TrieNodesMsg: 265 // A batch of trie nodes arrived to one of our previous requests 266 res := new(TrieNodesPacket) 267 if err := msg.Decode(res); err != nil { 268 return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) 269 } 270 requestTracker.Fulfil(peer.id, peer.version, TrieNodesMsg, res.ID) 271 272 return backend.Handle(peer, res) 273 274 default: 275 return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) 276 } 277 } 278 279 // ServiceGetAccountRangeQuery assembles the response to an account range query. 280 // It is exposed to allow external packages to test protocol behavior. 281 func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePacket) ([]*AccountData, [][]byte) { 282 if req.Bytes > softResponseLimit { 283 req.Bytes = softResponseLimit 284 } 285 // Retrieve the requested state and bail out if non existent 286 tr, err := trie.New(trie.StateTrieID(req.Root), chain.StateCache().TrieDB()) 287 if err != nil { 288 return nil, nil 289 } 290 it, err := chain.Snapshots().AccountIterator(req.Root, req.Origin) 291 if err != nil { 292 return nil, nil 293 } 294 // Iterate over the requested range and pile accounts up 295 var ( 296 accounts []*AccountData 297 size uint64 298 last common.Hash 299 ) 300 for it.Next() { 301 hash, account := it.Hash(), common.CopyBytes(it.Account()) 302 303 // Track the returned interval for the Merkle proofs 304 last = hash 305 306 // Assemble the reply item 307 size += uint64(common.HashLength + len(account)) 308 accounts = append(accounts, &AccountData{ 309 Hash: hash, 310 Body: account, 311 }) 312 // If we've exceeded the request threshold, abort 313 if bytes.Compare(hash[:], req.Limit[:]) >= 0 { 314 break 315 } 316 if size > req.Bytes { 317 break 318 } 319 } 320 it.Release() 321 322 // Generate the Merkle proofs for the first and last account 323 proof := light.NewNodeSet() 324 if err := tr.Prove(req.Origin[:], 0, proof); err != nil { 325 log.Warn("Failed to prove account range", "origin", req.Origin, "err", err) 326 return nil, nil 327 } 328 if last != (common.Hash{}) { 329 if err := tr.Prove(last[:], 0, proof); err != nil { 330 log.Warn("Failed to prove account range", "last", last, "err", err) 331 return nil, nil 332 } 333 } 334 var proofs [][]byte 335 for _, blob := range proof.NodeList() { 336 proofs = append(proofs, blob) 337 } 338 return accounts, proofs 339 } 340 341 func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesPacket) ([][]*StorageData, [][]byte) { 342 if req.Bytes > softResponseLimit { 343 req.Bytes = softResponseLimit 344 } 345 // TODO(karalabe): Do we want to enforce > 0 accounts and 1 account if origin is set? 346 // TODO(karalabe): - Logging locally is not ideal as remote faults annoy the local user 347 // TODO(karalabe): - Dropping the remote peer is less flexible wrt client bugs (slow is better than non-functional) 348 349 // Calculate the hard limit at which to abort, even if mid storage trie 350 hardLimit := uint64(float64(req.Bytes) * (1 + stateLookupSlack)) 351 352 // Retrieve storage ranges until the packet limit is reached 353 var ( 354 slots [][]*StorageData 355 proofs [][]byte 356 size uint64 357 ) 358 for _, account := range req.Accounts { 359 // If we've exceeded the requested data limit, abort without opening 360 // a new storage range (that we'd need to prove due to exceeded size) 361 if size >= req.Bytes { 362 break 363 } 364 // The first account might start from a different origin and end sooner 365 var origin common.Hash 366 if len(req.Origin) > 0 { 367 origin, req.Origin = common.BytesToHash(req.Origin), nil 368 } 369 var limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") 370 if len(req.Limit) > 0 { 371 limit, req.Limit = common.BytesToHash(req.Limit), nil 372 } 373 // Retrieve the requested state and bail out if non existent 374 it, err := chain.Snapshots().StorageIterator(req.Root, account, origin) 375 if err != nil { 376 return nil, nil 377 } 378 // Iterate over the requested range and pile slots up 379 var ( 380 storage []*StorageData 381 last common.Hash 382 abort bool 383 ) 384 for it.Next() { 385 if size >= hardLimit { 386 abort = true 387 break 388 } 389 hash, slot := it.Hash(), common.CopyBytes(it.Slot()) 390 391 // Track the returned interval for the Merkle proofs 392 last = hash 393 394 // Assemble the reply item 395 size += uint64(common.HashLength + len(slot)) 396 storage = append(storage, &StorageData{ 397 Hash: hash, 398 Body: slot, 399 }) 400 // If we've exceeded the request threshold, abort 401 if bytes.Compare(hash[:], limit[:]) >= 0 { 402 break 403 } 404 } 405 if len(storage) > 0 { 406 slots = append(slots, storage) 407 } 408 it.Release() 409 410 // Generate the Merkle proofs for the first and last storage slot, but 411 // only if the response was capped. If the entire storage trie included 412 // in the response, no need for any proofs. 413 if origin != (common.Hash{}) || (abort && len(storage) > 0) { 414 // Request started at a non-zero hash or was capped prematurely, add 415 // the endpoint Merkle proofs 416 accTrie, err := trie.NewStateTrie(trie.StateTrieID(req.Root), chain.StateCache().TrieDB()) 417 if err != nil { 418 return nil, nil 419 } 420 acc, err := accTrie.TryGetAccountByHash(account) 421 if err != nil || acc == nil { 422 return nil, nil 423 } 424 id := trie.StorageTrieID(req.Root, account, acc.Root) 425 stTrie, err := trie.NewStateTrie(id, chain.StateCache().TrieDB()) 426 if err != nil { 427 return nil, nil 428 } 429 proof := light.NewNodeSet() 430 if err := stTrie.Prove(origin[:], 0, proof); err != nil { 431 log.Warn("Failed to prove storage range", "origin", req.Origin, "err", err) 432 return nil, nil 433 } 434 if last != (common.Hash{}) { 435 if err := stTrie.Prove(last[:], 0, proof); err != nil { 436 log.Warn("Failed to prove storage range", "last", last, "err", err) 437 return nil, nil 438 } 439 } 440 for _, blob := range proof.NodeList() { 441 proofs = append(proofs, blob) 442 } 443 // Proof terminates the reply as proofs are only added if a node 444 // refuses to serve more data (exception when a contract fetch is 445 // finishing, but that's that). 446 break 447 } 448 } 449 return slots, proofs 450 } 451 452 // ServiceGetByteCodesQuery assembles the response to a byte codes query. 453 // It is exposed to allow external packages to test protocol behavior. 454 func ServiceGetByteCodesQuery(chain *core.BlockChain, req *GetByteCodesPacket) [][]byte { 455 if req.Bytes > softResponseLimit { 456 req.Bytes = softResponseLimit 457 } 458 if len(req.Hashes) > maxCodeLookups { 459 req.Hashes = req.Hashes[:maxCodeLookups] 460 } 461 // Retrieve bytecodes until the packet size limit is reached 462 var ( 463 codes [][]byte 464 bytes uint64 465 ) 466 for _, hash := range req.Hashes { 467 if hash == emptyCode { 468 // Peers should not request the empty code, but if they do, at 469 // least sent them back a correct response without db lookups 470 codes = append(codes, []byte{}) 471 } else if blob, err := chain.ContractCodeWithPrefix(hash); err == nil { 472 codes = append(codes, blob) 473 bytes += uint64(len(blob)) 474 } 475 if bytes > req.Bytes { 476 break 477 } 478 } 479 return codes 480 } 481 482 // ServiceGetTrieNodesQuery assembles the response to a trie nodes query. 483 // It is exposed to allow external packages to test protocol behavior. 484 func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, start time.Time) ([][]byte, error) { 485 if req.Bytes > softResponseLimit { 486 req.Bytes = softResponseLimit 487 } 488 // Make sure we have the state associated with the request 489 triedb := chain.StateCache().TrieDB() 490 491 accTrie, err := trie.NewStateTrie(trie.StateTrieID(req.Root), triedb) 492 if err != nil { 493 // We don't have the requested state available, bail out 494 return nil, nil 495 } 496 // The 'snap' might be nil, in which case we cannot serve storage slots. 497 snap := chain.Snapshots().Snapshot(req.Root) 498 // Retrieve trie nodes until the packet size limit is reached 499 var ( 500 nodes [][]byte 501 bytes uint64 502 loads int // Trie hash expansions to count database reads 503 ) 504 for _, pathset := range req.Paths { 505 switch len(pathset) { 506 case 0: 507 // Ensure we penalize invalid requests 508 return nil, fmt.Errorf("%w: zero-item pathset requested", errBadRequest) 509 510 case 1: 511 // If we're only retrieving an account trie node, fetch it directly 512 blob, resolved, err := accTrie.TryGetNode(pathset[0]) 513 loads += resolved // always account database reads, even for failures 514 if err != nil { 515 break 516 } 517 nodes = append(nodes, blob) 518 bytes += uint64(len(blob)) 519 520 default: 521 var stRoot common.Hash 522 // Storage slots requested, open the storage trie and retrieve from there 523 if snap == nil { 524 // We don't have the requested state snapshotted yet (or it is stale), 525 // but can look up the account via the trie instead. 526 account, err := accTrie.TryGetAccountByHash(common.BytesToHash(pathset[0])) 527 loads += 8 // We don't know the exact cost of lookup, this is an estimate 528 if err != nil || account == nil { 529 break 530 } 531 stRoot = account.Root 532 } else { 533 account, err := snap.Account(common.BytesToHash(pathset[0])) 534 loads++ // always account database reads, even for failures 535 if err != nil || account == nil { 536 break 537 } 538 stRoot = common.BytesToHash(account.Root) 539 } 540 id := trie.StorageTrieID(req.Root, common.BytesToHash(pathset[0]), stRoot) 541 stTrie, err := trie.NewStateTrie(id, triedb) 542 loads++ // always account database reads, even for failures 543 if err != nil { 544 break 545 } 546 for _, path := range pathset[1:] { 547 blob, resolved, err := stTrie.TryGetNode(path) 548 loads += resolved // always account database reads, even for failures 549 if err != nil { 550 break 551 } 552 nodes = append(nodes, blob) 553 bytes += uint64(len(blob)) 554 555 // Sanity check limits to avoid DoS on the store trie loads 556 if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent { 557 break 558 } 559 } 560 } 561 // Abort request processing if we've exceeded our limits 562 if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent { 563 break 564 } 565 } 566 return nodes, nil 567 } 568 569 // NodeInfo represents a short summary of the `snap` sub-protocol metadata 570 // known about the host peer. 571 type NodeInfo struct{} 572 573 // nodeInfo retrieves some `snap` protocol metadata about the running host node. 574 func nodeInfo(chain *core.BlockChain) *NodeInfo { 575 return &NodeInfo{} 576 }