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);