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.

144 lines
4.4 KiB

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