forked from Gravity-Bridge/Gravity-Bridge
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ethereum_event_watcher.rs
267 lines (251 loc) · 11.1 KB
/
ethereum_event_watcher.rs
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! Ethereum Event watcher watches for events such as a deposit to the Gravity Ethereum contract or a validator set update
//! or a transaction batch update. It then responds to these events by performing actions on the Cosmos chain if required
use clarity::{utils::bytes_to_hex_str, Address as EthAddress, Uint256};
use cosmos_gravity::{query::get_last_event_nonce_for_validator, send::send_ethereum_claims};
use deep_space::Contact;
use deep_space::{coin::Coin, private_key::PrivateKey as CosmosPrivateKey};
use gravity_proto::gravity::query_client::QueryClient as GravityQueryClient;
use gravity_utils::get_with_retry::get_block_number_with_retry;
use gravity_utils::get_with_retry::get_net_version_with_retry;
use gravity_utils::types::event_signatures::*;
use gravity_utils::{
error::GravityError,
types::{
Erc20DeployedEvent, LogicCallExecutedEvent, SendToCosmosEvent,
TransactionBatchExecutedEvent, ValsetUpdatedEvent,
},
};
use metrics_exporter::metrics_errors_counter;
use tonic::transport::Channel;
use web30::client::Web3;
use web30::jsonrpc::error::Web3Error;
pub struct CheckedNonces {
pub block_number: Uint256,
pub event_nonce: Uint256,
}
#[allow(clippy::too_many_arguments)]
pub async fn check_for_events(
web3: &Web3,
contact: &Contact,
grpc_client: &mut GravityQueryClient<Channel>,
gravity_contract_address: EthAddress,
our_private_key: CosmosPrivateKey,
fee: Coin,
starting_block: Uint256,
block_delay: Uint256,
) -> Result<CheckedNonces, GravityError> {
let our_cosmos_address = our_private_key.to_address(&contact.get_prefix()).unwrap();
let latest_block = get_block_number_with_retry(web3).await;
let latest_block = latest_block - block_delay;
let deposits = web3
.check_for_events(
starting_block.clone(),
Some(latest_block.clone()),
vec![gravity_contract_address],
vec![SENT_TO_COSMOS_EVENT_SIG],
)
.await;
trace!("Deposits {:?}", deposits);
let batches = web3
.check_for_events(
starting_block.clone(),
Some(latest_block.clone()),
vec![gravity_contract_address],
vec![TRANSACTION_BATCH_EXECUTED_EVENT_SIG],
)
.await;
trace!("Batches {:?}", batches);
let valsets = web3
.check_for_events(
starting_block.clone(),
Some(latest_block.clone()),
vec![gravity_contract_address],
vec![VALSET_UPDATED_EVENT_SIG],
)
.await;
trace!("Valsets {:?}", valsets);
let erc20_deployed = web3
.check_for_events(
starting_block.clone(),
Some(latest_block.clone()),
vec![gravity_contract_address],
vec![ERC20_DEPLOYED_EVENT_SIG],
)
.await;
trace!("ERC20 Deployments {:?}", erc20_deployed);
let logic_call_executed = web3
.check_for_events(
starting_block.clone(),
Some(latest_block.clone()),
vec![gravity_contract_address],
vec![LOGIC_CALL_EVENT_SIG],
)
.await;
trace!("Logic call executions {:?}", logic_call_executed);
if let (Ok(valsets), Ok(batches), Ok(deposits), Ok(deploys), Ok(logic_calls)) = (
valsets,
batches,
deposits,
erc20_deployed,
logic_call_executed,
) {
let valsets = ValsetUpdatedEvent::from_logs(&valsets)?;
trace!("parsed valsets {:?}", valsets);
let withdraws = TransactionBatchExecutedEvent::from_logs(&batches)?;
trace!("parsed batches {:?}", batches);
let deposits = SendToCosmosEvent::from_logs(&deposits)?;
trace!("parsed deposits {:?}", deposits);
let erc20_deploys = Erc20DeployedEvent::from_logs(&deploys)?;
trace!("parsed erc20 deploys {:?}", erc20_deploys);
let logic_calls = LogicCallExecutedEvent::from_logs(&logic_calls)?;
trace!("logic call executions {:?}", logic_calls);
// note that starting block overlaps with our last checked block, because we have to deal with
// the possibility that the relayer was killed after relaying only one of multiple events in a single
// block, so we also need this routine so make sure we don't send in the first event in this hypothetical
// multi event block again. In theory we only send all events for every block and that will pass of fail
// atomicly but lets not take that risk.
let last_event_nonce = get_last_event_nonce_for_validator(
grpc_client,
our_cosmos_address,
contact.get_prefix(),
)
.await?;
let valsets = ValsetUpdatedEvent::filter_by_event_nonce(last_event_nonce, &valsets);
let deposits = SendToCosmosEvent::filter_by_event_nonce(last_event_nonce, &deposits);
let withdraws =
TransactionBatchExecutedEvent::filter_by_event_nonce(last_event_nonce, &withdraws);
let erc20_deploys =
Erc20DeployedEvent::filter_by_event_nonce(last_event_nonce, &erc20_deploys);
let logic_calls =
LogicCallExecutedEvent::filter_by_event_nonce(last_event_nonce, &logic_calls);
if !valsets.is_empty() {
info!(
"Oracle observed Valset update with nonce {} and event nonce {}",
valsets[0].valset_nonce, valsets[0].event_nonce
)
}
if !deposits.is_empty() {
info!(
"Oracle observed deposit with sender {}, destination {:?}, amount {}, and event nonce {}",
deposits[0].sender, deposits[0].validated_destination, deposits[0].amount, deposits[0].event_nonce
)
}
if !withdraws.is_empty() {
info!(
"Oracle observed batch with nonce {}, contract {}, and event nonce {}",
withdraws[0].batch_nonce, withdraws[0].erc20, withdraws[0].event_nonce
)
}
if !erc20_deploys.is_empty() {
let v = erc20_deploys[0].clone();
if v.cosmos_denom.len() < 1000 && v.name.len() < 1000 && v.symbol.len() < 1000 {
info!(
"Oracle observed ERC20 deployment with denom {} erc20 name {} and symbol {} and event nonce {}",
erc20_deploys[0].cosmos_denom, erc20_deploys[0].name, erc20_deploys[0].symbol, erc20_deploys[0].event_nonce,
);
} else {
info!(
"Oracle observed ERC20 deployment with event nonce {}",
erc20_deploys[0].event_nonce,
);
}
}
if !logic_calls.is_empty() {
info!(
"Oracle observed logic call execution with ID {} Nonce {} and event nonce {}",
bytes_to_hex_str(&logic_calls[0].invalidation_id),
logic_calls[0].invalidation_nonce,
logic_calls[0].event_nonce
)
}
let mut new_event_nonce: Uint256 = last_event_nonce.into();
if !deposits.is_empty()
|| !withdraws.is_empty()
|| !erc20_deploys.is_empty()
|| !logic_calls.is_empty()
|| !valsets.is_empty()
{
let res = send_ethereum_claims(
contact,
our_private_key,
deposits,
withdraws,
erc20_deploys,
logic_calls,
valsets,
fee,
)
.await?;
new_event_nonce = get_last_event_nonce_for_validator(
grpc_client,
our_cosmos_address,
contact.get_prefix(),
)
.await?
.into();
info!("Current event nonce is {}", new_event_nonce);
// since we can't actually trust that the above txresponse is correct we have to check here
// we may be able to trust the tx response post grpc
if new_event_nonce == last_event_nonce.into() {
return Err(GravityError::InvalidBridgeStateError(
format!("Claims did not process, trying to update but still on {}, trying again in a moment, check txhash {} for errors", last_event_nonce, res.txhash),
));
} else {
info!("Claims processed, new nonce {}", new_event_nonce);
}
}
Ok(CheckedNonces {
block_number: latest_block,
event_nonce: new_event_nonce,
})
} else {
error!("Failed to get events");
metrics_errors_counter(1, "Failed to get events");
Err(GravityError::EthereumRestError(Web3Error::BadResponse(
"Failed to get logs!".to_string(),
)))
}
}
/// The number of blocks behind the 'latest block' on Ethereum our event checking should be.
/// Ethereum does not have finality and as such is subject to chain reorgs and temporary forks
/// if we check for events up to the very latest block we may process an event which did not
/// 'actually occur' in the longest POW chain.
///
/// Obviously we must chose some delay in order to prevent incorrect events from being claimed
///
/// For EVM chains with finality the correct value for this is zero. As there's no need
/// to concern ourselves with re-orgs or forking. This function checks the netID of the
/// provided Ethereum RPC and adjusts the block delay accordingly
///
/// The value used here for Ethereum is a balance between being reasonably fast and reasonably secure
/// As you can see on https://etherscan.io/blocks_forked uncles (one block deep reorgs)
/// occur once every few minutes. Two deep once or twice a day.
/// https://etherscan.io/chart/uncles
/// Let's make a conservative assumption of 1% chance of an uncle being a two block deep reorg
/// (actual is closer to 0.3%) and assume that continues as we increase the depth.
/// Given an uncle every 2.8 minutes, a 6 deep reorg would be 2.8 minutes * (100^4) or one
/// 6 deep reorg every 53,272 years.
///
/// Of course the above assume that no mining attacks occur. Once we bring that potential into
/// the equation the question becomes 'how much money'. There is no depth safe from infinite
/// spending. Taking some source values from https://blog.ethereum.org/2016/05/09/on-settlement-finality/
/// we will use 13 blocks providing a 1/1_000_000 chance of an attacker with 25% of network hash
/// power succeeding
///
pub async fn get_block_delay(web3: &Web3) -> Uint256 {
let net_version = get_net_version_with_retry(web3).await;
match net_version {
// Mainline Ethereum, Ethereum classic, or the Ropsten, Kotti, Mordor testnets
// all POW Chains
1 | 3 | 6 | 7 => 13u8.into(),
// Dev, our own Gravity Ethereum testnet, and Hardhat respectively
// all single signer chains with no chance of any reorgs
2018 | 15 | 31337 => 0u8.into(),
// Rinkeby and Goerli use Clique (POA) Consensus, finality takes
// up to num validators blocks. Number is higher than Ethereum based
// on experience with operational issues
4 | 5 => 10u8.into(),
// assume the safe option (POW) where we don't know
_ => 13u8.into(),
}
}