github.com/n1ghtfa1l/go-vnt@v0.6.4-alpha.6/les/handler.go (about) 1 // Copyright 2016 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 les implements the Light VNT Subprotocol. 18 package les 19 20 import ( 21 "encoding/binary" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "math/big" 26 "net" 27 28 // "net" 29 "sync" 30 "time" 31 32 libp2p "github.com/libp2p/go-libp2p-peer" 33 "github.com/vntchain/go-vnt/common" 34 "github.com/vntchain/go-vnt/consensus" 35 "github.com/vntchain/go-vnt/core" 36 "github.com/vntchain/go-vnt/core/rawdb" 37 "github.com/vntchain/go-vnt/core/state" 38 "github.com/vntchain/go-vnt/core/types" 39 "github.com/vntchain/go-vnt/event" 40 "github.com/vntchain/go-vnt/light" 41 "github.com/vntchain/go-vnt/log" 42 "github.com/vntchain/go-vnt/params" 43 "github.com/vntchain/go-vnt/rlp" 44 "github.com/vntchain/go-vnt/trie" 45 "github.com/vntchain/go-vnt/vnt/downloader" 46 "github.com/vntchain/go-vnt/vntdb" 47 "github.com/vntchain/go-vnt/vntp2p" 48 ) 49 50 const ( 51 softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data. 52 estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header 53 54 vntVersion = 63 // equivalent vnt version for the downloader 55 56 MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request 57 MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request 58 MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request 59 MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request 60 MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request 61 MaxHelperTrieProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request 62 MaxTxSend = 64 // Amount of transactions to be send per request 63 MaxTxStatus = 256 // Amount of transactions to queried per request 64 65 disableClientRemovePeer = false 66 ) 67 68 // errIncompatibleConfig is returned if the requested protocols and configs are 69 // not compatible (low protocol version restrictions and high requirements). 70 var errIncompatibleConfig = errors.New("incompatible configuration") 71 72 func errResp(code errCode, format string, v ...interface{}) error { 73 return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...)) 74 } 75 76 type BlockChain interface { 77 Config() *params.ChainConfig 78 HasHeader(hash common.Hash, number uint64) bool 79 GetHeader(hash common.Hash, number uint64) *types.Header 80 GetHeaderByHash(hash common.Hash) *types.Header 81 CurrentHeader() *types.Header 82 GetTd(hash common.Hash, number uint64) *big.Int 83 State() (*state.StateDB, error) 84 InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error) 85 Rollback(chain []common.Hash) 86 GetHeaderByNumber(number uint64) *types.Header 87 GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) 88 Genesis() *types.Block 89 SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription 90 } 91 92 type txPool interface { 93 AddRemotes(txs []*types.Transaction) []error 94 Status(hashes []common.Hash) []core.TxStatus 95 } 96 97 type ProtocolManager struct { 98 lightSync bool 99 txpool txPool 100 txrelay *LesTxRelay 101 networkId uint64 102 chainConfig *params.ChainConfig 103 blockchain BlockChain 104 chainDb vntdb.Database 105 odr *LesOdr 106 server *LesServer 107 serverPool *serverPool 108 // lesTopic discv5.Topic 109 reqDist *requestDistributor 110 retriever *retrieveManager 111 112 downloader *downloader.Downloader 113 fetcher *lightFetcher 114 peers *peerSet 115 maxPeers int 116 117 SubProtocols []vntp2p.Protocol 118 119 eventMux *event.TypeMux 120 121 // channels for fetcher, syncer, txsyncLoop 122 newPeerCh chan *peer 123 quitSync chan struct{} 124 noMorePeers chan struct{} 125 126 // wait group is used for graceful shutdowns during downloading 127 // and processing 128 wg *sync.WaitGroup 129 } 130 131 // NewProtocolManager returns a new hubble sub protocol manager. The VNT sub protocol manages peers capable 132 // with the hubble network. 133 func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protocolVersions []uint, networkId uint64, mux *event.TypeMux, engine consensus.Engine, peers *peerSet, blockchain BlockChain, txpool txPool, chainDb vntdb.Database, odr *LesOdr, txrelay *LesTxRelay, serverPool *serverPool, quitSync chan struct{}, wg *sync.WaitGroup) (*ProtocolManager, error) { 134 // Create the protocol manager with the base fields 135 manager := &ProtocolManager{ 136 lightSync: lightSync, 137 eventMux: mux, 138 blockchain: blockchain, 139 chainConfig: chainConfig, 140 chainDb: chainDb, 141 odr: odr, 142 networkId: networkId, 143 txpool: txpool, 144 txrelay: txrelay, 145 serverPool: serverPool, 146 peers: peers, 147 newPeerCh: make(chan *peer), 148 quitSync: quitSync, 149 wg: wg, 150 noMorePeers: make(chan struct{}), 151 } 152 if odr != nil { 153 manager.retriever = odr.retriever 154 manager.reqDist = odr.retriever.dist 155 } 156 157 // Initiate a sub-protocol for every implemented version we can handle 158 manager.SubProtocols = make([]vntp2p.Protocol, 0, len(protocolVersions)) 159 for _, version := range protocolVersions { 160 // Compatible, initialize the sub-protocol 161 version := version // Closure for the run 162 fmt.Println(version) 163 manager.SubProtocols = append(manager.SubProtocols, vntp2p.Protocol{ 164 Name: "les", 165 Version: version, 166 Length: ProtocolLengths[version], 167 Run: func(p *vntp2p.Peer, rw vntp2p.MsgReadWriter) error { 168 var entry *poolEntry 169 peer := manager.newPeer(int(version), networkId, p, rw) 170 if manager.serverPool != nil { 171 addr := p.RemoteAddr().(*net.TCPAddr) 172 entry = manager.serverPool.connect(peer, addr.IP, uint16(addr.Port)) 173 } 174 peer.poolEntry = entry 175 select { 176 case manager.newPeerCh <- peer: 177 manager.wg.Add(1) 178 defer manager.wg.Done() 179 err := manager.handle(peer) 180 if entry != nil { 181 manager.serverPool.disconnect(entry) 182 } 183 return err 184 case <-manager.quitSync: 185 if entry != nil { 186 manager.serverPool.disconnect(entry) 187 } 188 return vntp2p.DiscQuitting 189 } 190 }, 191 NodeInfo: func() interface{} { 192 return manager.NodeInfo() 193 }, 194 PeerInfo: func(id libp2p.ID) interface{} { 195 if p := manager.peers.Peer(id); p != nil { 196 return p.Info() 197 } 198 return nil 199 }, 200 }) 201 } 202 if len(manager.SubProtocols) == 0 { 203 return nil, errIncompatibleConfig 204 } 205 206 removePeer := manager.removePeer 207 if disableClientRemovePeer { 208 removePeer = func(id libp2p.ID) {} 209 } 210 211 if lightSync { 212 manager.downloader = downloader.New(downloader.LightSync, chainDb, manager.eventMux, nil, blockchain, removePeer) 213 manager.peers.notify((*downloaderPeerNotify)(manager)) 214 manager.fetcher = newLightFetcher(manager) 215 } 216 217 return manager, nil 218 } 219 220 // removePeer initiates disconnection from a peer by removing it from the peer set 221 func (pm *ProtocolManager) removePeer(id libp2p.ID) { 222 pm.peers.Unregister(id) 223 } 224 225 func (pm *ProtocolManager) Start(maxPeers int) { 226 pm.maxPeers = maxPeers 227 228 if pm.lightSync { 229 go pm.syncer() 230 } else { 231 go func() { 232 for range pm.newPeerCh { 233 } 234 }() 235 } 236 } 237 238 func (pm *ProtocolManager) Stop() { 239 // Showing a log message. During download / process this could actually 240 // take between 5 to 10 seconds and therefor feedback is required. 241 log.Info("Stopping light VNT protocol") 242 243 // Quit the sync loop. 244 // After this send has completed, no new peers will be accepted. 245 pm.noMorePeers <- struct{}{} 246 247 close(pm.quitSync) // quits syncer, fetcher 248 249 // Disconnect existing sessions. 250 // This also closes the gate for any new registrations on the peer set. 251 // sessions which are already established but not added to pm.peers yet 252 // will exit when they try to register. 253 pm.peers.Close() 254 255 // Wait for any process action 256 pm.wg.Wait() 257 258 log.Info("Light VNT protocol stopped") 259 } 260 261 func (pm *ProtocolManager) newPeer(pv int, nv uint64, p *vntp2p.Peer, rw vntp2p.MsgReadWriter) *peer { 262 return newPeer(pv, nv, p, newMeteredMsgWriter(rw)) 263 } 264 265 // handle is the callback invoked to manage the life cycle of a les peer. When 266 // this function terminates, the peer is disconnected. 267 func (pm *ProtocolManager) handle(p *peer) error { 268 // Ignore maxPeers if this is a trusted peer 269 if pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted { 270 return vntp2p.DiscTooManyPeers 271 } 272 273 // p.Log().Debug("Light VNT peer connected", "name", p.Name()) 274 275 // Execute the LES handshake 276 var ( 277 genesis = pm.blockchain.Genesis() 278 head = pm.blockchain.CurrentHeader() 279 hash = head.Hash() 280 number = head.Number.Uint64() 281 td = pm.blockchain.GetTd(hash, number) 282 ) 283 if err := p.Handshake(td, hash, number, genesis.Hash(), pm.server); err != nil { 284 p.Log().Debug("Light VNT handshake failed", "err", err) 285 return err 286 } 287 if rw, ok := p.rw.(*meteredMsgReadWriter); ok { 288 rw.Init(p.version) 289 } 290 // Register the peer locally 291 if err := pm.peers.Register(p); err != nil { 292 p.Log().Error("Light VNT peer registration failed", "err", err) 293 return err 294 } 295 defer func() { 296 if pm.server != nil && pm.server.fcManager != nil && p.fcClient != nil { 297 p.fcClient.Remove(pm.server.fcManager) 298 } 299 pm.removePeer(p.id) 300 }() 301 // Register the peer in the downloader. If the downloader considers it banned, we disconnect 302 if pm.lightSync { 303 p.lock.Lock() 304 head := p.headInfo 305 p.lock.Unlock() 306 if pm.fetcher != nil { 307 pm.fetcher.announce(p, head) 308 } 309 310 if p.poolEntry != nil { 311 pm.serverPool.registered(p.poolEntry) 312 } 313 } 314 315 stop := make(chan struct{}) 316 defer close(stop) 317 go func() { 318 // new block announce loop 319 for { 320 select { 321 case announce := <-p.announceChn: 322 p.SendAnnounce(announce) 323 case <-stop: 324 return 325 } 326 } 327 }() 328 329 // main loop. handle incoming messages. 330 for { 331 if err := pm.handleMsg(p); err != nil { 332 p.Log().Debug("Light VNT message handling failed", "err", err) 333 return err 334 } 335 } 336 } 337 338 var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsV1Msg, SendTxMsg, SendTxV2Msg, GetTxStatusMsg, GetHeaderProofsMsg, GetProofsV2Msg, GetHelperTrieProofsMsg} 339 340 // handleMsg is invoked whenever an inbound message is received from a remote 341 // peer. The remote connection is torn down upon returning any error. 342 func (pm *ProtocolManager) handleMsg(p *peer) error { 343 // Read the next message from the remote peer, and ensure it's fully consumed 344 msg, err := p.rw.ReadMsg() 345 if err != nil { 346 return err 347 } 348 p.Log().Trace("Light VNT message arrived", "code", msg.Body.Type, "bytes", msg.GetBodySize()) 349 350 costs := p.fcCosts[uint64(msg.Body.Type)] 351 reject := func(reqCnt, maxCnt uint64) bool { 352 if p.fcClient == nil || reqCnt > maxCnt { 353 return true 354 } 355 bufValue, _ := p.fcClient.AcceptRequest() 356 cost := costs.baseCost + reqCnt*costs.reqCost 357 if cost > pm.server.defParams.BufLimit { 358 cost = pm.server.defParams.BufLimit 359 } 360 if cost > bufValue { 361 recharge := time.Duration((cost - bufValue) * 1000000 / pm.server.defParams.MinRecharge) 362 p.Log().Error("Request came too early", "recharge", common.PrettyDuration(recharge)) 363 return true 364 } 365 return false 366 } 367 size := msg.GetBodySize() 368 if size > ProtocolMaxMsgSize { 369 return errResp(ErrMsgTooLarge, "%v > %v", size, ProtocolMaxMsgSize) 370 } 371 372 //-- 按理说,新版的协议处理方式,不会有残留数据得不到处理 373 //defer msg.Discard() 374 375 var deliverMsg *Msg 376 377 // Handle the message depending on its contents 378 switch msg.Body.Type { 379 case StatusMsg: 380 p.Log().Trace("Received status message") 381 // Status messages should never arrive after the handshake 382 return errResp(ErrExtraStatusMsg, "uncontrolled status message") 383 384 // Block header query, collect the requested headers and reply 385 case AnnounceMsg: 386 p.Log().Trace("Received announce message") 387 if p.requestAnnounceType == announceTypeNone { 388 return errResp(ErrUnexpectedResponse, "") 389 } 390 391 var req announceData 392 if err := msg.Decode(&req); err != nil { 393 return errResp(ErrDecode, "%v: %v", msg, err) 394 } 395 396 if p.requestAnnounceType == announceTypeSigned { 397 if err := req.checkSignature(p.pubKey); err != nil { 398 p.Log().Trace("Invalid announcement signature", "err", err) 399 return err 400 } 401 p.Log().Trace("Valid announcement signature") 402 } 403 404 p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth) 405 if pm.fetcher != nil { 406 pm.fetcher.announce(p, &req) 407 } 408 409 case GetBlockHeadersMsg: 410 p.Log().Trace("Received block header request") 411 // Decode the complex header query 412 var req struct { 413 ReqID uint64 414 Query getBlockHeadersData 415 } 416 if err := msg.Decode(&req); err != nil { 417 return errResp(ErrDecode, "%v: %v", msg, err) 418 } 419 420 query := req.Query 421 if reject(query.Amount, MaxHeaderFetch) { 422 return errResp(ErrRequestRejected, "") 423 } 424 425 hashMode := query.Origin.Hash != (common.Hash{}) 426 first := true 427 maxNonCanonical := uint64(100) 428 429 // Gather headers until the fetch or network limits is reached 430 var ( 431 bytes common.StorageSize 432 headers []*types.Header 433 unknown bool 434 ) 435 for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit { 436 // Retrieve the next header satisfying the query 437 var origin *types.Header 438 if hashMode { 439 if first { 440 first = false 441 origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash) 442 if origin != nil { 443 query.Origin.Number = origin.Number.Uint64() 444 } 445 } else { 446 origin = pm.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number) 447 } 448 } else { 449 origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number) 450 } 451 if origin == nil { 452 break 453 } 454 headers = append(headers, origin) 455 bytes += estHeaderRlpSize 456 457 // Advance to the next header of the query 458 switch { 459 case hashMode && query.Reverse: 460 // Hash based traversal towards the genesis block 461 ancestor := query.Skip + 1 462 if ancestor == 0 { 463 unknown = true 464 } else { 465 query.Origin.Hash, query.Origin.Number = pm.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) 466 unknown = (query.Origin.Hash == common.Hash{}) 467 } 468 case hashMode && !query.Reverse: 469 // Hash based traversal towards the leaf block 470 var ( 471 current = origin.Number.Uint64() 472 next = current + query.Skip + 1 473 ) 474 if next <= current { 475 infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ") 476 p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) 477 unknown = true 478 } else { 479 if header := pm.blockchain.GetHeaderByNumber(next); header != nil { 480 nextHash := header.Hash() 481 expOldHash, _ := pm.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) 482 if expOldHash == query.Origin.Hash { 483 query.Origin.Hash, query.Origin.Number = nextHash, next 484 } else { 485 unknown = true 486 } 487 } else { 488 unknown = true 489 } 490 } 491 case query.Reverse: 492 // Number based traversal towards the genesis block 493 if query.Origin.Number >= query.Skip+1 { 494 query.Origin.Number -= query.Skip + 1 495 } else { 496 unknown = true 497 } 498 499 case !query.Reverse: 500 // Number based traversal towards the leaf block 501 query.Origin.Number += query.Skip + 1 502 } 503 } 504 505 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + query.Amount*costs.reqCost) 506 pm.server.fcCostStats.update(uint64(msg.Body.Type), query.Amount, rcost) 507 return p.SendBlockHeaders(req.ReqID, bv, headers) 508 509 case BlockHeadersMsg: 510 if pm.downloader == nil { 511 return errResp(ErrUnexpectedResponse, "") 512 } 513 514 p.Log().Trace("Received block header response message") 515 // A batch of headers arrived to one of our previous requests 516 var resp struct { 517 ReqID, BV uint64 518 Headers []*types.Header 519 } 520 if err := msg.Decode(&resp); err != nil { 521 return errResp(ErrDecode, "msg %v: %v", msg, err) 522 } 523 p.fcServer.GotReply(resp.ReqID, resp.BV) 524 if pm.fetcher != nil && pm.fetcher.requestedID(resp.ReqID) { 525 pm.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers) 526 } else { 527 err := pm.downloader.DeliverHeaders(p.id, resp.Headers) 528 if err != nil { 529 log.Debug(fmt.Sprint(err)) 530 } 531 } 532 533 case GetBlockBodiesMsg: 534 p.Log().Trace("Received block bodies request") 535 // Decode the retrieval message 536 var req struct { 537 ReqID uint64 538 Hashes []common.Hash 539 } 540 if err := msg.Decode(&req); err != nil { 541 return errResp(ErrDecode, "msg %v: %v", msg, err) 542 } 543 // Gather blocks until the fetch or network limits is reached 544 var ( 545 bytes int 546 bodies []rlp.RawValue 547 ) 548 reqCnt := len(req.Hashes) 549 if reject(uint64(reqCnt), MaxBodyFetch) { 550 return errResp(ErrRequestRejected, "") 551 } 552 for _, hash := range req.Hashes { 553 if bytes >= softResponseLimit { 554 break 555 } 556 // Retrieve the requested block body, stopping if enough was found 557 if number := rawdb.ReadHeaderNumber(pm.chainDb, hash); number != nil { 558 if data := rawdb.ReadBodyRLP(pm.chainDb, hash, *number); len(data) != 0 { 559 bodies = append(bodies, data) 560 bytes += len(data) 561 } 562 } 563 } 564 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 565 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 566 return p.SendBlockBodiesRLP(req.ReqID, bv, bodies) 567 568 case BlockBodiesMsg: 569 if pm.odr == nil { 570 return errResp(ErrUnexpectedResponse, "") 571 } 572 573 p.Log().Trace("Received block bodies response") 574 // A batch of block bodies arrived to one of our previous requests 575 var resp struct { 576 ReqID, BV uint64 577 Data []*types.Body 578 } 579 if err := msg.Decode(&resp); err != nil { 580 return errResp(ErrDecode, "msg %v: %v", msg, err) 581 } 582 p.fcServer.GotReply(resp.ReqID, resp.BV) 583 deliverMsg = &Msg{ 584 MsgType: MsgBlockBodies, 585 ReqID: resp.ReqID, 586 Obj: resp.Data, 587 } 588 589 case GetCodeMsg: 590 p.Log().Trace("Received code request") 591 // Decode the retrieval message 592 var req struct { 593 ReqID uint64 594 Reqs []CodeReq 595 } 596 if err := msg.Decode(&req); err != nil { 597 return errResp(ErrDecode, "msg %v: %v", msg, err) 598 } 599 // Gather state data until the fetch or network limits is reached 600 var ( 601 bytes int 602 data [][]byte 603 ) 604 reqCnt := len(req.Reqs) 605 if reject(uint64(reqCnt), MaxCodeFetch) { 606 return errResp(ErrRequestRejected, "") 607 } 608 for _, req := range req.Reqs { 609 // Retrieve the requested state entry, stopping if enough was found 610 if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil { 611 if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil { 612 statedb, err := pm.blockchain.State() 613 if err != nil { 614 continue 615 } 616 account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey)) 617 if err != nil { 618 continue 619 } 620 code, _ := statedb.Database().TrieDB().Node(common.BytesToHash(account.CodeHash)) 621 622 data = append(data, code) 623 if bytes += len(code); bytes >= softResponseLimit { 624 break 625 } 626 } 627 } 628 } 629 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 630 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 631 return p.SendCode(req.ReqID, bv, data) 632 633 case CodeMsg: 634 if pm.odr == nil { 635 return errResp(ErrUnexpectedResponse, "") 636 } 637 638 p.Log().Trace("Received code response") 639 // A batch of node state data arrived to one of our previous requests 640 var resp struct { 641 ReqID, BV uint64 642 Data [][]byte 643 } 644 if err := msg.Decode(&resp); err != nil { 645 return errResp(ErrDecode, "msg %v: %v", msg, err) 646 } 647 p.fcServer.GotReply(resp.ReqID, resp.BV) 648 deliverMsg = &Msg{ 649 MsgType: MsgCode, 650 ReqID: resp.ReqID, 651 Obj: resp.Data, 652 } 653 654 case GetReceiptsMsg: 655 p.Log().Trace("Received receipts request") 656 // Decode the retrieval message 657 var req struct { 658 ReqID uint64 659 Hashes []common.Hash 660 } 661 if err := msg.Decode(&req); err != nil { 662 return errResp(ErrDecode, "msg %v: %v", msg, err) 663 } 664 // Gather state data until the fetch or network limits is reached 665 var ( 666 bytes int 667 receipts []rlp.RawValue 668 ) 669 reqCnt := len(req.Hashes) 670 if reject(uint64(reqCnt), MaxReceiptFetch) { 671 return errResp(ErrRequestRejected, "") 672 } 673 for _, hash := range req.Hashes { 674 if bytes >= softResponseLimit { 675 break 676 } 677 // Retrieve the requested block's receipts, skipping if unknown to us 678 var results types.Receipts 679 if number := rawdb.ReadHeaderNumber(pm.chainDb, hash); number != nil { 680 results = rawdb.ReadReceipts(pm.chainDb, hash, *number) 681 } 682 if results == nil { 683 if header := pm.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { 684 continue 685 } 686 } 687 // If known, encode and queue for response packet 688 if encoded, err := rlp.EncodeToBytes(results); err != nil { 689 log.Error("Failed to encode receipt", "err", err) 690 } else { 691 receipts = append(receipts, encoded) 692 bytes += len(encoded) 693 } 694 } 695 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 696 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 697 return p.SendReceiptsRLP(req.ReqID, bv, receipts) 698 699 case ReceiptsMsg: 700 if pm.odr == nil { 701 return errResp(ErrUnexpectedResponse, "") 702 } 703 704 p.Log().Trace("Received receipts response") 705 // A batch of receipts arrived to one of our previous requests 706 var resp struct { 707 ReqID, BV uint64 708 Receipts []types.Receipts 709 } 710 if err := msg.Decode(&resp); err != nil { 711 return errResp(ErrDecode, "msg %v: %v", msg, err) 712 } 713 p.fcServer.GotReply(resp.ReqID, resp.BV) 714 deliverMsg = &Msg{ 715 MsgType: MsgReceipts, 716 ReqID: resp.ReqID, 717 Obj: resp.Receipts, 718 } 719 720 case GetProofsV1Msg: 721 p.Log().Trace("Received proofs request") 722 // Decode the retrieval message 723 var req struct { 724 ReqID uint64 725 Reqs []ProofReq 726 } 727 if err := msg.Decode(&req); err != nil { 728 return errResp(ErrDecode, "msg %v: %v", msg, err) 729 } 730 // Gather state data until the fetch or network limits is reached 731 var ( 732 bytes int 733 proofs proofsData 734 ) 735 reqCnt := len(req.Reqs) 736 if reject(uint64(reqCnt), MaxProofsFetch) { 737 return errResp(ErrRequestRejected, "") 738 } 739 for _, req := range req.Reqs { 740 // Retrieve the requested state entry, stopping if enough was found 741 if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil { 742 if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil { 743 statedb, err := pm.blockchain.State() 744 if err != nil { 745 continue 746 } 747 var trie state.Trie 748 if len(req.AccKey) > 0 { 749 account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey)) 750 if err != nil { 751 continue 752 } 753 trie, _ = statedb.Database().OpenStorageTrie(common.BytesToHash(req.AccKey), account.Root) 754 } else { 755 trie, _ = statedb.Database().OpenTrie(header.Root) 756 } 757 if trie != nil { 758 var proof light.NodeList 759 trie.Prove(req.Key, 0, &proof) 760 761 proofs = append(proofs, proof) 762 if bytes += proof.DataSize(); bytes >= softResponseLimit { 763 break 764 } 765 } 766 } 767 } 768 } 769 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 770 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 771 return p.SendProofs(req.ReqID, bv, proofs) 772 773 case GetProofsV2Msg: 774 p.Log().Trace("Received les/2 proofs request") 775 // Decode the retrieval message 776 var req struct { 777 ReqID uint64 778 Reqs []ProofReq 779 } 780 if err := msg.Decode(&req); err != nil { 781 return errResp(ErrDecode, "msg %v: %v", msg, err) 782 } 783 // Gather state data until the fetch or network limits is reached 784 var ( 785 lastBHash common.Hash 786 statedb *state.StateDB 787 root common.Hash 788 ) 789 reqCnt := len(req.Reqs) 790 if reject(uint64(reqCnt), MaxProofsFetch) { 791 return errResp(ErrRequestRejected, "") 792 } 793 794 nodes := light.NewNodeSet() 795 796 for _, req := range req.Reqs { 797 // Look up the state belonging to the request 798 if statedb == nil || req.BHash != lastBHash { 799 statedb, root, lastBHash = nil, common.Hash{}, req.BHash 800 801 if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil { 802 if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil { 803 statedb, _ = pm.blockchain.State() 804 root = header.Root 805 } 806 } 807 } 808 if statedb == nil { 809 continue 810 } 811 // Pull the account or storage trie of the request 812 var trie state.Trie 813 if len(req.AccKey) > 0 { 814 account, err := pm.getAccount(statedb, root, common.BytesToHash(req.AccKey)) 815 if err != nil { 816 continue 817 } 818 trie, _ = statedb.Database().OpenStorageTrie(common.BytesToHash(req.AccKey), account.Root) 819 } else { 820 trie, _ = statedb.Database().OpenTrie(root) 821 } 822 if trie == nil { 823 continue 824 } 825 // Prove the user's request from the account or stroage trie 826 trie.Prove(req.Key, req.FromLevel, nodes) 827 if nodes.DataSize() >= softResponseLimit { 828 break 829 } 830 } 831 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 832 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 833 return p.SendProofsV2(req.ReqID, bv, nodes.NodeList()) 834 835 case ProofsV1Msg: 836 if pm.odr == nil { 837 return errResp(ErrUnexpectedResponse, "") 838 } 839 840 p.Log().Trace("Received proofs response") 841 // A batch of merkle proofs arrived to one of our previous requests 842 var resp struct { 843 ReqID, BV uint64 844 Data []light.NodeList 845 } 846 if err := msg.Decode(&resp); err != nil { 847 return errResp(ErrDecode, "msg %v: %v", msg, err) 848 } 849 p.fcServer.GotReply(resp.ReqID, resp.BV) 850 deliverMsg = &Msg{ 851 MsgType: MsgProofsV1, 852 ReqID: resp.ReqID, 853 Obj: resp.Data, 854 } 855 856 case ProofsV2Msg: 857 if pm.odr == nil { 858 return errResp(ErrUnexpectedResponse, "") 859 } 860 861 p.Log().Trace("Received les/2 proofs response") 862 // A batch of merkle proofs arrived to one of our previous requests 863 var resp struct { 864 ReqID, BV uint64 865 Data light.NodeList 866 } 867 if err := msg.Decode(&resp); err != nil { 868 return errResp(ErrDecode, "msg %v: %v", msg, err) 869 } 870 p.fcServer.GotReply(resp.ReqID, resp.BV) 871 deliverMsg = &Msg{ 872 MsgType: MsgProofsV2, 873 ReqID: resp.ReqID, 874 Obj: resp.Data, 875 } 876 877 case GetHeaderProofsMsg: 878 p.Log().Trace("Received headers proof request") 879 // Decode the retrieval message 880 var req struct { 881 ReqID uint64 882 Reqs []ChtReq 883 } 884 if err := msg.Decode(&req); err != nil { 885 return errResp(ErrDecode, "msg %v: %v", msg, err) 886 } 887 // Gather state data until the fetch or network limits is reached 888 var ( 889 bytes int 890 proofs []ChtResp 891 ) 892 reqCnt := len(req.Reqs) 893 if reject(uint64(reqCnt), MaxHelperTrieProofsFetch) { 894 return errResp(ErrRequestRejected, "") 895 } 896 trieDb := trie.NewDatabase(vntdb.NewTable(pm.chainDb, light.ChtTablePrefix)) 897 for _, req := range req.Reqs { 898 if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil { 899 sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, req.ChtNum*light.CHTFrequencyServer-1) 900 if root := light.GetChtRoot(pm.chainDb, req.ChtNum-1, sectionHead); root != (common.Hash{}) { 901 trie, err := trie.New(root, trieDb) 902 if err != nil { 903 continue 904 } 905 var encNumber [8]byte 906 binary.BigEndian.PutUint64(encNumber[:], req.BlockNum) 907 908 var proof light.NodeList 909 trie.Prove(encNumber[:], 0, &proof) 910 911 proofs = append(proofs, ChtResp{Header: header, Proof: proof}) 912 if bytes += proof.DataSize() + estHeaderRlpSize; bytes >= softResponseLimit { 913 break 914 } 915 } 916 } 917 } 918 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 919 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 920 return p.SendHeaderProofs(req.ReqID, bv, proofs) 921 922 case GetHelperTrieProofsMsg: 923 p.Log().Trace("Received helper trie proof request") 924 // Decode the retrieval message 925 var req struct { 926 ReqID uint64 927 Reqs []HelperTrieReq 928 } 929 if err := msg.Decode(&req); err != nil { 930 return errResp(ErrDecode, "msg %v: %v", msg, err) 931 } 932 // Gather state data until the fetch or network limits is reached 933 var ( 934 auxBytes int 935 auxData [][]byte 936 ) 937 reqCnt := len(req.Reqs) 938 if reject(uint64(reqCnt), MaxHelperTrieProofsFetch) { 939 return errResp(ErrRequestRejected, "") 940 } 941 942 var ( 943 lastIdx uint64 944 lastType uint 945 root common.Hash 946 auxTrie *trie.Trie 947 ) 948 nodes := light.NewNodeSet() 949 for _, req := range req.Reqs { 950 if auxTrie == nil || req.Type != lastType || req.TrieIdx != lastIdx { 951 auxTrie, lastType, lastIdx = nil, req.Type, req.TrieIdx 952 953 var prefix string 954 if root, prefix = pm.getHelperTrie(req.Type, req.TrieIdx); root != (common.Hash{}) { 955 auxTrie, _ = trie.New(root, trie.NewDatabase(vntdb.NewTable(pm.chainDb, prefix))) 956 } 957 } 958 if req.AuxReq == auxRoot { 959 var data []byte 960 if root != (common.Hash{}) { 961 data = root[:] 962 } 963 auxData = append(auxData, data) 964 auxBytes += len(data) 965 } else { 966 if auxTrie != nil { 967 auxTrie.Prove(req.Key, req.FromLevel, nodes) 968 } 969 if req.AuxReq != 0 { 970 data := pm.getHelperTrieAuxData(req) 971 auxData = append(auxData, data) 972 auxBytes += len(data) 973 } 974 } 975 if nodes.DataSize()+auxBytes >= softResponseLimit { 976 break 977 } 978 } 979 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 980 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 981 return p.SendHelperTrieProofs(req.ReqID, bv, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}) 982 983 case HeaderProofsMsg: 984 if pm.odr == nil { 985 return errResp(ErrUnexpectedResponse, "") 986 } 987 988 p.Log().Trace("Received headers proof response") 989 var resp struct { 990 ReqID, BV uint64 991 Data []ChtResp 992 } 993 if err := msg.Decode(&resp); err != nil { 994 return errResp(ErrDecode, "msg %v: %v", msg, err) 995 } 996 p.fcServer.GotReply(resp.ReqID, resp.BV) 997 deliverMsg = &Msg{ 998 MsgType: MsgHeaderProofs, 999 ReqID: resp.ReqID, 1000 Obj: resp.Data, 1001 } 1002 1003 case HelperTrieProofsMsg: 1004 if pm.odr == nil { 1005 return errResp(ErrUnexpectedResponse, "") 1006 } 1007 1008 p.Log().Trace("Received helper trie proof response") 1009 var resp struct { 1010 ReqID, BV uint64 1011 Data HelperTrieResps 1012 } 1013 if err := msg.Decode(&resp); err != nil { 1014 return errResp(ErrDecode, "msg %v: %v", msg, err) 1015 } 1016 1017 p.fcServer.GotReply(resp.ReqID, resp.BV) 1018 deliverMsg = &Msg{ 1019 MsgType: MsgHelperTrieProofs, 1020 ReqID: resp.ReqID, 1021 Obj: resp.Data, 1022 } 1023 1024 case SendTxMsg: 1025 if pm.txpool == nil { 1026 return errResp(ErrRequestRejected, "") 1027 } 1028 // Transactions arrived, parse all of them and deliver to the pool 1029 var txs []*types.Transaction 1030 if err := msg.Decode(&txs); err != nil { 1031 return errResp(ErrDecode, "msg %v: %v", msg, err) 1032 } 1033 reqCnt := len(txs) 1034 if reject(uint64(reqCnt), MaxTxSend) { 1035 return errResp(ErrRequestRejected, "") 1036 } 1037 pm.txpool.AddRemotes(txs) 1038 1039 _, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 1040 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 1041 1042 case SendTxV2Msg: 1043 if pm.txpool == nil { 1044 return errResp(ErrRequestRejected, "") 1045 } 1046 // Transactions arrived, parse all of them and deliver to the pool 1047 var req struct { 1048 ReqID uint64 1049 Txs []*types.Transaction 1050 } 1051 if err := msg.Decode(&req); err != nil { 1052 return errResp(ErrDecode, "msg %v: %v", msg, err) 1053 } 1054 reqCnt := len(req.Txs) 1055 if reject(uint64(reqCnt), MaxTxSend) { 1056 return errResp(ErrRequestRejected, "") 1057 } 1058 1059 hashes := make([]common.Hash, len(req.Txs)) 1060 for i, tx := range req.Txs { 1061 hashes[i] = tx.Hash() 1062 } 1063 stats := pm.txStatus(hashes) 1064 for i, stat := range stats { 1065 if stat.Status == core.TxStatusUnknown { 1066 if errs := pm.txpool.AddRemotes([]*types.Transaction{req.Txs[i]}); errs[0] != nil { 1067 stats[i].Error = errs[0].Error() 1068 continue 1069 } 1070 stats[i] = pm.txStatus([]common.Hash{hashes[i]})[0] 1071 } 1072 } 1073 1074 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 1075 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 1076 1077 return p.SendTxStatus(req.ReqID, bv, stats) 1078 1079 case GetTxStatusMsg: 1080 if pm.txpool == nil { 1081 return errResp(ErrUnexpectedResponse, "") 1082 } 1083 // Transactions arrived, parse all of them and deliver to the pool 1084 var req struct { 1085 ReqID uint64 1086 Hashes []common.Hash 1087 } 1088 if err := msg.Decode(&req); err != nil { 1089 return errResp(ErrDecode, "msg %v: %v", msg, err) 1090 } 1091 reqCnt := len(req.Hashes) 1092 if reject(uint64(reqCnt), MaxTxStatus) { 1093 return errResp(ErrRequestRejected, "") 1094 } 1095 bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) 1096 pm.server.fcCostStats.update(uint64(msg.Body.Type), uint64(reqCnt), rcost) 1097 1098 return p.SendTxStatus(req.ReqID, bv, pm.txStatus(req.Hashes)) 1099 1100 case TxStatusMsg: 1101 if pm.odr == nil { 1102 return errResp(ErrUnexpectedResponse, "") 1103 } 1104 1105 p.Log().Trace("Received tx status response") 1106 var resp struct { 1107 ReqID, BV uint64 1108 Status []txStatus 1109 } 1110 if err := msg.Decode(&resp); err != nil { 1111 return errResp(ErrDecode, "msg %v: %v", msg, err) 1112 } 1113 1114 p.fcServer.GotReply(resp.ReqID, resp.BV) 1115 1116 default: 1117 p.Log().Trace("Received unknown message", "code", msg.Body.Type) 1118 return errResp(ErrInvalidMsgCode, "%v", msg.Body.Type) 1119 } 1120 1121 if deliverMsg != nil { 1122 err := pm.retriever.deliver(p, deliverMsg) 1123 if err != nil { 1124 p.responseErrors++ 1125 if p.responseErrors > maxResponseErrors { 1126 return err 1127 } 1128 } 1129 } 1130 return nil 1131 } 1132 1133 // getAccount retrieves an account from the state based at root. 1134 func (pm *ProtocolManager) getAccount(statedb *state.StateDB, root, hash common.Hash) (state.Account, error) { 1135 trie, err := trie.New(root, statedb.Database().TrieDB()) 1136 if err != nil { 1137 return state.Account{}, err 1138 } 1139 blob, err := trie.TryGet(hash[:]) 1140 if err != nil { 1141 return state.Account{}, err 1142 } 1143 var account state.Account 1144 if err = rlp.DecodeBytes(blob, &account); err != nil { 1145 return state.Account{}, err 1146 } 1147 return account, nil 1148 } 1149 1150 // getHelperTrie returns the post-processed trie root for the given trie ID and section index 1151 func (pm *ProtocolManager) getHelperTrie(id uint, idx uint64) (common.Hash, string) { 1152 switch id { 1153 case htCanonical: 1154 sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, (idx+1)*light.CHTFrequencyClient-1) 1155 return light.GetChtV2Root(pm.chainDb, idx, sectionHead), light.ChtTablePrefix 1156 case htBloomBits: 1157 sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, (idx+1)*light.BloomTrieFrequency-1) 1158 return light.GetBloomTrieRoot(pm.chainDb, idx, sectionHead), light.BloomTrieTablePrefix 1159 } 1160 return common.Hash{}, "" 1161 } 1162 1163 // getHelperTrieAuxData returns requested auxiliary data for the given HelperTrie request 1164 func (pm *ProtocolManager) getHelperTrieAuxData(req HelperTrieReq) []byte { 1165 switch { 1166 case req.Type == htCanonical && req.AuxReq == auxHeader && len(req.Key) == 8: 1167 blockNum := binary.BigEndian.Uint64(req.Key) 1168 hash := rawdb.ReadCanonicalHash(pm.chainDb, blockNum) 1169 return rawdb.ReadHeaderRLP(pm.chainDb, hash, blockNum) 1170 } 1171 return nil 1172 } 1173 1174 func (pm *ProtocolManager) txStatus(hashes []common.Hash) []txStatus { 1175 stats := make([]txStatus, len(hashes)) 1176 for i, stat := range pm.txpool.Status(hashes) { 1177 // Save the status we've got from the transaction pool 1178 stats[i].Status = stat 1179 1180 // If the transaction is unknown to the pool, try looking it up locally 1181 if stat == core.TxStatusUnknown { 1182 if block, number, index := rawdb.ReadTxLookupEntry(pm.chainDb, hashes[i]); block != (common.Hash{}) { 1183 stats[i].Status = core.TxStatusIncluded 1184 stats[i].Lookup = &rawdb.TxLookupEntry{BlockHash: block, BlockIndex: number, Index: index} 1185 } 1186 } 1187 } 1188 return stats 1189 } 1190 1191 // NodeInfo represents a short summary of the VNT sub-protocol metadata 1192 // known about the host peer. 1193 type NodeInfo struct { 1194 Network uint64 `json:"network"` // VNT network ID (1=Frontier) 1195 Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain 1196 Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block 1197 Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules 1198 Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block 1199 } 1200 1201 // NodeInfo retrieves some protocol metadata about the running host node. 1202 func (self *ProtocolManager) NodeInfo() *NodeInfo { 1203 head := self.blockchain.CurrentHeader() 1204 hash := head.Hash() 1205 1206 return &NodeInfo{ 1207 Network: self.networkId, 1208 Difficulty: self.blockchain.GetTd(hash, head.Number.Uint64()), 1209 Genesis: self.blockchain.Genesis().Hash(), 1210 Config: self.blockchain.Config(), 1211 Head: hash, 1212 } 1213 } 1214 1215 // downloaderPeerNotify implements peerSetNotify 1216 type downloaderPeerNotify ProtocolManager 1217 1218 type peerConnection struct { 1219 manager *ProtocolManager 1220 peer *peer 1221 } 1222 1223 func (pc *peerConnection) Head() (common.Hash, *big.Int) { 1224 return pc.peer.HeadAndTd() 1225 } 1226 1227 func (pc *peerConnection) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { 1228 reqID := genReqID() 1229 rq := &distReq{ 1230 getCost: func(dp distPeer) uint64 { 1231 peer := dp.(*peer) 1232 return peer.GetRequestCost(GetBlockHeadersMsg, amount) 1233 }, 1234 canSend: func(dp distPeer) bool { 1235 return dp.(*peer) == pc.peer 1236 }, 1237 request: func(dp distPeer) func() { 1238 peer := dp.(*peer) 1239 cost := peer.GetRequestCost(GetBlockHeadersMsg, amount) 1240 peer.fcServer.QueueRequest(reqID, cost) 1241 return func() { peer.RequestHeadersByHash(reqID, cost, origin, amount, skip, reverse) } 1242 }, 1243 } 1244 _, ok := <-pc.manager.reqDist.queue(rq) 1245 if !ok { 1246 return ErrNoPeers 1247 } 1248 return nil 1249 } 1250 1251 func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { 1252 reqID := genReqID() 1253 rq := &distReq{ 1254 getCost: func(dp distPeer) uint64 { 1255 peer := dp.(*peer) 1256 return peer.GetRequestCost(GetBlockHeadersMsg, amount) 1257 }, 1258 canSend: func(dp distPeer) bool { 1259 return dp.(*peer) == pc.peer 1260 }, 1261 request: func(dp distPeer) func() { 1262 peer := dp.(*peer) 1263 cost := peer.GetRequestCost(GetBlockHeadersMsg, amount) 1264 peer.fcServer.QueueRequest(reqID, cost) 1265 return func() { peer.RequestHeadersByNumber(reqID, cost, origin, amount, skip, reverse) } 1266 }, 1267 } 1268 _, ok := <-pc.manager.reqDist.queue(rq) 1269 if !ok { 1270 return ErrNoPeers 1271 } 1272 return nil 1273 } 1274 1275 func (d *downloaderPeerNotify) registerPeer(p *peer) { 1276 pm := (*ProtocolManager)(d) 1277 pc := &peerConnection{ 1278 manager: pm, 1279 peer: p, 1280 } 1281 pm.downloader.RegisterLightPeer(p.id, vntVersion, pc) 1282 } 1283 1284 func (d *downloaderPeerNotify) unregisterPeer(p *peer) { 1285 pm := (*ProtocolManager)(d) 1286 pm.downloader.UnregisterPeer(p.id) 1287 }