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

Extend CalculateNetworkFee with contract verification #3385

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

Conversation

shargon
Copy link
Member

@shargon shargon commented Jul 2, 2024

Description

Close #2805

Type of change

  • Optimization (the change is only an optimization)
  • Style (the change is only a code style for better maintenance or standard purpose)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • We don't have a unit test system that allow us to deploy a dummy contract for verify methods... I think that it should be tested locally

Test Configuration:

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

@shargon shargon marked this pull request as ready for review July 2, 2024 08:47
@shargon shargon changed the title Calculate fee Extend CalculateNetworkFee with contract verification Jul 2, 2024
src/Neo/Wallets/Helper.cs Outdated Show resolved Hide resolved
src/Neo/Wallets/Helper.cs Outdated Show resolved Hide resolved
script.Emit(OpCode.NEWMAP);
break;
default:
throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script.");
Copy link
Member

Choose a reason for hiding this comment

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

It differs slightly from NeoGo behaviour, we need to discuss it: NeoGo doesn't return error on unknown type; instead it tries to do the best and emit as much parameters as possible, skipping the others. Then "verify" method is processed in any case, and an error is returned only if "verify" doesn't return boolean. I think that this approach is good because it tries to do as much as possible without extra user's data.

Copy link
Member

@cschuchardt88 cschuchardt88 Jul 4, 2024

Choose a reason for hiding this comment

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

Yes you need to support contract as signer with verify function with custom parameters.

Copy link
Contributor

Choose a reason for hiding this comment

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

The only thing that is missing is Map and InteropInterface currently. For maps some zero value can be added (empty map), but for interop interfaces it's harder to say, maybe NULL.

Copy link
Member Author

Choose a reason for hiding this comment

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

The only thing that is missing is Map and InteropInterface currently. For maps some zero value can be added (empty map), but for interop interfaces it's harder to say, maybe NULL.

#3385 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, but then throwing an error is not the best way to handle this.

Copy link
Member Author

Choose a reason for hiding this comment

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

src/Neo/Wallets/Helper.cs Outdated Show resolved Hide resolved
Comment on lines 186 to 158
else
{
int size_inv = 66 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize();
networkFee += exec_fee_factor * MultiSignatureContractCost(m, n);
// Regular signature verification.
if (IsSignatureContract(witnessScript))
{
size += 67 + witnessScript.GetVarSize();
networkFee += exec_fee_factor * SignatureContractCost();
}
else if (IsMultiSigContract(witnessScript, out int m, out int n))
{
int size_inv = 66 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize();
networkFee += exec_fee_factor * MultiSignatureContractCost(m, n);
}
}
// We can support more contract types in the future.
Copy link
Member

Choose a reason for hiding this comment

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

It's a nice time to support custom verification contracts, it's a part of the issue. So we need to perform witness verification by executing scripts and record GAS consumed not only for contract-based witnesses, but also for unknown witness types. For example, Koblitz-based verification scripts (#3209) could be perfectly handled by running this custom verification script (with user-defined invocation script).

So e.g. in NeoGo we removed this part with signature/multisignature scripts fee calculation and use unified approach with witness script invocation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Currently should work as the same, this optimization (if it's faster) can come in a different PR

Copy link
Contributor

Choose a reason for hiding this comment

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

It's not faster, but it allows to remove some code and reduce cognitive overhead for developers.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, and BTW, running non-contract scripts (let's put default sig/multisig aside for a moment) is an important part of the deal. Koblitz scripts are the best check for this --- if you can handle them without any specific code you're good.

src/Neo/Wallets/Helper.cs Outdated Show resolved Hide resolved
script.Emit(OpCode.NEWMAP);
break;
default:
throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script.");
Copy link
Member

@cschuchardt88 cschuchardt88 Jul 4, 2024

Choose a reason for hiding this comment

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

Yes you need to support contract as signer with verify function with custom parameters.

@cschuchardt88 cschuchardt88 dismissed their stale review July 5, 2024 07:53

accidentally picked Changed Requested


// Check verify cost
using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: settings, gas: maxExecutionCost);
engine.LoadContract(contract, md, CallFlags.ReadOnly);
if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None);
if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault.");
if (!engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.Hash} returns false.");
_ = engine.Execute(); // https://github.com/neo-project/neo/issues/2805
Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
_ = engine.Execute(); // https://github.com/neo-project/neo/issues/2805
if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault.");
_ = engine.ResultStack.Pop().GetBoolean(); // The result must be convertible to boolean

Copy link
Member

Choose a reason for hiding this comment

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

Yes, that's right, but here we should add one more check that there's no extra items on stack.

