-
Notifications
You must be signed in to change notification settings - Fork 56
/
Copy pathvalset_update.rs
147 lines (135 loc) · 4.86 KB
/
valset_update.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
use crate::{
types::{EthClient, EthSignerMiddleware},
utils::{get_send_transaction_gas_price, get_valset_nonce, GasCost},
};
use ethers::contract::builders::ContractCall;
use ethers::prelude::*;
use ethers::types::Address as EthAddress;
use gravity_abi::gravity::*;
use gravity_utils::{
error::GravityError, message_signatures::encode_valset_confirm_hashed, types::*,
};
use std::time::Duration;
/// this function generates an appropriate Ethereum transaction
/// to submit the provided validator set and signatures.
#[allow(clippy::too_many_arguments)]
pub async fn send_eth_valset_update(
new_valset: Valset,
old_valset: Valset,
confirms: &[ValsetConfirmResponse],
timeout: Duration,
gravity_contract_address: EthAddress,
gravity_id: String,
gas_cost: GasCost,
eth_client: EthClient,
) -> Result<(), GravityError> {
let old_nonce = old_valset.nonce;
let new_nonce = new_valset.nonce;
assert!(new_nonce > old_nonce);
info!(
"Ordering signatures and submitting validator set {} -> {} update to Ethereum",
old_nonce, new_nonce
);
let before_nonce = get_valset_nonce(gravity_contract_address, eth_client.clone()).await?;
if before_nonce != old_nonce {
info!(
"Someone else updated the valset to {}, exiting early",
before_nonce
);
return Ok(());
}
let contract_call = build_valset_update_contract_call(
&new_valset,
&old_valset,
confirms,
gravity_contract_address,
gravity_id,
eth_client.clone(),
)?;
let contract_call = contract_call
.gas(gas_cost.gas)
.gas_price(gas_cost.gas_price);
let pending_tx = contract_call.send().await?;
let tx_hash = *pending_tx;
info!("Sent valset update with txid {}", tx_hash);
// TODO(bolten): ethers interval default is 7s, this mirrors what web30 was doing, should we adjust?
// additionally we are mirroring only waiting for 1 confirmation by leaving that as default
let pending_tx = pending_tx.interval(Duration::from_secs(1));
match tokio::time::timeout(timeout, pending_tx).await?? {
Some(_) => (),
None => error!(
"Did not receive transaction receipt when sending valset update: {}",
tx_hash
),
}
let last_nonce = get_valset_nonce(gravity_contract_address, eth_client.clone()).await?;
if last_nonce != new_nonce {
error!(
"Current nonce is {} expected to update to nonce {}",
last_nonce, new_nonce
);
} else {
info!(
"Successfully updated Valset with new Nonce {:?}",
last_nonce
);
}
Ok(())
}
/// Returns the cost in Eth of sending this valset update
pub async fn estimate_valset_cost(
new_valset: &Valset,
old_valset: &Valset,
confirms: &[ValsetConfirmResponse],
gravity_contract_address: EthAddress,
gravity_id: String,
eth_client: EthClient,
) -> Result<GasCost, GravityError> {
let contract_call = build_valset_update_contract_call(
new_valset,
old_valset,
confirms,
gravity_contract_address,
gravity_id,
eth_client.clone(),
)?;
Ok(GasCost {
gas: contract_call.estimate_gas().await?,
gas_price: get_send_transaction_gas_price(eth_client.clone()).await?,
})
}
pub fn build_valset_update_contract_call(
new_valset: &Valset,
old_valset: &Valset,
confirms: &[ValsetConfirmResponse],
gravity_contract_address: EthAddress,
gravity_id: String,
eth_client: EthClient,
) -> Result<ContractCall<EthSignerMiddleware, ()>, GravityError> {
let (old_addresses, old_powers) = old_valset.filter_empty_addresses();
let (new_addresses, new_powers) = new_valset.filter_empty_addresses();
let old_powers: Vec<U256> = old_powers.iter().map(|power| (*power).into()).collect();
let new_powers: Vec<U256> = new_powers.iter().map(|power| (*power).into()).collect();
// remember the signatures are over the new valset and therefore this is the value we must encode
// the old valset exists only as a hash in the ethereum store
let hash = encode_valset_confirm_hashed(gravity_id, new_valset.clone());
// we need to use the old valset here because our signatures need to match the current
// members of the validator set in the contract.
let sig_data = old_valset.order_sigs(&hash, confirms)?;
let sig_arrays = to_arrays(sig_data);
let contract = Gravity::new(gravity_contract_address, eth_client.clone());
Ok(contract
.update_valset(
new_addresses,
new_powers,
new_valset.nonce.into(),
old_addresses,
old_powers,
old_valset.nonce.into(),
sig_arrays.v,
sig_arrays.r,
sig_arrays.s,
)
.from(eth_client.address())
.value(U256::zero()))
}