Przeglądaj źródła

[validator/verification] Reject massive txs

Add a check in verify_transaction() to reject transactions that have a
number of calls greater than the GAS_LIMIT defined in the runtime.

This prevents a potential denial-of-service vector where an attacker
could submit extremely large transactions that are guaranteed to revert
but still tie up system resources before the revert happens.
y 2 lat temu
rodzic
commit
a0636984e4
2 zmienionych plików z 17 dodań i 3 usunięć
  1. 1 1
      src/runtime/vm_runtime.rs
  2. 16 2
      src/validator/verification.rs

+ 1 - 1
src/runtime/vm_runtime.rs

@@ -45,7 +45,7 @@ use crate::{
 const MEMORY: &str = "memory";
 
 /// Gas limit for a contract
-const GAS_LIMIT: u64 = 400_000_000;
+pub const GAS_LIMIT: u64 = 400_000_000;
 
 #[derive(Clone, Copy, PartialEq)]
 pub enum ContractSection {

+ 16 - 2
src/validator/verification.rs

@@ -204,12 +204,11 @@ pub async fn verify_producer_transaction(
     let tx_hash = tx.hash()?;
     debug!(target: "validator::verification::verify_producer_transaction", "Validating proposal transaction {}", tx_hash);
 
-    // Producer transactions must contain a single, non-empty call 
+    // Producer transactions must contain a single, non-empty call
     if tx.calls.len() != 1 || tx.calls[0].data.is_empty() {
         return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
     }
 
-
     // Verify call based on version
     let call = &tx.calls[0];
     match block_version {
@@ -337,6 +336,21 @@ pub async fn verify_transaction(
     let tx_hash = tx.hash()?;
     debug!(target: "validator::verification::verify_transaction", "Validating transaction {}", tx_hash);
 
+    // Ensure that the number of transaction calls does not exceed the
+    // maximum limit.
+    // As of now, each opcode is worth 1 gas. Each tx call will contain at
+    // least one opcode. Therefore, if a transaction has a number of calls
+    // that exceeds the GAS_LIMIT (defined by the [`Runtime`]), it is
+    // guaranteed to run out gas and ultimately fail.
+    // Instead of wasting time executing all those calls, we can reject
+    // the transaction early.
+    // Note: this limit could be reduced as each call is likely to contain
+    // much more than one opcode.
+    if tx.calls.len() as u64 > GAS_LIMIT {
+        error!(target: "validator::verification::verify_transaction", "Transaction contains too many calls");
+        return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
+    }
+
     // Table of public inputs used for ZK proof verification
     let mut zkp_table = vec![];
     // Table of public keys used for signature verification