You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

444 lines
13 KiB

lite2: light client with weak subjectivity (#3989) Refs #1771 ADR: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-044-lite-client-with-weak-subjectivity.md ## Commits: * add Verifier and VerifyCommitTrusting * add two more checks make trustLevel an option * float32 for trustLevel * check newHeader time * started writing lite Client * unify Verify methods * ensure h2.Header.bfttime < h1.Header.bfttime + tp * move trust checks into Verify function * add more comments * more docs * started writing tests * unbonding period failures * tests are green * export ErrNewHeaderTooFarIntoFuture * make golangci happy * test for non-adjusted headers * more precision * providers and stores * VerifyHeader and VerifyHeaderAtHeight funcs * fix compile errors * remove lastVerifiedHeight, persist new trusted header * sequential verification * remove TrustedStore option * started writing tests for light client * cover basic cases for linear verification * bisection tests PASS * rename BisectingVerification to SkippingVerification * refactor the code * add TrustedHeader method * consolidate sequential verification tests * consolidate skipping verification tests * rename trustedVals to trustedNextVals * start writing docs * ValidateTrustLevel func and ErrOldHeaderExpired error * AutoClient and example tests * fix errors * update doc * remove ErrNewHeaderTooFarIntoFuture This check is unnecessary given existing a) ErrOldHeaderExpired b) h2.Time > now checks. * return an error if we're at more recent height * add comments * add LastSignedHeaderHeight method to Store I think it's fine if Store tracks last height * copy over proxy from old lite package * make TrustedHeader return latest if height=0 * modify LastSignedHeaderHeight to return an error if no headers exist * copy over proxy impl * refactor proxy and start http lite client * Tx and BlockchainInfo methods * Block method * commit method * code compiles again * lite client compiles * extract updateLiteClientIfNeededTo func * move final parts * add placeholder for tests * force usage of lite http client in proxy * comment out query tests for now * explicitly mention tp: trusting period * verify nextVals in VerifyHeader * refactor bisection * move the NextValidatorsHash check into updateTrustedHeaderAndVals + update the comment * add ConsensusParams method to RPC client * add ConsensusParams to rpc/mock/client * change trustLevel type to a new cmn.Fraction type + update SkippingVerification comment * stress out trustLevel is only used for non-adjusted headers * fixes after Fede's review Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * compare newHeader with a header from an alternative provider * save pivot header Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r349122824 * check header can still be trusted in TrustedHeader Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r349101424 * lite: update Validators and Block endpoints - Block no longer contains BlockMeta - Validators now accept two additional params: page and perPage * make linter happy
5 years ago
lite2: light client with weak subjectivity (#3989) Refs #1771 ADR: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-044-lite-client-with-weak-subjectivity.md ## Commits: * add Verifier and VerifyCommitTrusting * add two more checks make trustLevel an option * float32 for trustLevel * check newHeader time * started writing lite Client * unify Verify methods * ensure h2.Header.bfttime < h1.Header.bfttime + tp * move trust checks into Verify function * add more comments * more docs * started writing tests * unbonding period failures * tests are green * export ErrNewHeaderTooFarIntoFuture * make golangci happy * test for non-adjusted headers * more precision * providers and stores * VerifyHeader and VerifyHeaderAtHeight funcs * fix compile errors * remove lastVerifiedHeight, persist new trusted header * sequential verification * remove TrustedStore option * started writing tests for light client * cover basic cases for linear verification * bisection tests PASS * rename BisectingVerification to SkippingVerification * refactor the code * add TrustedHeader method * consolidate sequential verification tests * consolidate skipping verification tests * rename trustedVals to trustedNextVals * start writing docs * ValidateTrustLevel func and ErrOldHeaderExpired error * AutoClient and example tests * fix errors * update doc * remove ErrNewHeaderTooFarIntoFuture This check is unnecessary given existing a) ErrOldHeaderExpired b) h2.Time > now checks. * return an error if we're at more recent height * add comments * add LastSignedHeaderHeight method to Store I think it's fine if Store tracks last height * copy over proxy from old lite package * make TrustedHeader return latest if height=0 * modify LastSignedHeaderHeight to return an error if no headers exist * copy over proxy impl * refactor proxy and start http lite client * Tx and BlockchainInfo methods * Block method * commit method * code compiles again * lite client compiles * extract updateLiteClientIfNeededTo func * move final parts * add placeholder for tests * force usage of lite http client in proxy * comment out query tests for now * explicitly mention tp: trusting period * verify nextVals in VerifyHeader * refactor bisection * move the NextValidatorsHash check into updateTrustedHeaderAndVals + update the comment * add ConsensusParams method to RPC client * add ConsensusParams to rpc/mock/client * change trustLevel type to a new cmn.Fraction type + update SkippingVerification comment * stress out trustLevel is only used for non-adjusted headers * fixes after Fede's review Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * compare newHeader with a header from an alternative provider * save pivot header Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r349122824 * check header can still be trusted in TrustedHeader Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r349101424 * lite: update Validators and Block endpoints - Block no longer contains BlockMeta - Validators now accept two additional params: page and perPage * make linter happy
5 years ago
lite2: light client with weak subjectivity (#3989) Refs #1771 ADR: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-044-lite-client-with-weak-subjectivity.md ## Commits: * add Verifier and VerifyCommitTrusting * add two more checks make trustLevel an option * float32 for trustLevel * check newHeader time * started writing lite Client * unify Verify methods * ensure h2.Header.bfttime < h1.Header.bfttime + tp * move trust checks into Verify function * add more comments * more docs * started writing tests * unbonding period failures * tests are green * export ErrNewHeaderTooFarIntoFuture * make golangci happy * test for non-adjusted headers * more precision * providers and stores * VerifyHeader and VerifyHeaderAtHeight funcs * fix compile errors * remove lastVerifiedHeight, persist new trusted header * sequential verification * remove TrustedStore option * started writing tests for light client * cover basic cases for linear verification * bisection tests PASS * rename BisectingVerification to SkippingVerification * refactor the code * add TrustedHeader method * consolidate sequential verification tests * consolidate skipping verification tests * rename trustedVals to trustedNextVals * start writing docs * ValidateTrustLevel func and ErrOldHeaderExpired error * AutoClient and example tests * fix errors * update doc * remove ErrNewHeaderTooFarIntoFuture This check is unnecessary given existing a) ErrOldHeaderExpired b) h2.Time > now checks. * return an error if we're at more recent height * add comments * add LastSignedHeaderHeight method to Store I think it's fine if Store tracks last height * copy over proxy from old lite package * make TrustedHeader return latest if height=0 * modify LastSignedHeaderHeight to return an error if no headers exist * copy over proxy impl * refactor proxy and start http lite client * Tx and BlockchainInfo methods * Block method * commit method * code compiles again * lite client compiles * extract updateLiteClientIfNeededTo func * move final parts * add placeholder for tests * force usage of lite http client in proxy * comment out query tests for now * explicitly mention tp: trusting period * verify nextVals in VerifyHeader * refactor bisection * move the NextValidatorsHash check into updateTrustedHeaderAndVals + update the comment * add ConsensusParams method to RPC client * add ConsensusParams to rpc/mock/client * change trustLevel type to a new cmn.Fraction type + update SkippingVerification comment * stress out trustLevel is only used for non-adjusted headers * fixes after Fede's review Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * compare newHeader with a header from an alternative provider * save pivot header Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r349122824 * check header can still be trusted in TrustedHeader Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r349101424 * lite: update Validators and Block endpoints - Block no longer contains BlockMeta - Validators now accept two additional params: page and perPage * make linter happy
5 years ago
  1. package lite
  2. import (
  3. "bytes"
  4. "fmt"
  5. "time"
  6. "github.com/pkg/errors"
  7. cmn "github.com/tendermint/tendermint/libs/common"
  8. "github.com/tendermint/tendermint/libs/log"
  9. "github.com/tendermint/tendermint/libs/rand"
  10. "github.com/tendermint/tendermint/lite2/provider"
  11. "github.com/tendermint/tendermint/lite2/store"
  12. "github.com/tendermint/tendermint/types"
  13. )
  14. // TrustOptions are the trust parameters needed when a new light client
  15. // connects to the network or when an existing light client that has been
  16. // offline for longer than the trusting period connects to the network.
  17. //
  18. // The expectation is the user will get this information from a trusted source
  19. // like a validator, a friend, or a secure website. A more user friendly
  20. // solution with trust tradeoffs is that we establish an https based protocol
  21. // with a default end point that populates this information. Also an on-chain
  22. // registry of roots-of-trust (e.g. on the Cosmos Hub) seems likely in the
  23. // future.
  24. type TrustOptions struct {
  25. // tp: trusting period.
  26. //
  27. // Should be significantly less than the unbonding period (e.g. unbonding
  28. // period = 3 weeks, trusting period = 2 weeks).
  29. //
  30. // More specifically, trusting period + time needed to check headers + time
  31. // needed to report and punish misbehavior should be less than the unbonding
  32. // period.
  33. Period time.Duration
  34. // Header's Height and Hash must both be provided to force the trusting of a
  35. // particular header.
  36. Height int64
  37. Hash []byte
  38. }
  39. type mode byte
  40. const (
  41. sequential mode = iota + 1
  42. skipping
  43. )
  44. // Option sets a parameter for the light client.
  45. type Option func(*Client)
  46. // SequentialVerification option configures the light client to sequentially
  47. // check the headers. Note this is much slower than SkippingVerification,
  48. // albeit more secure.
  49. func SequentialVerification() Option {
  50. return func(c *Client) {
  51. c.verificationMode = sequential
  52. }
  53. }
  54. // SkippingVerification option configures the light client to skip headers as
  55. // long as {trustLevel} of the old validator set signed the new header. The
  56. // bisection algorithm from the specification is used for finding the minimal
  57. // "trust path".
  58. //
  59. // trustLevel - fraction of the old validator set (in terms of voting power),
  60. // which must sign the new header in order for us to trust it. NOTE this only
  61. // applies to non-adjusted headers. For adjusted headers, sequential
  62. // verification is used.
  63. func SkippingVerification(trustLevel cmn.Fraction) Option {
  64. if err := ValidateTrustLevel(trustLevel); err != nil {
  65. panic(err)
  66. }
  67. return func(c *Client) {
  68. c.verificationMode = skipping
  69. c.trustLevel = trustLevel
  70. }
  71. }
  72. // AlternativeSources option can be used to supply alternative providers, which
  73. // will be used for cross-checking the primary provider.
  74. func AlternativeSources(providers []provider.Provider) Option {
  75. return func(c *Client) {
  76. c.alternatives = providers
  77. }
  78. }
  79. // Client represents a light client, connected to a single chain, which gets
  80. // headers from a primary provider, verifies them either sequentially or by
  81. // skipping some and stores them in a trusted store (usually, a local FS).
  82. //
  83. // Default verification: SkippingVerification(DefaultTrustLevel)
  84. type Client struct {
  85. chainID string
  86. trustingPeriod time.Duration // see TrustOptions.Period
  87. verificationMode mode
  88. trustLevel cmn.Fraction
  89. // Primary provider of new headers.
  90. primary provider.Provider
  91. // Alternative providers for checking the primary for misbehavior by
  92. // comparing data.
  93. alternatives []provider.Provider
  94. // Where trusted headers are stored.
  95. trustedStore store.Store
  96. // Highest trusted header from the store (height=H).
  97. trustedHeader *types.SignedHeader
  98. // Highest next validator set from the store (height=H+1).
  99. trustedNextVals *types.ValidatorSet
  100. logger log.Logger
  101. }
  102. // NewClient returns a new light client. It returns an error if it fails to
  103. // obtain the header & vals from the primary or they are invalid (e.g. trust
  104. // hash does not match with the one from the header).
  105. //
  106. // See all Option(s) for the additional configuration.
  107. func NewClient(
  108. chainID string,
  109. trustOptions TrustOptions,
  110. primary provider.Provider,
  111. trustedStore store.Store,
  112. options ...Option) (*Client, error) {
  113. c := &Client{
  114. chainID: chainID,
  115. trustingPeriod: trustOptions.Period,
  116. verificationMode: skipping,
  117. trustLevel: DefaultTrustLevel,
  118. primary: primary,
  119. trustedStore: trustedStore,
  120. logger: log.NewNopLogger(),
  121. }
  122. for _, o := range options {
  123. o(c)
  124. }
  125. if err := c.initializeWithTrustOptions(trustOptions); err != nil {
  126. return nil, err
  127. }
  128. return c, nil
  129. }
  130. func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
  131. // 1) Fetch and verify the header.
  132. h, err := c.primary.SignedHeader(options.Height)
  133. if err != nil {
  134. return err
  135. }
  136. // NOTE: Verify func will check if it's expired or not.
  137. if err := h.ValidateBasic(c.chainID); err != nil {
  138. return errors.Wrap(err, "ValidateBasic failed")
  139. }
  140. if !bytes.Equal(h.Hash(), options.Hash) {
  141. return errors.Errorf("expected header's hash %X, but got %X", options.Hash, h.Hash())
  142. }
  143. // 2) Fetch and verify the next vals.
  144. vals, err := c.primary.ValidatorSet(options.Height + 1)
  145. if err != nil {
  146. return err
  147. }
  148. // 3) Persist both of them and continue.
  149. return c.updateTrustedHeaderAndVals(h, vals)
  150. }
  151. // SetLogger sets a logger.
  152. func (c *Client) SetLogger(l log.Logger) {
  153. c.logger = l
  154. }
  155. // TrustedHeader returns a trusted header at the given height (0 - the latest)
  156. // or nil if no such header exist.
  157. // TODO: mention how many headers will be kept by the light client.
  158. // .
  159. // height must be >= 0.
  160. //
  161. // It returns an error if:
  162. // - the header expired (ErrOldHeaderExpired). In that case, update your
  163. // client to more recent height;
  164. // - there are some issues with the trusted store, although that should not
  165. // happen normally.
  166. func (c *Client) TrustedHeader(height int64, now time.Time) (*types.SignedHeader, error) {
  167. if height < 0 {
  168. return nil, errors.New("negative height")
  169. }
  170. if height == 0 {
  171. var err error
  172. height, err = c.LastTrustedHeight()
  173. if err != nil {
  174. return nil, err
  175. }
  176. }
  177. h, err := c.trustedStore.SignedHeader(height)
  178. if err != nil {
  179. return nil, err
  180. }
  181. // Ensure header can still be trusted.
  182. expirationTime := h.Time.Add(c.trustingPeriod)
  183. if !expirationTime.After(now) {
  184. return nil, ErrOldHeaderExpired{expirationTime, now}
  185. }
  186. return h, nil
  187. }
  188. // LastTrustedHeight returns a last trusted height.
  189. func (c *Client) LastTrustedHeight() (int64, error) {
  190. return c.trustedStore.LastSignedHeaderHeight()
  191. }
  192. // ChainID returns the chain ID.
  193. func (c *Client) ChainID() string {
  194. return c.chainID
  195. }
  196. // VerifyHeaderAtHeight fetches the header and validators at the given height
  197. // and calls VerifyHeader.
  198. //
  199. // If the trusted header is more recent than one here, an error is returned.
  200. func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) error {
  201. if c.trustedHeader.Height >= height {
  202. return errors.Errorf("height #%d is already trusted (last: #%d)", height, c.trustedHeader.Height)
  203. }
  204. // Request the header and the vals.
  205. newHeader, newVals, err := c.fetchHeaderAndValsAtHeight(height)
  206. if err != nil {
  207. return err
  208. }
  209. return c.VerifyHeader(newHeader, newVals, now)
  210. }
  211. // VerifyHeader verifies new header against the trusted state.
  212. //
  213. // SequentialVerification: verifies that 2/3 of the trusted validator set has
  214. // signed the new header. If the headers are not adjacent, **all** intermediate
  215. // headers will be requested.
  216. //
  217. // SkippingVerification(trustLevel): verifies that {trustLevel} of the trusted
  218. // validator set has signed the new header. If it's not the case and the
  219. // headers are not adjacent, bisection is performed and necessary (not all)
  220. // intermediate headers will be requested. See the specification for the
  221. // algorithm.
  222. //
  223. // If the trusted header is more recent than one here, an error is returned.
  224. func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error {
  225. if c.trustedHeader.Height >= newHeader.Height {
  226. return errors.Errorf("height #%d is already trusted (last: #%d)", newHeader.Height, c.trustedHeader.Height)
  227. }
  228. if len(c.alternatives) > 0 {
  229. if err := c.compareNewHeaderWithRandomAlternative(newHeader); err != nil {
  230. return err
  231. }
  232. }
  233. var err error
  234. switch c.verificationMode {
  235. case sequential:
  236. err = c.sequence(newHeader, newVals, now)
  237. case skipping:
  238. err = c.bisection(c.trustedHeader, c.trustedNextVals, newHeader, newVals, now)
  239. default:
  240. panic(fmt.Sprintf("Unknown verification mode: %b", c.verificationMode))
  241. }
  242. if err != nil {
  243. return err
  244. }
  245. // Update trusted header and vals.
  246. nextVals, err := c.primary.ValidatorSet(newHeader.Height + 1)
  247. if err != nil {
  248. return err
  249. }
  250. return c.updateTrustedHeaderAndVals(newHeader, nextVals)
  251. }
  252. func (c *Client) sequence(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error {
  253. // 1) Verify any intermediate headers.
  254. var (
  255. interimHeader *types.SignedHeader
  256. nextVals *types.ValidatorSet
  257. err error
  258. )
  259. for height := c.trustedHeader.Height + 1; height < newHeader.Height; height++ {
  260. interimHeader, err = c.primary.SignedHeader(height)
  261. if err != nil {
  262. return errors.Wrapf(err, "failed to obtain the header #%d", height)
  263. }
  264. err = Verify(c.chainID, c.trustedHeader, c.trustedNextVals, interimHeader, c.trustedNextVals,
  265. c.trustingPeriod, now, c.trustLevel)
  266. if err != nil {
  267. return errors.Wrapf(err, "failed to verify the header #%d", height)
  268. }
  269. // Update trusted header and vals.
  270. if height == newHeader.Height-1 {
  271. nextVals = newVals
  272. } else {
  273. nextVals, err = c.primary.ValidatorSet(height + 1)
  274. if err != nil {
  275. return errors.Wrapf(err, "failed to obtain the vals #%d", height+1)
  276. }
  277. }
  278. err = c.updateTrustedHeaderAndVals(interimHeader, nextVals)
  279. if err != nil {
  280. return errors.Wrapf(err, "failed to update trusted state #%d", height)
  281. }
  282. }
  283. // 2) Verify the new header.
  284. return Verify(c.chainID, c.trustedHeader, c.trustedNextVals, newHeader, newVals, c.trustingPeriod, now, c.trustLevel)
  285. }
  286. func (c *Client) bisection(
  287. lastHeader *types.SignedHeader,
  288. lastVals *types.ValidatorSet,
  289. newHeader *types.SignedHeader,
  290. newVals *types.ValidatorSet,
  291. now time.Time) error {
  292. err := Verify(c.chainID, lastHeader, lastVals, newHeader, newVals, c.trustingPeriod, now, c.trustLevel)
  293. switch err.(type) {
  294. case nil:
  295. return nil
  296. case types.ErrTooMuchChange:
  297. // continue bisection
  298. default:
  299. return errors.Wrapf(err, "failed to verify the header #%d ", newHeader.Height)
  300. }
  301. if newHeader.Height == c.trustedHeader.Height+1 {
  302. // TODO: submit evidence here
  303. return errors.Errorf("adjacent headers (#%d and #%d) that are not matching", lastHeader.Height, newHeader.Height)
  304. }
  305. pivot := (c.trustedHeader.Height + newHeader.Header.Height) / 2
  306. pivotHeader, pivotVals, err := c.fetchHeaderAndValsAtHeight(pivot)
  307. if err != nil {
  308. return err
  309. }
  310. // left branch
  311. {
  312. err := c.bisection(lastHeader, lastVals, pivotHeader, pivotVals, now)
  313. if err != nil {
  314. return errors.Wrapf(err, "bisection of #%d and #%d", lastHeader.Height, pivot)
  315. }
  316. }
  317. // right branch
  318. {
  319. nextVals, err := c.primary.ValidatorSet(pivot + 1)
  320. if err != nil {
  321. return errors.Wrapf(err, "failed to obtain the vals #%d", pivot+1)
  322. }
  323. if !bytes.Equal(pivotHeader.NextValidatorsHash, nextVals.Hash()) {
  324. return errors.Errorf("expected next validator's hash %X, but got %X (height #%d)",
  325. pivotHeader.NextValidatorsHash,
  326. nextVals.Hash(),
  327. pivot)
  328. }
  329. err = c.updateTrustedHeaderAndVals(pivotHeader, nextVals)
  330. if err != nil {
  331. return errors.Wrapf(err, "failed to update trusted state #%d", pivot)
  332. }
  333. err = c.bisection(pivotHeader, nextVals, newHeader, newVals, now)
  334. if err != nil {
  335. return errors.Wrapf(err, "bisection of #%d and #%d", pivot, newHeader.Height)
  336. }
  337. }
  338. return nil
  339. }
  340. func (c *Client) updateTrustedHeaderAndVals(h *types.SignedHeader, vals *types.ValidatorSet) error {
  341. if !bytes.Equal(h.NextValidatorsHash, vals.Hash()) {
  342. return errors.Errorf("expected next validator's hash %X, but got %X", h.NextValidatorsHash, vals.Hash())
  343. }
  344. if err := c.trustedStore.SaveSignedHeader(h); err != nil {
  345. return errors.Wrap(err, "failed to save trusted header")
  346. }
  347. if err := c.trustedStore.SaveValidatorSet(vals, h.Height+1); err != nil {
  348. return errors.Wrap(err, "failed to save trusted vals")
  349. }
  350. c.trustedHeader = h
  351. c.trustedNextVals = vals
  352. return nil
  353. }
  354. func (c *Client) fetchHeaderAndValsAtHeight(height int64) (*types.SignedHeader, *types.ValidatorSet, error) {
  355. h, err := c.primary.SignedHeader(height)
  356. if err != nil {
  357. return nil, nil, errors.Wrapf(err, "failed to obtain the header #%d", height)
  358. }
  359. vals, err := c.primary.ValidatorSet(height)
  360. if err != nil {
  361. return nil, nil, errors.Wrapf(err, "failed to obtain the vals #%d", height)
  362. }
  363. return h, vals, nil
  364. }
  365. func (c *Client) compareNewHeaderWithRandomAlternative(h *types.SignedHeader) error {
  366. // 1. Pick an alternative provider.
  367. p := c.alternatives[rand.RandIntn(len(c.alternatives))]
  368. // 2. Fetch the header.
  369. altHeader, err := p.SignedHeader(h.Height)
  370. if err != nil {
  371. return errors.Wrapf(err,
  372. "failed to obtain header #%d from alternative provider %v", h.Height, p)
  373. }
  374. // 3. Compare hashes.
  375. if !bytes.Equal(h.Hash(), altHeader.Hash()) {
  376. // TODO: One of the providers is lying. Send the evidence to fork
  377. // accountability server.
  378. return errors.Errorf(
  379. "new header hash %X does not match one from alternative provider %X",
  380. h.Hash(), altHeader.Hash())
  381. }
  382. return nil
  383. }