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.

145 lines
4.4 KiB

7 years ago
  1. package proxy
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/pkg/errors"
  6. "github.com/tendermint/tendermint/crypto/merkle"
  7. cmn "github.com/tendermint/tendermint/libs/common"
  8. "github.com/tendermint/tendermint/lite"
  9. lerr "github.com/tendermint/tendermint/lite/errors"
  10. rpcclient "github.com/tendermint/tendermint/rpc/client"
  11. ctypes "github.com/tendermint/tendermint/rpc/core/types"
  12. "github.com/tendermint/tendermint/types"
  13. )
  14. // GetWithProof will query the key on the given node, and verify it has
  15. // a valid proof, as defined by the Verifier.
  16. //
  17. // If there is any error in checking, returns an error.
  18. func GetWithProof(prt *merkle.ProofRuntime, key []byte, reqHeight int64, node rpcclient.Client,
  19. cert lite.Verifier) (
  20. val cmn.HexBytes, height int64, proof *merkle.Proof, err error) {
  21. if reqHeight < 0 {
  22. err = cmn.NewError("Height cannot be negative")
  23. return
  24. }
  25. res, err := GetWithProofOptions(prt, "/key", key,
  26. rpcclient.ABCIQueryOptions{Height: int64(reqHeight), Prove: true},
  27. node, cert)
  28. if err != nil {
  29. return
  30. }
  31. resp := res.Response
  32. val, height = resp.Value, resp.Height
  33. return val, height, proof, err
  34. }
  35. // GetWithProofOptions is useful if you want full access to the ABCIQueryOptions.
  36. // XXX Usage of path? It's not used, and sometimes it's /, sometimes /key, sometimes /store.
  37. func GetWithProofOptions(prt *merkle.ProofRuntime, path string, key []byte, opts rpcclient.ABCIQueryOptions,
  38. node rpcclient.Client, cert lite.Verifier) (
  39. *ctypes.ResultABCIQuery, error) {
  40. opts.Prove = true
  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. storeName, err := parseQueryStorePath(path)
  67. if err != nil {
  68. return nil, err
  69. }
  70. kp := merkle.KeyPath{}
  71. kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL)
  72. kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL)
  73. err = prt.VerifyValue(resp.Proof, signedHeader.AppHash, kp.String(), resp.Value)
  74. if err != nil {
  75. return nil, errors.Wrap(err, "Couldn't verify value proof")
  76. }
  77. return &ctypes.ResultABCIQuery{Response: resp}, nil
  78. } else {
  79. // Value absent
  80. // Validate the proof against the certified header to ensure data integrity.
  81. // XXX How do we encode the key into a string...
  82. err = prt.VerifyAbsence(resp.Proof, signedHeader.AppHash, string(resp.Key))
  83. if err != nil {
  84. return nil, errors.Wrap(err, "Couldn't verify absence proof")
  85. }
  86. return &ctypes.ResultABCIQuery{Response: resp}, nil
  87. }
  88. }
  89. func parseQueryStorePath(path string) (storeName string, err error) {
  90. if !strings.HasPrefix(path, "/") {
  91. return "", fmt.Errorf("expected path to start with /")
  92. }
  93. paths := strings.SplitN(path[1:], "/", 3)
  94. switch {
  95. case len(paths) != 3:
  96. return "", fmt.Errorf("expected format like /store/<storeName>/key")
  97. case paths[0] != "store":
  98. return "", fmt.Errorf("expected format like /store/<storeName>/key")
  99. case paths[2] != "key":
  100. return "", fmt.Errorf("expected format like /store/<storeName>/key")
  101. }
  102. return paths[1], nil
  103. }
  104. // GetCertifiedCommit gets the signed header for a given height and certifies
  105. // it. Returns error if unable to get a proven header.
  106. func GetCertifiedCommit(h int64, client rpcclient.Client, cert lite.Verifier) (types.SignedHeader, error) {
  107. // FIXME: cannot use cert.GetByHeight for now, as it also requires
  108. // Validators and will fail on querying tendermint for non-current height.
  109. // When this is supported, we should use it instead...
  110. rpcclient.WaitForHeight(client, h, nil)
  111. cresp, err := client.Commit(&h)
  112. if err != nil {
  113. return types.SignedHeader{}, err
  114. }
  115. // Validate downloaded checkpoint with our request and trust store.
  116. sh := cresp.SignedHeader
  117. if sh.Height != h {
  118. return types.SignedHeader{}, fmt.Errorf("height mismatch: want %v got %v",
  119. h, sh.Height)
  120. }
  121. if err = cert.Verify(sh); err != nil {
  122. return types.SignedHeader{}, err
  123. }
  124. return sh, nil
  125. }