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

SmartContract: restrict the number of allowed notifications #3548

Open
wants to merge 7 commits into
base: HF_Echidna
Choose a base branch
from

Conversation

AnnaShaleva
Copy link
Member

Description

The problem is described in nspcc-dev/neo-go#3490. In short words, an arbitrary number of smart contract notifications is allowed (up to VM's GasLimit and MaxBlockSystemFee, but it's huge), which results in a possibility to DoS the node. 10K of large notifications emitted in a single transaction result in block acceptance delays for both Go and C# nodes. 50K of large notifications may result in the node failure. I did these tests on privnet, you may find the results in nspcc-dev/neo-go#3490 (comment). Please, don't try to DoS mainnet/testnet using this vulnerability.

Port the solution from nspcc-dev/neo-go#3640, but an alternative solution is described in nspcc-dev/neo-go#3640 (comment) and in nspcc-dev/neo-go#3490 (comment). MaxNotificationsCount constraint is chosen based on the Mainnet statistics of the number of notifications per every transaction, ref. nspcc-dev/neo-go#3490 (comment).

This PR is a subject of discussion, hence please, share your thoughts on the described problem and proposed solution. Once we agree on the solution, I'll add unit-tests to this PR.

Type of change

  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

How Has This Been Tested?

  • Unit-tests should be added once we agree on solution.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Fix the problem described in
nspcc-dev/neo-go#3490. Port the solution from
nspcc-dev/neo-go#3640.

MaxNotificationsCount constraint is chosen based on the Mainnet
statistics of the number of notifications per every transaction, ref.
nspcc-dev/neo-go#3490 (comment).

Signed-off-by: Anna Shaleva <[email protected]>
Signed-off-by: Anna Shaleva <[email protected]>
@cschuchardt88
Copy link
Member

This needs to be in VM limit class.

@@ -78,7 +78,7 @@ private void DesignateAsRole(ApplicationEngine engine, Role role, ECPoint[] node
list.AddRange(nodes);
list.Sort();
engine.SnapshotCache.Add(key, new StorageItem(list));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert

Copy link
Member Author

@AnnaShaleva AnnaShaleva Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting job is failing without this change.

@AnnaShaleva
Copy link
Member Author

This needs to be in VM limit class.

Strictly speaking, it's not a VM limit. Notifications are not a part of VM, they are a part of execution engine.

// Restrict the number of notifications for Application executions. Do not check
// persisting triggers to avoid native persist failure. Do not check verification
// trigger since verification context is loaded with ReadOnly flag.
if (IsHardforkEnabled(Hardfork.HF_Echidna) && Trigger == TriggerType.Application && notifications.Count == MaxNotificationCount)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, we just need to ensure .Count is correct calculated at this step.

Copy link
Member

@cschuchardt88 cschuchardt88 Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is trigger really needed? Limits shouldn't have a trigger.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's needed, because we can't allow OnPersist and PostPersist failures (imagine that in future MaxNotificationCount may be reduced to some lower value). And for Verification trigger this check is useless by design, no notifications are possible with this trigger.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cschuchardt88
Copy link
Member

cschuchardt88 commented Oct 23, 2024

This needs to be in VM limit class.

Strictly speaking, it's not a VM limit. Notifications are not a part of VM, they are a part of execution engine.

All the same thing. Class is called ExecutionEngineLimits in Neo.VM namespace. How to use engine.Limit.MaxNotification

@Jim8y
Copy link
Contributor

Jim8y commented Oct 24, 2024

instead of limiting number of notifactions, i prefer to make the price related to the notifaction size. From @AnnaShaleva 's excellent benchmark, i think size * number is the real reason. Cause i think its a normal practice contract may need to send a lof of notifactions, but definately abnormal to send large notifactions.

@AnnaShaleva
Copy link
Member Author

AnnaShaleva commented Oct 24, 2024

i think size * number is the real reason

Exactly, as described in nspcc-dev/neo-go#3490 (comment).

i prefer to make the price related to the notifaction size.

Yes, it's an alternative solution proposed in nspcc-dev/neo-go#3490 (comment). But we need to discuss it because even with extremely expensive notifications there's a chance that CNs will be killed by a single transaction. Yes, the user will have to pay for it, but CNs may not be able to process this transaction. Also, large number of notifications is harmful for both C# and Go RPC servers.

So to me, the restriction of the overall notifications number is required, but at the same time it can be combined with dynamic price solution.

Copy link
Member

@shargon shargon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @Jim8y pay extra per notification size

@roman-khimov
Copy link
Contributor

Economics.

Currently System.Runtime.Notify is priced at 32768×ExecutionFeeFactor, for mainnet that's roughly 98K datoshis.

Storage fee price is 10K datoshis per byte for mainnet.

Typical NEP-17 Transfer is ~50 bytes. So storing it on-chain is 500K datoshis. Emitting as an event is 98K. Seems to be fair enough, roughly 2K per byte or five times cheaper. If we're to extend this multiplier to 1024-byte event that'd be 2M datoshis or 0.02048 GAS. Multiply it over 50K events and you get a 1024 GAS cost. Which is 4-5K USD at the moment and is allowed by MaxBlockSystemFee (#3552). So currently this economics doesn't really work to solve the problem.

Based on nspcc-dev/neo-go#3490 even 10K are problematic (204.8 GAS with 2K per byte), so we better target at 2-5K as being practically impossible. Which doesn't really work even with 10K per byte since 10K events would cost the same 1024 GAS then. (side note: @steven1227, these calculations are also relevant for any fee reduction talks)

Not sure we can balance this economically.

@shargon
Copy link
Member

shargon commented Oct 24, 2024

Which doesn't really work even with 10K per byte since 10K events wo

More than X notifications (100?), can be priced exponentially by the count.

@roman-khimov
Copy link
Contributor

priced exponentially by the count.

Yeah, I had this thought as well, but you can easily avoid it with some number of transactions. Maybe they'd be a little less problematic than a single one, but still.

/// <summary>
/// The maximum number of notifications per application execution.
/// </summary>
public const int MaxNotificationCount = 512;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ExecutionEngineLimits Class engine.Limits.MaxNotificationCount

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notifications is a syscall, outside from VM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its still an Engine Limitation, just cause the VM is splitted into two libraries shouldn't make it be to separate things.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have MaxNotificationSize and MaxEventName declared in the same class as MaxNotificationCount, these three constants must be kept together.

@Jim8y Jim8y mentioned this pull request Nov 19, 2024
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Initial issue state - proposed but not yet accepted Hardfork Waiting for Review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants