Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve MaxNestingDepth of WitnessCondition #3761

Merged
merged 10 commits into from
Feb 17, 2025
4 changes: 1 addition & 3 deletions src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ public override int GetHashCode()

protected override void DeserializeWithoutType(ref MemoryReader reader, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
Expressions = DeserializeConditions(ref reader, maxNestDepth - 1);
Expressions = DeserializeConditions(ref reader, maxNestDepth);
if (Expressions.Length == 0) throw new FormatException();
}

Expand All @@ -78,7 +77,6 @@ protected override void SerializeWithoutType(BinaryWriter writer)

private protected override void ParseJson(JObject json, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
JArray expressions = (JArray)json["expressions"];
if (expressions.Count > MaxSubitems) throw new FormatException();
Expressions = expressions.Select(p => FromJson((JObject)p, maxNestDepth - 1)).ToArray();
Expand Down
2 changes: 0 additions & 2 deletions src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ public override int GetHashCode()

protected override void DeserializeWithoutType(ref MemoryReader reader, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
Expression = DeserializeFrom(ref reader, maxNestDepth - 1);
}

Expand All @@ -76,7 +75,6 @@ protected override void SerializeWithoutType(BinaryWriter writer)

private protected override void ParseJson(JObject json, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
Expression = FromJson((JObject)json["expression"], maxNestDepth - 1);
}

Expand Down
4 changes: 1 addition & 3 deletions src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ public override int GetHashCode()

protected override void DeserializeWithoutType(ref MemoryReader reader, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
Expressions = DeserializeConditions(ref reader, maxNestDepth - 1);
Expressions = DeserializeConditions(ref reader, maxNestDepth);
if (Expressions.Length == 0) throw new FormatException();
}

Expand All @@ -78,7 +77,6 @@ protected override void SerializeWithoutType(BinaryWriter writer)

private protected override void ParseJson(JObject json, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
JArray expressions = (JArray)json["expressions"];
if (expressions.Count > MaxSubitems) throw new FormatException();
Expressions = expressions.Select(p => FromJson((JObject)p, maxNestDepth - 1)).ToArray();
Expand Down
6 changes: 4 additions & 2 deletions src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace Neo.Network.P2P.Payloads.Conditions
public abstract class WitnessCondition : IInteroperable, ISerializable
{
internal const int MaxSubitems = 16;
internal const int MaxNestingDepth = 2;
internal const int MaxNestingDepth = 3;

/// <summary>
/// The type of the <see cref="WitnessCondition"/>.
Expand Down Expand Up @@ -54,7 +54,7 @@ protected static WitnessCondition[] DeserializeConditions(ref MemoryReader reade
{
WitnessCondition[] conditions = new WitnessCondition[reader.ReadVarInt(MaxSubitems)];
for (int i = 0; i < conditions.Length; i++)
conditions[i] = DeserializeFrom(ref reader, maxNestDepth);
conditions[i] = DeserializeFrom(ref reader, maxNestDepth - 1);
return conditions;
}

Expand All @@ -66,6 +66,7 @@ protected static WitnessCondition[] DeserializeConditions(ref MemoryReader reade
/// <returns>The deserialized <see cref="WitnessCondition"/>.</returns>
public static WitnessCondition DeserializeFrom(ref MemoryReader reader, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
WitnessConditionType type = (WitnessConditionType)reader.ReadByte();
if (ReflectionCache<WitnessConditionType>.CreateInstance(type) is not WitnessCondition condition)
throw new FormatException();
Expand Down Expand Up @@ -109,6 +110,7 @@ void ISerializable.Serialize(BinaryWriter writer)
/// <returns>The converted <see cref="WitnessCondition"/>.</returns>
public static WitnessCondition FromJson(JObject json, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
WitnessConditionType type = Enum.Parse<WitnessConditionType>(json["type"].GetString());
if (ReflectionCache<WitnessConditionType>.CreateInstance(type) is not WitnessCondition condition)
throw new FormatException("Invalid WitnessConditionType.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// UT_WitnessContition.cs file belongs to the neo project and is free
// UT_WitnessCondition.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
Expand All @@ -11,8 +11,11 @@

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Cryptography.ECC;
using Neo.Extensions;
using Neo.IO;
using Neo.Json;
using Neo.Network.P2P.Payloads.Conditions;
using System;

namespace Neo.UnitTests.Network.P2P.Payloads
{
Expand Down Expand Up @@ -347,7 +350,7 @@ public void TestFromJson2()
var hash2 = UInt160.Parse("0xd2a4cff31913016155e38e474a2c06d08be276cf");
var jstr = "{\"type\":\"Or\",\"expressions\":[{\"type\":\"And\",\"expressions\":[{\"type\":\"CalledByContract\",\"hash\":\"0x0000000000000000000000000000000000000000\"},{\"type\":\"ScriptHash\",\"hash\":\"0xd2a4cff31913016155e38e474a2c06d08be276cf\"}]},{\"type\":\"Or\",\"expressions\":[{\"type\":\"CalledByGroup\",\"group\":\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"},{\"type\":\"Boolean\",\"expression\":true}]}]}";
var json = (JObject)JToken.Parse(jstr);
var condi = WitnessCondition.FromJson(json, 2);
var condi = WitnessCondition.FromJson(json, WitnessCondition.MaxNestingDepth);
var or_condi = (OrCondition)condi;
Assert.AreEqual(2, or_condi.Expressions.Length);
var and_condi = (AndCondition)or_condi.Expressions[0];
Expand All @@ -363,5 +366,128 @@ public void TestFromJson2()
Assert.IsTrue(cbgc.Group.Equals(point));
Assert.IsTrue(bc.Expression);
}

[TestMethod]
public void Test_WitnessCondition_Nesting()
{
WitnessCondition nested = new OrCondition
{
Expressions = [
new OrCondition { Expressions = [new BooleanCondition { Expression = true }] }
]
};

var buf = nested.ToArray();
var reader = new MemoryReader(buf);

var deser = WitnessCondition.DeserializeFrom(ref reader, WitnessCondition.MaxNestingDepth);
Assert.AreEqual(nested, deser);

nested = new AndCondition
{
Expressions = [
new AndCondition { Expressions = [new BooleanCondition { Expression = true }] }
]
};

buf = nested.ToArray();
reader = new MemoryReader(buf);

deser = WitnessCondition.DeserializeFrom(ref reader, WitnessCondition.MaxNestingDepth);
Assert.AreEqual(nested, deser);

nested = new NotCondition
{
Expression = new NotCondition
{
Expression = new BooleanCondition { Expression = true }
}
};

buf = nested.ToArray();
reader = new MemoryReader(buf);

deser = WitnessCondition.DeserializeFrom(ref reader, WitnessCondition.MaxNestingDepth);
Assert.AreEqual(nested, deser);

// Overflow maxNestingDepth
nested = new OrCondition
{
Expressions = [
new OrCondition {
Expressions = [
new OrCondition { Expressions = [new BooleanCondition { Expression = true }] }
]
}
]
};

buf = nested.ToArray();
reader = new MemoryReader(buf);

var exceptionHappened = false;
// CS8175 prevents from using Assert.ThrowsException here
try
{
WitnessCondition.DeserializeFrom(ref reader, WitnessCondition.MaxNestingDepth);
}
catch (FormatException)
{
exceptionHappened = true;
}
Assert.IsTrue(exceptionHappened);

nested = new AndCondition
{
Expressions = [
new AndCondition {
Expressions = [
new AndCondition { Expressions = [new BooleanCondition { Expression = true }] }
]
}
]
};

buf = nested.ToArray();
reader = new MemoryReader(buf);

exceptionHappened = false;
// CS8175 prevents from using Assert.ThrowsException here
try
{
WitnessCondition.DeserializeFrom(ref reader, WitnessCondition.MaxNestingDepth);
}
catch (FormatException)
{
exceptionHappened = true;
}
Assert.IsTrue(exceptionHappened);

nested = new NotCondition
{
Expression = new NotCondition
{
Expression = new NotCondition
{
Expression = new BooleanCondition { Expression = true }
}
}
};

buf = nested.ToArray();
reader = new MemoryReader(buf);

exceptionHappened = false;
// CS8175 prevents from using Assert.ThrowsException here
try
{
WitnessCondition.DeserializeFrom(ref reader, WitnessCondition.MaxNestingDepth);
}
catch (FormatException)
{
exceptionHappened = true;
}
Assert.IsTrue(exceptionHappened);
}
}
}