github.com/evdatsion/aphelion-dpos-bft@v0.32.1/lite/proxy/query.go (about)

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