diff --git a/src/neo/SmartContract/InteropService.Blockchain.cs b/src/neo/SmartContract/InteropService.Blockchain.cs index b31aba4a32..1183c5e462 100644 --- a/src/neo/SmartContract/InteropService.Blockchain.cs +++ b/src/neo/SmartContract/InteropService.Blockchain.cs @@ -1,5 +1,6 @@ using Neo.Ledger; using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.VM; using Neo.VM.Types; using System; @@ -11,6 +12,8 @@ partial class InteropService { public static class Blockchain { + public const uint MaxTraceableBlocks = Transaction.MaxValidUntilBlockIncrement; + public static readonly InteropDescriptor GetHeight = Register("System.Blockchain.GetHeight", Blockchain_GetHeight, 0_00000400, TriggerType.Application, CallFlags.None); public static readonly InteropDescriptor GetBlock = Register("System.Blockchain.GetBlock", Blockchain_GetBlock, 0_02500000, TriggerType.Application, CallFlags.None); public static readonly InteropDescriptor GetTransaction = Register("System.Blockchain.GetTransaction", Blockchain_GetTransaction, 0_01000000, TriggerType.Application, CallFlags.None); @@ -26,68 +29,72 @@ private static bool Blockchain_GetHeight(ApplicationEngine engine) private static bool Blockchain_GetBlock(ApplicationEngine engine) { - ReadOnlySpan data = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); UInt256 hash; - if (data.Length <= 5) - hash = Ledger.Blockchain.Singleton.GetBlockHash((uint)new BigInteger(data)); - else if (data.Length == 32) + if (engine.TryPop(out uint height)) + { + hash = Ledger.Blockchain.Singleton.GetBlockHash(height); + } + else if (engine.TryPop(out ReadOnlySpan data)) + { + if (data.Length != 32) return false; hash = new UInt256(data); + } else + { return false; - + } Block block = hash != null ? engine.Snapshot.GetBlock(hash) : null; - if (block == null) - engine.CurrentContext.EvaluationStack.Push(StackItem.Null); - else - engine.CurrentContext.EvaluationStack.Push(block.ToStackItem(engine.ReferenceCounter)); + if (block != null && !IsTraceableBlock(engine.Snapshot, block.Index)) block = null; + engine.Push(block?.ToStackItem(engine.ReferenceCounter) ?? StackItem.Null); return true; } private static bool Blockchain_GetTransaction(ApplicationEngine engine) { - ReadOnlySpan hash = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); - Transaction tx = engine.Snapshot.GetTransaction(new UInt256(hash)); - if (tx == null) - engine.CurrentContext.EvaluationStack.Push(StackItem.Null); - else - engine.CurrentContext.EvaluationStack.Push(tx.ToStackItem(engine.ReferenceCounter)); + if (!engine.TryPop(out ReadOnlySpan hash)) return false; + TransactionState state = engine.Snapshot.Transactions.TryGet(new UInt256(hash)); + if (state != null && !IsTraceableBlock(engine.Snapshot, state.BlockIndex)) state = null; + engine.Push(state?.Transaction.ToStackItem(engine.ReferenceCounter) ?? StackItem.Null); return true; } private static bool Blockchain_GetTransactionHeight(ApplicationEngine engine) { - ReadOnlySpan hash = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); - var tx = engine.Snapshot.Transactions.TryGet(new UInt256(hash)); - engine.CurrentContext.EvaluationStack.Push(tx != null ? new BigInteger(tx.BlockIndex) : BigInteger.MinusOne); + if (!engine.TryPop(out ReadOnlySpan hash)) return false; + TransactionState state = engine.Snapshot.Transactions.TryGet(new UInt256(hash)); + if (state != null && !IsTraceableBlock(engine.Snapshot, state.BlockIndex)) state = null; + engine.Push(state?.BlockIndex ?? BigInteger.MinusOne); return true; } private static bool Blockchain_GetTransactionFromBlock(ApplicationEngine engine) { - ReadOnlySpan data = engine.CurrentContext.EvaluationStack.Pop().GetSpan(); UInt256 hash; - if (data.Length <= 5) - hash = Ledger.Blockchain.Singleton.GetBlockHash((uint)new BigInteger(data)); - else if (data.Length == 32) + if (engine.TryPop(out uint height)) + { + hash = Ledger.Blockchain.Singleton.GetBlockHash(height); + } + else if (engine.TryPop(out ReadOnlySpan data)) + { + if (data.Length != 32) return false; hash = new UInt256(data); + } else + { return false; - + } TrimmedBlock block = hash != null ? engine.Snapshot.Blocks.TryGet(hash) : null; - if (block == null) + if (block != null && !IsTraceableBlock(engine.Snapshot, block.Index)) block = null; + if (block is null) { - engine.CurrentContext.EvaluationStack.Push(StackItem.Null); + engine.Push(StackItem.Null); } else { - int index = (int)engine.CurrentContext.EvaluationStack.Pop().GetBigInteger(); + if (!engine.TryPop(out int index)) return false; if (index < 0 || index >= block.Hashes.Length - 1) return false; - Transaction tx = engine.Snapshot.GetTransaction(block.Hashes[index + 1]); - if (tx == null) - engine.CurrentContext.EvaluationStack.Push(StackItem.Null); - else - engine.CurrentContext.EvaluationStack.Push(tx.ToStackItem(engine.ReferenceCounter)); + engine.Push(tx?.ToStackItem(engine.ReferenceCounter) ?? StackItem.Null); } return true; } @@ -102,6 +109,12 @@ private static bool Blockchain_GetContract(ApplicationEngine engine) engine.CurrentContext.EvaluationStack.Push(contract.ToStackItem(engine.ReferenceCounter)); return true; } + + private static bool IsTraceableBlock(StoreView snapshot, uint index) + { + if (index > snapshot.Height) return false; + return index + MaxTraceableBlocks > snapshot.Height; + } } } } diff --git a/src/neo/neo.csproj b/src/neo/neo.csproj index 6b06ca6960..2b146e992e 100644 --- a/src/neo/neo.csproj +++ b/src/neo/neo.csproj @@ -27,7 +27,7 @@ - + diff --git a/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs b/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs index 04f60db7ff..8044d8ae95 100644 --- a/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs +++ b/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -37,7 +37,7 @@ public void System_Blockchain_GetBlock() var block = new Block() { - Index = 1, + Index = 0, Timestamp = 2, Version = 3, Witness = new Witness() @@ -68,13 +68,27 @@ public void System_Blockchain_GetBlock() Assert.AreEqual(1, engine.ResultStack.Count); Assert.IsTrue(engine.ResultStack.Peek().IsNull); - // With block + // Not traceable block + + var height = snapshot.BlockHashIndex.GetAndChange(); + height.Index = block.Index + Transaction.MaxValidUntilBlockIncrement; var blocks = snapshot.Blocks; var txs = snapshot.Transactions; blocks.Add(block.Hash, block.Trim()); txs.Add(tx.Hash, new TransactionState() { Transaction = tx, BlockIndex = block.Index, VMState = VMState.HALT }); + engine = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, true); + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsTrue(engine.ResultStack.Peek().IsNull); + + // With block + + height.Index = block.Index; + script.EmitSysCall(InteropService.Json.Serialize); engine = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, true); engine.LoadScript(script.ToArray()); @@ -83,10 +97,11 @@ public void System_Blockchain_GetBlock() Assert.AreEqual(1, engine.ResultStack.Count); Assert.IsInstanceOfType(engine.ResultStack.Peek(), typeof(ByteArray)); Assert.AreEqual(engine.ResultStack.Pop().GetSpan().ToHexString(), - "5b22556168352f4b6f446d39723064555950636353714346745a30594f726b583164646e7334366e676e3962383d222c332c22414141414141414141414141414141414141414141414141414141414141414141414141414141414141413d222c22414141414141414141414141414141414141414141414141414141414141414141414141414141414141413d222c322c312c224141414141414141414141414141414141414141414141414141413d222c315d"); + "5b22366b4139757552614430373634585358466c706674686b436b5954702f6e34623878715057476c6a6659303d222c332c22414141414141414141414141414141414141414141414141414141414141414141414141414141414141413d222c22414141414141414141414141414141414141414141414141414141414141414141414141414141414141413d222c322c302c224141414141414141414141414141414141414141414141414141413d222c315d"); Assert.AreEqual(0, engine.ResultStack.Count); // Clean + blocks.Delete(block.Hash); txs.Delete(tx.Hash); }