Copy link
Member

Choose a reason for hiding this comment

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

Exactly like a standard verification code.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Copy link

Choose a reason for hiding this comment

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

but here we should add one more check that there's no extra items on stack.

FYI. That's a good point for users. But it's not the case the real verification process doing.
I'm OK with both versions. It's not a bad thing to have a strict check before submitting to the blockchain.

if (engine.Execute() == VMState.FAULT) return false;
if (!engine.ResultStack.Peek().GetBoolean()) return false;
fee = engine.FeeConsumed;

Copy link

@dusmart dusmart Jul 11, 2024

Choose a reason for hiding this comment

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

If we can reuse the VerifyWitness from neo/src/Neo/SmartContract/Helper.cs, it would be much better. If not, ignore me and let's keep going on.

@shargon
Copy link
Member Author

shargon commented Jul 23, 2024

@neo-project/core anything else?

cschuchardt88
cschuchardt88 previously approved these changes Jul 23, 2024
src/Neo/Wallets/Helper.cs Show resolved Hide resolved
Jim8y
Jim8y previously approved these changes Jul 30, 2024
Copy link
Contributor

@roman-khimov roman-khimov left a comment

Choose a reason for hiding this comment

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

It mostly looks OK, but more tests are needed to ensure it works correctly and the old code works is not broken somehow.

src/Neo/Wallets/Helper.cs Outdated Show resolved Hide resolved
script.Emit(OpCode.NEWMAP);
break;
default:
throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script.");
Copy link
Contributor

Choose a reason for hiding this comment

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

The only thing that is missing is Map and InteropInterface currently. For maps some zero value can be added (empty map), but for interop interfaces it's harder to say, maybe NULL.

src/Neo/Wallets/Helper.cs Outdated Show resolved Hide resolved
@shargon shargon dismissed stale reviews from Jim8y and cschuchardt88 via 3812a45 July 30, 2024 08:23
@Jim8y
Copy link
Contributor

Jim8y commented Jul 30, 2024

@shargon how about roman's comment: #3385 (comment)

@shargon
Copy link
Member Author

shargon commented Jul 30, 2024

@shargon how about roman's comment: #3385 (comment)

Map was before, and was removed because a review #3385 (comment)

@shargon
Copy link
Member Author

shargon commented Aug 1, 2024

merge?

@Jim8y
Copy link
Contributor

Jim8y commented Aug 1, 2024

is it possible to add some tests to demonstrate that your change achieves what you intended to? i will reapprove immediately if you can do it.

@roman-khimov
Copy link
Contributor

merge?

#3385 (comment)

Non-standard verification scripts are still not supported.

@shargon
Copy link
Member Author

shargon commented Aug 9, 2024

merge?

#3385 (comment)

Non-standard verification scripts are still not supported.

You mean without a contract?

@roman-khimov
Copy link
Contributor

Yes. Like in #3209.

@shargon
Copy link
Member Author

shargon commented Aug 9, 2024

Yes. Like in #3209.

It's done in neo-go? i can't find it

@AnnaShaleva
Copy link
Member

AnnaShaleva commented Aug 9, 2024

It's done in neo-go? i can't find it

Take a look at https://github.com/nspcc-dev/neo-go/blob/79e78980c4679e8929ceb509ed56359c5b017279/pkg/services/rpcsrv/server.go#L1001, we take signer's witness, infer invocation script (if needed) and don't change verification script. And hence, verification script is executed by s.chain.VerifyWitness "as is" without any changes even if it's not a contract/sig/multisig script.

@Jim8y
Copy link
Contributor

Jim8y commented Sep 5, 2024

It's done in neo-go? i can't find it

Take a look at https://github.com/nspcc-dev/neo-go/blob/79e78980c4679e8929ceb509ed56359c5b017279/pkg/services/rpcsrv/server.go#L1001, we take signer's witness, infer invocation script (if needed) and don't change verification script. And hence, verification script is executed by s.chain.VerifyWitness "as is" without any changes even if it's not a contract/sig/multisig script.

Any following work?

@shargon
Copy link
Member Author

shargon commented Sep 16, 2024

We may merge #3385 as it is now and move non-standard verification scripts support to another issue. I think it's acceptable way since #3385 contains everything that's required except non-standard verification scripts handling.

https://github.com/neo-project/neo/pull/3434/files#r1725538241

@cschuchardt88
Copy link
Member

Anyway you can do this #3481 (comment)

@shargon
Copy link
Member Author

shargon commented Sep 16, 2024

Anyway you can do this #3481 (comment)

in a different pr please, this is for a different thing

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.

Improve CalculateNetworkFee for contract signers
6 participants