Skip to content

Commit

Permalink
Added support for PoA key rotation (#2086)
Browse files Browse the repository at this point in the history
Closes #1901
Closes #1919

The `ConsensusConfig` now contains one more variant, `PoAV2`, that also
overrides the PoA at specific block heights. It means that starting this
block the node expects blocks signed by another PoA key.

Along with that, I've added additional features to automate the
migration of the state so it is compatible with the new chain
configuration:
- During start-up, the `fuel-core` binary checks if there are any
changes in the chain config. If they are, the binary re-inserts the new
`Conesnsus::Genesis` type in the `SealedBlockConsensus` table. Now we
can do FuelLabs/chain-configuration#1.
- The `fuel-core` to react to `signing_key_overrides`. If the blocks at
the corresponding overwritten block height have different signers, the
`fuel-core` rollbacks to the height before overriding occurs.

Side changes:
- Renamed the methods of the `FuelService` to be more explicit on the
logic.
- Moved business logic from `FuelService::Task` to `FuelService`.

## Checklist
- [x] New behavior is reflected in tests

### Before requesting review
- [x] I have reviewed the code myself
  • Loading branch information
xgreenx authored Aug 15, 2024
1 parent be0b630 commit a2d8d2d
Show file tree
Hide file tree
Showing 29 changed files with 957 additions and 256 deletions.
11 changes: 8 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- [2079](https://github.com/FuelLabs/fuel-core/pull/2079): Open unknown columns in the RocksDB for forward compatibility.

### Changed
-[2076](https://github.com/FuelLabs/fuel-core/pull/2076): Replace usages of `iter_all` with `iter_all_keys` where necessary.
- [2076](https://github.com/FuelLabs/fuel-core/pull/2076): Replace usages of `iter_all` with `iter_all_keys` where necessary.

#### Breaking

- [2086](https://github.com/FuelLabs/fuel-core/pull/2086): Added support for PoA key rotation.
- [2086](https://github.com/FuelLabs/fuel-core/pull/2086): Support overriding of the non consensus parameters in the chan config.

### Breaking
-[2080](https://github.com/FuelLabs/fuel-core/pull/2080): Reject Upgrade txs with invalid wasm on txpool level.
Expand All @@ -21,8 +26,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- [2061](https://github.com/FuelLabs/fuel-core/pull/2061): Allow querying filled transaction body from the status.

### Changed
-[2067](https://github.com/FuelLabs/fuel-core/pull/2067): Return error from TxPool level if the `BlobId` is known.
-[2064](https://github.com/FuelLabs/fuel-core/pull/2064): Allow gas price metadata values to be overridden with config
- [2067](https://github.com/FuelLabs/fuel-core/pull/2067): Return error from TxPool level if the `BlobId` is known.
- [2064](https://github.com/FuelLabs/fuel-core/pull/2064): Allow gas price metadata values to be overridden with config

### Fixes
- [2060](https://github.com/FuelLabs/fuel-core/pull/2060): Use `min-gas-price` as a starting point if `start-gas-price` is zero.
Expand Down
2 changes: 0 additions & 2 deletions Cargo.lock

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

25 changes: 13 additions & 12 deletions benches/benches/block_target_gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ use fuel_core_chain_config::{
ContractConfig,
StateConfig,
};
use fuel_core_services::Service;
use fuel_core_storage::{
tables::ContractsRawCode,
vm_storage::IncreaseStorageKey,
Expand Down Expand Up @@ -337,17 +336,19 @@ fn service_with_many_contracts(
.unwrap();
}

let service = FuelService::new(
CombinedDatabase::new(
database,
Default::default(),
Default::default(),
Default::default(),
),
config.clone(),
)
.expect("Unable to start a FuelService");
service.start().expect("Unable to start the service");
let service = rt.block_on(async move {
FuelService::from_combined_database(
CombinedDatabase::new(
database,
Default::default(),
Default::default(),
Default::default(),
),
config.clone(),
)
.await
.expect("Unable to start FuelService")
});
(service, rt)
}

Expand Down
2 changes: 0 additions & 2 deletions bin/fuel-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ path = "src/main.rs"

[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
clap = { workspace = true, features = ["derive", "env", "string"] }
const_format = { version = "0.2", optional = true }
dirs = "4.0"
Expand All @@ -32,7 +31,6 @@ pyroscope_pprofrs = "0.2"
serde_json = { workspace = true }
tikv-jemallocator = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
tokio-util = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = [
"ansi",
Expand Down
5 changes: 3 additions & 2 deletions bin/fuel-core/chainspec/local-testnet/chain_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,9 @@
},
"genesis_state_transition_version": 7,
"consensus": {
"PoA": {
"signing_key": "e0a9fcde1b73f545252e01b30b50819eb9547d07531fa3df0385c5695736634d"
"PoAV2": {
"genesis_signing_key": "e0a9fcde1b73f545252e01b30b50819eb9547d07531fa3df0385c5695736634d",
"signing_key_overrides": {}
}
}
}
Binary file not shown.
55 changes: 1 addition & 54 deletions bin/fuel-core/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::Parser;
use fuel_core::service::genesis::NotifyCancel;
use fuel_core::ShutdownListener;
use fuel_core_chain_config::{
ChainConfig,
SnapshotReader,
Expand All @@ -10,7 +10,6 @@ use std::{
path::PathBuf,
str::FromStr,
};
use tokio_util::sync::CancellationToken;
use tracing_subscriber::{
filter::EnvFilter,
layer::SubscriberExt,
Expand Down Expand Up @@ -167,58 +166,6 @@ pub fn local_testnet_reader() -> SnapshotReader {
SnapshotReader::new_in_memory(local_testnet_chain_config(), state_config)
}

#[derive(Clone)]
pub struct ShutdownListener {
token: CancellationToken,
}

impl ShutdownListener {
pub fn spawn() -> Self {
let token = CancellationToken::new();
{
let token = token.clone();
tokio::spawn(async move {
let mut sigterm = tokio::signal::unix::signal(
tokio::signal::unix::SignalKind::terminate(),
)?;

let mut sigint = tokio::signal::unix::signal(
tokio::signal::unix::SignalKind::interrupt(),
)?;
#[cfg(unix)]
tokio::select! {
_ = sigterm.recv() => {
tracing::info!("Received SIGTERM");
}
_ = sigint.recv() => {
tracing::info!("Received SIGINT");
}
}
#[cfg(not(unix))]
{
tokio::signal::ctrl_c().await?;
tracing::info!("Received ctrl_c");
}
token.cancel();
tokio::io::Result::Ok(())
});
}
Self { token }
}
}

#[async_trait::async_trait]
impl NotifyCancel for ShutdownListener {
async fn wait_until_cancelled(&self) -> anyhow::Result<()> {
self.token.cancelled().await;
Ok(())
}

fn is_cancelled(&self) -> bool {
self.token.is_cancelled()
}
}

#[cfg(feature = "rocksdb")]
#[cfg(test)]
mod tests {
Expand Down
52 changes: 2 additions & 50 deletions bin/fuel-core/src/cli/rollback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use anyhow::Context;
use clap::Parser;
use fuel_core::{
combined_database::CombinedDatabase,
service::genesis::NotifyCancel,
state::historical_rocksdb::StateRewindPolicy,
};
use std::path::PathBuf;
Expand Down Expand Up @@ -37,57 +36,10 @@ pub async fn exec(command: Command) -> anyhow::Result<()> {
.map_err(Into::<anyhow::Error>::into)
.context(format!("failed to open combined database at path {path:?}"))?;

let shutdown_listener = ShutdownListener::spawn();
let mut shutdown_listener = ShutdownListener::spawn();
let target_block_height = command.target_block_height.into();

while !shutdown_listener.is_cancelled() {
let on_chain_height = db
.on_chain()
.latest_height()?
.ok_or(anyhow::anyhow!("on-chain database doesn't have height"))?;
db.rollback_to(target_block_height, &mut shutdown_listener)?;

let off_chain_height = db
.off_chain()
.latest_height()?
.ok_or(anyhow::anyhow!("on-chain database doesn't have height"))?;

if on_chain_height == target_block_height
&& off_chain_height == target_block_height
{
break;
}

if off_chain_height == target_block_height
&& on_chain_height < target_block_height
{
return Err(anyhow::anyhow!(
"on-chain database height is less than target height"
));
}

if on_chain_height == target_block_height
&& off_chain_height < target_block_height
{
return Err(anyhow::anyhow!(
"off-chain database height is less than target height"
));
}

if on_chain_height > target_block_height {
db.on_chain().rollback_last_block()?;
tracing::info!(
"Rolled back on-chain database to height {:?}",
on_chain_height.pred()
);
}

if off_chain_height > target_block_height {
db.off_chain().rollback_last_block()?;
tracing::info!(
"Rolled back off-chain database to height {:?}",
on_chain_height.pred()
);
}
}
Ok(())
}
26 changes: 18 additions & 8 deletions bin/fuel-core/src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ use fuel_core::{
Config,
DbType,
RelayerConsensusConfig,
ServiceTrait,
VMConfig,
},
txpool::{
Expand Down Expand Up @@ -445,7 +444,9 @@ impl Command {
}
}

pub fn get_service(command: Command) -> anyhow::Result<FuelService> {
pub fn get_service_with_shutdown_listeners(
command: Command,
) -> anyhow::Result<(FuelService, ShutdownListener)> {
#[cfg(feature = "rocksdb")]
if command.db_prune && command.database_path.exists() {
fuel_core::combined_database::CombinedDatabase::prune(&command.database_path)?;
Expand All @@ -463,33 +464,42 @@ pub fn get_service(command: Command) -> anyhow::Result<FuelService> {
// initialize the server
let combined_database = CombinedDatabase::from_config(&config.combined_db_config)?;

FuelService::new(combined_database, config)
let mut shutdown_listener = ShutdownListener::spawn();

Ok((
FuelService::new(combined_database, config, &mut shutdown_listener)?,
shutdown_listener,
))
}

pub fn get_service(command: Command) -> anyhow::Result<FuelService> {
let (service, _) = get_service_with_shutdown_listeners(command)?;
Ok(service)
}

pub async fn exec(command: Command) -> anyhow::Result<()> {
let service = get_service(command)?;
let (service, shutdown_listener) = get_service_with_shutdown_listeners(command)?;

let shutdown_listener = ShutdownListener::spawn();
// Genesis could take a long time depending on the snapshot size. Start needs to be
// interruptible by the shutdown_signal
tokio::select! {
result = service.start_and_await() => {
result?;
}
_ = shutdown_listener.wait_until_cancelled() => {
service.stop();
service.send_stop_signal();
}
}

// pause the main task while service is running
tokio::select! {
result = service.await_stop() => {
result = service.await_shutdown() => {
result?;
}
_ = shutdown_listener.wait_until_cancelled() => {}
}

service.stop_and_await().await?;
service.send_stop_signal_and_await_shutdown().await?;

Ok(())
}
Expand Down
Loading

0 comments on commit a2d8d2d

Please sign in to comment.