From 37a028b5656a6b3510110c1003ef87a7f6e07a6b Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Sun, 18 Feb 2024 11:37:41 +0100 Subject: [PATCH 1/4] Decouple calculate fee from Wallet --- src/Neo/Wallets/Helper.cs | 94 +++++++++++++++++++++++++++++++++++++++ src/Neo/Wallets/Wallet.cs | 89 +----------------------------------- 2 files changed, 95 insertions(+), 88 deletions(-) diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index cced44aa9d..95a2d67902 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -13,7 +13,12 @@ using Neo.IO; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; using System; +using static Neo.SmartContract.Helper; namespace Neo.Wallets { @@ -72,5 +77,94 @@ internal static byte[] XOR(byte[] x, byte[] y) r[i] = (byte)(x[i] ^ y[i]); return r; } + + /// + /// Calculates the network fee for the specified transaction. + /// + /// The snapshot used to read data. + /// The transaction to calculate. + /// Thr protocol settings to use. + /// Function to retrive the script's account from a hash. + /// The network fee of the transaction. + public static long CalculateNetworkFee(DataCache snapshot, Transaction tx, ProtocolSettings settings, Func accountScript) + { + UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); + + // base size for transaction: includes const_header + signers + attributes + script + hashes + long maxExecutionCost = ApplicationEngine.TestModeGas; + int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); + uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot); + long networkFee = 0; + int index = -1; + foreach (UInt160 hash in hashes) + { + index++; + byte[] witnessScript = accountScript(hash); + byte[] invocationScript = null; + + if (tx.Witnesses != null) + { + if (witnessScript is null) + { + // Try to find the script in the witnesses + Witness witness = tx.Witnesses[index]; + witnessScript = witness?.VerificationScript.ToArray(); + + if (witnessScript is null || witnessScript.Length == 0) + { + // Then it's a contract-based witness, so try to get the corresponding invocation script for it + invocationScript = witness?.InvocationScript.ToArray(); + } + } + } + + if (witnessScript is null || witnessScript.Length == 0) + { + var contract = NativeContract.ContractManagement.GetContract(snapshot, hash); + if (contract is null) + throw new ArgumentException($"The smart contract or address {hash} is not found"); + var md = contract.Manifest.Abi.GetMethod("verify", -1); + if (md is null) + throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method"); + if (md.ReturnType != ContractParameterType.Boolean) + throw new ArgumentException("The verify method doesn't return boolean value."); + if (md.Parameters.Length > 0 && invocationScript is null) + throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script."); + + // Empty verification and non-empty invocation scripts + var invSize = invocationScript?.GetVarSize() ?? Array.Empty().GetVarSize(); + size += Array.Empty().GetVarSize() + invSize; + + // 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."); + + maxExecutionCost -= engine.GasConsumed; + if (maxExecutionCost <= 0) throw new InvalidOperationException("Insufficient GAS."); + networkFee += engine.GasConsumed; + } + else 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. + } + networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot); + foreach (TransactionAttribute attr in tx.Attributes) + { + networkFee += attr.CalculateNetworkFee(snapshot, tx); + } + return networkFee; + } } } diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs index ecffe85366..ab29aaad24 100644 --- a/src/Neo/Wallets/Wallet.cs +++ b/src/Neo/Wallets/Wallet.cs @@ -578,99 +578,12 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory scr tx.SystemFee = engine.GasConsumed; } - tx.NetworkFee = CalculateNetworkFee(snapshot, tx, maxGas); + tx.NetworkFee = CalculateNetworkFee(snapshot, tx, maxGas, ProtocolSettings, (a) => GetAccount(a)?.Contract?.Script); if (value >= tx.SystemFee + tx.NetworkFee) return tx; } throw new InvalidOperationException("Insufficient GAS"); } - /// - /// Calculates the network fee for the specified transaction. - /// - /// The snapshot used to read data. - /// The transaction to calculate. - /// The maximum cost that can be spent when a contract is executed. - /// The network fee of the transaction. - public long CalculateNetworkFee(DataCache snapshot, Transaction tx, long maxExecutionCost = ApplicationEngine.TestModeGas) - { - UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); - - // base size for transaction: includes const_header + signers + attributes + script + hashes - int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); - uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot); - long networkFee = 0; - int index = -1; - foreach (UInt160 hash in hashes) - { - index++; - byte[] witness_script = GetAccount(hash)?.Contract?.Script; - byte[] invocationScript = null; - - if (tx.Witnesses != null) - { - if (witness_script is null) - { - // Try to find the script in the witnesses - Witness witness = tx.Witnesses[index]; - witness_script = witness?.VerificationScript.ToArray(); - - if (witness_script is null || witness_script.Length == 0) - { - // Then it's a contract-based witness, so try to get the corresponding invocation script for it - invocationScript = witness?.InvocationScript.ToArray(); - } - } - } - - if (witness_script is null || witness_script.Length == 0) - { - var contract = NativeContract.ContractManagement.GetContract(snapshot, hash); - if (contract is null) - throw new ArgumentException($"The smart contract or address {hash} is not found"); - var md = contract.Manifest.Abi.GetMethod("verify", -1); - if (md is null) - throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method"); - if (md.ReturnType != ContractParameterType.Boolean) - throw new ArgumentException("The verify method doesn't return boolean value."); - if (md.Parameters.Length > 0 && invocationScript is null) - throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script."); - - // Empty verification and non-empty invocation scripts - var invSize = invocationScript?.GetVarSize() ?? Array.Empty().GetVarSize(); - size += Array.Empty().GetVarSize() + invSize; - - // Check verify cost - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: ProtocolSettings, 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."); - - maxExecutionCost -= engine.GasConsumed; - if (maxExecutionCost <= 0) throw new InvalidOperationException("Insufficient GAS."); - networkFee += engine.GasConsumed; - } - else if (IsSignatureContract(witness_script)) - { - size += 67 + witness_script.GetVarSize(); - networkFee += exec_fee_factor * SignatureContractCost(); - } - else if (IsMultiSigContract(witness_script, out int m, out int n)) - { - int size_inv = 66 * m; - size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize(); - networkFee += exec_fee_factor * MultiSignatureContractCost(m, n); - } - // We can support more contract types in the future. - } - networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot); - foreach (TransactionAttribute attr in tx.Attributes) - { - networkFee += attr.CalculateNetworkFee(snapshot, tx); - } - return networkFee; - } - /// /// Signs the in the specified with the wallet. /// From 08dbb7e85a7993e3fb91046664a24710980d9c98 Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Sun, 18 Feb 2024 11:39:36 +0100 Subject: [PATCH 2/4] clean iff --- src/Neo/Wallets/Helper.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index 95a2d67902..182045f63d 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -102,19 +102,16 @@ public static long CalculateNetworkFee(DataCache snapshot, Transaction tx, Proto byte[] witnessScript = accountScript(hash); byte[] invocationScript = null; - if (tx.Witnesses != null) + if (tx.Witnesses != null && witnessScript is null) { - if (witnessScript is null) - { - // Try to find the script in the witnesses - Witness witness = tx.Witnesses[index]; - witnessScript = witness?.VerificationScript.ToArray(); + // Try to find the script in the witnesses + Witness witness = tx.Witnesses[index]; + witnessScript = witness?.VerificationScript.ToArray(); - if (witnessScript is null || witnessScript.Length == 0) - { - // Then it's a contract-based witness, so try to get the corresponding invocation script for it - invocationScript = witness?.InvocationScript.ToArray(); - } + if (witnessScript is null || witnessScript.Length == 0) + { + // Then it's a contract-based witness, so try to get the corresponding invocation script for it + invocationScript = witness?.InvocationScript.ToArray(); } } From d174346e70b099ea2bb6f54e21ec8e700a98d3cf Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Sun, 18 Feb 2024 11:42:24 +0100 Subject: [PATCH 3/4] Convert into extension --- src/Neo/Wallets/Helper.cs | 6 +++--- src/Neo/Wallets/Wallet.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index 182045f63d..584de970ef 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -81,17 +81,17 @@ internal static byte[] XOR(byte[] x, byte[] y) /// /// Calculates the network fee for the specified transaction. /// - /// The snapshot used to read data. /// The transaction to calculate. + /// The snapshot used to read data. /// Thr protocol settings to use. /// Function to retrive the script's account from a hash. + /// The maximum cost that can be spent when a contract is executed. /// The network fee of the transaction. - public static long CalculateNetworkFee(DataCache snapshot, Transaction tx, ProtocolSettings settings, Func accountScript) + public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, ProtocolSettings settings, Func accountScript, long maxExecutionCost = ApplicationEngine.TestModeGas) { UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); // base size for transaction: includes const_header + signers + attributes + script + hashes - long maxExecutionCost = ApplicationEngine.TestModeGas; int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot); long networkFee = 0; diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs index ab29aaad24..761b0b1999 100644 --- a/src/Neo/Wallets/Wallet.cs +++ b/src/Neo/Wallets/Wallet.cs @@ -578,7 +578,7 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory scr tx.SystemFee = engine.GasConsumed; } - tx.NetworkFee = CalculateNetworkFee(snapshot, tx, maxGas, ProtocolSettings, (a) => GetAccount(a)?.Contract?.Script); + tx.NetworkFee = tx.CalculateNetworkFee(snapshot, ProtocolSettings, (a) => GetAccount(a)?.Contract?.Script, maxGas); if (value >= tx.SystemFee + tx.NetworkFee) return tx; } throw new InvalidOperationException("Insufficient GAS"); From ee66982f7932e6d2ac50e7b5df984e215ee6d6a4 Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 18 Feb 2024 12:39:27 -0800 Subject: [PATCH 4/4] Update src/Neo/Wallets/Helper.cs --- src/Neo/Wallets/Helper.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index 584de970ef..960baa27af 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -92,10 +92,9 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); // base size for transaction: includes const_header + signers + attributes + script + hashes - int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); + int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length), index = -1; uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot); long networkFee = 0; - int index = -1; foreach (UInt160 hash in hashes) { index++;