Fix: handle panic on Ws error (#1915)

* On Ws error close all active subscriptions and force clients to reconnect. the Websocket.

* Comment typos

* Unit tests
Lint
cargo +nightly fmt

* - Added CHANGELOG entry
- Added `#` prefix to issue IDs where missing

* ownership typo

Co-authored-by: Andrea Simeoni <>
This commit is contained in:
Andrea Simeoni 2022-12-01 20:00:10 +01:00 committed by GitHub
parent 0ce6b107d6
commit c4e09f261e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 24 deletions

View File

@ -3,9 +3,10 @@
## ethers-core ## ethers-core
### Unreleased ### Unreleased
- Graceful handling of WebSocket transport errors [#1889](https://github.com/gakonst/ethers-rs/issues/1889) [#1815](https://github.com/gakonst/ethers-rs/issues/1815)
- `MiddlewareBuilder` trait to instantiate a `Provider` as `Middleware` layers. - `MiddlewareBuilder` trait to instantiate a `Provider` as `Middleware` layers.
- An `Event` builder can be instantiated specifying the event filter type, without the need to instantiate a contract. - An `Event` builder can be instantiated specifying the event filter type, without the need to instantiate a contract.
- Add 'ethers_core::types::OpCode' and use in 'ethers_core::types::VMOperation' [1857](https://github.com/gakonst/ethers-rs/issues/1857) - Add 'ethers_core::types::OpCode' and use in 'ethers_core::types::VMOperation' [#1857](https://github.com/gakonst/ethers-rs/issues/1857)
- Remove rust_decimals dependency for ethers-core - Remove rust_decimals dependency for ethers-core
- Add support for numbers greater than 2^96 for `ethers_core::utils::parse_units` [#1822](https://github.com/gakonst/ethers-rs/issues/1822) - Add support for numbers greater than 2^96 for `ethers_core::utils::parse_units` [#1822](https://github.com/gakonst/ethers-rs/issues/1822)
- Add comment about safety of u8 -> u64 cast in `ethers_core::types::Signature` - Add comment about safety of u8 -> u64 cast in `ethers_core::types::Signature`
@ -26,38 +27,38 @@
- Add `as_*_mut` methods on `TypedTransaction` - Add `as_*_mut` methods on `TypedTransaction`
[#1310](https://github.com/gakonst/ethers-rs/pull/1310) [#1310](https://github.com/gakonst/ethers-rs/pull/1310)
- AWS EIP712 data signing no longer signs with EIP155 - AWS EIP712 data signing no longer signs with EIP155
- Added Cronos testnet to etherscan options [1276](https://github.com/gakonst/ethers-rs/pull/1276) - Added Cronos testnet to etherscan options [#1276](https://github.com/gakonst/ethers-rs/pull/1276)
- Fix parsing of a pending block - Fix parsing of a pending block
[1272](https://github.com/gakonst/ethers-rs/pull/1272) [#1272](https://github.com/gakonst/ethers-rs/pull/1272)
- Removed Cronos mainnet beta from `is_legacy` [1246](https://github.com/gakonst/ethers-rs/pull/1246) - Removed Cronos mainnet beta from `is_legacy` [1246](https://github.com/gakonst/ethers-rs/pull/1246)
- Fix RLP decoding of `from` field for `Eip1559TransactionRequest` and - Fix RLP decoding of `from` field for `Eip1559TransactionRequest` and
`Eip2930TransactionRequest`, remove `Eip1559TransactionRequest` `sighash` `Eip2930TransactionRequest`, remove `Eip1559TransactionRequest` `sighash`
method [1180](https://github.com/gakonst/ethers-rs/pull/1180) method [#1180](https://github.com/gakonst/ethers-rs/pull/1180)
- Fix RLP encoding of absent access list in `Transaction` [1137](https://github.com/gakonst/ethers-rs/pull/1137) - Fix RLP encoding of absent access list in `Transaction` [1137](https://github.com/gakonst/ethers-rs/pull/1137)
- Pass compilation time as additional argument to `Reporter::on_solc_success` [1098](https://github.com/gakonst/ethers-rs/pull/1098) - Pass compilation time as additional argument to `Reporter::on_solc_success` [#1098](https://github.com/gakonst/ethers-rs/pull/1098)
- Fix aws signer bug which maps un-normalized signature to error if no normalization occurs (in `aws::utils::decode_signature`) - Fix aws signer bug which maps un-normalized signature to error if no normalization occurs (in `aws::utils::decode_signature`)
- Implement signed transaction RLP decoding [#1096](https://github.com/gakonst/ethers-rs/pull/1096) - Implement signed transaction RLP decoding [#1096](https://github.com/gakonst/ethers-rs/pull/1096)
- `Transaction::from` will default to `Address::zero()`. Add `recover_from` and - `Transaction::from` will default to `Address::zero()`. Add `recover_from` and
`recover_from_mut` methods for recovering the sender from signature, and also `recover_from_mut` methods for recovering the sender from signature, and also
setting the same on tx [1075](https://github.com/gakonst/ethers-rs/pull/1075). setting the same on tx [#1075](https://github.com/gakonst/ethers-rs/pull/1075).
- Add Etherscan account API endpoints [939](https://github.com/gakonst/ethers-rs/pull/939) - Add Etherscan account API endpoints [#939](https://github.com/gakonst/ethers-rs/pull/939)
- Add FTM Mainet and testnet to parse method "try_from" from Chain.rs and add cronos mainet and testnet to "from_str" - Add FTM Mainet and testnet to parse method "try_from" from Chain.rs and add cronos mainet and testnet to "from_str"
- Add FTM mainnet and testnet Multicall addresses [927](https://github.com/gakonst/ethers-rs/pull/927) - Add FTM mainnet and testnet Multicall addresses [#927](https://github.com/gakonst/ethers-rs/pull/927)
- Add Cronos mainnet beta and testnet to the list of known chains - Add Cronos mainnet beta and testnet to the list of known chains
[926](https://github.com/gakonst/ethers-rs/pull/926) [#926](https://github.com/gakonst/ethers-rs/pull/926)
- `Chain::to_string` will return the same chain name as `Chain::from_str` - `Chain::to_string` will return the same chain name as `Chain::from_str`
- Add `eth_syncing` [848](https://github.com/gakonst/ethers-rs/pull/848) - Add `eth_syncing` [#848](https://github.com/gakonst/ethers-rs/pull/848)
- Fix overflow and possible divide-by-zero in `estimate_priority_fee` - Fix overflow and possible divide-by-zero in `estimate_priority_fee`
- Add BSC mainnet and testnet to the list of known chains - Add BSC mainnet and testnet to the list of known chains
[831](https://github.com/gakonst/ethers-rs/pull/831) [#831](https://github.com/gakonst/ethers-rs/pull/831)
- Returns error on invalid type conversion instead of panicking - Returns error on invalid type conversion instead of panicking
[691](https://github.com/gakonst/ethers-rs/pull/691/files) [#691](https://github.com/gakonst/ethers-rs/pull/691/files)
- Change types mapping for solidity `bytes` to rust `ethers::core::Bytes` and - Change types mapping for solidity `bytes` to rust `ethers::core::Bytes` and
solidity `uint8[]` to rust `Vec<u8>`. solidity `uint8[]` to rust `Vec<u8>`.
[613](https://github.com/gakonst/ethers-rs/pull/613) [#613](https://github.com/gakonst/ethers-rs/pull/613)
- Fix `format_units` to return a `String` of representing a decimal point float - Fix `format_units` to return a `String` of representing a decimal point float
such that the decimal places don't get truncated. such that the decimal places don't get truncated.
[597](https://github.com/gakonst/ethers-rs/pull/597) [#597](https://github.com/gakonst/ethers-rs/pull/597)
- Implement hex display format for `ethers::core::Bytes` - Implement hex display format for `ethers::core::Bytes`
[#624](https://github.com/gakonst/ethers-rs/pull/624). [#624](https://github.com/gakonst/ethers-rs/pull/624).
- Fix `fee_history` to first try with `block_count` encoded as a hex `QUANTITY`. - Fix `fee_history` to first try with `block_count` encoded as a hex `QUANTITY`.
@ -83,7 +84,7 @@
- Add a getter to `ProjectCompileOutput` that returns a mapping of compiler - Add a getter to `ProjectCompileOutput` that returns a mapping of compiler
versions to a vector of name + contract struct tuples versions to a vector of name + contract struct tuples
[#908](https://github.com/gakonst/ethers-rs/pull/908) [#908](https://github.com/gakonst/ethers-rs/pull/908)
- Add Yul compilation [994](https://github.com/gakonst/ethers-rs/pull/994) - Add Yul compilation [#994](https://github.com/gakonst/ethers-rs/pull/994)
- Enforce commutativity of ENS reverse resolution - Enforce commutativity of ENS reverse resolution
[#996](https://github.com/gakonst/ethers-rs/pull/996) [#996](https://github.com/gakonst/ethers-rs/pull/996)
- Add `TransactionReceipt::to` and `TransactionReceipt::from` - Add `TransactionReceipt::to` and `TransactionReceipt::from`

View File

@ -246,16 +246,12 @@ where
debug!("work complete"); debug!("work complete");
break break
} }
match self.tick().await {
Err(ClientError::UnexpectedClose) => { if let Err(e) = self.tick().await {
error!("{}", ClientError::UnexpectedClose); error!("Received a WebSocket error: {:?}", e);
self.close_all_subscriptions();
break break
} }
Err(e) => {
panic!("WS Server panic: {}", e);
}
_ => {}
}
} }
}; };
@ -266,6 +262,15 @@ where
tokio::spawn(f); tokio::spawn(f);
} }
// This will close all active subscriptions. Each process listening for
// updates will observe the end of their subscription streams.
fn close_all_subscriptions(&self) {
error!("Tearing down subscriptions");
for (_, sub) in self.subscriptions.iter() {
sub.close_channel();
}
}
// dispatch an RPC request // dispatch an RPC request
async fn service_request( async fn service_request(
&mut self, &mut self,

View File

@ -0,0 +1,79 @@
#![cfg(not(target_arch = "wasm32"))]
use ethers_providers::{Middleware, Provider, StreamExt};
use futures_util::SinkExt;
use std::time::Duration;
use tokio::net::{TcpListener, TcpStream};
use tokio_tungstenite::{
accept_async,
tungstenite::{
self,
protocol::{frame::coding::CloseCode, CloseFrame},
Error,
},
};
use tungstenite::protocol::Message;
const WS_ENDPOINT: &str = "127.0.0.1:9002";
#[cfg(not(feature = "celo"))]
mod eth_tests {
use ethers_core::types::Filter;
use ethers_providers::{StreamExt, Ws};
use tokio_tungstenite::connect_async;
use super::*;
#[tokio::test]
async fn graceful_disconnect_on_ws_errors() {
// Spawn a fake Ws server that will drop our connection after a while
spawn_ws_server().await;
// Connect to the fake server
let (ws, _) = connect_async(format!("ws://{}", WS_ENDPOINT)).await.unwrap();
let provider = Provider::new(Ws::new(ws));
let filter = Filter::new().event("Transfer(address,address,uint256)");
let mut stream = provider.subscribe_logs(&filter).await.unwrap();
while let Some(_) = stream.next().await {
assert!(false); // force test to fail
}
assert!(true);
}
}
async fn spawn_ws_server() {
let listener = TcpListener::bind(&WS_ENDPOINT).await.expect("Can't listen");
tokio::spawn(async move {
while let Ok((stream, _)) = listener.accept().await {
tokio::spawn(handle_conn(stream));
}
});
}
async fn handle_conn(stream: TcpStream) -> Result<(), Error> {
let mut ws_stream = accept_async(stream).await?;
while let Some(_) = ws_stream.next().await {
let res: String =
"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0xcd0c3e8af590364c09d0fa6a1210faf5\"}"
.into();
// Answer with a valid RPC response to keep the connection alive
ws_stream.send(Message::Text(res)).await?;
// Wait for a while
let timeout = Duration::from_secs(2);
tokio::time::sleep(timeout).await;
// Drop the connection
ws_stream
.send(Message::Close(Some(CloseFrame {
code: CloseCode::Error,
reason: "Upstream went away".into(),
})))
.await?;
}
Ok(())
}