From 801f890b1e43d32b462bf5b86802d0127b0a2e8a Mon Sep 17 00:00:00 2001 From: nan01ab Date: Wed, 8 Jan 2025 00:15:34 +0800 Subject: [PATCH 1/7] Feature: use IReadOnlyStoreView when readonly access on storage --- src/Neo/SmartContract/ApplicationEngine.cs | 2 +- .../Native/ContractManagement.cs | 16 ++--- .../SmartContract/Native/LedgerContract.cs | 69 +++++++++++-------- src/Plugins/StorageDumper/StorageDumper.cs | 6 +- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index f8f2332b4e..c48e3300a8 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -601,7 +601,7 @@ protected override void PostExecuteInstruction(Instruction instruction) Diagnostic?.PostExecuteInstruction(instruction); } - private static Block CreateDummyBlock(DataCache snapshot, ProtocolSettings settings) + private static Block CreateDummyBlock(IReadOnlyStoreView snapshot, ProtocolSettings settings) { UInt256 hash = NativeContract.Ledger.CurrentHash(snapshot); Block currentBlock = NativeContract.Ledger.GetBlock(snapshot, hash); diff --git a/src/Neo/SmartContract/Native/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs index 9fb66a063c..32c4141a88 100644 --- a/src/Neo/SmartContract/Native/ContractManagement.cs +++ b/src/Neo/SmartContract/Native/ContractManagement.cs @@ -137,9 +137,10 @@ private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value/ /// The hash of the deployed contract. /// The deployed contract. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public ContractState GetContract(DataCache snapshot, UInt160 hash) + public ContractState GetContract(IReadOnlyStoreView snapshot, UInt160 hash) { - return snapshot.TryGet(CreateStorageKey(Prefix_Contract).Add(hash))?.GetInteroperable(false); + var key = CreateStorageKey(Prefix_Contract).Add(hash); + return snapshot.TryGet(key, out var item) ? item.GetInteroperable(false) : null; } /// @@ -149,12 +150,11 @@ public ContractState GetContract(DataCache snapshot, UInt160 hash) /// Contract ID. /// The deployed contract. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public ContractState GetContractById(DataCache snapshot, int id) + public ContractState GetContractById(IReadOnlyStoreView snapshot, int id) { - StorageItem item = snapshot.TryGet(CreateStorageKey(Prefix_ContractHash).AddBigEndian(id)); - if (item is null) return null; - var hash = new UInt160(item.Value.Span); - return GetContract(snapshot, hash); + if (snapshot.TryGet(CreateStorageKey(Prefix_ContractHash).AddBigEndian(id), out var item)) + return GetContract(snapshot, new UInt160(item.Value.Span)); + return null; } /// @@ -184,7 +184,7 @@ private IIterator GetContractHashes(DataCache snapshot) /// The number of parameters /// True if the method exists. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public bool HasMethod(DataCache snapshot, UInt160 hash, string method, int pcount) + public bool HasMethod(IReadOnlyStoreView snapshot, UInt160 hash, string method, int pcount) { var contract = GetContract(snapshot, hash); if (contract is null) return false; diff --git a/src/Neo/SmartContract/Native/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index ec88701d9c..bc990f8cf6 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -67,6 +67,7 @@ internal override ContractTask OnPersistAsync(ApplicationEngine engine) } } } + engine.SetState(transactions); return ContractTask.CompletedTask; } @@ -87,7 +88,7 @@ internal bool Initialized(DataCache snapshot) return snapshot.Find(CreateStorageKey(Prefix_Block).ToArray()).Any(); } - private bool IsTraceableBlock(DataCache snapshot, uint index, uint maxTraceableBlocks) + private bool IsTraceableBlock(IReadOnlyStoreView snapshot, uint index, uint maxTraceableBlocks) { uint currentIndex = CurrentIndex(snapshot); if (index > currentIndex) return false; @@ -100,14 +101,15 @@ private bool IsTraceableBlock(DataCache snapshot, uint index, uint maxTraceableB /// The snapshot used to read data. /// The index of the block. /// The hash of the block. - public UInt256 GetBlockHash(DataCache snapshot, uint index) + public UInt256 GetBlockHash(IReadOnlyStoreView snapshot, uint index) { if (snapshot is null) throw new ArgumentNullException(nameof(snapshot)); - StorageItem item = snapshot.TryGet(CreateStorageKey(Prefix_BlockHash).AddBigEndian(index)); - if (item is null) return null; - return new UInt256(item.Value.Span); + var key = CreateStorageKey(Prefix_BlockHash).AddBigEndian(index); + if (snapshot.TryGet(key, out var item)) + return new UInt256(item.Value.Span); + return null; } /// @@ -116,7 +118,7 @@ public UInt256 GetBlockHash(DataCache snapshot, uint index) /// The snapshot used to read data. /// The hash of the current block. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public UInt256 CurrentHash(DataCache snapshot) + public UInt256 CurrentHash(IReadOnlyStoreView snapshot) { if (snapshot is null) throw new ArgumentNullException(nameof(snapshot)); @@ -130,7 +132,7 @@ public UInt256 CurrentHash(DataCache snapshot) /// The snapshot used to read data. /// The index of the current block. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public uint CurrentIndex(DataCache snapshot) + public uint CurrentIndex(IReadOnlyStoreView snapshot) { if (snapshot is null) throw new ArgumentNullException(nameof(snapshot)); @@ -143,8 +145,10 @@ public uint CurrentIndex(DataCache snapshot) /// /// The snapshot used to read data. /// The hash of the block. - /// if the blockchain contains the block; otherwise, . - public bool ContainsBlock(DataCache snapshot, UInt256 hash) + /// + /// if the blockchain contains the block; otherwise, . + /// + public bool ContainsBlock(IReadOnlyStoreView snapshot, UInt256 hash) { if (snapshot is null) throw new ArgumentNullException(nameof(snapshot)); @@ -157,8 +161,10 @@ public bool ContainsBlock(DataCache snapshot, UInt256 hash) /// /// The snapshot used to read data. /// The hash of the transaction. - /// if the blockchain contains the transaction; otherwise, . - public bool ContainsTransaction(DataCache snapshot, UInt256 hash) + /// + /// if the blockchain contains the transaction; otherwise, . + /// + public bool ContainsTransaction(IReadOnlyStoreView snapshot, UInt256 hash) { var txState = GetTransactionState(snapshot, hash); return txState != null; @@ -172,8 +178,11 @@ public bool ContainsTransaction(DataCache snapshot, UInt256 hash) /// The hash of the conflicting transaction. /// The list of signer accounts of the conflicting transaction. /// MaxTraceableBlocks protocol setting. - /// if the blockchain contains the hash of the conflicting transaction; otherwise, . - public bool ContainsConflictHash(DataCache snapshot, UInt256 hash, IEnumerable signers, uint maxTraceableBlocks) + /// + /// if the blockchain contains the hash of the conflicting transaction; + /// otherwise, . + /// + public bool ContainsConflictHash(IReadOnlyStoreView snapshot, UInt256 hash, IEnumerable signers, uint maxTraceableBlocks) { if (snapshot is null) throw new ArgumentNullException(nameof(snapshot)); @@ -182,14 +191,16 @@ public bool ContainsConflictHash(DataCache snapshot, UInt256 hash, IEnumerable(); + var key = CreateStorageKey(Prefix_Transaction).Add(hash); + var stub = snapshot.TryGet(key, out var item) ? item.GetInteroperable() : null; if (stub is null || stub.Transaction is not null || !IsTraceableBlock(snapshot, stub.BlockIndex, maxTraceableBlocks)) return false; // At least one conflict record is found, then need to check signers intersection. foreach (var signer in signers) { - var state = snapshot.TryGet(CreateStorageKey(Prefix_Transaction).Add(hash).Add(signer))?.GetInteroperable(); + key = CreateStorageKey(Prefix_Transaction).Add(hash).Add(signer); + var state = snapshot.TryGet(key, out var txState) ? txState.GetInteroperable() : null; if (state is not null && IsTraceableBlock(snapshot, state.BlockIndex, maxTraceableBlocks)) return true; } @@ -203,14 +214,15 @@ public bool ContainsConflictHash(DataCache snapshot, UInt256 hash, IEnumerableThe snapshot used to read data. /// The hash of the block. /// The trimmed block. - public TrimmedBlock GetTrimmedBlock(DataCache snapshot, UInt256 hash) + public TrimmedBlock GetTrimmedBlock(IReadOnlyStoreView snapshot, UInt256 hash) { if (snapshot is null) throw new ArgumentNullException(nameof(snapshot)); - StorageItem item = snapshot.TryGet(CreateStorageKey(Prefix_Block).Add(hash)); - if (item is null) return null; - return item.Value.AsSerializable(); + var key = CreateStorageKey(Prefix_Block).Add(hash); + if (snapshot.TryGet(key, out var item)) + return item.Value.AsSerializable(); + return null; } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] @@ -235,7 +247,7 @@ private TrimmedBlock GetBlock(ApplicationEngine engine, byte[] indexOrHash) /// The snapshot used to read data. /// The hash of the block. /// The block with the specified hash. - public Block GetBlock(DataCache snapshot, UInt256 hash) + public Block GetBlock(IReadOnlyStoreView snapshot, UInt256 hash) { TrimmedBlock state = GetTrimmedBlock(snapshot, hash); if (state is null) return null; @@ -252,7 +264,7 @@ public Block GetBlock(DataCache snapshot, UInt256 hash) /// The snapshot used to read data. /// The index of the block. /// The block with the specified index. - public Block GetBlock(DataCache snapshot, uint index) + public Block GetBlock(IReadOnlyStoreView snapshot, uint index) { UInt256 hash = GetBlockHash(snapshot, index); if (hash is null) return null; @@ -265,7 +277,7 @@ public Block GetBlock(DataCache snapshot, uint index) /// The snapshot used to read data. /// The hash of the block. /// The block header with the specified hash. - public Header GetHeader(DataCache snapshot, UInt256 hash) + public Header GetHeader(IReadOnlyStoreView snapshot, UInt256 hash) { return GetTrimmedBlock(snapshot, hash)?.Header; } @@ -276,7 +288,7 @@ public Header GetHeader(DataCache snapshot, UInt256 hash) /// The snapshot used to read data. /// The index of the block. /// The block header with the specified index. - public Header GetHeader(DataCache snapshot, uint index) + public Header GetHeader(IReadOnlyStoreView snapshot, uint index) { UInt256 hash = GetBlockHash(snapshot, index); if (hash is null) return null; @@ -289,14 +301,15 @@ public Header GetHeader(DataCache snapshot, uint index) /// The snapshot used to read data. /// The hash of the transaction. /// The with the specified hash. - public TransactionState GetTransactionState(DataCache snapshot, UInt256 hash) + public TransactionState GetTransactionState(IReadOnlyStoreView snapshot, UInt256 hash) { if (snapshot is null) throw new ArgumentNullException(nameof(snapshot)); - var state = snapshot.TryGet(CreateStorageKey(Prefix_Transaction).Add(hash))?.GetInteroperable(); - if (state?.Transaction is null) return null; - return state; + var key = CreateStorageKey(Prefix_Transaction).Add(hash); + if (snapshot.TryGet(key, out var item)) + return item.GetInteroperable(); + return null; } /// @@ -305,7 +318,7 @@ public TransactionState GetTransactionState(DataCache snapshot, UInt256 hash) /// The snapshot used to read data. /// The hash of the transaction. /// The transaction with the specified hash. - public Transaction GetTransaction(DataCache snapshot, UInt256 hash) + public Transaction GetTransaction(IReadOnlyStoreView snapshot, UInt256 hash) { return GetTransactionState(snapshot, hash)?.Transaction; } diff --git a/src/Plugins/StorageDumper/StorageDumper.cs b/src/Plugins/StorageDumper/StorageDumper.cs index 184b6f61ac..c36bce92e1 100644 --- a/src/Plugins/StorageDumper/StorageDumper.cs +++ b/src/Plugins/StorageDumper/StorageDumper.cs @@ -135,10 +135,10 @@ private void OnPersistStorage(uint network, DataCache snapshot) void ICommittedHandler.Blockchain_Committed_Handler(NeoSystem system, Block block) { - OnCommitStorage(system.Settings.Network, system.StoreView); + OnCommitStorage(system.Settings.Network); } - void OnCommitStorage(uint network, DataCache snapshot) + void OnCommitStorage(uint network) { if (_currentBlock != null && _writer != null) { @@ -147,7 +147,7 @@ void OnCommitStorage(uint network, DataCache snapshot) } } - private void InitFileWriter(uint network, DataCache snapshot) + private void InitFileWriter(uint network, IReadOnlyStoreView snapshot) { uint blockIndex = NativeContract.Ledger.CurrentIndex(snapshot); if (_writer == null From 734448656435e6817c5aa4f3046b92df2a0584ad Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 21 Jan 2025 09:41:59 +0800 Subject: [PATCH 2/7] fix an issue --- src/Neo/SmartContract/Native/LedgerContract.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Neo/SmartContract/Native/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index 710f8509a5..e6590e41da 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -200,7 +200,7 @@ public bool ContainsConflictHash(IReadOnlyStoreView snapshot, UInt256 hash, IEnu foreach (var signer in signers) { key = CreateStorageKey(Prefix_Transaction).Add(hash).Add(signer); - var state = snapshot.TryGet(key, out var txState) ? txState.GetInteroperable() : null; + var state = snapshot.TryGet(key, out var tx) ? tx.GetInteroperable() : null; if (state is not null && IsTraceableBlock(snapshot, state.BlockIndex, maxTraceableBlocks)) return true; } @@ -307,9 +307,8 @@ public TransactionState GetTransactionState(IReadOnlyStoreView snapshot, UInt256 throw new ArgumentNullException(nameof(snapshot)); var key = CreateStorageKey(Prefix_Transaction).Add(hash); - if (snapshot.TryGet(key, out var item)) - return item.GetInteroperable(); - return null; + var state = snapshot.TryGet(key, out var item) ? item.GetInteroperable() : null; + return state?.Transaction is null ? null : state; } /// From ae57b1066eebd7ee2d47d8784f9ef6ac08e1c4d5 Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Tue, 21 Jan 2025 09:56:54 +0100 Subject: [PATCH 3/7] Fix `NeedSnapshot` --- src/Neo/SmartContract/Native/ContractMethodMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs index 14996a3da6..bab0eb9927 100644 --- a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs +++ b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs @@ -51,7 +51,7 @@ public ContractMethodMetadata(MemberInfo member, ContractMethodAttribute attribu if (parameterInfos.Length > 0) { NeedApplicationEngine = parameterInfos[0].ParameterType.IsAssignableFrom(typeof(ApplicationEngine)); - NeedSnapshot = parameterInfos[0].ParameterType.IsAssignableFrom(typeof(DataCache)); + NeedSnapshot = parameterInfos[0].ParameterType.IsAssignableFrom(typeof(IReadOnlyStoreView)); } if (NeedApplicationEngine || NeedSnapshot) Parameters = parameterInfos.Skip(1).Select(p => new InteropParameterDescriptor(p)).ToArray(); From 625ccf4d2271d52bcfe72b5be857e40d709a1933 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 21 Jan 2025 20:31:41 +0800 Subject: [PATCH 4/7] fix: unify style --- src/Neo/SmartContract/Native/ContractManagement.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Neo/SmartContract/Native/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs index 8584d0ed6a..30f344ef11 100644 --- a/src/Neo/SmartContract/Native/ContractManagement.cs +++ b/src/Neo/SmartContract/Native/ContractManagement.cs @@ -152,9 +152,8 @@ public ContractState GetContract(IReadOnlyStoreView snapshot, UInt160 hash) [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public ContractState GetContractById(IReadOnlyStoreView snapshot, int id) { - if (snapshot.TryGet(CreateStorageKey(Prefix_ContractHash).AddBigEndian(id), out var item)) - return GetContract(snapshot, new UInt160(item.Value.Span)); - return null; + var key = CreateStorageKey(Prefix_ContractHash).AddBigEndian(id); + return snapshot.TryGet(key, out var item) ? GetContract(snapshot, new UInt160(item.Value.Span)) : null; } /// From 01182408fa8f50a2ce62e92a55262ee148093a2d Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 21 Jan 2025 21:37:59 +0800 Subject: [PATCH 5/7] Feature: use IReadOnlyStoreView when readonly access on storage --- .../Native/ContractManagement.cs | 2 +- .../Native/ContractMethodMetadata.cs | 3 ++- src/Neo/SmartContract/Native/FungibleToken.cs | 16 +++++------ src/Neo/SmartContract/Native/NeoToken.cs | 27 ++++++++++--------- .../SmartContract/Native/OracleContract.cs | 21 ++++++++++----- .../SmartContract/Native/PolicyContract.cs | 15 +++++------ 6 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/Neo/SmartContract/Native/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs index 30f344ef11..a0ddd63d11 100644 --- a/src/Neo/SmartContract/Native/ContractManagement.cs +++ b/src/Neo/SmartContract/Native/ContractManagement.cs @@ -116,7 +116,7 @@ internal override async ContractTask OnPersistAsync(ApplicationEngine engine) } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - private long GetMinimumDeploymentFee(DataCache snapshot) + private long GetMinimumDeploymentFee(IReadOnlyStoreView snapshot) { // In the unit of datoshi, 1 datoshi = 1e-8 GAS return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_MinimumDeploymentFee)]; diff --git a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs index bab0eb9927..fa2a08fc9c 100644 --- a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs +++ b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs @@ -51,7 +51,8 @@ public ContractMethodMetadata(MemberInfo member, ContractMethodAttribute attribu if (parameterInfos.Length > 0) { NeedApplicationEngine = parameterInfos[0].ParameterType.IsAssignableFrom(typeof(ApplicationEngine)); - NeedSnapshot = parameterInfos[0].ParameterType.IsAssignableFrom(typeof(IReadOnlyStoreView)); + // snapshot is a DataCache instance, and DataCache implements IReadOnlyStoreView + NeedSnapshot = parameterInfos[0].ParameterType.IsAssignableFrom(typeof(DataCache)); } if (NeedApplicationEngine || NeedSnapshot) Parameters = parameterInfos.Skip(1).Select(p => new InteropParameterDescriptor(p)).ToArray(); diff --git a/src/Neo/SmartContract/Native/FungibleToken.cs b/src/Neo/SmartContract/Native/FungibleToken.cs index a4b0b8a128..2ab4e24cb0 100644 --- a/src/Neo/SmartContract/Native/FungibleToken.cs +++ b/src/Neo/SmartContract/Native/FungibleToken.cs @@ -107,11 +107,10 @@ internal async ContractTask Burn(ApplicationEngine engine, UInt160 account, BigI /// The snapshot used to read data. /// The total supply of the token. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public virtual BigInteger TotalSupply(DataCache snapshot) + public virtual BigInteger TotalSupply(IReadOnlyStoreView snapshot) { - StorageItem storage = snapshot.TryGet(CreateStorageKey(Prefix_TotalSupply)); - if (storage is null) return BigInteger.Zero; - return storage; + var key = CreateStorageKey(Prefix_TotalSupply); + return snapshot.TryGet(key, out var item) ? item : BigInteger.Zero; } /// @@ -121,11 +120,12 @@ public virtual BigInteger TotalSupply(DataCache snapshot) /// The owner of the account. /// The balance of the account. Or 0 if the account doesn't exist. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public virtual BigInteger BalanceOf(DataCache snapshot, UInt160 account) + public virtual BigInteger BalanceOf(IReadOnlyStoreView snapshot, UInt160 account) { - StorageItem storage = snapshot.TryGet(CreateStorageKey(Prefix_Account).Add(account)); - if (storage is null) return BigInteger.Zero; - return storage.GetInteroperable().Balance; + var key = CreateStorageKey(Prefix_Account).Add(account); + if (snapshot.TryGet(key, out var item)) + return item.GetInteroperable().Balance; + return BigInteger.Zero; } [ContractMethod(CpuFee = 1 << 17, StorageFee = 50, RequiredCallFlags = CallFlags.States | CallFlags.AllowCall | CallFlags.AllowNotify)] diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index c72220b833..29f85fe7f2 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -77,7 +77,7 @@ internal NeoToken() : base() _registerPrice = CreateStorageKey(Prefix_RegisterPrice); } - public override BigInteger TotalSupply(DataCache snapshot) + public override BigInteger TotalSupply(IReadOnlyStoreView snapshot) { return TotalAmount; } @@ -302,7 +302,7 @@ private void SetRegisterPrice(ApplicationEngine engine, long registerPrice) /// The snapshot used to read data. /// The amount of the fees. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public long GetRegisterPrice(DataCache snapshot) + public long GetRegisterPrice(IReadOnlyStoreView snapshot) { // In the unit of datoshi, 1 datoshi = 1e-8 GAS return (long)(BigInteger)snapshot[_registerPrice]; @@ -452,8 +452,8 @@ private IIterator GetAllCandidates(DataCache snapshot) internal IEnumerable<(StorageKey Key, StorageItem Value, ECPoint PublicKey, CandidateState State)> GetCandidatesInternal(DataCache snapshot) { - byte[] prefix_key = CreateStorageKey(Prefix_Candidate).ToArray(); - return snapshot.Find(prefix_key) + byte[] prefixKey = CreateStorageKey(Prefix_Candidate).ToArray(); + return snapshot.Find(prefixKey) .Select(p => (p.Key, p.Value, PublicKey: p.Key.Key[1..].AsSerializable(), State: p.Value.GetInteroperable())) .Where(p => p.State.Registered) .Where(p => !Policy.IsBlocked(snapshot, Contract.CreateSignatureRedeemScript(p.PublicKey).ToScriptHash())); @@ -466,10 +466,10 @@ private IIterator GetAllCandidates(DataCache snapshot) /// Specific public key /// Votes or -1 if it was not found. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public BigInteger GetCandidateVote(DataCache snapshot, ECPoint pubKey) + public BigInteger GetCandidateVote(IReadOnlyStoreView snapshot, ECPoint pubKey) { - StorageItem storage = snapshot.TryGet(CreateStorageKey(Prefix_Candidate).Add(pubKey)); - CandidateState state = storage?.GetInteroperable(); + var key = CreateStorageKey(Prefix_Candidate).Add(pubKey); + var state = snapshot.TryGet(key, out var item) ? item.GetInteroperable() : null; return state?.Registered == true ? state.Votes : -1; } @@ -479,7 +479,7 @@ public BigInteger GetCandidateVote(DataCache snapshot, ECPoint pubKey) /// The snapshot used to read data. /// The public keys of the members. [ContractMethod(CpuFee = 1 << 16, RequiredCallFlags = CallFlags.ReadStates)] - public ECPoint[] GetCommittee(DataCache snapshot) + public ECPoint[] GetCommittee(IReadOnlyStoreView snapshot) { return GetCommitteeFromCache(snapshot).Select(p => p.PublicKey).OrderBy(p => p).ToArray(); } @@ -491,9 +491,10 @@ public ECPoint[] GetCommittee(DataCache snapshot) /// account /// The state of the account. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public NeoAccountState GetAccountState(DataCache snapshot, UInt160 account) + public NeoAccountState GetAccountState(IReadOnlyStoreView snapshot, UInt160 account) { - return snapshot.TryGet(CreateStorageKey(Prefix_Account).Add(account))?.GetInteroperable(); + var key = CreateStorageKey(Prefix_Account).Add(account); + return snapshot.TryGet(key, out var item) ? item.GetInteroperable() : null; } /// @@ -502,13 +503,13 @@ public NeoAccountState GetAccountState(DataCache snapshot, UInt160 account) /// The snapshot used to read data. /// The address of the committee. [ContractMethod(Hardfork.HF_Cockatrice, CpuFee = 1 << 16, RequiredCallFlags = CallFlags.ReadStates)] - public UInt160 GetCommitteeAddress(DataCache snapshot) + public UInt160 GetCommitteeAddress(IReadOnlyStoreView snapshot) { ECPoint[] committees = GetCommittee(snapshot); return Contract.CreateMultiSigRedeemScript(committees.Length - (committees.Length - 1) / 2, committees).ToScriptHash(); } - private CachedCommittee GetCommitteeFromCache(DataCache snapshot) + private CachedCommittee GetCommitteeFromCache(IReadOnlyStoreView snapshot) { return snapshot[CreateStorageKey(Prefix_Committee)].GetInteroperable(); } @@ -551,7 +552,7 @@ private ECPoint[] GetNextBlockValidators(ApplicationEngine engine) /// The snapshot used to read data. /// The number of validators in the system. /// The public keys of the validators. - public ECPoint[] GetNextBlockValidators(DataCache snapshot, int validatorsCount) + public ECPoint[] GetNextBlockValidators(IReadOnlyStoreView snapshot, int validatorsCount) { return GetCommitteeFromCache(snapshot) .Take(validatorsCount) diff --git a/src/Neo/SmartContract/Native/OracleContract.cs b/src/Neo/SmartContract/Native/OracleContract.cs index 80c7096d19..9062674681 100644 --- a/src/Neo/SmartContract/Native/OracleContract.cs +++ b/src/Neo/SmartContract/Native/OracleContract.cs @@ -65,7 +65,7 @@ private void SetPrice(ApplicationEngine engine, long price) /// The snapshot used to read data. /// The price for an Oracle request, in the unit of datoshi, 1 datoshi = 1e-8 GAS. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public long GetPrice(DataCache snapshot) + public long GetPrice(IReadOnlyStoreView snapshot) { return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_Price)]; } @@ -100,9 +100,10 @@ private UInt256 GetOriginalTxid(ApplicationEngine engine) /// The snapshot used to read data. /// The id of the request. /// The pending request. Or if no request with the specified id is found. - public OracleRequest GetRequest(DataCache snapshot, ulong id) + public OracleRequest GetRequest(IReadOnlyStoreView snapshot, ulong id) { - return snapshot.TryGet(CreateStorageKey(Prefix_Request).AddBigEndian(id))?.GetInteroperable(); + var key = CreateStorageKey(Prefix_Request).AddBigEndian(id); + return snapshot.TryGet(key, out var item) ? item.GetInteroperable() : null; } /// @@ -112,7 +113,9 @@ public OracleRequest GetRequest(DataCache snapshot, ulong id) /// All the pending requests. public IEnumerable<(ulong, OracleRequest)> GetRequests(DataCache snapshot) { - return snapshot.Find(CreateStorageKey(Prefix_Request).ToArray()).Select(p => (BinaryPrimitives.ReadUInt64BigEndian(p.Key.Key.Span[1..]), p.Value.GetInteroperable())); + var key = CreateStorageKey(Prefix_Request); + return snapshot.Find(key.ToArray()) + .Select(p => (BinaryPrimitives.ReadUInt64BigEndian(p.Key.Key.Span[1..]), p.Value.GetInteroperable())); } /// @@ -121,12 +124,16 @@ public OracleRequest GetRequest(DataCache snapshot, ulong id) /// The snapshot used to read data. /// The url of the requests. /// All the requests with the specified url. - public IEnumerable<(ulong, OracleRequest)> GetRequestsByUrl(DataCache snapshot, string url) + public IEnumerable<(ulong, OracleRequest)> GetRequestsByUrl(IReadOnlyStoreView snapshot, string url) { - IdList list = snapshot.TryGet(CreateStorageKey(Prefix_IdList).Add(GetUrlHash(url)))?.GetInteroperable(); + var listKey = CreateStorageKey(Prefix_IdList).Add(GetUrlHash(url)); + IdList list = snapshot.TryGet(listKey, out var item) ? item.GetInteroperable() : null; if (list is null) yield break; foreach (ulong id in list) - yield return (id, snapshot[CreateStorageKey(Prefix_Request).AddBigEndian(id)].GetInteroperable()); + { + var key = CreateStorageKey(Prefix_Request).AddBigEndian(id); + yield return (id, snapshot[key].GetInteroperable()); + } } private static byte[] GetUrlHash(string url) diff --git a/src/Neo/SmartContract/Native/PolicyContract.cs b/src/Neo/SmartContract/Native/PolicyContract.cs index d034c06b46..2cc8dc37a3 100644 --- a/src/Neo/SmartContract/Native/PolicyContract.cs +++ b/src/Neo/SmartContract/Native/PolicyContract.cs @@ -94,7 +94,7 @@ internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfor /// The snapshot used to read data. /// The network fee per transaction byte. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public long GetFeePerByte(DataCache snapshot) + public long GetFeePerByte(IReadOnlyStoreView snapshot) { return (long)(BigInteger)snapshot[_feePerByte]; } @@ -105,7 +105,7 @@ public long GetFeePerByte(DataCache snapshot) /// The snapshot used to read data. /// The execution fee factor. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public uint GetExecFeeFactor(DataCache snapshot) + public uint GetExecFeeFactor(IReadOnlyStoreView snapshot) { return (uint)(BigInteger)snapshot[_execFeeFactor]; } @@ -116,7 +116,7 @@ public uint GetExecFeeFactor(DataCache snapshot) /// The snapshot used to read data. /// The storage price. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public uint GetStoragePrice(DataCache snapshot) + public uint GetStoragePrice(IReadOnlyStoreView snapshot) { return (uint)(BigInteger)snapshot[_storagePrice]; } @@ -128,13 +128,12 @@ public uint GetStoragePrice(DataCache snapshot) /// Attribute type /// The fee for attribute. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public uint GetAttributeFee(DataCache snapshot, byte attributeType) + public uint GetAttributeFee(IReadOnlyStoreView snapshot, byte attributeType) { if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType)) throw new InvalidOperationException(); - StorageItem entry = snapshot.TryGet(CreateStorageKey(Prefix_AttributeFee).Add(attributeType)); - if (entry == null) return DefaultAttributeFee; - return (uint)(BigInteger)entry; + var key = CreateStorageKey(Prefix_AttributeFee).Add(attributeType); + return snapshot.TryGet(key, out var item) ? (uint)(BigInteger)item : DefaultAttributeFee; } /// @@ -144,7 +143,7 @@ public uint GetAttributeFee(DataCache snapshot, byte attributeType) /// The account to be checked. /// if the account is blocked; otherwise, . [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] - public bool IsBlocked(DataCache snapshot, UInt160 account) + public bool IsBlocked(IReadOnlyStoreView snapshot, UInt160 account) { return snapshot.Contains(CreateStorageKey(Prefix_BlockedAccount).Add(account)); } From 2014d5c6705b9d7e10431216d3a10f3233d6051e Mon Sep 17 00:00:00 2001 From: nan01ab Date: Tue, 21 Jan 2025 21:43:47 +0800 Subject: [PATCH 6/7] Feature: use IReadOnlyStoreView when readonly access on storage --- src/Neo/SmartContract/Native/LedgerContract.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Neo/SmartContract/Native/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index e6590e41da..a494b635a9 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -107,9 +107,7 @@ public UInt256 GetBlockHash(IReadOnlyStoreView snapshot, uint index) throw new ArgumentNullException(nameof(snapshot)); var key = CreateStorageKey(Prefix_BlockHash).AddBigEndian(index); - if (snapshot.TryGet(key, out var item)) - return new UInt256(item.Value.Span); - return null; + return snapshot.TryGet(key, out var item) ? new UInt256(item.Value.Span) : null; } /// From 1ea6b78b892a43d96801fee39a09aa8ab6a0ea62 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Sat, 25 Jan 2025 00:28:04 +0800 Subject: [PATCH 7/7] Feature: add unit tests for NeedSnapshot and NeedApplicationEngine --- .../Native/UT_ContractMethodAttribute.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs index 780c270b6b..abf7cc1fe5 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs @@ -10,7 +10,10 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Persistence; +using Neo.SmartContract; using Neo.SmartContract.Native; +using System.Reflection; namespace Neo.UnitTests.SmartContract.Native { @@ -28,5 +31,48 @@ public void TestConstructorOneArg() Assert.AreEqual(Hardfork.HF_Aspidochelone, arg.ActiveIn); } + + class NeedSnapshot + { + [ContractMethod] + public bool MethodReadOnlyStoreView(IReadOnlyStoreView view) => view is null; + + [ContractMethod] + public bool MethodDataCache(DataCache dataCache) => dataCache is null; + } + + class NoNeedSnapshot + { + [ContractMethod] + public bool MethodTwo(ApplicationEngine engine, UInt160 account) + => engine is null || account is null; + + [ContractMethod] + public bool MethodOne(ApplicationEngine engine) => engine is null; + } + + [TestMethod] + public void TestNeedSnapshot() + { + var flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; + foreach (var member in typeof(NeedSnapshot).GetMembers(flags)) + { + foreach (var attribute in member.GetCustomAttributes()) + { + var metadata = new ContractMethodMetadata(member, attribute); + Assert.IsTrue(metadata.NeedSnapshot); + } + } + + foreach (var member in typeof(NoNeedSnapshot).GetMembers(flags)) + { + foreach (var attribute in member.GetCustomAttributes()) + { + var metadata = new ContractMethodMetadata(member, attribute); + Assert.IsFalse(metadata.NeedSnapshot); + Assert.IsTrue(metadata.NeedApplicationEngine); + } + } + } } }