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