Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate WASM on txpool level before allowing upgrade tx #2080

Merged
merged 27 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5d24e71
Validate WASM on txpool level before allowing upgrade tx
Dentosal Aug 14, 2024
feeb9a7
Merge branch 'master' into dento/validate-wasm-before-upgrade
Dentosal Aug 14, 2024
ffa7b9f
Add changelog entry
Dentosal Aug 14, 2024
85d91c9
Add more granular error check
Dentosal Aug 14, 2024
a166082
Use separate errors for different cases
Dentosal Aug 14, 2024
a56b1cd
Naming fix
Dentosal Aug 14, 2024
7eb7c6e
fmt
Dentosal Aug 14, 2024
71744b6
Address PR feedback
Dentosal Aug 14, 2024
420cc8b
Merge branch 'master' into dento/validate-wasm-before-upgrade
Dentosal Aug 14, 2024
45eb7fd
Add a todo comment
Dentosal Aug 14, 2024
030ceae
Add one unit test as well
Dentosal Aug 14, 2024
957179f
Remove redundant clone
Dentosal Aug 14, 2024
aeb6d26
no-default-features fix
Dentosal Aug 14, 2024
5d8edfc
Hide "wasm not enabled" behind a feature gate as much as possible
Dentosal Aug 15, 2024
f743c12
Cargo sort
Dentosal Aug 15, 2024
7cd76dc
Fix some cfg-related issues
Dentosal Aug 15, 2024
4c107ad
Merge branch 'master' into dento/validate-wasm-before-upgrade
Dentosal Aug 15, 2024
ed9169d
Move UpgradableError to fuel-core-upgradable-executor
Dentosal Aug 15, 2024
4f15617
Make IncompleteUploadedBytecode a distinct error type
Dentosal Aug 15, 2024
ff98366
Handle UpgradableError -> ExecutorError conversion inside get_module
Dentosal Aug 15, 2024
0f4b187
Fix deps for no-default-features
Dentosal Aug 15, 2024
49afa9a
Cleanup
Dentosal Aug 15, 2024
426dfb2
more cfg fixes
Dentosal Aug 15, 2024
1d3f677
Add serde dep
Dentosal Aug 15, 2024
556ce0b
Depend on serde only when needed
Dentosal Aug 15, 2024
61cf7df
Merge branch 'master' into dento/validate-wasm-before-upgrade
Dentosal Aug 15, 2024
6d9309a
Address PR comments
Dentosal Aug 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed
-[2076](https://github.com/FuelLabs/fuel-core/pull/2076): Replace usages of `iter_all` with `iter_all_keys` where necessary.

### Breaking
-[2080](https://github.com/FuelLabs/fuel-core/pull/2080): Reject Upgrade txs with invalid wasm on txpool level.

## [Version 0.32.1]

### Added
Expand Down
Binary file not shown.
33 changes: 33 additions & 0 deletions crates/fuel-core/src/service/adapters/block_importer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,17 @@ use fuel_core_storage::{
Result as StorageResult,
StorageAsRef,
};
use fuel_core_txpool::ports::{
WasmChecker,
WasmValidityError,
};
use fuel_core_types::{
blockchain::{
block::Block,
consensus::Consensus,
SealedBlock,
},
fuel_tx::Bytes32,
fuel_types::{
BlockHeight,
ChainId,
Expand Down Expand Up @@ -106,3 +111,31 @@ impl Validator for ExecutorAdapter {
self.executor.validate(block)
}
}

#[cfg(feature = "wasm-executor")]
impl WasmChecker for ExecutorAdapter {
fn validate_uploaded_wasm(
&self,
wasm_root: &Bytes32,
) -> Result<(), WasmValidityError> {
self.executor
.validate_uploaded_wasm(wasm_root)
.map_err(|err| match err {
fuel_core_types::services::executor::UpgradableError::InvalidWasm(
_,
_,
) => WasmValidityError::NotValid,
_ => WasmValidityError::NotFound,
})
}
}

#[cfg(not(feature = "wasm-executor"))]
impl WasmChecker for ExecutorAdapter {
fn validate_uploaded_wasm(
&self,
_wasm_root: &Bytes32,
) -> Result<(), WasmValidityError> {
Err(WasmValidityError::NotEnabled)
}
}
2 changes: 2 additions & 0 deletions crates/fuel-core/src/service/sub_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub type P2PService = fuel_core_p2p::service::Service<Database>;
pub type TxPoolSharedState = fuel_core_txpool::service::SharedState<
P2PAdapter,
Database,
ExecutorAdapter,
FuelGasPriceProvider<Algorithm>,
ConsensusParametersProvider,
SharedMemoryPool,
Expand Down Expand Up @@ -206,6 +207,7 @@ pub fn init_sub_services(
database.on_chain().clone(),
importer_adapter.clone(),
p2p_adapter.clone(),
executor.clone(),
last_height,
gas_price_provider.clone(),
consensus_parameters_provider.clone(),
Expand Down
18 changes: 18 additions & 0 deletions crates/services/txpool/src/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use fuel_core_types::{
relayer::message::Message,
},
fuel_tx::{
Bytes32,
ConsensusParameters,
Transaction,
UtxoId,
Expand Down Expand Up @@ -65,6 +66,23 @@ pub trait TxPoolDb: Send + Sync {
fn message(&self, message_id: &Nonce) -> StorageResult<Option<Message>>;
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WasmValidityError {
/// Wasm support is not enabled.
NotEnabled,
/// The supposedly-uploaded wasm was not found.
NotFound,
/// The uploaded bytecode was found but it's is not valid wasm.
NotValid,
}

pub trait WasmChecker {
fn validate_uploaded_wasm(
&self,
wasm_root: &Bytes32,
) -> Result<(), WasmValidityError>;
}

#[async_trait::async_trait]
/// Trait for getting gas price for the Tx Pool code to look up the gas price for a given block height
pub trait GasPriceProvider {
Expand Down
91 changes: 69 additions & 22 deletions crates/services/txpool/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ use crate::{
MemoryPool,
PeerToPeer,
TxPoolDb,
WasmChecker as WasmCheckerConstraint,
},
transaction_selector::select_transactions,
txpool::{
Expand All @@ -75,7 +76,7 @@ use self::update_sender::{

mod update_sender;

pub type Service<P2P, DB, GP, CP, MP> = ServiceRunner<Task<P2P, DB, GP, CP, MP>>;
pub type Service<P2P, DB, WC, GP, CP, MP> = ServiceRunner<Task<P2P, DB, WC, GP, CP, MP>>;

#[derive(Clone)]
pub struct TxStatusChange {
Expand Down Expand Up @@ -123,9 +124,16 @@ impl TxStatusChange {
}
}

pub struct SharedState<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP> {
pub struct SharedState<
P2P,
ViewProvider,
WasmChecker,
GasPriceProvider,
ConsensusProvider,
MP,
> {
tx_status_sender: TxStatusChange,
txpool: Arc<ParkingMutex<TxPool<ViewProvider>>>,
txpool: Arc<ParkingMutex<TxPool<ViewProvider, WasmChecker>>>,
p2p: Arc<P2P>,
utxo_validation: bool,
current_height: Arc<ParkingMutex<BlockHeight>>,
Expand All @@ -134,8 +142,15 @@ pub struct SharedState<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, M
memory_pool: Arc<MP>,
}

impl<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP> Clone
for SharedState<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>
impl<P2P, ViewProvider, GasPriceProvider, WasmChecker, ConsensusProvider, MP> Clone
for SharedState<
P2P,
ViewProvider,
WasmChecker,
GasPriceProvider,
ConsensusProvider,
MP,
>
{
fn clone(&self) -> Self {
Self {
Expand All @@ -151,30 +166,45 @@ impl<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP> Clone
}
}

pub struct Task<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP> {
pub struct Task<P2P, ViewProvider, WasmChecker, GasPriceProvider, ConsensusProvider, MP> {
gossiped_tx_stream: BoxStream<TransactionGossipData>,
committed_block_stream: BoxStream<SharedImportResult>,
tx_pool_shared_state:
SharedState<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>,
tx_pool_shared_state: SharedState<
P2P,
ViewProvider,
WasmChecker,
GasPriceProvider,
ConsensusProvider,
MP,
>,
ttl_timer: tokio::time::Interval,
}

#[async_trait::async_trait]
impl<P2P, ViewProvider, View, GasPriceProvider, ConsensusProvider, MP> RunnableService
for Task<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>
impl<P2P, ViewProvider, View, WasmChecker, GasPriceProvider, ConsensusProvider, MP>
RunnableService
for Task<P2P, ViewProvider, WasmChecker, GasPriceProvider, ConsensusProvider, MP>
where
P2P: PeerToPeer<GossipedTransaction = TransactionGossipData>,
ViewProvider: AtomicView<LatestView = View>,
View: TxPoolDb,
WasmChecker: WasmCheckerConstraint + Send + Sync,
GasPriceProvider: GasPriceProviderConstraint + Send + Sync,
ConsensusProvider: ConsensusParametersProvider + Send + Sync,
MP: MemoryPool + Send + Sync,
{
const NAME: &'static str = "TxPool";

type SharedData =
SharedState<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>;
type Task = Task<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>;
type SharedData = SharedState<
P2P,
ViewProvider,
WasmChecker,
GasPriceProvider,
ConsensusProvider,
MP,
>;
type Task =
Task<P2P, ViewProvider, WasmChecker, GasPriceProvider, ConsensusProvider, MP>;
type TaskParams = ();

fn shared_data(&self) -> Self::SharedData {
Expand All @@ -192,12 +222,14 @@ where
}

#[async_trait::async_trait]
impl<P2P, ViewProvider, View, GasPriceProvider, ConsensusProvider, MP> RunnableTask
for Task<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>
impl<P2P, ViewProvider, View, WasmChecker, GasPriceProvider, ConsensusProvider, MP>
RunnableTask
for Task<P2P, ViewProvider, WasmChecker, GasPriceProvider, ConsensusProvider, MP>
where
P2P: PeerToPeer<GossipedTransaction = TransactionGossipData>,
ViewProvider: AtomicView<LatestView = View>,
View: TxPoolDb,
WasmChecker: WasmCheckerConstraint + Send + Sync,
GasPriceProvider: GasPriceProviderConstraint + Send + Sync,
ConsensusProvider: ConsensusParametersProvider + Send + Sync,
MP: MemoryPool + Send + Sync,
Expand Down Expand Up @@ -320,8 +352,8 @@ where
// Instead, `fuel-core` can create a `DatabaseWithTxPool` that aggregates `TxPool` and
// storage `Database` together. GraphQL will retrieve data from this `DatabaseWithTxPool` via
// `StorageInspect` trait.
impl<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>
SharedState<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>
impl<P2P, ViewProvider, WasmChecker, GasPriceProvider, ConsensusProvider, MP>
SharedState<P2P, ViewProvider, WasmChecker, GasPriceProvider, ConsensusProvider, MP>
{
pub fn pending_number(&self) -> usize {
self.txpool.lock().pending_number()
Expand Down Expand Up @@ -383,12 +415,13 @@ impl<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>
}
}

impl<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, View, MP>
SharedState<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>
impl<P2P, ViewProvider, WasmChecker, GasPriceProvider, ConsensusProvider, View, MP>
SharedState<P2P, ViewProvider, WasmChecker, GasPriceProvider, ConsensusProvider, MP>
where
P2P: PeerToPeer<GossipedTransaction = TransactionGossipData>,
ViewProvider: AtomicView<LatestView = View>,
View: TxPoolDb,
WasmChecker: WasmCheckerConstraint + Send + Sync,
GasPriceProvider: GasPriceProviderConstraint + Send + Sync,
ConsensusProvider: ConsensusParametersProvider,
MP: MemoryPool + Send + Sync,
Expand Down Expand Up @@ -493,21 +526,31 @@ pub enum TxStatusMessage {
}

#[allow(clippy::too_many_arguments)]
pub fn new_service<P2P, Importer, ViewProvider, GasPriceProvider, ConsensusProvider, MP>(
pub fn new_service<
P2P,
Importer,
ViewProvider,
WasmChecker,
GasPriceProvider,
ConsensusProvider,
MP,
>(
config: Config,
provider: ViewProvider,
importer: Importer,
p2p: P2P,
wasm_checker: WasmChecker,
current_height: BlockHeight,
gas_price_provider: GasPriceProvider,
consensus_parameters_provider: ConsensusProvider,
memory_pool: MP,
) -> Service<P2P, ViewProvider, GasPriceProvider, ConsensusProvider, MP>
) -> Service<P2P, ViewProvider, WasmChecker, GasPriceProvider, ConsensusProvider, MP>
where
Importer: BlockImporter,
P2P: PeerToPeer<GossipedTransaction = TransactionGossipData> + 'static,
ViewProvider: AtomicView,
ViewProvider::LatestView: TxPoolDb,
WasmChecker: WasmCheckerConstraint + Send + Sync,
GasPriceProvider: GasPriceProviderConstraint + Send + Sync,
ConsensusProvider: ConsensusParametersProvider + Send + Sync,
MP: MemoryPool + Send + Sync,
Expand All @@ -518,7 +561,11 @@ where
let mut ttl_timer = tokio::time::interval(config.transaction_ttl);
ttl_timer.set_missed_tick_behavior(MissedTickBehavior::Skip);
let number_of_active_subscription = config.number_of_active_subscription;
let txpool = Arc::new(ParkingMutex::new(TxPool::new(config.clone(), provider)));
let txpool = Arc::new(ParkingMutex::new(TxPool::new(
config.clone(),
provider,
wasm_checker,
)));
let task = Task {
gossiped_tx_stream,
committed_block_stream,
Expand Down
4 changes: 4 additions & 0 deletions crates/services/txpool/src/service/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
BlockImporter,
MockConsensusParametersProvider,
},
test_helpers::MockWasmChecker,
types::GasPrice,
MockDb,
};
Expand Down Expand Up @@ -52,6 +53,7 @@ pub struct TestContext {
pub(crate) service: Service<
MockP2P,
MockDBProvider,
MockWasmChecker,
MockTxPoolGasPrice,
MockConsensusParametersProvider,
DummyPool,
Expand Down Expand Up @@ -96,6 +98,7 @@ impl TestContext {
) -> &Service<
MockP2P,
MockDBProvider,
MockWasmChecker,
MockTxPoolGasPrice,
MockConsensusParametersProvider,
DummyPool,
Expand Down Expand Up @@ -272,6 +275,7 @@ impl TestContextBuilder {
MockDBProvider(mock_db.clone()),
importer,
p2p,
MockWasmChecker { result: Ok(()) },
Default::default(),
gas_price_provider,
consensus_parameters_provider,
Expand Down
Loading
Loading