Skip to content

Commit

Permalink
New relayer logic
Browse files Browse the repository at this point in the history
  • Loading branch information
danil-lashin committed Dec 4, 2021
1 parent bafb6e4 commit edc530b
Show file tree
Hide file tree
Showing 9 changed files with 583 additions and 291 deletions.
4 changes: 4 additions & 0 deletions module/proto/mhub2/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ service Query {
rpc LatestSignerSetTx(LatestSignerSetTxRequest) returns (SignerSetTxResponse) {
option (google.api.http).get = "/mhub2/v1/signer_set/latest/{chain_id}";
}
rpc LastObservedSignerSetTx(LastObservedSignerSetTxRequest) returns (SignerSetTxResponse) {
option (google.api.http).get = "/mhub2/v1/signer_set/last_observed/{chain_id}";
}
rpc BatchTx(BatchTxRequest) returns (BatchTxResponse) {
option (google.api.http).get = "/mhub2/v1/batch_txs/{chain_id}/{external_token_id}/{batch_nonce}";
}
Expand Down Expand Up @@ -150,6 +153,7 @@ message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; }
// rpc SignerSetTx
message SignerSetTxRequest { uint64 signer_set_nonce = 1; string chain_id = 2; }
message LatestSignerSetTxRequest { string chain_id = 1; }
message LastObservedSignerSetTxRequest { string chain_id = 1; }
message SignerSetTxResponse { SignerSetTx signer_set = 1; }

