Skip to content

Commit

Permalink
Merge branch 'master' into fix/neo-projects
Browse files Browse the repository at this point in the history
  • Loading branch information
shargon authored Feb 26, 2025
2 parents a4629a8 + 2e2fbe9 commit 3d812b5
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 85 deletions.
9 changes: 8 additions & 1 deletion neo.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32516.85
MinimumVisualStudioVersion = 10.0.40219.1
Expand Down Expand Up @@ -80,6 +80,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcClient", "src\Plugins\Rp
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.ApplicationLogs.Tests", "tests\Neo.Plugins.ApplicationLogs.Tests\Neo.Plugins.ApplicationLogs.Tests.csproj", "{8C866DC8-2E55-4399-9563-2F47FD4602EC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.DBFTPlugin.Tests", "tests\Neo.Plugins.DBFTPlugin.Tests\Neo.Plugins.DBFTPlugin.Tests.csproj", "{72997EAB-9B0C-4BC8-B797-955C219C2C97}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions.Tests", "tests\Neo.Extensions.Tests\Neo.Extensions.Tests.csproj", "{77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions.Benchmarks", "benchmarks\Neo.Extensions.Benchmarks\Neo.Extensions.Benchmarks.csproj", "{B6CB2559-10F9-41AC-8D58-364BFEF9688B}"
Expand Down Expand Up @@ -240,6 +242,10 @@ Global
{5F984D2B-793F-4683-B53A-80050E6E0286}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F984D2B-793F-4683-B53A-80050E6E0286}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F984D2B-793F-4683-B53A-80050E6E0286}.Release|Any CPU.Build.0 = Release|Any CPU
{72997EAB-9B0C-4BC8-B797-955C219C2C97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72997EAB-9B0C-4BC8-B797-955C219C2C97}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72997EAB-9B0C-4BC8-B797-955C219C2C97}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72997EAB-9B0C-4BC8-B797-955C219C2C97}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -283,6 +289,7 @@ Global
{77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1}
{B6CB2559-10F9-41AC-8D58-364BFEF9688B} = {C25EB0B0-0CAC-4CC1-8F36-F9229EFB99EC}
{5F984D2B-793F-4683-B53A-80050E6E0286} = {C25EB0B0-0CAC-4CC1-8F36-F9229EFB99EC}
{72997EAB-9B0C-4BC8-B797-955C219C2C97} = {7F257712-D033-47FF-B439-9D4320D06599}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BCBA19D9-F868-4C6D-8061-A2B91E06E3EC}
Expand Down
27 changes: 27 additions & 0 deletions src/Neo/Wallets/SignException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// SignException.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
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System;

namespace Neo.Wallets
{
/// <summary>
/// The exception that is thrown when `Sign` fails.
/// </summary>
public class SignException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="SignException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public SignException(string message) : base(message) { }
}
}
48 changes: 47 additions & 1 deletion src/Neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,10 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory<byte> scr
/// Signs the <see cref="IVerifiable"/> in the specified <see cref="ContractParametersContext"/> with the wallet.
/// </summary>
/// <param name="context">The <see cref="ContractParametersContext"/> to be used.</param>
/// <returns><see langword="true"/> if the signature is successfully added to the context; otherwise, <see langword="false"/>.</returns>
/// <returns>
/// <see langword="true"/> if any signature is successfully added to the context;
/// otherwise, <see langword="false"/>.
/// </returns>
public bool Sign(ContractParametersContext context)
{
if (context.Network != ProtocolSettings.Network) return false;
Expand Down Expand Up @@ -657,6 +660,49 @@ public bool Sign(ContractParametersContext context)
return fSuccess;
}

/// <summary>
/// Signs the specified data with the corresponding private key of the specified public key.
/// </summary>
/// <param name="signData">The data to sign.</param>
/// <param name="publicKey">The public key.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="signData"/> or <paramref name="publicKey"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="SignException">
/// Thrown when no account is found for the given public key or no private key is found for the given public key.
/// </exception>
/// <returns>The signature</returns>
public byte[] Sign(byte[] signData, ECPoint publicKey)
{
if (signData is null) throw new ArgumentNullException(nameof(signData));
if (publicKey is null) throw new ArgumentNullException(nameof(publicKey));

var account = GetAccount(publicKey);
if (account is null)
throw new SignException("No such account found");

var privateKey = account.GetKey()?.PrivateKey;
if (privateKey is null)
throw new SignException("No private key found for the given public key");

return Crypto.Sign(signData, privateKey);
}

/// <summary>
/// Checks if the wallet contains the specified public key and the corresponding private key.
/// If the wallet has the public key but not the private key, it will return <see langword="false"/>.
/// </summary>
/// <param name="publicKey">The public key.</param>
/// <returns>
/// <see langword="true"/> if the wallet contains the specified public key and the corresponding private key;
/// otherwise, <see langword="false"/>.
/// </returns>
public bool ContainsKeyPair(ECPoint publicKey)
{
var account = GetAccount(publicKey);
return account != null && account.HasKey;
}

