diff --git a/src/neo/Ledger/TransactionVerificationContext.cs b/src/neo/Ledger/TransactionVerificationContext.cs index a2bae45b0b..e291974137 100644 --- a/src/neo/Ledger/TransactionVerificationContext.cs +++ b/src/neo/Ledger/TransactionVerificationContext.cs @@ -13,8 +13,16 @@ public class TransactionVerificationContext /// private readonly Dictionary senderFee = new Dictionary(); + /// + /// Store oracle responses + /// + private readonly Dictionary oracleResponses = new Dictionary(); + public void AddTransaction(Transaction tx) { + var oracle = tx.GetAttribute(); + if (oracle != null) oracleResponses.Add(oracle.Id, tx.Hash); + if (senderFee.TryGetValue(tx.Sender, out var value)) senderFee[tx.Sender] = value + tx.SystemFee + tx.NetworkFee; else @@ -25,13 +33,23 @@ public bool CheckTransaction(Transaction tx, StoreView snapshot) { BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, tx.Sender); senderFee.TryGetValue(tx.Sender, out var totalSenderFeeFromPool); + BigInteger fee = tx.SystemFee + tx.NetworkFee + totalSenderFeeFromPool; - return balance >= fee; + if (balance < fee) return false; + + var oracle = tx.GetAttribute(); + if (oracle != null && oracleResponses.ContainsKey(oracle.Id)) + return false; + + return true; } public void RemoveTransaction(Transaction tx) { if ((senderFee[tx.Sender] -= tx.SystemFee + tx.NetworkFee) == 0) senderFee.Remove(tx.Sender); + + var oracle = tx.GetAttribute(); + if (oracle != null) oracleResponses.Remove(oracle.Id); } } } diff --git a/src/neo/SmartContract/Native/Oracle/OracleContract.cs b/src/neo/SmartContract/Native/Oracle/OracleContract.cs index 80217a2c37..68794c112e 100644 --- a/src/neo/SmartContract/Native/Oracle/OracleContract.cs +++ b/src/neo/SmartContract/Native/Oracle/OracleContract.cs @@ -147,7 +147,8 @@ protected override void PostPersist(ApplicationEngine engine) //Remove the request from storage StorageKey key = CreateStorageKey(Prefix_Request).Add(response.Id); - OracleRequest request = engine.Snapshot.Storages[key].GetInteroperable(); + OracleRequest request = engine.Snapshot.Storages.TryGet(key)?.GetInteroperable(); + if (request == null) continue; engine.Snapshot.Storages.Delete(key); //Remove the id from IdList @@ -157,7 +158,7 @@ protected override void PostPersist(ApplicationEngine engine) if (list.Count == 0) engine.Snapshot.Storages.Delete(key); //Mint GAS for oracle nodes - nodes ??= NativeContract.Designate.GetDesignatedByRole(engine.Snapshot, Role.Oracle, engine.Snapshot.PersistingBlock.Index).Select(p => (Contract.CreateSignatureRedeemScript(p).ToScriptHash(), BigInteger.Zero)).ToArray(); + nodes ??= Designate.GetDesignatedByRole(engine.Snapshot, Role.Oracle, engine.Snapshot.PersistingBlock.Index).Select(p => (Contract.CreateSignatureRedeemScript(p).ToScriptHash(), BigInteger.Zero)).ToArray(); if (nodes.Length > 0) { int index = (int)(response.Id % (ulong)nodes.Length); diff --git a/tests/neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs b/tests/neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs index a34a8cfb91..311cf7fd51 100644 --- a/tests/neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs +++ b/tests/neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs @@ -47,6 +47,29 @@ private Transaction CreateTransactionWithFee(long networkFee, long systemFee) return mock.Object; } + [TestMethod] + public void TestDuplicateOracle() + { + // Fake balance + var snapshot = Blockchain.Singleton.GetSnapshot(); + + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, long.MaxValue); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, UInt160.Zero); + NativeContract.GAS.Burn(engine, UInt160.Zero, balance); + NativeContract.GAS.Mint(engine, UInt160.Zero, 8); + + // Test + TransactionVerificationContext verificationContext = new TransactionVerificationContext(); + var tx = CreateTransactionWithFee(1, 2); + tx.Attributes = new TransactionAttribute[] { new OracleResponse() { Code = OracleResponseCode.ConsensusUnreachable, Id = 1, Result = new byte[0] } }; + verificationContext.CheckTransaction(tx, snapshot).Should().BeTrue(); + verificationContext.AddTransaction(tx); + + tx = CreateTransactionWithFee(2, 1); + tx.Attributes = new TransactionAttribute[] { new OracleResponse() { Code = OracleResponseCode.ConsensusUnreachable, Id = 1, Result = new byte[0] } }; + verificationContext.CheckTransaction(tx, snapshot).Should().BeFalse(); + } + [TestMethod] public void TestTransactionSenderFee() {