diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index ff9c8e29d8..4da4f45efd 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -12,6 +12,7 @@ using Akka.Actor; using Akka.Configuration; using Akka.IO; +using Neo.Cryptography; using Neo.IO.Actors; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; @@ -443,6 +444,9 @@ private void Persist(Block block) using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, tx, clonedSnapshot, block, system.Settings, tx.SystemFee); engine.LoadScript(tx.Script); transactionState.State = engine.Execute(); + ApplicationExecuted application_executed = new(engine); + transactionState.NotificationMerkleRoot = + MerkleTree.ComputeRoot(application_executed.Notifications.Select(p => p.GetNotificationHash()).ToArray()); if (transactionState.State == VMState.HALT) { clonedSnapshot.Commit(); @@ -451,7 +455,6 @@ private void Persist(Block block) { clonedSnapshot = snapshot.CreateSnapshot(); } - ApplicationExecuted application_executed = new(engine); Context.System.EventStream.Publish(application_executed); all_application_executed.Add(application_executed); } diff --git a/src/Neo/SmartContract/Native/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index d72f07c022..76cd1bb6c0 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -40,7 +40,8 @@ internal override ContractTask OnPersist(ApplicationEngine engine) { BlockIndex = engine.PersistingBlock.Index, Transaction = p, - State = VMState.NONE + State = VMState.NONE, + NotificationMerkleRoot = UInt256.Zero }).ToArray(); engine.Snapshot.Add(CreateStorageKey(Prefix_BlockHash).AddBigEndian(engine.PersistingBlock.Index), new StorageItem(engine.PersistingBlock.Hash.ToArray())); engine.Snapshot.Add(CreateStorageKey(Prefix_Block).Add(engine.PersistingBlock.Hash), new StorageItem(Trim(engine.PersistingBlock).ToArray())); diff --git a/src/Neo/SmartContract/Native/TransactionState.cs b/src/Neo/SmartContract/Native/TransactionState.cs index b17296b42d..ea5c4672f0 100644 --- a/src/Neo/SmartContract/Native/TransactionState.cs +++ b/src/Neo/SmartContract/Native/TransactionState.cs @@ -38,6 +38,11 @@ public class TransactionState : IInteroperable /// public VMState State; + /// + /// The merkle root of the notifications. + /// + public UInt256 NotificationMerkleRoot; + private ReadOnlyMemory _rawTransaction; IInteroperable IInteroperable.Clone() @@ -47,6 +52,7 @@ IInteroperable IInteroperable.Clone() BlockIndex = BlockIndex, Transaction = Transaction, State = State, + NotificationMerkleRoot = NotificationMerkleRoot, _rawTransaction = _rawTransaction }; } @@ -57,6 +63,7 @@ void IInteroperable.FromReplica(IInteroperable replica) BlockIndex = from.BlockIndex; Transaction = from.Transaction; State = from.State; + NotificationMerkleRoot = from.NotificationMerkleRoot; if (_rawTransaction.IsEmpty) _rawTransaction = from._rawTransaction; } @@ -73,6 +80,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) _rawTransaction = ((ByteString)@struct[1]).Memory; Transaction = _rawTransaction.AsSerializable(); State = (VMState)(byte)@struct[2].GetInteger(); + NotificationMerkleRoot = new UInt256(@struct[3].GetSpan()); } StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) @@ -81,7 +89,7 @@ StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) return new Struct(referenceCounter) { BlockIndex }; if (_rawTransaction.IsEmpty) _rawTransaction = Transaction.ToArray(); - return new Struct(referenceCounter) { BlockIndex, _rawTransaction, (byte)State }; + return new Struct(referenceCounter) { BlockIndex, _rawTransaction, (byte)State, NotificationMerkleRoot.ToArray() }; } } } diff --git a/src/Neo/SmartContract/NotifyEventArgs.cs b/src/Neo/SmartContract/NotifyEventArgs.cs index 0a509101c1..211cce6311 100644 --- a/src/Neo/SmartContract/NotifyEventArgs.cs +++ b/src/Neo/SmartContract/NotifyEventArgs.cs @@ -9,11 +9,13 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Cryptography; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.VM; using Neo.VM.Types; using System; +using System.IO; using Array = Neo.VM.Types.Array; namespace Neo.SmartContract @@ -58,6 +60,31 @@ public NotifyEventArgs(IVerifiable container, UInt160 script_hash, string eventN this.State = state; } + /// + /// Get notification Hash, or UInt256.Zero if something is wrong + /// + public UInt256 GetNotificationHash() + { + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); + writer.Write(ScriptHash); + + try + { + writer.Write(EventName); + writer.Write(BinarySerializer.Serialize(State, 32)); + } + catch + { + // It might have more state entries than expected or an unsupported event name encoding. + + return UInt256.Zero; + } + + writer.Flush(); + return new UInt256(ms.ToArray().Sha256()); + } + public void FromStackItem(StackItem stackItem) { throw new NotSupportedException(); diff --git a/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs b/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs index 0a5f1a102d..11ce1654eb 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs @@ -42,10 +42,17 @@ public void Initialize() InvocationScript=Array.Empty(), VerificationScript=Array.Empty() } } - } + }, + NotificationMerkleRoot = UInt256.Zero }; originTrimmed = new TransactionState { + NotificationMerkleRoot = UInt256.Zero, + ConflictingSigners = new UInt160[] + { + new UInt160(Crypto.Hash160(new byte[] { 1, 2, 3 })), + new UInt160(Crypto.Hash160(new byte[] { 4, 5, 6 })) + } BlockIndex = 1, }; } @@ -56,7 +63,10 @@ public void TestDeserialize() var data = BinarySerializer.Serialize(((IInteroperable)origin).ToStackItem(null), ExecutionEngineLimits.Default); var reader = new MemoryReader(data); - TransactionState dest = new(); + TransactionState dest = new() + { + NotificationMerkleRoot = UInt256.Zero + }; ((IInteroperable)dest).FromStackItem(BinarySerializer.Deserialize(ref reader, ExecutionEngineLimits.Default, null)); dest.BlockIndex.Should().Be(origin.BlockIndex); @@ -70,7 +80,10 @@ public void TestDeserializeTrimmed() var data = BinarySerializer.Serialize(((IInteroperable)originTrimmed).ToStackItem(null), ExecutionEngineLimits.Default); var reader = new MemoryReader(data); - TransactionState dest = new(); + TransactionState dest = new() + { + NotificationMerkleRoot = UInt256.Zero + }; ((IInteroperable)dest).FromStackItem(BinarySerializer.Deserialize(ref reader, ExecutionEngineLimits.Default, null)); dest.BlockIndex.Should().Be(originTrimmed.BlockIndex);