-
Notifications
You must be signed in to change notification settings - Fork 750
Expand file tree
/
Copy pathrelay.go
More file actions
171 lines (143 loc) · 6.84 KB
/
relay.go
File metadata and controls
171 lines (143 loc) · 6.84 KB
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
package keeper
import (
"context"
"strconv"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
channeltypesv2 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
hostv2 "github.com/cosmos/ibc-go/v9/modules/core/24-host/v2"
"github.com/cosmos/ibc-go/v9/modules/core/exported"
"github.com/cosmos/ibc-go/v9/modules/core/packet-server/types"
)
// getV1Counterparty attempts to retrieve a v1 channel from the channel keeper if it exists, then converts it
// to a v2 counterparty and stores it in the v2 channel keeper for future use
func (k *Keeper) getV1Counterparty(ctx context.Context, port, id string) (channeltypesv2.Counterparty, bool) {
if counterparty, ok := k.AliasV1Channel(ctx, port, id); ok {
// we can key on just the channel here since channel ids are globally unique
k.SetCounterparty(ctx, id, counterparty)
return counterparty, true
}
return channeltypesv2.Counterparty{}, false
}
// sendPacket constructs a packet from the input arguments, writes a packet commitment to state
// in order for the packet to be sent to the counterparty.
func (k *Keeper) sendPacket(
ctx context.Context,
sourceID string,
timeoutTimestamp uint64,
data []channeltypesv2.PacketData,
) (uint64, error) {
// Lookup counterparty associated with our source channel to retrieve the destination channel
counterparty, ok := k.GetCounterparty(ctx, sourceID)
if !ok {
// TODO: figure out how aliasing will work when more than one packet data is sent.
counterparty, ok = k.getV1Counterparty(ctx, data[0].SourcePort, sourceID)
if !ok {
return 0, errorsmod.Wrap(types.ErrCounterpartyNotFound, sourceID)
}
}
destID := counterparty.CounterpartyChannelId
clientID := counterparty.ClientId
// retrieve the sequence send for this channel
// if no packets have been sent yet, initialize the sequence to 1.
sequence, found := k.GetNextSequenceSend(ctx, sourceID)
if !found {
sequence = 1
}
// construct packet from given fields and channel state
packet := channeltypesv2.NewPacket(sequence, sourceID, destID, timeoutTimestamp, data...)
if err := packet.ValidateBasic(); err != nil {
return 0, errorsmod.Wrapf(channeltypes.ErrInvalidPacket, "constructed packet failed basic validation: %v", err)
}
// check that the client of counterparty chain is still active
if status := k.ClientKeeper.GetClientStatus(ctx, clientID); status != exported.Active {
return 0, errorsmod.Wrapf(clienttypes.ErrClientNotActive, "client (%s) status is %s", clientID, status)
}
// retrieve latest height and timestamp of the client of counterparty chain
latestHeight := k.ClientKeeper.GetClientLatestHeight(ctx, clientID)
if latestHeight.IsZero() {
return 0, errorsmod.Wrapf(clienttypes.ErrInvalidHeight, "cannot send packet using client (%s) with zero height", clientID)
}
latestTimestamp, err := k.ClientKeeper.GetClientTimestampAtHeight(ctx, clientID, latestHeight)
if err != nil {
return 0, err
}
// check if packet is timed out on the receiving chain
timeout := channeltypes.NewTimeoutWithTimestamp(timeoutTimestamp)
if timeout.TimestampElapsed(latestTimestamp) {
return 0, errorsmod.Wrap(timeout.ErrTimeoutElapsed(latestHeight, latestTimestamp), "invalid packet timeout")
}
commitment := channeltypesv2.CommitPacket(packet)
// bump the sequence and set the packet commitment, so it is provable by the counterparty
k.SetNextSequenceSend(ctx, sourceID, sequence+1)
k.SetPacketCommitment(ctx, sourceID, packet.GetSequence(), commitment)
k.Logger(ctx).Info("packet sent", "sequence", strconv.FormatUint(packet.Sequence, 10), "dest_id", packet.DestinationId, "src_id", packet.SourceId)
EmitSendPacketEvents(ctx, packet)
return sequence, nil
}
// recvPacket implements the packet receiving logic required by a packet handler.
// The packet is checked for correctness including asserting that the packet was
// sent and received on clients which are counterparties for one another.
// If the packet has already been received a no-op error is returned.
// The packet handler will verify that the packet has not timed out and that the
// counterparty stored a packet commitment. If successful, a packet receipt is stored
// to indicate to the counterparty successful delivery.
func (k Keeper) recvPacket(
ctx context.Context,
packet channeltypesv2.Packet,
proof []byte,
proofHeight exported.Height,
) error {
// Lookup counterparty associated with our channel and ensure
// that the packet was indeed sent by our counterparty.
counterparty, ok := k.GetCounterparty(ctx, packet.DestinationId)
if !ok {
// TODO: figure out how aliasing will work when more than one packet data is sent.
counterparty, ok = k.getV1Counterparty(ctx, packet.Data[0].DestinationPort, packet.DestinationId)
if !ok {
return errorsmod.Wrap(types.ErrCounterpartyNotFound, packet.DestinationId)
}
}
if counterparty.ClientId != packet.SourceId {
return channeltypes.ErrInvalidChannelIdentifier
}
// check if packet timed out by comparing it with the latest height of the chain
sdkCtx := sdk.UnwrapSDKContext(ctx)
selfHeight, selfTimestamp := clienttypes.GetSelfHeight(ctx), uint64(sdkCtx.BlockTime().UnixNano())
timeout := channeltypes.NewTimeoutWithTimestamp(packet.GetTimeoutTimestamp())
if timeout.Elapsed(selfHeight, selfTimestamp) {
return errorsmod.Wrap(timeout.ErrTimeoutElapsed(selfHeight, selfTimestamp), "packet timeout elapsed")
}
// REPLAY PROTECTION: Packet receipts will indicate that a packet has already been received
// on unordered channels. Packet receipts must not be pruned, unless it has been marked stale
// by the increase of the recvStartSequence.
_, found := k.GetPacketReceipt(ctx, packet.DestinationId, packet.Sequence)
if found {
EmitRecvPacketEvents(ctx, packet)
// This error indicates that the packet has already been relayed. Core IBC will
// treat this error as a no-op in order to prevent an entire relay transaction
// from failing and consuming unnecessary fees.
return channeltypes.ErrNoOpMsg
}
path := hostv2.PacketCommitmentKey(packet.SourceId, sdk.Uint64ToBigEndian(packet.Sequence))
merklePath := types.BuildMerklePath(counterparty.MerklePathPrefix, path)
commitment := channeltypesv2.CommitPacket(packet)
if err := k.ClientKeeper.VerifyMembership(
ctx,
packet.DestinationId,
proofHeight,
0, 0,
proof,
merklePath,
commitment,
); err != nil {
return errorsmod.Wrapf(err, "failed packet commitment verification for client (%s)", packet.DestinationId)
}
// Set Packet Receipt to prevent timeout from occurring on counterparty
k.SetPacketReceipt(ctx, packet.DestinationId, packet.Sequence)
k.Logger(ctx).Info("packet received", "sequence", strconv.FormatUint(packet.Sequence, 10), "src_id", packet.SourceId, "dst_id", packet.DestinationId)
EmitRecvPacketEvents(ctx, packet)
return nil
}