Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,17 @@ jobs:
- name: Install dependencies
run: sudo apt-get install -y libleveldb-dev expect
- name: Run tests with expect
run: expect ./.github/workflows/test-neo-cli-plugins.expect
run: |
# Count loadable plugins only (exclude MPTTrie dependency and RpcClient library).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the exclude?

Copy link
Copy Markdown
Member Author

@superboyiii superboyiii May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the exclude?

RpcClient is not Plugin, it's SDK. I don't release it in zip package. It will be only update to Nuget.
MPTTire is packed inside StateService.zip, so it's not necessary to be packed alone again.

expected_count=0
for csproj in plugins/*/*.csproj; do
plugin_name=$(basename "$(dirname "$csproj")")
case "$plugin_name" in
MPTTrie|RpcClient) continue ;;
esac
expected_count=$((expected_count + 1))
done
expect ./.github/workflows/test-neo-cli-plugins.expect "$expected_count"

Release:
if: github.ref == 'refs/heads/master-n3' && github.repository == 'neo-project/neo-node'
Expand Down Expand Up @@ -210,7 +220,7 @@ jobs:
declare -A cli_dll_set
for f in "${cli_dlls[@]}"; do cli_dll_set["$f"]=1; done

# Package every plugin project under plugins/*/*.csproj (stage only, zip after all are staged)
# Package every plugin project under plugins/*/*.csproj (e.g. DeferredRelay), except MPTTrie (StateService dependency only).
for csproj in plugins/*/*.csproj; do
plugin_dir="$(dirname "$csproj")"
plugin_name="$(basename "$plugin_dir")"
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/test-neo-cli-plugins.expect
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ expect {
}

#
# Test 'plugins' command and verify all 12 plugins are installed
# Test 'plugins' command and verify all plugins are installed
# Expected count is passed as argv[0] (plugins/*/*.csproj except MPTTrie and RpcClient).
#
send "plugins\n"

# Count [Installed] occurrences - we need 12
set count 0
set expected_count 12
if {[llength $argv] >= 1} {
set expected_count [lindex $argv 0]
} else {
set expected_count 13
}

