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.

122 lines
3.7 KiB

7 years ago
  1. package proxy
  2. import (
  3. "fmt"
  4. cmn "github.com/tendermint/tendermint/libs/common"
  5. "github.com/tendermint/tendermint/crypto/merkle"
  6. "github.com/tendermint/tendermint/lite"
  7. lerr "github.com/tendermint/tendermint/lite/errors"
  8. rpcclient "github.com/tendermint/tendermint/rpc/client"
  9. ctypes "github.com/tendermint/tendermint/rpc/core/types"
  10. "github.com/tendermint/tendermint/types"
  11. )
  12. // GetWithProof will query the key on the given node, and verify it has
  13. // a valid proof, as defined by the Verifier.
  14. //
  15. // If there is any error in checking, returns an error.
  16. func GetWithProof(prt *merkle.ProofRuntime, key []byte, reqHeight int64, node rpcclient.Client,
  17. cert lite.Verifier) (
  18. val cmn.HexBytes, height int64, proof *merkle.Proof, err error) {
  19. if reqHeight < 0 {
  20. err = cmn.NewError("Height cannot be negative")
  21. return
  22. }
  23. res, err := GetWithProofOptions(prt, "/key", key,
  24. rpcclient.ABCIQueryOptions{Height: int64(reqHeight), Prove: true},
  25. node, cert)
  26. if err != nil {
  27. return
  28. }
  29. resp := res.Response
  30. val, height = resp.Value, resp.Height
  31. return val, height, proof, err
  32. }
  33. // GetWithProofOptions is useful if you want full access to the ABCIQueryOptions.
  34. // XXX Usage of path? It's not used, and sometimes it's /, sometimes /key, sometimes /store.
  35. func GetWithProofOptions(prt *merkle.ProofRuntime, path string, key []byte, opts rpcclient.ABCIQueryOptions,
  36. node rpcclient.Client, cert lite.Verifier) (
  37. *ctypes.ResultABCIQuery, error) {
  38. if !opts.Prove {
  39. return nil, cmn.NewError("require ABCIQueryOptions.Prove to be true")
  40. }
  41. res, err := node.ABCIQueryWithOptions(path, key, opts)
  42. if err != nil {
  43. return nil, err
  44. }
  45. resp := res.Response
  46. // Validate the response, e.g. height.
  47. if resp.IsErr() {
  48. err = cmn.NewError("Query error for key %d: %d", key, resp.Code)
  49. return nil, err
  50. }
  51. if len(resp.Key) == 0 || resp.Proof == nil {
  52. return nil, lerr.ErrEmptyTree()
  53. }
  54. if resp.Height == 0 {
  55. return nil, cmn.NewError("Height returned is zero")
  56. }
  57. // AppHash for height H is in header H+1
  58. signedHeader, err := GetCertifiedCommit(resp.Height+1, node, cert)
  59. if err != nil {
  60. return nil, err
  61. }
  62. // Validate the proof against the certified header to ensure data integrity.
  63. if resp.Value != nil {
  64. // Value exists
  65. // XXX How do we encode the key into a string...
  66. err = prt.VerifyValue(resp.Proof, signedHeader.AppHash, string(resp.Key), resp.Value)
  67. if err != nil {
  68. return nil, cmn.ErrorWrap(err, "Couldn't verify value proof")
  69. }
  70. return &ctypes.ResultABCIQuery{Response: resp}, nil
  71. } else {
  72. // Value absent
  73. // Validate the proof against the certified header to ensure data integrity.
  74. // XXX How do we encode the key into a string...
  75. err = prt.VerifyAbsence(resp.Proof, signedHeader.AppHash, string(resp.Key))
  76. if err != nil {
  77. return nil, cmn.ErrorWrap(err, "Couldn't verify absence proof")
  78. }
  79. return &ctypes.ResultABCIQuery{Response: resp}, nil
  80. }
  81. }
  82. // GetCertifiedCommit gets the signed header for a given height and certifies
  83. // it. Returns error if unable to get a proven header.
  84. func GetCertifiedCommit(h int64, client rpcclient.Client, cert lite.Verifier) (types.SignedHeader, error) {
  85. // FIXME: cannot use cert.GetByHeight for now, as it also requires
  86. // Validators and will fail on querying tendermint for non-current height.
  87. // When this is supported, we should use it instead...
  88. rpcclient.WaitForHeight(client, h, nil)
  89. cresp, err := client.Commit(&h)
  90. if err != nil {
  91. return types.SignedHeader{}, err
  92. }
  93. // Validate downloaded checkpoint with our request and trust store.
  94. sh := cresp.SignedHeader
  95. if sh.Height != h {
  96. return types.SignedHeader{}, fmt.Errorf("height mismatch: want %v got %v",
  97. h, sh.Height)
  98. }
  99. if err = cert.Verify(sh); err != nil {
  100. return types.SignedHeader{}, err
  101. }
  102. return sh, nil
  103. }