5

Premise

  1. Substrate will, by default, store every event in a block in a storage value Event of type Vec<EventRecord> in the system module, which involves reading & writing this full vector each time, as explained here https://stackoverflow.com/questions/57219830/what-is-the-cost-of-event-storage-in-substrate.
  2. The time it takes to read/write to the database increases with the size of the object being read, at least eventually in expectation.

Problem

If an extrinsic emits an event with has some payload with some size, say like a user provided Vec<u8>, then an attacker can submit a series of such extrinsics, radically increasing the size of this vector, and as a result making the real computational weight of all subsequent extrinsics in that same block much greater than what is reported by their weight functions, and also no fee is paid for this computation. In essence all weight functions for those extrinsics are wrong now. This problem increases in severity independently with block weight/length limits, and the size of individual events, because both independently can be used to bloat the size of Events.

Unfortunately, it's also in practice not feasible to opt out of the default behavior in the System module, as there is no storage of logs or events in Substrate that can be exposed to the myriad of tools that need to inspect this, they all rely on storage state snapshots in archival nodes.

Questions

  1. Is this an accurate summary?
  2. How was it determined that this is already not a problem in Polkadot or Kusama?

2 Answers 2

4

Substrate will, by default, store every event in a block in a storage value Event of type Vec in the system module, which involves reading & writing this full vector each time, as explained here https://stackoverflow.com/questions/57219830/what-is-the-cost-of-event-storage-in-substrate.

You don't read the entire vector every time in the runtime. Instead we have this specialized append function. This takes the value on the host side, appends your new data and puts back the value.

With the old state trie implementation it could lead to problems for Parachains, because in the next block when we delete the events they could may end up in the storage proof. However, with the new state trie implementation state_version = 1 this isn't a problem any more.

The time it takes to read/write to the database increases with the size of the object being read, at least eventually in expectation.

These read/writes are also not done in the database. No write while executing a block in Substrate is ever going directly to the database. Writes are always being stored in the Overlay. Any further read of a value that was written will also be answered by the Overlay. As this Overlay is in memory the access to it is "free" when seen from the benchmarking perspective.

If an extrinsic emits an event with has some payload with some size, say like a user provided Vec, then an attacker can submit a series of such extrinsics, radically increasing the size of this vector, and as a result making the real computational weight of all subsequent extrinsics in that same block much greater than what is reported by their weight functions, and also no fee is paid for this computation. In essence all weight functions for those extrinsics are wrong now. This problem increases in severity independently with block weight/length limits, and the size of individual events, because both independently can be used to bloat the size of Events.

Given my statements above, the problem statement is not really true. However, you could start by only storing the hash of some data in the event. Why store the full data? The full data can be restored from the state if needed. Then appending an event should always be constant time. This mainly depends on the system allocator on how fast it can allocate memory and if allocating memory will take more time with more memory.

2
  • Thank you very much for this reply :) - I had indeed forgotten about the overlay :D - I would be interested in seeing how this special append function works, seems strange that it can work, as Vec::append presumably is ignorant of underlying data storage. Where should I look to learn more? - I did not understand how the cleverness of append is relevant to this not being a problem though, it seems the overlay is already fully solving the db io problem, where you just pointing that out to correct my mistaken assumption Aug 26, 2022 at 10:15
  • 1
    append can work because the encoding of any Vec<T> is Compact<u32> followed by all elements encoded. Where the Compact<u32> is the number of elements in the vector. append takes the encoded value, adds one to the length at the beginning of the encoded vector and appends the encoded value to the encoded vector representation. It is really simple. Nothing special.
    – bkchr
    Aug 27, 2022 at 22:09
0

If an extrinsic emits an event with has some payload with some size, say like a user provided Vec<u8>, then an attacker can submit a series of such extrinsics, radically increasing the size of this vector
...
How was it determined that this is already not a problem in Polkadot or Kusama?

Developers pay attention to never put user-provided unbound data directly into events.

It is smarter, as noted above, to only use hashes. This holds generally for all on-chain data; don't use the data itself but rather hashes wherever possible.
This keeps on-chain data small and cheap to scale.
If you still need to store blobs of data, you can use the preimage pallet.

2
  • Thank you for a reply. - The reason it is in an event is for easier off-chain indexing, as it gets hard to index extrinsics properly for other technical reasons: basically handling meta invocations from sudo, batch,... - Seems though that prior answer claims there is no penalty? Aug 26, 2022 at 10:17
  • There is no penalty for full nodes, but archive nodes need to store all events forever. Storage requirements would grow too fast if Polkadot would put everything in events, that is not what they were designed for. Aug 29, 2022 at 9:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.