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

Implement native Notary contract #3178

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open

Implement native Notary contract #3178

wants to merge 45 commits into from

Conversation

AnnaShaleva
Copy link
Member

@AnnaShaleva AnnaShaleva commented Mar 19, 2024

Description

Implement native Notary contract.

Close #2897.

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

  • Native Gas contract unit tests;
  • Native Notary contract unit tests.

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:

AnnaShaleva and others added 8 commits March 6, 2024 12:00
Close #2896. Use a stub for native Notary contract hash since this
contract is not implemented yet. Thus, technically, NotaryAssisted
attribute verification will always fail on real network until native
Notary is implemented.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
…ribute

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
Transactions network fee should be split between Primary node and Notary
nodes.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
Once Notary contract is implemented, this hash will be replaced by a
proper Notary contract hash. The exact value won't be changed since
Notary contract has constant hash as any other native contract.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
No functional changes, just a refactoring for better code readability.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
Close #2897. Depends on #3175.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
@vncoelho
Copy link
Member

Is this PR to be merged now or after next release?
I was planning to review current release and then merge these next feature.

@roman-khimov
Copy link
Contributor

Is this PR to be merged now or after next release?

https://github.com/neo-project/neo/milestone/2

@dusmart
Copy link

dusmart commented Mar 21, 2024

This contract has a Verify method which is very dangerous.

private bool Verify(ApplicationEngine engine, byte[] sig)

In this method, we verify that tx.Signers[1] has enough balance stored in the contract. Plus, we verify that a signature from any notary is provided.

Here is an attack vector. A malicious notary can sign as many transactions as it wants and then publish at most 500 txs on chain while tx.Signers[1] has a little balance only sufficient for one tx.

If it's not a malicious notary, someone could still get a chance to make the notary sign something multiple times and cache them then publish them at once.

By the way, do you know how many core modules will be affected by this notary feature? Is this the last PR?

Copy link
Member

@vncoelho vncoelho left a comment

Choose a reason for hiding this comment

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

I am not seeing the part of the code we discussed on Discord, @AnnaShaleva, you mentioned about a delegated powered notary signing.
"The contract itself don't send the transactions. It's a designated Notary node who is powered to send transactions on behalf of notary service users. "

I want to check that because it should be, at least, similar to the oracles design.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
No functional changes, just a refactoring.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
@AnnaShaleva
Copy link
Member Author

By the way, do you know how many core modules will be affected by this notary feature? Is this the last PR?

