|
|
- package rpc
-
- import (
- "context"
- "encoding/hex"
- "fmt"
- "testing"
-
- ics23 "github.com/confio/ics23/go"
- "github.com/cosmos/iavl"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
- dbm "github.com/tendermint/tm-db"
-
- abci "github.com/tendermint/tendermint/abci/types"
- "github.com/tendermint/tendermint/crypto/merkle"
- "github.com/tendermint/tendermint/libs/bytes"
- lcmock "github.com/tendermint/tendermint/light/rpc/mocks"
- tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto"
- rpcmock "github.com/tendermint/tendermint/rpc/client/mocks"
- ctypes "github.com/tendermint/tendermint/rpc/core/types"
- "github.com/tendermint/tendermint/types"
- )
-
- // TestABCIQuery tests ABCIQuery requests and verifies proofs. HAPPY PATH 😀
- func TestABCIQuery(t *testing.T) {
- tree, err := iavl.NewMutableTree(dbm.NewMemDB(), 100)
- require.NoError(t, err)
-
- var (
- key = []byte("foo")
- value = []byte("bar")
- )
- tree.Set(key, value)
-
- commitmentProof, err := tree.GetMembershipProof(key)
- require.NoError(t, err)
-
- op := &testOp{
- Spec: ics23.IavlSpec,
- Key: key,
- Proof: commitmentProof,
- }
-
- next := &rpcmock.Client{}
- next.On(
- "ABCIQueryWithOptions",
- context.Background(),
- mock.AnythingOfType("string"),
- bytes.HexBytes(key),
- mock.AnythingOfType("client.ABCIQueryOptions"),
- ).Return(&ctypes.ResultABCIQuery{
- Response: abci.ResponseQuery{
- Code: 0,
- Key: key,
- Value: value,
- Height: 1,
- ProofOps: &tmcrypto.ProofOps{
- Ops: []tmcrypto.ProofOp{op.ProofOp()},
- },
- },
- }, nil)
-
- lc := &lcmock.LightClient{}
- appHash, _ := hex.DecodeString("5EFD44055350B5CC34DBD26085347A9DBBE44EA192B9286A9FC107F40EA1FAC5")
- lc.On("VerifyLightBlockAtHeight", context.Background(), int64(2), mock.AnythingOfType("time.Time")).Return(
- &types.LightBlock{
- SignedHeader: &types.SignedHeader{
- Header: &types.Header{AppHash: appHash},
- },
- },
- nil,
- )
-
- c := NewClient(next, lc,
- KeyPathFn(func(_ string, key []byte) (merkle.KeyPath, error) {
- kp := merkle.KeyPath{}
- kp = kp.AppendKey(key, merkle.KeyEncodingURL)
- return kp, nil
- }))
- c.RegisterOpDecoder("ics23:iavl", testOpDecoder)
- res, err := c.ABCIQuery(context.Background(), "/store/accounts/key", key)
- require.NoError(t, err)
-
- assert.NotNil(t, res)
- }
-
- type testOp struct {
- Spec *ics23.ProofSpec
- Key []byte
- Proof *ics23.CommitmentProof
- }
-
- var _ merkle.ProofOperator = testOp{}
-
- func (op testOp) GetKey() []byte {
- return op.Key
- }
-
- func (op testOp) ProofOp() tmcrypto.ProofOp {
- bz, err := op.Proof.Marshal()
- if err != nil {
- panic(err.Error())
- }
- return tmcrypto.ProofOp{
- Type: "ics23:iavl",
- Key: op.Key,
- Data: bz,
- }
- }
-
- func (op testOp) Run(args [][]byte) ([][]byte, error) {
- // calculate root from proof
- root, err := op.Proof.Calculate()
- if err != nil {
- return nil, fmt.Errorf("could not calculate root for proof: %v", err)
- }
- // Only support an existence proof or nonexistence proof (batch proofs currently unsupported)
- switch len(args) {
- case 0:
- // Args are nil, so we verify the absence of the key.
- absent := ics23.VerifyNonMembership(op.Spec, root, op.Proof, op.Key)
- if !absent {
- return nil, fmt.Errorf("proof did not verify absence of key: %s", string(op.Key))
- }
- case 1:
- // Args is length 1, verify existence of key with value args[0]
- if !ics23.VerifyMembership(op.Spec, root, op.Proof, op.Key, args[0]) {
- return nil, fmt.Errorf("proof did not verify existence of key %s with given value %x", op.Key, args[0])
- }
- default:
- return nil, fmt.Errorf("args must be length 0 or 1, got: %d", len(args))
- }
-
- return [][]byte{root}, nil
- }
-
- func testOpDecoder(pop tmcrypto.ProofOp) (merkle.ProofOperator, error) {
- proof := &ics23.CommitmentProof{}
- err := proof.Unmarshal(pop.Data)
- if err != nil {
- return nil, err
- }
-
- op := testOp{
- Key: pop.Key,
- Spec: ics23.IavlSpec,
- Proof: proof,
- }
- return op, nil
- }
|