# Match [Installed] pattern multiple times until we reach the prompt
expect {
Expand Down
16 changes: 15 additions & 1 deletion neo-node.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11201.2
Expand Down Expand Up @@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StorageDumper", "plugins\St
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TokensTracker", "plugins\TokensTracker\TokensTracker.csproj", "{AF8E770D-07F7-CD2A-6F59-88A5AC52EC34}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeferredRelay", "plugins\DeferredRelay\DeferredRelay.csproj", "{7E3A9C12-4B5D-6E8F-A901-23456789ABCD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Cryptography.MPTTrie.Tests", "tests\Neo.Cryptography.MPTTrie.Tests\Neo.Cryptography.MPTTrie.Tests.csproj", "{1A6EB5BA-2FCD-3056-1C01-FC6FA24EF09C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Network.RPC.Tests", "tests\Neo.Network.RPC.Tests\Neo.Network.RPC.Tests.csproj", "{60E16A49-06EB-6F80-7228-21A7793B8250}"
Expand All @@ -69,6 +71,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Plugins.StateService.Te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Plugins.Storage.Tests", "tests\Neo.Plugins.Storage.Tests\Neo.Plugins.Storage.Tests.csproj", "{52D6F4D3-8AC9-DEA4-1D6F-FAF6943EE3D9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Plugins.DeferredRelay.Tests", "tests\Neo.Plugins.DeferredRelay.Tests\Neo.Plugins.DeferredRelay.Tests.csproj", "{8F4BAC23-5C6E-7F90-B012-34567890BCDE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -151,6 +155,10 @@ Global
{AF8E770D-07F7-CD2A-6F59-88A5AC52EC34}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF8E770D-07F7-CD2A-6F59-88A5AC52EC34}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF8E770D-07F7-CD2A-6F59-88A5AC52EC34}.Release|Any CPU.Build.0 = Release|Any CPU
{7E3A9C12-4B5D-6E8F-A901-23456789ABCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E3A9C12-4B5D-6E8F-A901-23456789ABCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E3A9C12-4B5D-6E8F-A901-23456789ABCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E3A9C12-4B5D-6E8F-A901-23456789ABCD}.Release|Any CPU.Build.0 = Release|Any CPU
{1A6EB5BA-2FCD-3056-1C01-FC6FA24EF09C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A6EB5BA-2FCD-3056-1C01-FC6FA24EF09C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A6EB5BA-2FCD-3056-1C01-FC6FA24EF09C}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -195,6 +203,10 @@ Global
{52D6F4D3-8AC9-DEA4-1D6F-FAF6943EE3D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52D6F4D3-8AC9-DEA4-1D6F-FAF6943EE3D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52D6F4D3-8AC9-DEA4-1D6F-FAF6943EE3D9}.Release|Any CPU.Build.0 = Release|Any CPU
{8F4BAC23-5C6E-7F90-B012-34567890BCDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F4BAC23-5C6E-7F90-B012-34567890BCDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F4BAC23-5C6E-7F90-B012-34567890BCDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F4BAC23-5C6E-7F90-B012-34567890BCDE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -219,6 +231,7 @@ Global
{C0327365-D644-C32A-1AEF-B7004899339B} = {876880F3-B389-4388-B3A4-00E6F2581D52}
{6D1FE94A-0769-61C6-D870-B32919AE3881} = {876880F3-B389-4388-B3A4-00E6F2581D52}
{AF8E770D-07F7-CD2A-6F59-88A5AC52EC34} = {876880F3-B389-4388-B3A4-00E6F2581D52}
{7E3A9C12-4B5D-6E8F-A901-23456789ABCD} = {876880F3-B389-4388-B3A4-00E6F2581D52}
{1A6EB5BA-2FCD-3056-1C01-FC6FA24EF09C} = {62F4DC79-BE3D-4E60-B402-8D5F9C4BB2D9}
{60E16A49-06EB-6F80-7228-21A7793B8250} = {62F4DC79-BE3D-4E60-B402-8D5F9C4BB2D9}
{8C5DFE38-01CC-DF44-54DF-D2D67D5AB30E} = {62F4DC79-BE3D-4E60-B402-8D5F9C4BB2D9}
Expand All @@ -230,6 +243,7 @@ Global
{099504A6-0275-8DA3-D52C-06726AD8E77D} = {62F4DC79-BE3D-4E60-B402-8D5F9C4BB2D9}
{834D4327-7DDF-4D6A-1624-B074700964F0} = {62F4DC79-BE3D-4E60-B402-8D5F9C4BB2D9}
{52D6F4D3-8AC9-DEA4-1D6F-FAF6943EE3D9} = {62F4DC79-BE3D-4E60-B402-8D5F9C4BB2D9}
{8F4BAC23-5C6E-7F90-B012-34567890BCDE} = {62F4DC79-BE3D-4E60-B402-8D5F9C4BB2D9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6C1293A1-8EC4-44E8-9EE9-67892696FE26}
Expand Down
31 changes: 31 additions & 0 deletions plugins/DeferredRelay/DeferredRelay.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Neo.ConsoleService\Neo.ConsoleService.csproj" />
<ProjectReference Include="..\RpcServer\RpcServer.csproj" AdditionalProperties="IncludeSettingsFileOutput=False">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>

<ItemGroup>
<Compile Include="..\PluginHelper\PluginHelper.cs">
<Link>PluginHelper\PluginHelper.cs</Link>
</Compile>
</ItemGroup>

<ItemGroup>
<None Update="DeferredRelay.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="$(PackageId).Tests" />
</ItemGroup>

</Project>
12 changes: 12 additions & 0 deletions plugins/DeferredRelay/DeferredRelay.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"PluginConfiguration": {
"Path": "DeferredRelay_{0}",
"Network": 860833102,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can relay to a different network, so this value should be taken from the ProtocolSettings

Copy link
Copy Markdown
Member Author

@superboyiii superboyiii May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can relay to a different network, so this value should be taken from the ProtocolSettings

No. It's no way to relay to another chain. If DeferredRelay.json Network ≠ the node’s ProtocolSettings.Network, OnSystemLoaded returns immediately: no store, no actor, no queue, no retry. The plugin does nothing on that node. Cross-chain relay is not possible here; The worst case is a silent misconfiguration (you think DeferredRelay is on, but it isn’t).

"MaxTransactions": 0,
"CheckFrequency": 0,
"UnhandledExceptionPolicy": "StopPlugin"
},
"Dependency": [
"RpcServer"
]
}
81 changes: 81 additions & 0 deletions plugins/DeferredRelay/DeferredRelayActor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (C) 2015-2026 The Neo Project.
//
// DeferredRelayActor.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 Akka.Actor;
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using System.Threading.Tasks;

namespace Neo.Plugins.DeferredRelay;

internal sealed class DeferredRelayActor : UntypedActor
{
private sealed class ProcessQueuedCompleted;

private readonly NeoSystem _neo;
private readonly IStore _store;
private readonly DeferredRelaySettings _settings;
private bool _processingQueued;

public DeferredRelayActor(NeoSystem neo, IStore store, DeferredRelaySettings settings)
{
_neo = neo;
_store = store;
_settings = settings;
}

protected override void PreStart()
{
Context.System.EventStream.Subscribe(Self, typeof(Blockchain.RelayResult));
Context.System.EventStream.Subscribe(Self, typeof(Blockchain.PersistCompleted));
}

protected override void PostStop() =>
Context.System.EventStream.Unsubscribe(Self);

protected override void OnReceive(object message)
{
switch (message)
{
case Blockchain.RelayResult { Inventory: Transaction tx, Result: VerifyResult.NotYetValid } rr:
DeferredRelayEngine.TryOffer(_neo, _store, _settings, tx, rr.Result);
break;
case Blockchain.PersistCompleted pc:
ScheduleProcessQueued(pc.Block);
break;
case ProcessQueuedCompleted:
_processingQueued = false;
break;
case Status.Failure failure:
_processingQueued = false;
Logs.RuntimeLogger.Warning(failure.Cause, "Deferred relay queue processing failed");
break;
}
}

private void ScheduleProcessQueued(Block block)
{
if (_processingQueued || !DeferredRelayEngine.ShouldProcessPersist(block, _settings))
return;

_processingQueued = true;
var self = Self;
_ = DeferredRelayEngine.ProcessQueuedAsync(_neo, _store)
.ContinueWith(t =>
{
if (t.IsFaulted)
self.Tell(new Status.Failure(t.Exception!.GetBaseException()));
else
self.Tell(new ProcessQueuedCompleted());
}, TaskScheduler.Default);
}
}
Loading
Loading