From 2f607c2726332ab3dcc7de11a406e5642ebdf467 Mon Sep 17 00:00:00 2001 From: pacamara Date: Wed, 6 Dec 2017 15:30:24 +0000 Subject: [PATCH] les, light: add GetCheckpoint to LES (status.im issue 320) --- les/handler.go | 59 ++++++++++++++++++++++++++++++++++++++++++++- les/odr.go | 1 + les/odr_requests.go | 57 +++++++++++++++++++++++++++++++++++++++++++ les/peer.go | 19 +++++++++++++++ les/protocol.go | 4 ++- les/sync.go | 31 ++++++++++++++++++++++++ light/lightchain.go | 8 ++++++ light/odr.go | 13 ++++++++++ 8 files changed, 190 insertions(+), 2 deletions(-) diff --git a/les/handler.go b/les/handler.go index 613fbb79fe..fd7c7e4fec 100644 --- a/les/handler.go +++ b/les/handler.go @@ -319,7 +319,7 @@ func (pm *ProtocolManager) handle(p *peer) error { } } -var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsV1Msg, SendTxMsg, SendTxV2Msg, GetTxStatusMsg, GetHeaderProofsMsg, GetProofsV2Msg, GetHelperTrieProofsMsg} +var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsV1Msg, SendTxMsg, SendTxV2Msg, GetTxStatusMsg, GetHeaderProofsMsg, GetProofsV2Msg, GetHelperTrieProofsMsg, GetCheckpointMsg} // handleMsg is invoked whenever an inbound message is received from a remote // peer. The remote connection is torn down upon returning any error. @@ -1063,6 +1063,63 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { p.fcServer.GotReply(resp.ReqID, resp.BV) + case GetCheckpointMsg: + p.Log().Debug("Received GetCheckpointMsg", "msg", msg) + // Decode the retrieval message + var req struct { + ReqID uint64 + Req CheckpointReq + } + if err := msg.Decode(&req); err != nil { + return errResp(ErrDecode, "msg %v: %v", msg, err) + } + p.Log().Debug("Got CheckpointReq", "req", req) + + sectionHead := pm.server.chtIndexer.SectionHead(req.Req.SectionIdx) + + var chtRoot common.Hash + var prefix string + chtRoot, prefix = pm.getHelperTrie(0, req.Req.SectionIdx) + p.Log().Debug("Got chtRoot, prefix=", "chtRoot", chtRoot, "prefix", prefix) + + var bloomRoot common.Hash + var prefix2 string + bloomRoot, prefix2 = pm.getHelperTrie(1, req.Req.SectionIdx) + p.Log().Debug("Got bloomRoot, prefix=", "bloomRoot", bloomRoot, "prefix2", prefix2) + + var roots [3]common.Hash + roots[0] = sectionHead + roots[1] = chtRoot + roots[2] = bloomRoot + + reqCnt := 1 + bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) + pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) + result := p.SendCheckpoint(req.ReqID, bv, roots) + p.Log().Debug("Called SendCheckpoint ok with ", "result", result) + return result + + case CheckpointMsg: + if pm.odr == nil { + return errResp(ErrUnexpectedResponse, "") + } + + p.Log().Debug("Received CheckpointMsg response", "msg", msg) + var resp struct { + ReqID, BV uint64 + Roots [3]common.Hash + } + if err := msg.Decode(&resp); err != nil { + return errResp(ErrDecode, "msg %v: %v", msg, err) + } + + p.fcServer.GotReply(resp.ReqID, resp.BV) + deliverMsg = &Msg{ + MsgType: MsgCheckpoint, + ReqID: resp.ReqID, + Obj: resp.Roots, + } + default: p.Log().Trace("Received unknown message", "code", msg.Code) return errResp(ErrInvalidMsgCode, "%v", msg.Code) diff --git a/les/odr.go b/les/odr.go index f8412aaad7..40c9cd8df1 100644 --- a/les/odr.go +++ b/les/odr.go @@ -77,6 +77,7 @@ const ( MsgProofsV2 MsgHeaderProofs MsgHelperTrieProofs + MsgCheckpoint ) // Msg encodes a LES message that delivers reply data for a request diff --git a/les/odr_requests.go b/les/odr_requests.go index 937a4f1d9d..0c79485a26 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -68,6 +68,8 @@ func LesRequest(req light.OdrRequest) LesOdrRequest { return (*ChtRequest)(r) case *light.BloomRequest: return (*BloomRequest)(r) + case *light.CheckpointRequest: + return (*CheckpointRequest)(r) default: return nil } @@ -543,6 +545,61 @@ func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error { return nil } +/////////////////////////////////////////////////////////////////////////////////////////// +// status.im request 320 + +type CheckpointReq struct { + SectionIdx uint64 +} + +// ODR request type for requesting a non-trusted checkpoint (CHT and Bloom hash values) +type CheckpointRequest light.CheckpointRequest + +// GetCost returns the cost of the given ODR request according to the serving +// peer's cost table (implementation of LesOdrRequest) +func (r *CheckpointRequest) GetCost(peer *peer) uint64 { + return peer.GetRequestCost(GetCheckpointMsg, 1) +} + +// CanSend tells if a certain peer is suitable for serving the given request +func (r *CheckpointRequest) CanSend(peer *peer) bool { + log.Debug("CheckpointRequest.CanSend", "peer", peer) + peer.lock.RLock() + defer peer.lock.RUnlock() + + if peer.version < lpv2 { + return false + } else { + return true + } +} + +// Request sends an ODR request to the LES network (implementation of LesOdrRequest) +func (r *CheckpointRequest) Request(reqID uint64, peer *peer) error { + peer.Log().Debug("Requesting Checkpoint", "SectionIdx", r.SectionIdx) + req := CheckpointReq{SectionIdx: r.SectionIdx} + return peer.RequestCheckpoint(reqID, r.GetCost(peer), req) +} + +// Validate processes an ODR request reply message from the LES network +// returns true and stores results in memory if the message was a valid reply +// to the request (implementation of LesOdrRequest) +func (r *CheckpointRequest) Validate(db ethdb.Database, msg *Msg) error { + // Ensure we have a correct message + if msg.MsgType != MsgCheckpoint { + return errInvalidMessageType + } + + log.Debug("Got GetCheckpoint response: ", "msg", msg) + hashes := msg.Obj.([3]common.Hash) + r.SectionHead = hashes[0] + r.ChtRoot = hashes[1] + r.BloomRoot = hashes[2] + return nil +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + // readTraceDB stores the keys of database reads. We use this to check that received node // sets contain only the trie nodes necessary to make proofs pass. type readTraceDB struct { diff --git a/les/peer.go b/les/peer.go index 04d747a6b3..8ab90dc12b 100644 --- a/les/peer.go +++ b/les/peer.go @@ -325,6 +325,25 @@ func (p *peer) SendTxs(reqID, cost uint64, txs types.Transactions) error { } } +// RequestCheckpoint a checkpoint at the specified section index from a remote node. +func (p *peer) RequestCheckpoint(reqID, cost uint64, req CheckpointReq) error { + p.Log().Debug("Fetching checkpoint", "req", req, "p.version", p.version, "lpv1", lpv1) + switch p.version { + case lpv1: + p.Log().Info("RequestCheckpoint: lpv1 so doing nothing") + return nil + case lpv2: + return sendRequest(p.rw, GetCheckpointMsg, reqID, cost, req) + default: + panic(nil) + } +} + +// SendCheckpoint sends a checkpoint, corresponding to the one requested. +func (p *peer) SendCheckpoint(reqID, bv uint64, roots [3]common.Hash) error { + return sendResponse(p.rw, CheckpointMsg, reqID, bv, roots) +} + type keyValueEntry struct { Key string Value rlp.RawValue diff --git a/les/protocol.go b/les/protocol.go index 05e6654d6e..6e627c4f60 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -46,7 +46,7 @@ var ( ) // Number of implemented message corresponding to different protocol versions. -var ProtocolLengths = map[uint]uint64{lpv1: 15, lpv2: 22} +var ProtocolLengths = map[uint]uint64{lpv1: 15, lpv2: 24} const ( NetworkId = 1 @@ -79,6 +79,8 @@ const ( SendTxV2Msg = 0x13 GetTxStatusMsg = 0x14 TxStatusMsg = 0x15 + GetCheckpointMsg = 0x16 + CheckpointMsg = 0x17 ) type errCode int diff --git a/les/sync.go b/les/sync.go index c0e17f97d9..37bbfecf25 100644 --- a/les/sync.go +++ b/les/sync.go @@ -20,9 +20,11 @@ import ( "context" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" ) const ( @@ -79,6 +81,35 @@ func (pm *ProtocolManager) synchronise(peer *peer) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() + updateChtFromPeer(pm, peer, ctx) pm.blockchain.(*light.LightChain).SyncCht(ctx) pm.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync) } + +// Status.im issue 320: Use GetHeaderProofs to download the latest CHT from a peer. +func updateChtFromPeer(pm *ProtocolManager, peer *peer, ctx context.Context) { + log.Info("Downloading latest CHT root from peer", "peer.headBlockInfo", peer.headBlockInfo()) + + // Formula from lightchain.go:SyncCht: num := cht.Number*ChtFrequency – 1 + var hbl = peer.headBlockInfo() + var peerHeadBlockNum = hbl.Number + log.Debug("UpdateChtFromPeer", "peerHeadBlockNum", peerHeadBlockNum) + + var sectionIdx uint64 = ((peerHeadBlockNum + 1) / light.ChtFrequency) - 1 + log.Debug("Retrieving checkpoint with: ", "sectionIdx", sectionIdx) + + req := &light.CheckpointRequest{SectionIdx: uint64(sectionIdx)} + pm.odr.Retrieve(ctx, req) + log.Info("Retrieved checkpoint from peer: ", + "SectionHead=", common.ToHex(req.SectionHead.Bytes()), + "ChtRoot", common.ToHex(req.ChtRoot.Bytes()), + "BloomTrieRoot", common.ToHex(req.BloomRoot.Bytes())) + + pm.blockchain.(*light.LightChain).AddTrustedCheckpoint("live", sectionIdx, req.SectionHead, req.ChtRoot, req.BloomRoot) + log.Debug("Added checkpoint to LightChain") + + log.Info("Sanity check: can download some very old header?") + var sanityBlock uint64 = hbl.Number / 100 + sanityHeader, err := pm.blockchain.(*light.LightChain).GetHeaderByNumberOdr(ctx, sanityBlock) + log.Info("Sanity check result:", "sanityHeader", sanityHeader, "err", err) +} diff --git a/light/lightchain.go b/light/lightchain.go index 30baeaccb7..7d23fe3ee4 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -91,6 +91,7 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus. if err != nil { return nil, err } + bc.genesisBlock, _ = bc.GetBlockByNumber(NoOdr, 0) if bc.genesisBlock == nil { return nil, core.ErrNoGenesis @@ -113,6 +114,13 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus. return bc, nil } +func (self *LightChain) AddTrustedCheckpoint(name string, sectionIdx uint64, + sectionHead common.Hash, + chtRoot common.Hash, bloomTrieRoot common.Hash) { + cp := trustedCheckpoint{name, sectionIdx, sectionHead, chtRoot, bloomTrieRoot} + self.addTrustedCheckpoint(cp) +} + // addTrustedCheckpoint adds a trusted checkpoint to the blockchain func (self *LightChain) addTrustedCheckpoint(cp trustedCheckpoint) { if self.odr.ChtIndexer() != nil { diff --git a/light/odr.go b/light/odr.go index e2c3d9c5a4..caa29fa1e2 100644 --- a/light/odr.go +++ b/light/odr.go @@ -169,3 +169,16 @@ func (req *BloomRequest) StoreResult(db ethdb.Database) { core.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i]) } } + +// CheckpointRequest is the ODR request type for retrieving a CHT+BloomRoot checkpoint +type CheckpointRequest struct { + OdrRequest + SectionIdx uint64 + SectionHead common.Hash + ChtRoot common.Hash + BloomRoot common.Hash +} + +// StoreResult stores the retrieved data in local database +func (req *CheckpointRequest) StoreResult(db ethdb.Database) { +}