To be able to process existing NeoFS chains that use Notary - yes, it's the last PR. But ideally in future we'd like to implement P2PNotaryRequest payload with pool (ref. nspcc-dev/neo-go#1546) and Notary service plugin (ref. nspcc-dev/neo-go#1547). We'll add related issues to neo-project later.

"The contract itself don't send the transactions. It's a designated Notary node who is powered to send transactions on behalf of notary service users. "
I want to check that because it should be, at least, similar to the oracles design.

It's a part of Notary service plugin. See the example implementation in https://github.com/nspcc-dev/neo-go/blob/master/pkg/services/notary/notary.go.

Copy link
Contributor

@vang1ong7ang vang1ong7ang left a comment

Choose a reason for hiding this comment

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

as @dusmart said , verify method should be used with extreme caution. and is there a fork introduced?

@roman-khimov
Copy link
Contributor

verify method should be used with extreme caution

That's a valid concern, but there are multiple ways to handle it, we'll get to it after #3175 merge.

[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
private bool Verify(ApplicationEngine engine, byte[] sig)
{
Transaction tx = (Transaction)engine.ScriptContainer;
Copy link
Member

Choose a reason for hiding this comment

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

better to use as just in case we change ScriptContainer to something else like a block. Applications using ApplicationEngine will crash if the container is not Transaction type. Please change.

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 change ScriptContainer to something else like a block

It's impossible in this context. Contract verification always uses transaction as a script container, and if (for some reason) it's not true, then transaction will be FAULTed (which is the desired behaviour).

Copy link
Member

@cschuchardt88 cschuchardt88 Mar 6, 2025

Choose a reason for hiding this comment

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

Im asking is to

Transaction tx = engine.ScriptContainer as Transaction;
if (tx is null) return false;

No reason to crash. https://github.com/neo-project/proposals/blob/ff39b57eb67981b1ec7f0b244ec053aacdb7139a/nep-30.mediawiki#user-content-verify says it must return boolean, not crash and must check System.Runtime.GetTrigger

Security is number one priority

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not a security issue, but for better user experience we can do it. Fixed.

Copy link
Member

Choose a reason for hiding this comment

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

If it crashes it becomes caughtexception and someone can use trycatch and push true to the stack in a signature.

Wi1l-B0t
Wi1l-B0t previously approved these changes Mar 5, 2025
nFees += (long)nKeys + 1;
if (tx.Sender == Hash)
{
var payer = tx.Signers[1];
Copy link
Contributor

Choose a reason for hiding this comment

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

Won't it be out of range here?

Copy link
Member Author

Choose a reason for hiding this comment

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

It won't, it is guaranteed by Notary's Verify method.

Copy link
Member

@cschuchardt88 cschuchardt88 Mar 6, 2025

Choose a reason for hiding this comment

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

But if contrainer is null this will fail. Unless verify is fixed with #3178 (comment)

[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)]
private void SetMaxNotValidBeforeDelta(ApplicationEngine engine, uint value)
{
if (value > engine.ProtocolSettings.MaxValidUntilBlockIncrement / 2 || value < ProtocolSettings.Default.ValidatorsCount) throw new FormatException(string.Format("MaxNotValidBeforeDelta cannot be more than {0} or less than {1}", engine.ProtocolSettings.MaxValidUntilBlockIncrement / 2, ProtocolSettings.Default.ValidatorsCount));
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is too long.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@Wi1l-B0t Wi1l-B0t self-requested a review March 5, 2025 01:29
@Wi1l-B0t Wi1l-B0t dismissed their stale review March 5, 2025 01:30

Click on the wrong button

Comment on lines 102 to 103
Transaction tx = (Transaction)engine.ScriptContainer;
if (tx.GetAttribute<NotaryAssisted>() is null) return false;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Transaction tx = (Transaction)engine.ScriptContainer;
if (tx.GetAttribute<NotaryAssisted>() is null) return false;
var tx = engine.ScriptContainer as Transaction;
if (tx?.GetAttribute<NotaryAssisted>() is null) return false;

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you still think that it's needed? See #3178 (comment), please.

Copy link
Member

Choose a reason for hiding this comment

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

I think it's better, impossible, but don't hurt and compatible with both cases

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

shargon and others added 3 commits March 5, 2025 12:07
Co-authored-by: Will <201105916+Wi1l-B0t@users.noreply.github.com>
No functional changes.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
Comment on lines 137 to 138
var additionalParams = (Array)data;
if (additionalParams.Count() != 2) throw new FormatException("`data` parameter should be an array of 2 elements");
Copy link
Contributor

@Wi1l-B0t Wi1l-B0t Mar 6, 2025

Choose a reason for hiding this comment

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

OnNEP17Payment may be called by the contract.
So the data may not be an Array, and this line will throw an InvalidCastException if not an Array.
It's OK even if InvalidCastException?

Suggested change
var additionalParams = (Array)data;
if (additionalParams.Count() != 2) throw new FormatException("`data` parameter should be an array of 2 elements");
var additionalParams = data as Array;
if (additionalParams is null || additionalParams.Count() != 2)
throw new FormatException("`data` parameter should be an array of 2 elements");

Copy link
Member

Choose a reason for hiding this comment

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

This should be object type aka Any

Copy link
Member Author

Choose a reason for hiding this comment

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

OnNEP17Payment may be called by the contract.

It's impossible because several lines below we have a check that ensures that calling context is GasToken contract.

So the data may not be an Array

But even when the caller is GasToken, it's possible that data has an unexpected format, so you're right here.

It's OK even if InvalidCastException?

Yes, it's OK, the main point here is that exception will be thrown anyway and execution is aborted. But from user's PoW it's always nice to see a well-formatted exception message instead of just InvalidCastException, hence I've updated this code.

{
long nFees = 0;
ECPoint[] notaries = null;
foreach (Transaction tx in engine.PersistingBlock.Transactions)
Copy link
Member

Choose a reason for hiding this comment

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

We can check if PersistingBlock is null, neo express debugger fail with something similar in the past.

Copy link
Member Author

Choose a reason for hiding this comment

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

This particular part of the code is ported from

foreach (Transaction tx in engine.PersistingBlock.Transactions)

And we need to be unified with the behaviour. So we either fix it in both places or keep it as is.

{
var payer = tx.Signers[1];
var balance = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Deposit, payer.Account))?.GetInteroperable<Deposit>();
balance.Amount -= tx.SystemFee + tx.NetworkFee;
Copy link
Member

Choose a reason for hiding this comment

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

balance can be null

Copy link
Contributor

Choose a reason for hiding this comment

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

Theoretically it's not much different from GAS contract being unable to write the fee off the sender balance, valid blocks shouldn't allow for that.

Copy link
Member

@cschuchardt88 cschuchardt88 Mar 6, 2025

Choose a reason for hiding this comment

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

Anyone can call ApplicationEngine with whatever parameters they want. This needs to be fixed. This will crash testing environments. Safely is number one priority.

Copy link
Member Author

Choose a reason for hiding this comment

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

It can't. For a single transaction it is guaranteed by verify method of this contract:

if (balance is null || balance.Amount.CompareTo(tx.NetworkFee + tx.SystemFee) < 0) return false;

For multiple transactions in block it will be guaranteed by the mempool extension (not a part of this PR).

Copy link
Member

@cschuchardt88 cschuchardt88 Mar 6, 2025

Choose a reason for hiding this comment

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

verify is a CaughtException. So catch it then what?

Throw FormatException instead of InvalidCastException on invalid data
passed to OnNEP17Payment.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
Return `false` in case if execution container for `verify` is not a
transaction.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
@AnnaShaleva AnnaShaleva requested review from shargon and Wi1l-B0t March 6, 2025 16:07
Comment on lines 174 to 175
if (deposit is null) return false;
if (till < deposit.Till) return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (deposit is null) return false;
if (till < deposit.Till) return false;
if (deposit is null || till < deposit.Till) return false;

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

{
/// <summary>
/// A default value for maximum allowed NotValidBeforeDelta. It is set to be
/// 20 rounds for 7 validators, a little more than half an hour for 15-seconds blocks.
Copy link
Contributor

Choose a reason for hiding this comment

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

A block will be 3 seconds. Does this const value need to be adjusted?

Copy link
Member Author

Choose a reason for hiding this comment

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

Let's keep the default value as it as is for now, it's just a default. And then we'll customize it for different networks, e.g. Mainnet is still running with 15-seconds blocks and NeoFS networks are running with 1-second blocks.

cschuchardt88 and others added 2 commits March 10, 2025 21:11
No functional changes, just a refactoring.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
@AnnaShaleva AnnaShaleva requested a review from Wi1l-B0t March 11, 2025 16:39
@AnnaShaleva
Copy link
Member Author

@neo-project/core review, please.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Notary native contract
10 participants