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.

120 lines
3.7 KiB

  1. package proxy
  2. import (
  3. "github.com/pkg/errors"
  4. "github.com/tendermint/go-wire/data"
  5. "github.com/tendermint/iavl"
  6. "github.com/tendermint/tendermint/lite"
  7. "github.com/tendermint/tendermint/lite/client"
  8. certerr "github.com/tendermint/tendermint/lite/errors"
  9. rpcclient "github.com/tendermint/tendermint/rpc/client"
  10. ctypes "github.com/tendermint/tendermint/rpc/core/types"
  11. )
  12. // GetWithProof will query the key on the given node, and verify it has
  13. // a valid proof, as defined by the certifier.
  14. //
  15. // If there is any error in checking, returns an error.
  16. // If val is non-empty, proof should be KeyExistsProof
  17. // If val is empty, proof should be KeyMissingProof
  18. func GetWithProof(key []byte, reqHeight int64, node rpcclient.Client,
  19. cert lite.Certifier) (
  20. val data.Bytes, height int64, proof iavl.KeyProof, err error) {
  21. if reqHeight < 0 {
  22. err = errors.Errorf("Height cannot be negative")
  23. return
  24. }
  25. _resp, proof, err := GetWithProofOptions("/key", key,
  26. rpcclient.ABCIQueryOptions{Height: int64(reqHeight)},
  27. node, cert)
  28. if _resp != nil {
  29. resp := _resp.Response
  30. val, height = resp.Value, resp.Height
  31. }
  32. return val, height, proof, err
  33. }
  34. // GetWithProofOptions is useful if you want full access to the ABCIQueryOptions
  35. func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOptions,
  36. node rpcclient.Client, cert lite.Certifier) (
  37. *ctypes.ResultABCIQuery, iavl.KeyProof, error) {
  38. _resp, err := node.ABCIQueryWithOptions(path, key, opts)
  39. if err != nil {
  40. return nil, nil, err
  41. }
  42. resp := _resp.Response
  43. // make sure the proof is the proper height
  44. if resp.IsErr() {
  45. err = errors.Errorf("Query error %d: %d", resp.Code)
  46. return nil, nil, err
  47. }
  48. if len(resp.Key) == 0 || len(resp.Proof) == 0 {
  49. return nil, nil, ErrNoData()
  50. }
  51. if resp.Height == 0 {
  52. return nil, nil, errors.New("Height returned is zero")
  53. }
  54. // AppHash for height H is in header H+1
  55. commit, err := GetCertifiedCommit(resp.Height+1, node, cert)
  56. if err != nil {
  57. return nil, nil, err
  58. }
  59. if len(resp.Value) > 0 {
  60. // The key was found, construct a proof of existence.
  61. eproof, err := iavl.ReadKeyExistsProof(resp.Proof)
  62. if err != nil {
  63. return nil, nil, errors.Wrap(err, "Error reading proof")
  64. }
  65. // Validate the proof against the certified header to ensure data integrity.
  66. err = eproof.Verify(resp.Key, resp.Value, commit.Header.AppHash)
  67. if err != nil {
  68. return nil, nil, errors.Wrap(err, "Couldn't verify proof")
  69. }
  70. return &ctypes.ResultABCIQuery{resp}, eproof, nil
  71. }
  72. // The key wasn't found, construct a proof of non-existence.
  73. var aproof *iavl.KeyAbsentProof
  74. aproof, err = iavl.ReadKeyAbsentProof(resp.Proof)
  75. if err != nil {
  76. return nil, nil, errors.Wrap(err, "Error reading proof")
  77. }
  78. // Validate the proof against the certified header to ensure data integrity.
  79. err = aproof.Verify(resp.Key, nil, commit.Header.AppHash)
  80. if err != nil {
  81. return nil, nil, errors.Wrap(err, "Couldn't verify proof")
  82. }
  83. return &ctypes.ResultABCIQuery{resp}, aproof, ErrNoData()
  84. }
  85. // GetCertifiedCommit gets the signed header for a given height
  86. // and certifies it. Returns error if unable to get a proven header.
  87. func GetCertifiedCommit(h int64, node rpcclient.Client,
  88. cert lite.Certifier) (empty lite.Commit, err error) {
  89. // FIXME: cannot use cert.GetByHeight for now, as it also requires
  90. // Validators and will fail on querying tendermint for non-current height.
  91. // When this is supported, we should use it instead...
  92. rpcclient.WaitForHeight(node, h, nil)
  93. cresp, err := node.Commit(&h)
  94. if err != nil {
  95. return
  96. }
  97. commit := client.CommitFromResult(cresp)
  98. // validate downloaded checkpoint with our request and trust store.
  99. if commit.Height() != h {
  100. return empty, certerr.ErrHeightMismatch(h, commit.Height())
  101. }
  102. err = cert.Certify(commit)
  103. return commit, nil
  104. }