// rpc BatchTx
Expand Down
30 changes: 30 additions & 0 deletions module/swagger/mhub2/v1/query.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,36 @@
]
}
},
"/mhub2/v1/signer_set/last_observed/{chain_id}": {
"get": {
"operationId": "Query_LastObservedSignerSetTx",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1SignerSetTxResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
},
"parameters": [
{
"name": "chain_id",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"Query"
]
}
},
"/mhub2/v1/signer_set/latest/{chain_id}": {
"get": {
"operationId": "Query_LatestSignerSetTx",
Expand Down
6 changes: 6 additions & 0 deletions module/x/mhub2/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ import (

var _ types.QueryServer = Keeper{}

func (k Keeper) LastObservedSignerSetTx(c context.Context, request *types.LastObservedSignerSetTxRequest) (*types.SignerSetTxResponse, error) {
return &types.SignerSetTxResponse{
SignerSet: k.GetLastObservedSignerSetTx(sdk.UnwrapSDKContext(c), types.ChainID(request.ChainId)),
}, nil
}

func (k Keeper) DiscountForHolder(c context.Context, request *types.DiscountForHolderRequest) (*types.DiscountForHolderResponse, error) {
return &types.DiscountForHolderResponse{
Discount: sdk.NewDec(1).Sub(k.GetCommissionForHolder(sdk.UnwrapSDKContext(c), []string{request.Address}, sdk.NewDec(1))),
Expand Down
590 changes: 401 additions & 189 deletions module/x/mhub2/types/query.pb.go

Large diffs are not rendered by default.

101 changes: 101 additions & 0 deletions module/x/mhub2/types/query.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions orchestrator/cosmos_gravity/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ pub async fn get_latest_valset(
Ok(valset)
}

pub async fn get_last_observed_valset(
client: &mut Mhub2QueryClient<Channel>,
chain_id: String,
) -> Result<Option<Valset>, GravityError> {
let response = client
.last_observed_signer_set_tx(LastObservedSignerSetTxRequest {
chain_id: chain_id.clone(),
})
.await?;
let valset = response.into_inner().signer_set;
let valset = match valset {
Some(v) => Some(v.into()),
None => None,
};
Ok(valset)
}

/// get all valset confirmations for a given nonce
pub async fn get_all_valset_confirms(
client: &mut Mhub2QueryClient<Channel>,
Expand Down
20 changes: 20 additions & 0 deletions orchestrator/mhub2_proto/src/prost/mhub2.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,11 @@ pub struct LatestSignerSetTxRequest {
pub chain_id: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LastObservedSignerSetTxRequest {
#[prost(string, tag = "1")]
pub chain_id: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SignerSetTxResponse {
#[prost(message, optional, tag = "1")]
pub signer_set: ::core::option::Option<SignerSetTx>,
Expand Down Expand Up @@ -1109,6 +1114,21 @@ pub mod query_client {
let path = http::uri::PathAndQuery::from_static("/mhub2.v1.Query/LatestSignerSetTx");
self.inner.unary(request.into_request(), path, codec).await
}
pub async fn last_observed_signer_set_tx(
&mut self,
request: impl tonic::IntoRequest<super::LastObservedSignerSetTxRequest>,
) -> Result<tonic::Response<super::SignerSetTxResponse>, tonic::Status> {
self.inner.ready().await.map_err(|e| {
tonic::Status::new(
tonic::Code::Unknown,
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path =
http::uri::PathAndQuery::from_static("/mhub2.v1.Query/LastObservedSignerSetTx");
self.inner.unary(request.into_request(), path, codec).await
}
pub async fn batch_tx(
&mut self,
request: impl tonic::IntoRequest<super::BatchTxRequest>,
Expand Down
98 changes: 3 additions & 95 deletions orchestrator/relayer/src/find_latest_valset.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
use clarity::{Address, Uint256};
use ethereum_gravity::utils::downcast_uint256;
use mhub2_proto::mhub2::query_client::QueryClient as GravityQueryClient;
use mhub2_utils::types::ValsetUpdatedEvent;
use mhub2_utils::{error::GravityError, types::Valset};
use tonic::transport::Channel;
use web30::client::Web3;

/// This function finds the latest valset on the Gravity contract by looking back through the event
/// history and finding the most recent ValsetUpdatedEvent. Most of the time this will be very fast
Expand All @@ -13,98 +9,10 @@ use web30::client::Web3;
/// this will take longer.
pub async fn find_latest_valset(
grpc_client: &mut GravityQueryClient<Channel>,
gravity_contract_address: Address,
web3: &Web3,
chain_id: String,
) -> Result<Valset, GravityError> {
const BLOCKS_TO_SEARCH: u128 = 5_000u128;
let latest_block = web3.eth_block_number().await?;
let mut current_block: Uint256 = latest_block.clone();
let cosmos_chain_valset =
cosmos_gravity::query::get_last_observed_valset(grpc_client, chain_id.clone()).await?;

while current_block.clone() > 0u8.into() {
trace!(
"About to submit a Valset or Batch looking back into the history to find the last Valset Update, on block {}",
current_block
);
let end_search = if current_block.clone() < BLOCKS_TO_SEARCH.into() {
0u8.into()
} else {
current_block.clone() - BLOCKS_TO_SEARCH.into()
};
let mut all_valset_events = web3
.check_for_events(
end_search.clone(),
Some(current_block.clone()),
vec![gravity_contract_address],
vec!["ValsetUpdatedEvent(uint256,uint256,address[],uint256[])"],
)
.await?;
// by default the lowest found valset goes first, we want the highest.
all_valset_events.reverse();

trace!("Found events {:?}", all_valset_events);

// we take only the first event if we find any at all.
if !all_valset_events.is_empty() {
let event = &all_valset_events[0];
match ValsetUpdatedEvent::from_log(event) {
Ok(event) => {
let latest_eth_valset = Valset {
nonce: downcast_uint256(event.valset_nonce.clone()).unwrap(),
members: event.members,
};
let cosmos_chain_valset = cosmos_gravity::query::get_valset(
grpc_client,
latest_eth_valset.nonce,
chain_id.clone(),
)
.await?;
check_if_valsets_differ(cosmos_chain_valset, &&latest_eth_valset);
return Ok(latest_eth_valset);
}
Err(e) => error!("Got valset event that we can't parse {}", e),
}
}
current_block = end_search;
}

panic!("Could not find the last validator set for contract {}, probably not a valid Gravity contract!", gravity_contract_address)
}

/// This function exists to provide a warning if Cosmos and Ethereum have different validator sets
/// for a given nonce. In the mundane version of this warning the validator sets disagree on sorting order
/// which can happen if some relayer uses an unstable sort, or in a case of a mild griefing attack.
/// The Gravity contract validates signatures in order of highest to lowest power. That way it can exit
/// the loop early once a vote has enough power, if a relayer where to submit things in the reverse order
/// they could grief users of the contract into paying more in gas.
/// The other (and far worse) way a disagreement here could occur is if validators are colluding to steal
/// funds from the Gravity contract and have submitted a highjacking update. If slashing for off Cosmos chain
/// Ethereum signatures is implemented you would put that handler here.
fn check_if_valsets_differ(cosmos_valset: Option<Valset>, ethereum_valset: &Valset) {
if cosmos_valset.is_none() && ethereum_valset.nonce == 0 {
// bootstrapping case
return;
} else if cosmos_valset.is_none() {
error!("Cosmos does not have a valset for nonce {} but that is the one on the Ethereum chain! Possible bridge highjacking!", ethereum_valset.nonce);
return;
}
let cosmos_valset = cosmos_valset.unwrap();
if cosmos_valset != *ethereum_valset {
// if this is not true then we have a logic error on the Cosmos chain
// or with our Ethereum search
assert_eq!(cosmos_valset.nonce, ethereum_valset.nonce);

let mut c_valset = cosmos_valset.members;
let mut e_valset = ethereum_valset.members.clone();
c_valset.sort();
e_valset.sort();
if c_valset == e_valset {
info!(
"Sorting disagreement between Cosmos and Ethereum on Valset nonce {}",
ethereum_valset.nonce
);
} else {
info!("Validator sets for nonce {} Cosmos and Ethereum differ. Possible bridge highjacking!", ethereum_valset.nonce)
}
}
Ok(cosmos_chain_valset.unwrap())
}
8 changes: 1 addition & 7 deletions orchestrator/relayer/src/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,7 @@ pub async fn relayer_main_loop(
let loop_start = Instant::now();

let our_ethereum_address = ethereum_key.to_public_key().unwrap();
let current_eth_valset = find_latest_valset(
&mut grpc_client,
gravity_contract_address,
&web3,
chain_id.clone(),
)
.await;
let current_eth_valset = find_latest_valset(&mut grpc_client, chain_id.clone()).await;
if current_eth_valset.is_err() {
error!("Could not get current valset! {:?}", current_eth_valset);
continue;
Expand Down

0 comments on commit edc530b

Please sign in to comment.