-
Notifications
You must be signed in to change notification settings - Fork 42
/
xmsg.go
200 lines (167 loc) · 5.23 KB
/
xmsg.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
package app
import (
"context"
"encoding/json"
"math/rand/v2"
"github.com/omni-network/omni/contracts/bindings"
"github.com/omni-network/omni/e2e/types"
"github.com/omni-network/omni/lib/errors"
"github.com/omni-network/omni/lib/ethclient"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
var (
portalABI = mustGetABI(bindings.OmniPortalMetaData)
xMsgEvent = mustGetEvent(portalABI, "XMsg")
)
func parseAndFuzzXMsgs(perturb types.Perturb, respBody []byte) ([]byte, bool, error) {
var respMsg jsonRPCMessage
if err := json.Unmarshal(respBody, &respMsg); err != nil {
return nil, false, errors.Wrap(err, "unmarshal response")
}
var logs []ethtypes.Log
if err := json.Unmarshal(respMsg.Result, &logs); err != nil {
return nil, false, errors.Wrap(err, "unmarshal result")
}
if len(logs) == 0 {
return respBody, false, nil
}
filterer, err := bindings.NewOmniPortalFilterer(common.Address{}, nil)
if err != nil {
return nil, false, err
}
var msgs []*bindings.OmniPortalXMsg
for _, eventLog := range logs {
msg, err := filterer.ParseXMsg(eventLog)
if err != nil {
return nil, false, errors.Wrap(err, "parse xmsg log")
}
msgs = append(msgs, msg)
}
fuzzedMsgs, err := fuzzXMsgs(perturb, msgs)
if err != nil {
return nil, false, err
}
var fuzzedLogs []ethtypes.Log
for i, msg := range fuzzedMsgs {
data, err := portalABI.Events["XMsg"].Inputs.NonIndexed().Pack(msg.Sender, msg.To, msg.Data, msg.GasLimit, msg.Fees)
if err != nil {
return nil, false, errors.Wrap(err, "pack xmsg")
}
// Use the original log metadata (or the last one if we're out of bounds).
if i >= len(logs) {
i = len(logs) - 1
}
eventLog := logs[i]
eventLog.Data = data
fuzzedLogs = append(fuzzedLogs, eventLog)
}
result, err := json.Marshal(fuzzedLogs)
if err != nil {
return nil, false, errors.Wrap(err, "marshal result")
}
respMsg.Result = result
bz, err := json.Marshal(respMsg)
if err != nil {
return nil, false, errors.Wrap(err, "marshal response")
}
return bz, true, nil
}
func fuzzXMsgs(perturb types.Perturb, msgs []*bindings.OmniPortalXMsg) ([]*bindings.OmniPortalXMsg, error) {
switch perturb {
case types.PerturbFuzzyHeadAttRoot:
// Change 50% of 1st message gas limits.
// Consensus would not be possible.
do := rand.Float64() < 0.5 //nolint:gosec // Weak random ok for tests.
if do {
msgs[0].GasLimit++
}
case types.PerturbFuzzyHeadDropBlocks: // Every odd block.
// Remove all msgs.
// Will reach consensus, but results in BlockOffset mismatch of subsequent xblock with Finalized.
msgs = nil
case types.PerturbFuzzyHeadMoreMsgs:
// Duplicate last message, incrementing the offset.
last := *msgs[len(msgs)-1]
last.Offset++
msgs = append(msgs, &last)
case types.PerturbFuzzyHeadDropMsgs:
if len(msgs) > 1 {
// Drop last message.
msgs = msgs[:len(msgs)-1]
}
default:
return nil, errors.New("unknown perturbation", "perturb", perturb)
}
return msgs, nil
}
func isFuzzyXMsgLogFilter(ctx context.Context, perturb types.Perturb, target string, reqMsg jsonRPCMessage) (bool, uint64, error) {
if reqMsg.Method != "eth_getLogs" {
return false, 0, nil
}
var args []struct {
Topics [][]common.Hash `json:"topics"`
FromBlock string `json:"fromBlock"`
ToBlock string `json:"toBlock"`
}
if err := json.Unmarshal(reqMsg.Params, &args); err != nil {
return false, 0, errors.Wrap(err, "unmarshal params")
}
if len(args) != 1 {
return false, 0, nil
}
arg := args[0]
if len(arg.Topics) == 0 || len(arg.Topics[0]) != 1 {
return false, 0, nil
} else if arg.Topics[0][0] != xMsgEvent.ID {
return false, 0, nil
}
if arg.FromBlock != arg.ToBlock {
return false, 0, errors.New("fromBlock and toBlock must be equal")
}
block, err := hexutil.DecodeUint64(arg.FromBlock)
if err != nil {
return false, 0, errors.Wrap(err, "decode block number")
}
switch perturb {
case types.PerturbFuzzyHeadDropBlocks:
if block%2 != 0 {
return false, 0, nil // Only drop even blocks.
}
case types.PerturbFuzzyHeadAttRoot, types.PerturbFuzzyHeadMoreMsgs, types.PerturbFuzzyHeadDropMsgs:
// Always allow.
default:
return false, 0, errors.New("unexpected perturbation", "perturb", perturb)
}
ethCl, err := ethclient.Dial("proxy", target)
if err != nil {
return false, 0, errors.Wrap(err, "dial ethclient")
}
finalized, err := ethCl.HeaderByType(ctx, ethclient.HeadFinalized)
if err != nil {
return false, 0, errors.Wrap(err, "get finalized header")
}
const buffer = 2 // Avoid race conditions, require query to be more than 2 ahead of finalized.
return block-buffer > finalized.Number.Uint64(), block, nil
}
// mustGetABI returns the metadata's ABI as an abi.ABI type.
// It panics on error.
func mustGetABI(metadata *bind.MetaData) *abi.ABI {
abi, err := metadata.GetAbi()
if err != nil {
panic(err)
}
return abi
}
// mustGetEvent returns the event with the given name from the ABI.
// It panics if the event is not found.
func mustGetEvent(abi *abi.ABI, name string) abi.Event {
event, ok := abi.Events[name]
if !ok {
panic("event not found")
}
return event
}