/// <summary>
/// Checks that the specified password is correct for the wallet.
/// </summary>
Expand Down
32 changes: 24 additions & 8 deletions src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

using Neo.Extensions;
using Neo.Ledger;
using Neo.Network.P2P;
using Neo.Network.P2P.Payloads;
using Neo.Plugins.DBFTPlugin.Messages;
using Neo.Plugins.DBFTPlugin.Types;
using Neo.SmartContract;
using Neo.Wallets;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
Expand All @@ -37,10 +37,15 @@ public ExtensiblePayload MakeChangeView(ChangeViewReason reason)

public ExtensiblePayload MakeCommit()
{
return CommitPayloads[MyIndex] ?? (CommitPayloads[MyIndex] = MakeSignedPayload(new Commit
if (CommitPayloads[MyIndex] is not null)
return CommitPayloads[MyIndex];

var signData = EnsureHeader().GetSignData(dbftSettings.Network);
CommitPayloads[MyIndex] = MakeSignedPayload(new Commit
{
Signature = EnsureHeader().Sign(keyPair, neoSystem.Settings.Network)
}));
Signature = _wallet.Sign(signData, _myPublicKey)
});
return CommitPayloads[MyIndex];
}

private ExtensiblePayload MakeSignedPayload(ConsensusMessage message)
Expand All @@ -59,7 +64,7 @@ private void SignPayload(ExtensiblePayload payload)
try
{
sc = new ContractParametersContext(neoSystem.StoreView, payload, dbftSettings.Network);
wallet.Sign(sc);
_wallet.Sign(sc);
}
catch (InvalidOperationException exception)
{
Expand Down Expand Up @@ -149,11 +154,22 @@ public ExtensiblePayload MakeRecoveryMessage()
}
return MakeSignedPayload(new RecoveryMessage
{
ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null).Select(p => GetChangeViewPayloadCompact(p)).Take(M).ToDictionary(p => p.ValidatorIndex),
ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null)
.Select(p => GetChangeViewPayloadCompact(p))
.Take(M)
.ToDictionary(p => p.ValidatorIndex),
PrepareRequestMessage = prepareRequestMessage,
// We only need a PreparationHash set if we don't have the PrepareRequest information.
PreparationHash = TransactionHashes == null ? PreparationPayloads.Where(p => p != null).GroupBy(p => GetMessage<PrepareResponse>(p).PreparationHash, (k, g) => new { Hash = k, Count = g.Count() }).OrderByDescending(p => p.Count).Select(p => p.Hash).FirstOrDefault() : null,
PreparationMessages = PreparationPayloads.Where(p => p != null).Select(p => GetPreparationPayloadCompact(p)).ToDictionary(p => p.ValidatorIndex),
PreparationHash = TransactionHashes == null
? PreparationPayloads.Where(p => p != null)
.GroupBy(p => GetMessage<PrepareResponse>(p).PreparationHash, (k, g) => new { Hash = k, Count = g.Count() })
.OrderByDescending(p => p.Count)
.Select(p => p.Hash)
.FirstOrDefault()
: null,
PreparationMessages = PreparationPayloads.Where(p => p != null)
.Select(p => GetPreparationPayloadCompact(p))
.ToDictionary(p => p.ValidatorIndex),
CommitMessages = CommitSent
? CommitPayloads.Where(p => p != null).Select(p => GetCommitPayloadCompact(p)).ToDictionary(p => p.ValidatorIndex)
: new Dictionary<byte, RecoveryMessage.CommitPayloadCompact>()
Expand Down
15 changes: 8 additions & 7 deletions src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ public partial class ConsensusContext : IDisposable, ISerializable
public TransactionVerificationContext VerificationContext = new();

public StoreCache Snapshot { get; private set; }
private KeyPair keyPair;
private ECPoint _myPublicKey;
private int _witnessSize;
private readonly NeoSystem neoSystem;
private readonly Settings dbftSettings;
private readonly Wallet wallet;
private readonly Wallet _wallet;
private readonly IStore store;
private Dictionary<UInt256, ConsensusMessage> cachedMessages;

Expand Down Expand Up @@ -114,7 +114,7 @@ public bool ValidatorsChanged

public ConsensusContext(NeoSystem neoSystem, Settings settings, Wallet wallet)
{
this.wallet = wallet;
_wallet = wallet;
this.neoSystem = neoSystem;
dbftSettings = settings;

Expand Down Expand Up @@ -245,13 +245,14 @@ public void Reset(byte viewNumber)
LastSeenMessage[validator] = height;
}
}
keyPair = null;

