/
process_block_results.go
211 lines (174 loc) · 9.34 KB
/
process_block_results.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package keeper
import (
"bytes"
"encoding/hex"
"cosmossdk.io/errors"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
clientkeeper "github.com/cosmos/ibc-go/v7/modules/core/02-client/keeper"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/crypto/merkle"
tmtypes "github.com/cometbft/cometbft/types"
sdk "github.com/cosmos/cosmos-sdk/types"
ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
"github.com/cosmos/ibc-go/v7/modules/core/exported"
tendermintLightClientTypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint"
"github.com/neutron-org/neutron/v3/x/interchainqueries/types"
)
// deterministicResponseDeliverTx strips non-deterministic fields from
// ResponseDeliverTx and returns another ResponseDeliverTx.
func deterministicResponseDeliverTx(response *abci.ResponseDeliverTx) *abci.ResponseDeliverTx {
return &abci.ResponseDeliverTx{
Code: response.Code,
Data: response.Data,
GasWanted: response.GasWanted,
GasUsed: response.GasUsed,
}
}
// checkHeadersOrder do some basic checks to verify that nextHeader is really next for the header
func checkHeadersOrder(header, nextHeader *tendermintLightClientTypes.Header) error {
if nextHeader.Header.Height != header.Header.Height+1 {
return errors.Wrapf(types.ErrInvalidHeader, "nextHeader.Height (%d) is not actually next for a header with height %d", nextHeader.Header.Height, header.Header.Height)
}
tmHeader, err := tmtypes.HeaderFromProto(header.Header)
if err != nil {
return errors.Wrapf(types.ErrInvalidHeader, "failed to get tendermint header from proto header: %v", err)
}
tmNextHeader, err := tmtypes.HeaderFromProto(nextHeader.Header)
if err != nil {
return errors.Wrapf(types.ErrInvalidHeader, "failed to get tendermint header from proto header: %v", err)
}
if !bytes.Equal(tmHeader.NextValidatorsHash, tmNextHeader.ValidatorsHash) {
return errors.Wrapf(types.ErrInvalidHeader, "header.NextValidatorsHash is not equal to nextHeader.ValidatorsHash: %s != %s", tmHeader.NextValidatorsHash.String(), tmNextHeader.ValidatorsHash.String())
}
if !bytes.Equal(tmHeader.Hash(), tmNextHeader.LastBlockID.Hash) {
return errors.Wrapf(types.ErrInvalidHeader, "header.Hash() is not equal to nextHeader.LastBlockID.Hash: %s != %s", tmHeader.Hash().String(), tmNextHeader.LastBlockID.Hash.String())
}
return nil
}
type Verifier struct{}
// VerifyHeaders verify that headers are valid tendermint headers, checks them on validity by trying call ibcClient.UpdateClient(header)
// to update light client's consensus state and checks that they are sequential (tl;dr header.Height + 1 == nextHeader.Height)
func (v Verifier) VerifyHeaders(ctx sdk.Context, clientKeeper clientkeeper.Keeper, clientID string, header, nextHeader exported.ClientMessage) error {
// this IBC handler updates the consensus state and the state root from a provided header.
// But more importantly in the current situation, it checks that header is valid.
// Honestly we need only to verify headers, but since the check functions are private, and we don't want to duplicate the code,
// we update consensus state at the same time (because why not?)
if err := clientKeeper.UpdateClient(ctx, clientID, header); err != nil {
return errors.Wrapf(err, "failed to update client: %v", err)
}
if err := clientKeeper.UpdateClient(ctx, clientID, nextHeader); err != nil {
return errors.Wrapf(err, "failed to update client: %v", err)
}
tmHeader, ok := header.(*tendermintLightClientTypes.Header)
if !ok {
return errors.Wrapf(types.ErrInvalidType, "failed to cast header to tendermint Header")
}
tmNextHeader, ok := nextHeader.(*tendermintLightClientTypes.Header)
if !ok {
return errors.Wrapf(types.ErrInvalidType, "failed to cast header to tendermint Header")
}
// do some basic check to verify that tmNextHeader is next for the tmHeader
if err := checkHeadersOrder(tmHeader, tmNextHeader); err != nil {
return errors.Wrapf(types.ErrInvalidHeader, "block.NextBlockHeader is not next for the block.Header: %v", err)
}
return nil
}
func (v Verifier) UnpackHeader(any *codectypes.Any) (exported.ClientMessage, error) {
return ibcclienttypes.UnpackClientMessage(any)
}
// ProcessBlock verifies headers and transaction in the block, and then passes the tx query result to
// the querying contract's sudo handler.
func (k Keeper) ProcessBlock(ctx sdk.Context, queryOwner sdk.AccAddress, queryID uint64, clientID string, block *types.Block) error {
header, err := k.headerVerifier.UnpackHeader(block.Header)
if err != nil {
ctx.Logger().Debug("ProcessBlock: failed to unpack block header", "error", err)
return errors.Wrapf(types.ErrProtoUnmarshal, "failed to unpack block header: %v", err)
}
nextHeader, err := k.headerVerifier.UnpackHeader(block.NextBlockHeader)
if err != nil {
ctx.Logger().Debug("ProcessBlock: failed to unpack block header", "error", err)
return errors.Wrapf(types.ErrProtoUnmarshal, "failed to unpack next block header: %v", err)
}
if err := k.headerVerifier.VerifyHeaders(ctx, k.ibcKeeper.ClientKeeper, clientID, header, nextHeader); err != nil {
ctx.Logger().Debug("ProcessBlock: failed to verify headers", "error", err)
return errors.Wrapf(types.ErrInvalidHeader, "failed to verify headers: %v", err)
}
tmHeader, ok := header.(*tendermintLightClientTypes.Header)
if !ok {
ctx.Logger().Debug("ProcessBlock: failed to cast current header to tendermint Header", "query_id", queryID)
return errors.Wrap(types.ErrInvalidType, "failed to cast current header to tendermint Header")
}
tmNextHeader, ok := nextHeader.(*tendermintLightClientTypes.Header)
if !ok {
ctx.Logger().Debug("ProcessBlock: failed to cast next header to tendermint Header", "query_id", queryID)
return errors.Wrap(types.ErrInvalidType, "failed to cast next header to tendermint header")
}
var (
tx = block.GetTx()
txData = tx.GetData()
txHash = tmtypes.Tx(txData).Hash()
)
if !k.CheckTransactionIsAlreadyProcessed(ctx, queryID, txHash) {
// Check that cryptography is O.K. (tx is included in the block, tx was executed successfully)
if err = k.transactionVerifier.VerifyTransaction(tmHeader, tmNextHeader, tx); err != nil {
ctx.Logger().Debug("ProcessBlock: failed to verifyTransaction",
"error", err, "query_id", queryID, "tx_hash", hex.EncodeToString(txHash))
return errors.Wrapf(types.ErrInternal, "failed to verifyTransaction %s: %v", hex.EncodeToString(txHash), err)
}
// Let the query owner contract process the query result.
if _, err := k.contractManagerKeeper.SudoTxQueryResult(ctx, queryOwner, queryID, ibcclienttypes.NewHeight(tmHeader.TrustedHeight.GetRevisionNumber(), uint64(tmHeader.Header.Height)), txData); err != nil {
ctx.Logger().Debug("ProcessBlock: failed to SudoTxQueryResult",
"error", err, "query_id", queryID, "tx_hash", hex.EncodeToString(txHash))
return errors.Wrapf(err, "contract %s rejected transaction query result (tx_hash: %s)",
queryOwner, hex.EncodeToString(txHash))
}
k.SaveTransactionAsProcessed(ctx, queryID, txHash)
} else {
ctx.Logger().Debug("ProcessBlock: transaction was already submitted",
"query_id", queryID, "tx_hash", hex.EncodeToString(txHash))
}
return nil
}
type TransactionVerifier struct{}
// VerifyTransaction verifies that some transaction is included in block, and the transaction was executed successfully.
// The function checks:
// * transaction is included in block - header.DataHash merkle root contains transactions hash;
// * transactions was executed successfully - transaction's responseDeliveryTx.Code == 0;
// * transaction's responseDeliveryTx is legitimate - nextHeaderLastResultsDataHash merkle root contains
// deterministicResponseDeliverTx(ResponseDeliveryTx).Bytes()
func (v TransactionVerifier) VerifyTransaction(
header *tendermintLightClientTypes.Header,
nextHeader *tendermintLightClientTypes.Header,
tx *types.TxValue,
) error {
// verify inclusion proof
inclusionProof, err := merkle.ProofFromProto(tx.InclusionProof)
if err != nil {
return errors.Wrapf(types.ErrInvalidType, "failed to convert proto proof to merkle proof: %v", err)
}
if err = inclusionProof.Verify(header.Header.DataHash, tmtypes.Tx(tx.Data).Hash()); err != nil {
return errors.Wrapf(types.ErrInvalidProof, "failed to verify inclusion proof: %v", err)
}
// verify delivery proof
deliveryProof, err := merkle.ProofFromProto(tx.DeliveryProof)
if err != nil {
return errors.Wrapf(types.ErrInvalidType, "failed to convert proto proof to merkle proof: %v", err)
}
responseTx := deterministicResponseDeliverTx(tx.Response)
responseTxBz, err := responseTx.Marshal()
if err != nil {
return errors.Wrapf(types.ErrProtoMarshal, "failed to marshal ResponseDeliveryTx: %v", err)
}
if err = deliveryProof.Verify(nextHeader.Header.LastResultsHash, responseTxBz); err != nil {
return errors.Wrapf(types.ErrInvalidProof, "failed to verify delivery proof: %v", err)
}
// check that transaction was successful
if tx.Response.Code != abci.CodeTypeOK {
return errors.Wrapf(types.ErrInternal, "tx %s is unsuccessful: ResponseDelivery.Code = %d", hex.EncodeToString(tmtypes.Tx(tx.Data).Hash()), tx.Response.Code)
}
// check that inclusion proof and delivery proof are for the same transaction
if deliveryProof.Index != inclusionProof.Index {
return errors.Wrapf(types.ErrInvalidProof, "inclusion proof index and delivery proof index are not equal: %d != %d", inclusionProof.Index, deliveryProof.Index)
}
return nil
}