From a25faed5f073b3ad88b891521bd6849db68c4922 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 26 Mar 2020 12:38:02 +0100 Subject: [PATCH] lite2: cache headers in bisection (#4562) Closes: #4546 The algorithm uses an array to store the headers and validators and populates it at every bisection (which is an unsuccessful verification). When a successful verification finally occurs it updates the new trusted header, trims that header from the cache (the array) and sets the depth pointer back to 0. Instead of retrieving new headers it will use the cached headers, incrementing in depth until it reaches the end of the cache which by then it will start to retrieve new headers from the provider. Mathematically, this method doesn't properly bisect after the first round but it will always choose a pivot header that is within 1/8th of the upper header's height. I.e. if we are trying to jump 128 headers, the maximum offset from bisection height (64) is 64 + 16(128/8) = 80, therefore a better heuristic would be to obtain the new pivot header height as the middle of these two numbers which would therefore mean to multiply it by 9/16ths instead of 1/2 (sorry this might be a bit more complicated in writing but I can try better explain if someone is interested). Therefore I would also, upon consensus, propose that we change the pivot height to 9/16th's of the previous height --- CHANGELOG_PENDING.md | 1 + docs/imgs/light_client_bisection_alg.png | Bin 0 -> 50560 bytes lite2/client.go | 229 +++++++++++++---------- lite2/doc.go | 15 ++ 4 files changed, 142 insertions(+), 103 deletions(-) create mode 100644 docs/imgs/light_client_bisection_alg.png diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index d0446d314..5e3dc64b0 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,6 +21,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [p2p] [\#4548](https://github.com/tendermint/tendermint/pull/4548) Add ban list to address book (@cmwaters) - [privval] \#4534 Add `error` as a return value on`GetPubKey()` - [Docker] \#4569 Default configuration added to docker image (you can still mount your own config the same way) (@greg-szabo) +- [lite2] [\#4562](https://github.com/tendermint/tendermint/pull/4562) Cache headers when using bisection (@cmwaters) ### BUG FIXES: diff --git a/docs/imgs/light_client_bisection_alg.png b/docs/imgs/light_client_bisection_alg.png new file mode 100644 index 0000000000000000000000000000000000000000..2a12c7542e5f5f26789c97e532e9063c65e1614f GIT binary patch literal 50560 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�F>OVYk4$mRbn5s2w>2Oo08zERL0}Y}_ZuJp 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 replacing", "err", err) @@ -756,16 +697,98 @@ func (c *Client) bisection( c.logger.Error("Can't replace primary", "err", replaceErr) // return original error return errors.Wrapf(err, "verify from #%d to #%d failed", - trustedHeader.Height, interimHeader.Height) + trustedHeader.Height, headerCache[depth].sh.Height) } // attempt to verify the header again continue default: return errors.Wrapf(err, "verify from #%d to #%d failed", - trustedHeader.Height, interimHeader.Height) + trustedHeader.Height, headerCache[depth].sh.Height) + } + } +} + +// LastTrustedHeight returns a last trusted height. -1 and nil are returned if +// there are no trusted headers. +// +// Safe for concurrent use by multiple goroutines. +func (c *Client) LastTrustedHeight() (int64, error) { + return c.trustedStore.LastSignedHeaderHeight() +} + +// FirstTrustedHeight returns a first trusted height. -1 and nil are returned if +// there are no trusted headers. +// +// Safe for concurrent use by multiple goroutines. +func (c *Client) FirstTrustedHeight() (int64, error) { + return c.trustedStore.FirstSignedHeaderHeight() +} + +// ChainID returns the chain ID the light client was configured with. +// +// Safe for concurrent use by multiple goroutines. +func (c *Client) ChainID() string { + return c.chainID +} + +// Primary returns the primary provider. +// +// NOTE: provider may be not safe for concurrent access. +func (c *Client) Primary() provider.Provider { + c.providerMutex.Lock() + defer c.providerMutex.Unlock() + return c.primary +} + +// Witnesses returns the witness providers. +// +// NOTE: providers may be not safe for concurrent access. +func (c *Client) Witnesses() []provider.Provider { + c.providerMutex.Lock() + defer c.providerMutex.Unlock() + return c.witnesses +} + +// Cleanup removes all the data (headers and validator sets) stored. Note: the +// client must be stopped at this point. +func (c *Client) Cleanup() error { + c.logger.Info("Removing all the data") + c.latestTrustedHeader = nil + c.latestTrustedVals = nil + return c.trustedStore.Prune(0) +} + +// cleanupAfter deletes all headers & validator sets after +height+. It also +// resets latestTrustedHeader to the latest header. +func (c *Client) cleanupAfter(height int64) error { + nextHeight := height + + for { + h, err := c.trustedStore.SignedHeaderAfter(nextHeight) + if err == store.ErrSignedHeaderNotFound { + break + } else if err != nil { + return errors.Wrapf(err, "failed to get header after %d", nextHeight) + } + + err = c.trustedStore.DeleteSignedHeaderAndValidatorSet(h.Height) + if err != nil { + c.logger.Error("can't remove a trusted header & validator set", "err", err, + "height", h.Height) } + + nextHeight = h.Height } + + c.latestTrustedHeader = nil + c.latestTrustedVals = nil + err := c.restoreTrustedHeaderAndVals() + if err != nil { + return err + } + + return nil } func (c *Client) updateTrustedHeaderAndVals(h *types.SignedHeader, vals *types.ValidatorSet) error { diff --git a/lite2/doc.go b/lite2/doc.go index b61f5453f..f42aa64f1 100644 --- a/lite2/doc.go +++ b/lite2/doc.go @@ -97,6 +97,18 @@ Verify function verifies a new header against some trusted header. See https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/verification.md for details. +There are two methods of verification: sequential and bisection + +Sequential uses the headers hashes and the validator sets to verify each adjacent header until +it reaches the target header. + +Bisection finds the middle header between a trusted and new header, reiterating the action until it +verifies a header. A cache of headers requested by the primary is kept such that when a +verification is made, and the light client tries again to verify the new header in the middle, +the light client does not need to ask for all the same headers again. + +refer to docs/imgs/light_client_bisection_alg.png + ## 3. Secure RPC proxy Tendermint RPC exposes a lot of info, but a malicious node could return any @@ -108,5 +120,8 @@ some other node. See https://docs.tendermint.com/master/tendermint-core/light-client-protocol.html for usage example. +Or see +https://github.com/tendermint/spec/tree/master/spec/consensus/light-client +for the full spec */ package lite