_myPublicKey = null;
for (int i = 0; i < Validators.Length; i++)
{
WalletAccount account = wallet?.GetAccount(Validators[i]);
if (account?.HasKey != true) continue;
// ContainsKeyPair may be called multiple times
if (!_wallet.ContainsKeyPair(Validators[i])) continue;
MyIndex = i;
keyPair = account.GetKey();
_myPublicKey = Validators[MyIndex];
break;
}
cachedMessages = new Dictionary<UInt256, ConsensusMessage>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<EnableMSTestRunner>true</EnableMSTestRunner>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MSTest" Version="3.8.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Neo\Neo.csproj" />
<ProjectReference Include="..\..\src\Plugins\DBFTPlugin\DBFTPlugin.csproj" />
<ProjectReference Include="..\Neo.UnitTests\Neo.UnitTests.csproj" />
</ItemGroup>

</Project>
118 changes: 118 additions & 0 deletions tests/Neo.Plugins.DBFTPlugin.Tests/UT_ConsensusContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// UT_ConsensusContext.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
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Microsoft.Extensions.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Cryptography;
using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.Network.P2P;
using Neo.Plugins.DBFTPlugin.Consensus;
using Neo.Plugins.DBFTPlugin.Messages;
using Neo.SmartContract.Native;
using Neo.UnitTests;
using Neo.UnitTests.Persistence;
using System;
using System.Collections.Generic;

namespace Neo.Plugins.DBFTPlugin.Tests
{
[TestClass]
public class UT_ConsensusContext
{
static readonly ProtocolSettings ProtocolSettings = ProtocolSettings.Default with
{
Network = 0x334F454Eu,
StandbyCommittee =
[
// private key: [0] => 0x01 * 32, [1] => 0x02 * 32, [2] => 0x03 * 32, [3] => 0x04 * 32
ECPoint.Parse("026ff03b949241ce1dadd43519e6960e0a85b41a69a05c328103aa2bce1594ca16", ECCurve.Secp256r1),
ECPoint.Parse("02550f471003f3df97c3df506ac797f6721fb1a1fb7b8f6f83d224498a65c88e24", ECCurve.Secp256r1),
ECPoint.Parse("02591ab771ebbcfd6d9cb9094d106528add1a69d44c2c1f627f089ec58b9c61adf", ECCurve.Secp256r1),
ECPoint.Parse("0273103ec30b3ccf57daae08e93534aef144a35940cf6bbba12a0cf7cbd5d65a64", ECCurve.Secp256r1),
],
ValidatorsCount = 4,
SeedList = ["seed1.neo.org:10333"],
};

private static IConfigurationSection MockConfig()
{
return new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string> {
{ "PluginConfiguration:IgnoreRecoveryLogs", "true" },
{ "PluginConfiguration:Network", "0x334F454E" },
})
.Build()
.GetSection("PluginConfiguration");
}

[TestMethod]
public void TestReset()
{
var config = MockConfig();
var wallet = TestUtils.GenerateTestWallet("123");
var system = new NeoSystem(ProtocolSettings, new TestMemoryStoreProvider(new()));
var context = new ConsensusContext(system, new Settings(config), wallet);
context.Reset(0);
Assert.AreEqual(-1, context.MyIndex);

var validators = NativeContract.NEO.GetNextBlockValidators(system.GetSnapshotCache(), 4);
Assert.AreEqual(4, validators.Length);

var privateKey = new byte[32];
Array.Fill(privateKey, (byte)1);
wallet.CreateAccount(privateKey);

context = new ConsensusContext(system, new Settings(config), wallet);
context.Reset(0);
Assert.AreEqual(2, context.MyIndex);
}

[TestMethod]
public void TestMakeCommit()
{
var config = MockConfig();
var wallet = TestUtils.GenerateTestWallet("123");
var system = new NeoSystem(ProtocolSettings, new TestMemoryStoreProvider(new()));

var privateKey = new byte[32];
Array.Fill(privateKey, (byte)1);
wallet.CreateAccount(privateKey);

var context = new ConsensusContext(system, new Settings(config), wallet);
context.Reset(0);

context.Block = new()
{
Header = new() { PrevHash = UInt256.Zero, Index = 1, NextConsensus = UInt160.Zero },
Transactions = []
};
context.TransactionHashes = [];

var payload = context.MakeCommit();
Assert.IsNotNull(payload);
Assert.IsTrue(ReferenceEquals(payload, context.MakeCommit()));
Assert.IsNotNull(payload.Witness);

var data = context.CommitPayloads[context.MyIndex].Data;
var commit = new Commit();
var reader = new MemoryReader(data);
((ISerializable)commit).Deserialize(ref reader);
Assert.AreEqual(1u, commit.BlockIndex);
Assert.AreEqual(2, commit.ValidatorIndex);
Assert.AreEqual(0, commit.ViewNumber);
Assert.AreEqual(64, commit.Signature.Length);

var signData = context.EnsureHeader().GetSignData(ProtocolSettings.Network);
Assert.IsTrue(Crypto.VerifySignature(signData, commit.Signature.Span, context.Validators[context.MyIndex]));
}
}
}
Loading

0 comments on commit 3d812b5

Please sign in to comment.