From 95706d85f19500ca7bcc1c081fe9f5f8f5ccfed3 Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Mon, 22 Jun 2026 02:00:38 -0400 Subject: [PATCH 1/4] init new projects --- All.slnx | 2 + ...ngsModel.cs => ProtocolSettingsOptions.cs} | 5 +- src/Neo.Platform.CLI/Neo.Platform.CLI.csproj | 16 ++ src/Neo.Platform.CLI/Program.cs | 37 +++++ src/Neo.Platform.Hosting/HostingExtensions.cs | 156 ++++++++++++++++++ .../Logging/LoggingBuilderExtensions.cs | 4 +- .../Logging/NeoPlatformLogger.cs | 13 +- .../Logging/NeoPlatformLoggerOptions.cs | 2 +- .../Logging/NeoPlatformLoggerProvider.cs | 2 +- .../Neo.Platform.Hosting.csproj | 18 ++ .../NeoPlatformLifetime.cs | 119 +++++++++++++ .../NeoPlatformLifetimeOptions.cs | 30 ++++ .../ProtocolSettingNames.cs | 43 +++++ src/Neo.Wallet/Json/DevWalletAccountModel.cs | 4 +- src/Neo.Wallet/Json/Nep6WalletAccountModel.cs | 4 +- .../Json/UT_ProtocolSettingsModel.cs | 4 +- 16 files changed, 436 insertions(+), 23 deletions(-) rename src/Neo.Configuration/{Json/ProtocolSettingsModel.cs => ProtocolSettingsOptions.cs} (95%) create mode 100644 src/Neo.Platform.CLI/Neo.Platform.CLI.csproj create mode 100644 src/Neo.Platform.CLI/Program.cs create mode 100644 src/Neo.Platform.Hosting/HostingExtensions.cs rename src/{Neo.Core => Neo.Platform.Hosting}/Logging/LoggingBuilderExtensions.cs (96%) rename src/{Neo.Core => Neo.Platform.Hosting}/Logging/NeoPlatformLogger.cs (95%) rename src/{Neo.Core => Neo.Platform.Hosting}/Logging/NeoPlatformLoggerOptions.cs (97%) rename src/{Neo.Core => Neo.Platform.Hosting}/Logging/NeoPlatformLoggerProvider.cs (98%) create mode 100644 src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj create mode 100644 src/Neo.Platform.Hosting/NeoPlatformLifetime.cs create mode 100644 src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs create mode 100644 src/Neo.Platform.Hosting/ProtocolSettingNames.cs diff --git a/All.slnx b/All.slnx index 12eb905..7379cfb 100644 --- a/All.slnx +++ b/All.slnx @@ -4,6 +4,8 @@ + + diff --git a/src/Neo.Configuration/Json/ProtocolSettingsModel.cs b/src/Neo.Configuration/ProtocolSettingsOptions.cs similarity index 95% rename from src/Neo.Configuration/Json/ProtocolSettingsModel.cs rename to src/Neo.Configuration/ProtocolSettingsOptions.cs index d6b19f8..065068d 100644 --- a/src/Neo.Configuration/Json/ProtocolSettingsModel.cs +++ b/src/Neo.Configuration/ProtocolSettingsOptions.cs @@ -20,6 +20,7 @@ // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES +using Neo.Configuration.Json; using Neo.Core; using Neo.Core.Cryptography.ECC; using Neo.Core.Interfaces; @@ -28,9 +29,9 @@ using System.Collections.Immutable; using System.Linq; -namespace Neo.Configuration.Json +namespace Neo.Configuration { - public class ProtocolSettingsModel : JsonModel, IMap + public class ProtocolSettingsOptions : JsonModel, IMap { public uint Network { get; set; } diff --git a/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj b/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj new file mode 100644 index 0000000..31bbef8 --- /dev/null +++ b/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0 + enable + true + neo + neo;blockchain;cli;vm;test;rapid-loop + + + + + + + diff --git a/src/Neo.Platform.CLI/Program.cs b/src/Neo.Platform.CLI/Program.cs new file mode 100644 index 0000000..7c8f400 --- /dev/null +++ b/src/Neo.Platform.CLI/Program.cs @@ -0,0 +1,37 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using System; +using System.Threading.Tasks; + +namespace Neo.Platform.CLI +{ + internal class Program + { + private static Task Main(string[] args) + { + Console.WriteLine("Hello, World!"); + + return Task.CompletedTask; + } + } +} diff --git a/src/Neo.Platform.Hosting/HostingExtensions.cs b/src/Neo.Platform.Hosting/HostingExtensions.cs new file mode 100644 index 0000000..6d1d018 --- /dev/null +++ b/src/Neo.Platform.Hosting/HostingExtensions.cs @@ -0,0 +1,156 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.EventLog; +using Neo.Configuration; +using Neo.Core; +using Neo.Platform.Hosting.Logging; +using System; + +namespace Neo.Platform.Hosting +{ + public static class HostingExtensions + { + + public static IHostBuilder UseNeoPlatformConfiguration(this IHostBuilder builder) + { + // Host Configuration + builder.ConfigureHostConfiguration + ( + static config => + { + var manger = new ConfigurationManager(); + + config.AddConfiguration(manger); + // TODO: Add neo platform configuration + } + ); + + // Application Configuration + builder.ConfigureAppConfiguration + ( + static (context, config) => + { + var environmentName = context.HostingEnvironment.EnvironmentName; + + // Executable Directory (Config) + config.SetBasePath(AppContext.BaseDirectory); + config.AddJsonFile("config.json", optional: false); // application global settings (gets overwritten by environment) + + // Working Directory (Config) + var workingDir = context.Configuration[HostDefaults.ContentRootKey]; + + // NOTE: JSON Files Overwrite Environment Variables + config.SetBasePath($"{workingDir}"); + config.AddJsonFile($"config.{environmentName}.json", optional: true); // application settings by environment + config.AddJsonFile($"protocol.json", optional: false); // protocol global settings (gets overwritten by environment) + config.AddJsonFile($"protocol.{environmentName}.json", optional: true); // protocol settings by environment + config.AddJsonFile($"vm.json", optional: false); // vm global settings (gets overwritten by environment) + config.AddJsonFile($"vm.{environmentName}.json", optional: true); // vm settings by environment + } + ); + + // Logging Configuration + builder.ConfigureLogging + ( + static (context, logging) => + { + var isWindows = OperatingSystem.IsWindows(); + + if (isWindows) + logging.AddFilter + ( + level => level >= LogLevel.Warning + ); + + logging.AddConfiguration(context.Configuration.GetSection("Logging")); + logging.AddNeoPlatform(); +#if DEBUG + logging.AddDebug(); +#endif + logging.AddEventSourceLogger(); + + if (isWindows) + logging.AddEventLog(); + + logging.Configure + ( + static options => + { + options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId + | ActivityTrackingOptions.TraceId + | ActivityTrackingOptions.ParentId; + } + ); + } + ); + + builder.UseDefaultServiceProvider + ( + static (context, options) => + { + var isDevelopment = context.HostingEnvironment.IsDevelopment(); + + options.ValidateScopes = isDevelopment; + options.ValidateOnBuild = isDevelopment; + } + ); + + builder.AddNeoPlatformLifetime(); + builder.AddNeoPlatformOptions(); + + builder.ConfigureServices + ( + static (context, services) => + { + // Add default services + } + ); + + return builder; + } + + public static IHostBuilder AddNeoPlatformOptions(this IHostBuilder builder) => + builder.ConfigureServices + ( + static (context, services) => + { + var protocolSettingsSection = context.Configuration.GetSection(ProtocolSettingNames.SectionKey); + var protocolSettingsOptions = protocolSettingsSection.Get(); + services.AddSingleton(protocolSettingsOptions?.ToObject() ?? ProtocolSettings.Default); + } + ); + + public static IHostBuilder AddNeoPlatformLifetime(this IHostBuilder builder, Action? configure = default) => + builder.ConfigureServices( + services => + { + services.AddSingleton(); + if (configure is not null) + services.Configure(configure); + }); + } +} diff --git a/src/Neo.Core/Logging/LoggingBuilderExtensions.cs b/src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs similarity index 96% rename from src/Neo.Core/Logging/LoggingBuilderExtensions.cs rename to src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs index 1f422ae..201c77d 100644 --- a/src/Neo.Core/Logging/LoggingBuilderExtensions.cs +++ b/src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs @@ -26,14 +26,12 @@ using Microsoft.Extensions.Logging.Configuration; using System; -namespace Neo.Core.Logging +namespace Neo.Platform.Hosting.Logging { public static class LoggingBuilderExtensions { public static ILoggingBuilder AddNeoPlatform(this ILoggingBuilder builder) { - builder.AddConfiguration(); - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); LoggerProviderOptions.RegisterProviderOptions(builder.Services); diff --git a/src/Neo.Core/Logging/NeoPlatformLogger.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs similarity index 95% rename from src/Neo.Core/Logging/NeoPlatformLogger.cs rename to src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs index 236f2c2..eea4b4e 100644 --- a/src/Neo.Core/Logging/NeoPlatformLogger.cs +++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs @@ -22,10 +22,9 @@ using Microsoft.Extensions.Logging; using System; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -namespace Neo.Core.Logging +namespace Neo.Platform.Hosting.Logging { internal sealed class NeoPlatformLogger : ILogger { @@ -61,7 +60,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (string.IsNullOrEmpty(message) && exception == null) return; - message = $"{_name}[{eventId.Id}] {message}"; + message = $"{_name}[{eventId.Name ?? $"{eventId.Id:d}"}] {message}"; switch (logLevel) { @@ -80,7 +79,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except case LogLevel.Error: case LogLevel.Critical: ErrorMessage("{0}", message); - if (exception != null) + if (exception is not null) ErrorMessage(exception, ShowExceptionStackTrace); break; default: @@ -96,7 +95,6 @@ public static void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] s var message = string.Format(format, args); Console.Out.Write(message); - DebugWrite(message); } public static void WriteLine() => @@ -175,11 +173,6 @@ public void ErrorMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] s ResetColor(); } - private static void DebugWrite(string? message) - { - if (Debugger.IsAttached) Debug.Write(message); - } - public static void SetTerminalForegroundColor(ConsoleColor consoleColor) { Console.ForegroundColor = consoleColor; diff --git a/src/Neo.Core/Logging/NeoPlatformLoggerOptions.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs similarity index 97% rename from src/Neo.Core/Logging/NeoPlatformLoggerOptions.cs rename to src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs index b4930bc..90672d6 100644 --- a/src/Neo.Core/Logging/NeoPlatformLoggerOptions.cs +++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs @@ -22,7 +22,7 @@ using System.Diagnostics.CodeAnalysis; -namespace Neo.Core.Logging +namespace Neo.Platform.Hosting.Logging { public sealed class NeoPlatformLoggerOptions { diff --git a/src/Neo.Core/Logging/NeoPlatformLoggerProvider.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs similarity index 98% rename from src/Neo.Core/Logging/NeoPlatformLoggerProvider.cs rename to src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs index 908945a..e9b4a3f 100644 --- a/src/Neo.Core/Logging/NeoPlatformLoggerProvider.cs +++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs @@ -26,7 +26,7 @@ using System.Collections.Concurrent; using System.Runtime.Versioning; -namespace Neo.Core.Logging +namespace Neo.Platform.Hosting.Logging { [UnsupportedOSPlatform("browser")] [ProviderAlias("NeoPlatform")] diff --git a/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj b/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj new file mode 100644 index 0000000..6225137 --- /dev/null +++ b/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + + + + + + + + + + + + + diff --git a/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs b/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs new file mode 100644 index 0000000..45c2d80 --- /dev/null +++ b/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs @@ -0,0 +1,119 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.Platform.Hosting +{ + public class NeoPlatformLifetime : IHostLifetime, IDisposable + { + public NeoPlatformLifetimeOptions Options { get; } + public IHostEnvironment Environment { get; } + public IHostApplicationLifetime ApplicationLifetime { get; } + + private CancellationTokenRegistration _appStartedReg; + private CancellationTokenRegistration _appStoppingReg; + + private readonly ILogger _logger; + + public NeoPlatformLifetime( + IOptions options, + IHostEnvironment environment, + IHostApplicationLifetime applicationLifetime, + ILoggerFactory? loggerFactory = default) + { + Options = options?.Value ?? + throw new ArgumentNullException(nameof(options)); + Environment = environment ?? + throw new ArgumentNullException(nameof(environment)); + ApplicationLifetime = applicationLifetime ?? + throw new ArgumentNullException(nameof(applicationLifetime)); + + _logger = (loggerFactory ?? NullLoggerFactory.Instance) + .CreateLogger("Neo.Hosting.Lifetime"); + } + + public void Dispose() + { + _appStartedReg.Dispose(); + _appStoppingReg.Dispose(); + + GC.SuppressFinalize(this); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + // Nothing to do + return Task.CompletedTask; + } + + public Task WaitForStartAsync(CancellationToken cancellationToken) + { + if (Options.SuppressStatusMessages == false) + { + _appStartedReg = ApplicationLifetime.ApplicationStarted.Register + ( + static state => + { + if (state is NeoPlatformLifetime pl) + pl.OnApplicationStarted(); + }, + this + ); + + _appStoppingReg = ApplicationLifetime.ApplicationStopping.Register + ( + static state => + { + if (state is NeoPlatformLifetime pl) + pl.OnApplicationStopping(); + }, + this + ); + } + + return Task.CompletedTask; + } + + private void OnApplicationStarted() + { + if (_logger.IsEnabled(LogLevel.Information)) + { + _logger.LogInformation("Neo platform started. Press Ctrl+C to shut down."); + _logger.LogInformation("Hosting environment: {envName}", Environment.EnvironmentName); + _logger.LogInformation("Content root path: {contentRoot}", Environment.ContentRootPath); + } + } + + private void OnApplicationStopping() + { + if (_logger.IsEnabled(LogLevel.Information)) + _logger.LogInformation("Neo platform is shutting down..."); + } + } +} diff --git a/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs b/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs new file mode 100644 index 0000000..9a3e252 --- /dev/null +++ b/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs @@ -0,0 +1,30 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.Hosting; + +namespace Neo.Platform.Hosting +{ + public class NeoPlatformLifetimeOptions : ConsoleLifetimeOptions + { + } +} diff --git a/src/Neo.Platform.Hosting/ProtocolSettingNames.cs b/src/Neo.Platform.Hosting/ProtocolSettingNames.cs new file mode 100644 index 0000000..c74e3a7 --- /dev/null +++ b/src/Neo.Platform.Hosting/ProtocolSettingNames.cs @@ -0,0 +1,43 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Neo.Core; + +namespace Neo.Platform.Hosting +{ + public static class ProtocolSettingNames + { + public static readonly string SectionKey = nameof(ProtocolSettings); + + public static readonly string NetworkKey = $"{SectionKey}:{nameof(ProtocolSettings.Network)}"; + public static readonly string AddressVersionKey = $"{SectionKey}:{nameof(ProtocolSettings.AddressVersion)}"; + public static readonly string StandbyCommitteeKey = $"{SectionKey}:{nameof(ProtocolSettings.StandbyCommittee)}"; + public static readonly string ValidatorsCountKey = $"{SectionKey}:{nameof(ProtocolSettings.ValidatorsCount)}"; + public static readonly string HardForksKey = $"{SectionKey}:{nameof(ProtocolSettings.HardForks)}"; + public static readonly string SeedListKey = $"{SectionKey}:{nameof(ProtocolSettings.SeedList)}"; + public static readonly string MillisecondsPerBlockKey = $"{SectionKey}:{nameof(ProtocolSettings.MillisecondsPerBlock)}"; + public static readonly string MaxTransactionsPerBlockKey = $"{SectionKey}:{nameof(ProtocolSettings.MaxTransactionsPerBlock)}"; + public static readonly string MemoryPoolMaxTransactionsKey = $"{SectionKey}:{nameof(ProtocolSettings.MemoryPoolMaxTransactions)}"; + public static readonly string MaxTraceableBlocksKey = $"{SectionKey}:{nameof(ProtocolSettings.MaxTraceableBlocks)}"; + public static readonly string InitialGasDistributionKey = $"{SectionKey}:{nameof(ProtocolSettings.InitialGasDistribution)}"; + } +} diff --git a/src/Neo.Wallet/Json/DevWalletAccountModel.cs b/src/Neo.Wallet/Json/DevWalletAccountModel.cs index 0cb0e19..786b2b8 100644 --- a/src/Neo.Wallet/Json/DevWalletAccountModel.cs +++ b/src/Neo.Wallet/Json/DevWalletAccountModel.cs @@ -20,7 +20,7 @@ // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES -using Neo.Configuration.Json; +using Neo.Configuration; using Neo.Configuration.Json.Converters; using Neo.Core; using Neo.Core.Interfaces; @@ -30,7 +30,7 @@ namespace Neo.Wallet.Json { - public class DevWalletAccountModel : WalletAccountModel, IMap + public class DevWalletAccountModel : WalletAccountModel, IMap { [JsonConverter(typeof(JsonStringHexFormatConverter))] public override byte[]? Key { get => base.Key; set => base.Key = value; } diff --git a/src/Neo.Wallet/Json/Nep6WalletAccountModel.cs b/src/Neo.Wallet/Json/Nep6WalletAccountModel.cs index e05f6b5..3486a18 100644 --- a/src/Neo.Wallet/Json/Nep6WalletAccountModel.cs +++ b/src/Neo.Wallet/Json/Nep6WalletAccountModel.cs @@ -20,14 +20,14 @@ // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES -using Neo.Configuration.Json; +using Neo.Configuration; using Neo.Configuration.Json.Converters; using Neo.Core.Interfaces; using System.Text.Json.Serialization; namespace Neo.Wallet.Json { - public class Nep6WalletAccountModel : WalletAccountModel, IMap + public class Nep6WalletAccountModel : WalletAccountModel, IMap { [JsonConverter(typeof(JsonStringByteArrayConverter))] public override byte[]? Key { get => base.Key; set => base.Key = value; } diff --git a/tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs b/tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs index 2758e3f..b0c43e8 100644 --- a/tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs +++ b/tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs @@ -33,7 +33,7 @@ public class UT_ProtocolSettingsModel public void TestPropertyValues() { var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"seed1.neo.org:10333\"]}"; - var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions); + var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions); Assert.IsNotNull(actualProtocolOptionsModel); @@ -60,7 +60,7 @@ public void TestPropertyValues() public void TestObjectToProtocolSettings() { var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"seed1.neo.org:10333\"]}"; - var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions); + var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions); Assert.IsNotNull(actualProtocolOptionsModel); From 7754f17d289ff4405f1ea31e0479b687e26829cf Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Tue, 23 Jun 2026 01:36:47 -0400 Subject: [PATCH 2/4] Checkpoint added `hosting` project --- .../ProtocolSettingsOptions.cs | 3 +- src/Neo.Core/Cryptography/ECC/ECPoint.cs | 3 + src/Neo.Core/ProtocolSettings.cs | 3 +- .../Types/Converter/ECPointTypeConverter.cs | 54 +++++++++ .../Types/Converter/IPAddressTypeConverter.cs | 46 ++++++++ .../Converter/IPEndPointTypeConverter.cs | 72 ++++++++++++ .../NeoEnvironmentConfigurationSource.cs | 36 ++++++ .../ConfigurationBuilderExtensions.cs | 37 ++++++ .../Factory/NeoHostingFactory.cs | 52 ++++++++ .../HostEnvironmentExtensions.cs | 54 +++++++++ src/Neo.Platform.Hosting/HostingExtensions.cs | 9 +- .../Logging/NeoPlatformLoggerProvider.cs | 2 +- .../NeoHostingEnvironments.cs | 36 ++++++ .../ProtocolSettingNames.cs | 2 +- .../NeoEnvironmentConfigurationProvider.cs | 111 ++++++++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 4 +- ...Model.cs => UT_ProtocolSettingsOptions.cs} | 25 ++-- tests/Neo.VM.Tests/Neo.VM.Tests.csproj | 1 + tests/Neo.VM.Tests/TestUtilities.cs | 7 +- 19 files changed, 540 insertions(+), 17 deletions(-) create mode 100644 src/Neo.Core/Types/Converter/ECPointTypeConverter.cs create mode 100644 src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs create mode 100644 src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs create mode 100644 src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs create mode 100644 src/Neo.Platform.Hosting/ConfigurationBuilderExtensions.cs create mode 100644 src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs create mode 100644 src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs create mode 100644 src/Neo.Platform.Hosting/NeoHostingEnvironments.cs create mode 100644 src/Neo.Platform.Hosting/Provider/NeoEnvironmentConfigurationProvider.cs rename tests/Neo.Configuration.Tests/{Json/UT_ProtocolSettingsModel.cs => UT_ProtocolSettingsOptions.cs} (81%) diff --git a/src/Neo.Configuration/ProtocolSettingsOptions.cs b/src/Neo.Configuration/ProtocolSettingsOptions.cs index 065068d..5d1103f 100644 --- a/src/Neo.Configuration/ProtocolSettingsOptions.cs +++ b/src/Neo.Configuration/ProtocolSettingsOptions.cs @@ -28,6 +28,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Net; namespace Neo.Configuration { @@ -49,7 +50,7 @@ public class ProtocolSettingsOptions : JsonModel, IMap public int ValidatorsCount { get; set; } - public string[]? SeedList { get; set; } + public IPEndPoint[]? SeedList { get; set; } public IDictionary? HardForks { get; set; } diff --git a/src/Neo.Core/Cryptography/ECC/ECPoint.cs b/src/Neo.Core/Cryptography/ECC/ECPoint.cs index 4fbb837..7a611ca 100644 --- a/src/Neo.Core/Cryptography/ECC/ECPoint.cs +++ b/src/Neo.Core/Cryptography/ECC/ECPoint.cs @@ -22,7 +22,9 @@ using Neo.Core.Extensions; using Neo.Core.Serialization; +using Neo.Core.Types.Converter; using System; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Numerics; @@ -30,6 +32,7 @@ namespace Neo.Core.Cryptography.ECC { + [TypeConverter(typeof(ECPointTypeConverter))] public class ECPoint : IEquatable, IComparable, IComparable, INeoSerializable { public const int UncompressedLength = 65; diff --git a/src/Neo.Core/ProtocolSettings.cs b/src/Neo.Core/ProtocolSettings.cs index c4d11fd..8121922 100644 --- a/src/Neo.Core/ProtocolSettings.cs +++ b/src/Neo.Core/ProtocolSettings.cs @@ -26,6 +26,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Net; namespace Neo.Core { @@ -59,7 +60,7 @@ public class ProtocolSettings /// /// The default seed nodes list. /// - public IReadOnlyList SeedList { get; init; } = []; + public IPEndPoint[] SeedList { get; init; } = []; /// /// Indicates the time in milliseconds between two blocks. Note that starting from diff --git a/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs b/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs new file mode 100644 index 0000000..e98c0f2 --- /dev/null +++ b/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs @@ -0,0 +1,54 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Neo.Core.Cryptography.ECC; +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Neo.Core.Types.Converter +{ + internal class ECPointTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => + sourceType == typeof(string) && base.CanConvertFrom(context, sourceType); + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is string pointString) + { + if (ECPoint.TryParse(pointString, ECCurve.SecP256r1, out var point)) + return point; + } + + return base.ConvertFrom(context, culture, value); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) => + CanConvertTo(context, destinationType) ? + value switch + { + ECPoint e => e.Encode(shouldCompress: true), + _ => base.ConvertTo(context, culture, value, destinationType), + } : base.ConvertTo(context, culture, value, destinationType); + } +} diff --git a/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs b/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs new file mode 100644 index 0000000..295601b --- /dev/null +++ b/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs @@ -0,0 +1,46 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using System; +using System.ComponentModel; +using System.Globalization; +using System.Net; + +namespace Neo.Core.Types.Converter +{ + public class IPAddressTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => + sourceType == typeof(string) && base.CanConvertFrom(context, sourceType); + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is string ipString) + { + if (IPAddress.TryParse(ipString, out var ipAddress)) + return ipAddress; + } + + return base.ConvertFrom(context, culture, value); + } + } +} diff --git a/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs b/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs new file mode 100644 index 0000000..9002655 --- /dev/null +++ b/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs @@ -0,0 +1,72 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using System; +using System.ComponentModel; +using System.Globalization; +using System.Net; +using System.Net.Sockets; + +namespace Neo.Core.Types.Converter +{ + public class IPEndPointTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => + sourceType == typeof(string) && base.CanConvertFrom(context, sourceType); + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is string endPointString) + { + if (IPEndPoint.TryParse(endPointString, out var ipEndPoint)) + return ipEndPoint; + else + { + // Try to see if its a hostname + var spString = endPointString.Split(':', StringSplitOptions.TrimEntries); + if (spString.Length == 2) + { + try + { + var host = Dns.GetHostEntry(spString[0]); + if (host.AddressList.Length > 0) + { + var length = host.AddressList.Length - 1; + var ipAddress = host.AddressList + [ + length > 0 ? Random.Shared.Next(length) : 0 + ]; + + return new IPEndPoint(ipAddress, int.Parse(spString[1])); + } + } + catch (SocketException) // Host doesn't exist or network is down + { + } + } + } + } + + return base.ConvertFrom(context, culture, value); + } + } +} diff --git a/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs b/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs new file mode 100644 index 0000000..9f7a9be --- /dev/null +++ b/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs @@ -0,0 +1,36 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.Configuration; +using Neo.Platform.Hosting.Provider; +using System.Collections.Generic; + +namespace Neo.Platform.Hosting.Configuration +{ + internal class NeoEnvironmentConfigurationSource : IConfigurationSource + { + public IEnumerable>? InitialData { get; set; } + + public IConfigurationProvider Build(IConfigurationBuilder builder) => + new NeoEnvironmentConfigurationProvider(this); + } +} diff --git a/src/Neo.Platform.Hosting/ConfigurationBuilderExtensions.cs b/src/Neo.Platform.Hosting/ConfigurationBuilderExtensions.cs new file mode 100644 index 0000000..1d4eb0a --- /dev/null +++ b/src/Neo.Platform.Hosting/ConfigurationBuilderExtensions.cs @@ -0,0 +1,37 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.Configuration; +using Neo.Platform.Hosting.Configuration; +using System.Collections.Generic; + +namespace Neo.Platform.Hosting +{ + public static class ConfigurationBuilderExtensions + { + public static IConfigurationBuilder AddNeoPlatformConfiguration(this IConfigurationBuilder builder, IEnumerable>? initialData = default) + { + builder.Add(new NeoEnvironmentConfigurationSource() { InitialData = initialData, }); + return builder; + } + } +} diff --git a/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs b/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs new file mode 100644 index 0000000..f4b0ace --- /dev/null +++ b/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs @@ -0,0 +1,52 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using System.IO; +using System.Threading; + +namespace Neo.Platform.Hosting.Factory +{ + internal static class NeoHostingFactory + { + private static readonly uint s_networkSeed = 810960196u; // DEV0 Magic Code + private static readonly string s_globalString = "Global\\"; + + public static uint GetDevNetwork(uint index) => + s_networkSeed & ~(0xfu << 24) | (index << 24); + + public static FileInfo ResolveFileName(string filename, string rootPath) + { + if (Path.IsPathRooted(filename)) + return new(filename); + if (Path.IsPathRooted(rootPath) == false) + rootPath = Path.GetFullPath(rootPath); + return new(Path.Combine(rootPath, filename)); + } + + public static Mutex CreateMutex(string? name = null) + { + if (string.IsNullOrEmpty(name)) + name = Path.Combine(s_globalString, Path.GetRandomFileName()); + return new(initiallyOwned: true, name); + } + } +} diff --git a/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs b/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs new file mode 100644 index 0000000..2adefa1 --- /dev/null +++ b/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs @@ -0,0 +1,54 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.Hosting; +using System; + +namespace Neo.Platform.Hosting +{ + public static class HostEnvironmentExtensions + { + public static bool IsLocalNet(this IHostEnvironment hostEnvironment) + { + ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment)); + return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.LocalNet); + } + + public static bool IsTestNet(this IHostEnvironment hostEnvironment) + { + ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment)); + return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.TestNet); + } + + public static bool IsMainNet(this IHostEnvironment hostEnvironment) + { + ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment)); + return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.MainNet); + } + + public static bool IsPrivateNet(this IHostEnvironment hostEnvironment) + { + ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment)); + return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.PrivateNet); + } + } +} diff --git a/src/Neo.Platform.Hosting/HostingExtensions.cs b/src/Neo.Platform.Hosting/HostingExtensions.cs index 6d1d018..ee0b2e3 100644 --- a/src/Neo.Platform.Hosting/HostingExtensions.cs +++ b/src/Neo.Platform.Hosting/HostingExtensions.cs @@ -27,8 +27,11 @@ using Microsoft.Extensions.Logging.EventLog; using Neo.Configuration; using Neo.Core; +using Neo.Core.Types.Converter; using Neo.Platform.Hosting.Logging; using System; +using System.ComponentModel; +using System.Net; namespace Neo.Platform.Hosting { @@ -45,7 +48,7 @@ public static IHostBuilder UseNeoPlatformConfiguration(this IHostBuilder builder var manger = new ConfigurationManager(); config.AddConfiguration(manger); - // TODO: Add neo platform configuration + config.AddNeoPlatformConfiguration(); } ); @@ -130,6 +133,10 @@ public static IHostBuilder UseNeoPlatformConfiguration(this IHostBuilder builder } ); + // IConfiguration.Get() Converting + TypeDescriptor.AddAttributes(typeof(IPAddress), new TypeConverterAttribute(typeof(IPAddressTypeConverter))); + TypeDescriptor.AddAttributes(typeof(IPEndPoint), new TypeConverterAttribute(typeof(IPEndPointTypeConverter))); + return builder; } diff --git a/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs index e9b4a3f..6882d01 100644 --- a/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs +++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs @@ -30,7 +30,7 @@ namespace Neo.Platform.Hosting.Logging { [UnsupportedOSPlatform("browser")] [ProviderAlias("NeoPlatform")] - public sealed class NeoPlatformLoggerProvider : ILoggerProvider + internal sealed class NeoPlatformLoggerProvider : ILoggerProvider { private readonly IDisposable? _onChangeToken; private readonly ConcurrentDictionary _loggers = new(StringComparer.OrdinalIgnoreCase); diff --git a/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs b/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs new file mode 100644 index 0000000..f234eb9 --- /dev/null +++ b/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs @@ -0,0 +1,36 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +namespace Neo.Platform.Hosting +{ + internal static class NeoHostingEnvironmentNames + { + public static readonly string LocalNet = "localnet"; + + public static readonly string TestNet = "testnet"; + + public static readonly string MainNet = "mainnet"; + + public static readonly string PrivateNet = "privatenet"; + + } +} diff --git a/src/Neo.Platform.Hosting/ProtocolSettingNames.cs b/src/Neo.Platform.Hosting/ProtocolSettingNames.cs index c74e3a7..a34e65b 100644 --- a/src/Neo.Platform.Hosting/ProtocolSettingNames.cs +++ b/src/Neo.Platform.Hosting/ProtocolSettingNames.cs @@ -26,7 +26,7 @@ namespace Neo.Platform.Hosting { public static class ProtocolSettingNames { - public static readonly string SectionKey = nameof(ProtocolSettings); + public static readonly string SectionKey = "Protocol"; public static readonly string NetworkKey = $"{SectionKey}:{nameof(ProtocolSettings.Network)}"; public static readonly string AddressVersionKey = $"{SectionKey}:{nameof(ProtocolSettings.AddressVersion)}"; diff --git a/src/Neo.Platform.Hosting/Provider/NeoEnvironmentConfigurationProvider.cs b/src/Neo.Platform.Hosting/Provider/NeoEnvironmentConfigurationProvider.cs new file mode 100644 index 0000000..ac40e2e --- /dev/null +++ b/src/Neo.Platform.Hosting/Provider/NeoEnvironmentConfigurationProvider.cs @@ -0,0 +1,111 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Neo.Platform.Hosting.Configuration; +using Neo.Platform.Hosting.Factory; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Neo.Platform.Hosting.Provider +{ + internal sealed class NeoEnvironmentConfigurationProvider : ConfigurationProvider + { + private const string PREFIX = "NEO"; + + private readonly NeoEnvironmentConfigurationSource _source; + + public NeoEnvironmentConfigurationProvider( + NeoEnvironmentConfigurationSource source) + { + _source = source; + + // ================================== + // * NEO Platform Initial Defaults * + // ================================== + + // Hosting Environment + Data.Add(HostDefaults.EnvironmentKey, NeoHostingEnvironmentNames.LocalNet); + Data.Add(HostDefaults.ContentRootKey, Environment.CurrentDirectory); + + // Protocol Configuration + var protocolNetwork = NeoHostingFactory.GetDevNetwork(0); + + Data.Add(ProtocolSettingNames.NetworkKey, $"{protocolNetwork:d}"); + Data.Add(ProtocolSettingNames.AddressVersionKey, "53"); + Data.Add(ProtocolSettingNames.MillisecondsPerBlockKey, "1000"); + Data.Add(ProtocolSettingNames.MaxTransactionsPerBlockKey, "512"); + Data.Add(ProtocolSettingNames.MemoryPoolMaxTransactionsKey, "50000"); + Data.Add(ProtocolSettingNames.MaxTraceableBlocksKey, "2102400"); + Data.Add(ProtocolSettingNames.InitialGasDistributionKey, "5200000000000000"); + + // ================================== + // * End of Initial Defaults * + // ================================== + + // Application Overrides Above Defaults + + if (_source.InitialData is not null) + { + foreach (var pair in _source.InitialData) + Data.Add(pair.Key, pair.Value); + } + + // Environment Overrides Above Defaults + Load(Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User)); + Load(Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process)); + } + + private void Load(IDictionary envVariables) + { + var iter = envVariables.GetEnumerator(); + + try + { + while (iter.MoveNext()) + { + var key = (string)iter.Entry.Key; + var value = (string?)iter.Entry.Value; + + AddIfNormalizedKeyMatchesPrefix(Data, Normalize(key), value); + } + } + finally + { + (iter as IDisposable)?.Dispose(); + } + } + + private static void AddIfNormalizedKeyMatchesPrefix(IDictionary data, string normalizedKey, string? value) + { + var normalizedPrefix = PREFIX + ':'; + + if (normalizedKey.StartsWith(normalizedPrefix, StringComparison.OrdinalIgnoreCase)) + data[normalizedKey[normalizedPrefix.Length..]] = value; + } + + private static string Normalize(string key) => + key.Replace("_", ConfigurationPath.KeyDelimiter); + } +} diff --git a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs index 42edcd6..4e748b4 100644 --- a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs +++ b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs @@ -34,10 +34,10 @@ public static IServiceCollection AddExecutionEngine(this IServiceCollection serv // Register VirtualMachineEngine using the built pipeline services.AddScoped(sp => { - var middleware = sp.GetServices(); // NOTE: Get all custom middleware + var middleware = sp.GetServices(); // NOTE: Get all custom middleware var debugger = sp.GetRequiredService(); // NOTE: get the debugger middleware var pipeline = VirtualMachinePipelineBuilder.Create() - .Use([debugger, .. middleware]) // NOTE: ORDER MATTERS HERE + .Use([debugger, .. middleware]) // NOTE: ORDER MATTERS HERE .Build(); return VirtualMachineBuilder.Create() diff --git a/tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs b/tests/Neo.Configuration.Tests/UT_ProtocolSettingsOptions.cs similarity index 81% rename from tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs rename to tests/Neo.Configuration.Tests/UT_ProtocolSettingsOptions.cs index b0c43e8..c7e8166 100644 --- a/tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs +++ b/tests/Neo.Configuration.Tests/UT_ProtocolSettingsOptions.cs @@ -23,16 +23,17 @@ using Neo.Configuration.Json; using Neo.Core.Cryptography.ECC; using Neo.Core.VM; +using System.Net; -namespace Neo.Configuration.Tests.Json +namespace Neo.Configuration.Tests { [TestClass] - public class UT_ProtocolSettingsModel + public class UT_ProtocolSettingsOptions { [TestMethod] public void TestPropertyValues() { - var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"seed1.neo.org:10333\"]}"; + var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"127.0.0.1:10333\"]}"; var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions); Assert.IsNotNull(actualProtocolOptionsModel); @@ -50,16 +51,19 @@ public void TestPropertyValues() Assert.AreEqual(666u, actualProtocolOptionsModel.HardForks[HardFork.Aspidochelone]); Assert.IsNotNull(actualProtocolOptionsModel.StandbyCommittee); + Assert.HasCount(1, actualProtocolOptionsModel.StandbyCommittee); Assert.AreEqual(ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.SecP256r1), actualProtocolOptionsModel.StandbyCommittee[0]); Assert.IsNotNull(actualProtocolOptionsModel.SeedList); - Assert.AreEqual("seed1.neo.org:10333", actualProtocolOptionsModel.SeedList[0]); + Assert.HasCount(1, actualProtocolOptionsModel.SeedList); + Assert.AreEqual(IPAddress.Parse("127.0.0.1"), actualProtocolOptionsModel.SeedList[0].Address); + Assert.AreEqual(10333, actualProtocolOptionsModel.SeedList[0].Port); } [TestMethod] public void TestObjectToProtocolSettings() { - var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"seed1.neo.org:10333\"]}"; + var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"127.0.0.1:10333\"]}"; var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions); Assert.IsNotNull(actualProtocolOptionsModel); @@ -78,11 +82,14 @@ public void TestObjectToProtocolSettings() Assert.IsNotNull(actualProtocolSettings.HardForks); Assert.AreEqual(666u, actualProtocolSettings.HardForks[HardFork.Aspidochelone]); - Assert.IsNotNull(actualProtocolSettings.StandbyCommittee); - Assert.AreEqual(ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.SecP256r1), actualProtocolSettings.StandbyCommittee[0]); + Assert.IsNotNull(actualProtocolOptionsModel.StandbyCommittee); + Assert.HasCount(1, actualProtocolOptionsModel.StandbyCommittee); + Assert.AreEqual(ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.SecP256r1), actualProtocolOptionsModel.StandbyCommittee[0]); - Assert.IsNotNull(actualProtocolSettings.SeedList); - Assert.AreEqual("seed1.neo.org:10333", actualProtocolSettings.SeedList[0]); + Assert.IsNotNull(actualProtocolOptionsModel.SeedList); + Assert.HasCount(1, actualProtocolOptionsModel.SeedList); + Assert.AreEqual(IPAddress.Parse("127.0.0.1"), actualProtocolOptionsModel.SeedList[0].Address); + Assert.AreEqual(10333, actualProtocolOptionsModel.SeedList[0].Port); } } } diff --git a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj index 580c0eb..62edadf 100644 --- a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj +++ b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj @@ -19,6 +19,7 @@ + diff --git a/tests/Neo.VM.Tests/TestUtilities.cs b/tests/Neo.VM.Tests/TestUtilities.cs index 3504506..98b313d 100644 --- a/tests/Neo.VM.Tests/TestUtilities.cs +++ b/tests/Neo.VM.Tests/TestUtilities.cs @@ -20,9 +20,10 @@ // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Neo.Core.Logging; +using Neo.Platform.Hosting.Logging; using Neo.VM.Extensions; using System; @@ -35,6 +36,8 @@ internal class TestUtilities public static readonly ILoggerFactory TraceLoggerFactory = LoggerFactory.Create(logging => { + var manger = new ConfigurationManager(); + logging.AddConfiguration(manger); logging.ClearProviders(); logging.AddNeoPlatform(); logging.SetMinimumLevel(LogLevel.Trace); @@ -52,6 +55,8 @@ static TestUtilities() .AddLogging( logging => { + var manger = new ConfigurationManager(); + logging.AddConfiguration(manger); logging.ClearProviders(); logging.AddNeoPlatform(); // Custom logger for our platform (file, debugger, console, etc) logging.SetMinimumLevel(LogLevel.Trace); From 50664288b22b9c35d25fefe66eff4296b2a45144 Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Tue, 23 Jun 2026 06:06:11 -0400 Subject: [PATCH 3/4] Fixed tests --- .../Converters/JsonIPEndPointConverter.cs | 82 +++++++++++++++++++ src/Neo.Configuration/Json/JsonDefaults.cs | 1 + tests/Neo.Configuration.Tests/TestDefaults.cs | 1 + tests/Neo.Wallet.Tests/TestDefaults.cs | 1 + 4 files changed, 85 insertions(+) create mode 100644 src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs diff --git a/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs b/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs new file mode 100644 index 0000000..5a8658e --- /dev/null +++ b/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs @@ -0,0 +1,82 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using System; +using System.Net; +using System.Net.Sockets; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Neo.Configuration.Json.Converters +{ + public class JsonIPEndPointConverter : JsonConverter + { + public override IPEndPoint? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + throw new FormatException(); + + var valueString = reader.GetString(); + + if (string.IsNullOrEmpty(valueString)) + return default; + + if (IPEndPoint.TryParse(valueString, out var value)) + return value; + else + { + // Try to see if its a hostname + var spString = valueString.Split(':', StringSplitOptions.TrimEntries); + if (spString.Length == 2) + { + try + { + var host = Dns.GetHostEntry(spString[0]); + if (host.AddressList.Length > 0) + { + var length = host.AddressList.Length - 1; + var ipAddress = host.AddressList + [ + length > 0 ? Random.Shared.Next(length) : 0 + ]; + + return new IPEndPoint(ipAddress, int.Parse(spString[1])); + } + } + catch (SocketException) // Host doesn't exist or network is down + { + } + } + } + + return default; + } + + public override void Write(Utf8JsonWriter writer, IPEndPoint? value, JsonSerializerOptions options) + { + if (value is null) + writer.WriteNullValue(); + else + writer.WriteStringValue($"{value}"); + } + } +} diff --git a/src/Neo.Configuration/Json/JsonDefaults.cs b/src/Neo.Configuration/Json/JsonDefaults.cs index d86631c..6a34d92 100644 --- a/src/Neo.Configuration/Json/JsonDefaults.cs +++ b/src/Neo.Configuration/Json/JsonDefaults.cs @@ -48,6 +48,7 @@ public static class JsonDefaults new JsonStringEnumConverter(), new JsonStringECPointConverter(), new JsonStringUInt160Converter(), + new JsonIPEndPointConverter(), }, }; } diff --git a/tests/Neo.Configuration.Tests/TestDefaults.cs b/tests/Neo.Configuration.Tests/TestDefaults.cs index 6e40bb0..0721636 100644 --- a/tests/Neo.Configuration.Tests/TestDefaults.cs +++ b/tests/Neo.Configuration.Tests/TestDefaults.cs @@ -48,6 +48,7 @@ internal static class TestDefaults new JsonStringEnumConverter(), new JsonStringECPointConverter(), new JsonStringUInt160Converter(), + new JsonIPEndPointConverter(), } }; } diff --git a/tests/Neo.Wallet.Tests/TestDefaults.cs b/tests/Neo.Wallet.Tests/TestDefaults.cs index 7c7f7f5..3871683 100644 --- a/tests/Neo.Wallet.Tests/TestDefaults.cs +++ b/tests/Neo.Wallet.Tests/TestDefaults.cs @@ -53,6 +53,7 @@ internal static class TestDefaults new JsonStringEnumConverter(), new JsonStringECPointConverter(), new JsonStringUInt160Converter(), + new JsonIPEndPointConverter(), } }; From a31e23895cc3a97d8050ca2a4be64082d3d6b5c6 Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Thu, 25 Jun 2026 02:39:37 -0400 Subject: [PATCH 4/4] Added Hosting and CLI --- src/Neo.Platform.CLI/Commands/EmptyHandler.cs | 37 ++++++ .../Commands/ProgramRootCommand.cs | 36 ++++++ src/Neo.Platform.CLI/Commands/ShowCommand.cs | 50 ++++++++ src/Neo.Platform.CLI/Neo.Platform.CLI.csproj | 12 ++ src/Neo.Platform.CLI/Program.cs | 24 +++- .../Properties/launchSettings.json | 8 ++ src/Neo.Platform.CLI/config.json | 3 + src/Neo.Platform.CLI/protocol.json | 3 + src/Neo.Platform.CLI/vm.json | 3 + .../Builder/PlatformCommandLineBuilder.cs | 116 ++++++++++++++++++ .../ConfigurationBuilderExtensions.cs | 3 +- src/Neo.Platform.Hosting/HostingExtensions.cs | 31 ++++- .../Logging/NeoPlatformLogger.cs | 38 ++++-- .../Middleware/CommandLineMiddleware.cs | 28 +++++ .../PlatformCommandLine.cs | 56 +++++++++ .../VirtualMachineBuilder.cs | 2 +- .../VirtualMachinePipelineBuilder.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 2 +- 18 files changed, 431 insertions(+), 23 deletions(-) create mode 100644 src/Neo.Platform.CLI/Commands/EmptyHandler.cs create mode 100644 src/Neo.Platform.CLI/Commands/ProgramRootCommand.cs create mode 100644 src/Neo.Platform.CLI/Commands/ShowCommand.cs create mode 100644 src/Neo.Platform.CLI/Properties/launchSettings.json create mode 100644 src/Neo.Platform.CLI/config.json create mode 100644 src/Neo.Platform.CLI/protocol.json create mode 100644 src/Neo.Platform.CLI/vm.json create mode 100644 src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs rename src/Neo.Platform.Hosting/{ => Configuration}/ConfigurationBuilderExtensions.cs (95%) create mode 100644 src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs create mode 100644 src/Neo.Platform.Hosting/PlatformCommandLine.cs rename src/Neo.VM/{Builders => Builder}/VirtualMachineBuilder.cs (99%) rename src/Neo.VM/{Builders => Builder}/VirtualMachinePipelineBuilder.cs (99%) diff --git a/src/Neo.Platform.CLI/Commands/EmptyHandler.cs b/src/Neo.Platform.CLI/Commands/EmptyHandler.cs new file mode 100644 index 0000000..ccb8028 --- /dev/null +++ b/src/Neo.Platform.CLI/Commands/EmptyHandler.cs @@ -0,0 +1,37 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using System.CommandLine; +using System.CommandLine.Invocation; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.Platform.CLI.Commands +{ + public class EmptyHandler : AsynchronousCommandLineAction + { + public override Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken = default) + { + return Task.FromResult(0); + } + } +} diff --git a/src/Neo.Platform.CLI/Commands/ProgramRootCommand.cs b/src/Neo.Platform.CLI/Commands/ProgramRootCommand.cs new file mode 100644 index 0000000..b4e2d1a --- /dev/null +++ b/src/Neo.Platform.CLI/Commands/ProgramRootCommand.cs @@ -0,0 +1,36 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using System.CommandLine; + +namespace Neo.Platform.CLI.Commands +{ + internal class ProgramRootCommand : RootCommand + { + public ProgramRootCommand() : base("Neo Platform Command-line Tool") + { + var showCommand = new ShowCommand(); + + Add(showCommand); + } + } +} diff --git a/src/Neo.Platform.CLI/Commands/ShowCommand.cs b/src/Neo.Platform.CLI/Commands/ShowCommand.cs new file mode 100644 index 0000000..c1515fa --- /dev/null +++ b/src/Neo.Platform.CLI/Commands/ShowCommand.cs @@ -0,0 +1,50 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.Logging; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.Platform.CLI.Commands +{ + internal class ShowCommand : Command + { + public ShowCommand() : base("show", "Shows many configurations") + { + + } + + public class Handler(ILoggerFactory loggerFactory) : AsynchronousCommandLineAction + { + private readonly ILogger _logger = loggerFactory.CreateLogger(); + + public override Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken = default) + { + _logger.LogInformation("Hello World!!!!"); + + return Task.FromResult(0); + } + } + } +} diff --git a/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj b/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj index 31bbef8..8c6ee08 100644 --- a/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj +++ b/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj @@ -10,7 +10,19 @@ + + + + + + + + + Always + + + diff --git a/src/Neo.Platform.CLI/Program.cs b/src/Neo.Platform.CLI/Program.cs index 7c8f400..6f5a1b6 100644 --- a/src/Neo.Platform.CLI/Program.cs +++ b/src/Neo.Platform.CLI/Program.cs @@ -20,18 +20,34 @@ // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES -using System; +using Microsoft.Extensions.Hosting; +using Neo.Platform.CLI.Commands; +using Neo.Platform.Hosting; +using Neo.Platform.Hosting.Builder; using System.Threading.Tasks; namespace Neo.Platform.CLI { internal class Program { - private static Task Main(string[] args) + private static Task Main(string[] args) { - Console.WriteLine("Hello, World!"); + var rootCommand = new ProgramRootCommand(); + var cmd = new PlatformCommandLineBuilder(rootCommand, args) + .UseHost(DefaultNeoBuildHostFactory, builder => + { + //builder.UseCommandAction(); + builder.UseCommandAction(); + }) + .EnablePosixBundling() + .EnableDefaultExceptionHandler(false) + .Build(); - return Task.CompletedTask; + return cmd.InvokeAsync(); } + + private static IHostBuilder DefaultNeoBuildHostFactory(string[] args) => + new HostBuilder() + .UseNeoPlatformConfiguration(); } } diff --git a/src/Neo.Platform.CLI/Properties/launchSettings.json b/src/Neo.Platform.CLI/Properties/launchSettings.json new file mode 100644 index 0000000..54a8d6a --- /dev/null +++ b/src/Neo.Platform.CLI/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Neo.Platform.CLI": { + "commandName": "Project", + "commandLineArgs": "show" + } + } +} \ No newline at end of file diff --git a/src/Neo.Platform.CLI/config.json b/src/Neo.Platform.CLI/config.json new file mode 100644 index 0000000..0db3279 --- /dev/null +++ b/src/Neo.Platform.CLI/config.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/src/Neo.Platform.CLI/protocol.json b/src/Neo.Platform.CLI/protocol.json new file mode 100644 index 0000000..0db3279 --- /dev/null +++ b/src/Neo.Platform.CLI/protocol.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/src/Neo.Platform.CLI/vm.json b/src/Neo.Platform.CLI/vm.json new file mode 100644 index 0000000..0db3279 --- /dev/null +++ b/src/Neo.Platform.CLI/vm.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs b/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs new file mode 100644 index 0000000..43876ef --- /dev/null +++ b/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs @@ -0,0 +1,116 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Neo.Platform.Hosting.Middleware; +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.Linq; + +namespace Neo.Platform.Hosting.Builder +{ + public class PlatformCommandLineBuilder(Command? rootCommand = default, string[]? args = default) + { + internal Command Command { get; } = rootCommand ?? new RootCommand(); + internal ParserConfiguration ParserConfiguration { get; } = new(); + internal InvocationConfiguration InvocationConfiguration { get; } = new(); + + private readonly List> _middleware = []; + private IHostBuilder? _hostBuilder; + + internal void AddMiddleware(Action middleware) => + _middleware.Add(middleware); + + public PlatformCommandLineBuilder UseHost( + Func? hostBuilderFactory, + Action? configure = default) + { + AddMiddleware(async (parseResult, next) => + { + var argsRemaining = parseResult.UnmatchedTokens.ToArray(); + _hostBuilder = hostBuilderFactory?.Invoke(argsRemaining) ?? + new HostBuilder(); + + _hostBuilder.Properties[typeof(ParseResult)] = parseResult; + + _hostBuilder.ConfigureServices(services => + { + services.AddTransient(_ => parseResult); + }); + + _hostBuilder.UseNeoPlatformLifetime(); + configure?.Invoke(_hostBuilder); + + next(parseResult); + }); + + return this; + } + + public PlatformCommandLineBuilder EnableDefaultExceptionHandler(bool value = true) + { + InvocationConfiguration.EnableDefaultExceptionHandler = value; + return this; + } + + public PlatformCommandLineBuilder SetProcessTerminationTimeout(TimeSpan timeout) + { + InvocationConfiguration.ProcessTerminationTimeout = timeout; + return this; + } + + public PlatformCommandLineBuilder EnablePosixBundling(bool value = true) + { + ParserConfiguration.EnablePosixBundling = value; + return this; + } + + public PlatformCommandLine Build() + { + var parseResults = Command.Parse(args ?? [], ParserConfiguration); + + BuildChain().Invoke(parseResults); + + var host = (_hostBuilder ?? new HostBuilder()).Build(); + return new(parseResults, host, InvocationConfiguration); + } + + private PlatformCommandLineNextDelegate BuildChain() + { + PlatformCommandLineNextDelegate app = _ => { }; // terminal handler (no-op) + + // Build chain in reverse so first registered middleware runs first + for (var i = _middleware.Count - 1; i >= 0; i--) + { + var middleware = _middleware[i]; + var current = middleware; + var next = app; + + app = context => current(context, next); + } + + return app; + } + } +} diff --git a/src/Neo.Platform.Hosting/ConfigurationBuilderExtensions.cs b/src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs similarity index 95% rename from src/Neo.Platform.Hosting/ConfigurationBuilderExtensions.cs rename to src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs index 1d4eb0a..ebffc0c 100644 --- a/src/Neo.Platform.Hosting/ConfigurationBuilderExtensions.cs +++ b/src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs @@ -21,10 +21,9 @@ // SERVICES using Microsoft.Extensions.Configuration; -using Neo.Platform.Hosting.Configuration; using System.Collections.Generic; -namespace Neo.Platform.Hosting +namespace Neo.Platform.Hosting.Configuration { public static class ConfigurationBuilderExtensions { diff --git a/src/Neo.Platform.Hosting/HostingExtensions.cs b/src/Neo.Platform.Hosting/HostingExtensions.cs index ee0b2e3..de711ab 100644 --- a/src/Neo.Platform.Hosting/HostingExtensions.cs +++ b/src/Neo.Platform.Hosting/HostingExtensions.cs @@ -28,8 +28,11 @@ using Neo.Configuration; using Neo.Core; using Neo.Core.Types.Converter; +using Neo.Platform.Hosting.Configuration; using Neo.Platform.Hosting.Logging; using System; +using System.CommandLine; +using System.CommandLine.Invocation; using System.ComponentModel; using System.Net; @@ -38,6 +41,26 @@ namespace Neo.Platform.Hosting public static class HostingExtensions { + public static IHostBuilder UseCommandAction(this IHostBuilder builder) + where TCommand : Command + where TCommandAction : CommandLineAction + { + if (builder.Properties[typeof(ParseResult)] is ParseResult parseResult && + parseResult.CommandResult.Command is Command command && + command.GetType() == typeof(TCommand)) + { + builder.ConfigureServices(services => + { + services.AddTransient(); + + var sp = services.BuildServiceProvider(); + command.Action = sp.GetRequiredService(); + }); + } + + return builder; + } + public static IHostBuilder UseNeoPlatformConfiguration(this IHostBuilder builder) { // Host Configuration @@ -122,8 +145,8 @@ public static IHostBuilder UseNeoPlatformConfiguration(this IHostBuilder builder } ); - builder.AddNeoPlatformLifetime(); - builder.AddNeoPlatformOptions(); + builder.UseNeoPlatformLifetime(); + builder.UseNeoPlatformOptions(); builder.ConfigureServices ( @@ -140,7 +163,7 @@ public static IHostBuilder UseNeoPlatformConfiguration(this IHostBuilder builder return builder; } - public static IHostBuilder AddNeoPlatformOptions(this IHostBuilder builder) => + public static IHostBuilder UseNeoPlatformOptions(this IHostBuilder builder) => builder.ConfigureServices ( static (context, services) => @@ -151,7 +174,7 @@ public static IHostBuilder AddNeoPlatformOptions(this IHostBuilder builder) => } ); - public static IHostBuilder AddNeoPlatformLifetime(this IHostBuilder builder, Action? configure = default) => + public static IHostBuilder UseNeoPlatformLifetime(this IHostBuilder builder, Action? configure = default) => builder.ConfigureServices( services => { diff --git a/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs index eea4b4e..e04d3f8 100644 --- a/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs +++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs @@ -87,8 +87,14 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except } } - private void WriteDateTime() => - Write($"[{BuildFormatString(_getConfig().TimestampFormat)}] ", DateTimeNow); + private void WriteDateTime(bool isError = false) + { + var format = $"[{BuildFormatString(_getConfig().TimestampFormat)}] "; + if (isError) + ErrorWrite(format, DateTimeNow); + else + Write(format, DateTimeNow); + } public static void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) { @@ -96,6 +102,12 @@ public static void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] s Console.Out.Write(message); } + public static void ErrorWrite([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) + { + var message = string.Format(format, args); + + Console.Error.Write(message); + } public static void WriteLine() => Write(Environment.NewLine); @@ -103,6 +115,12 @@ public static void WriteLine() => public static void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) => Write(string.Format(format, args) + Environment.NewLine); + public static void ErrorWriteLine() => + ErrorWrite(Environment.NewLine); + + public static void ErrorWriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) => + ErrorWrite(string.Format(format, args) + Environment.NewLine); + public void InfoMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) { WriteDateTime(); @@ -146,17 +164,17 @@ public void ErrorMessage(Exception exception, bool showStackTrace = true) var stackTrace = exception.InnerException?.StackTrace ?? exception.StackTrace; SetTerminalForegroundColor(ConsoleColor.Red); - WriteDateTime(); + WriteDateTime(isError: true); SetTerminalForegroundColor(ConsoleColor.DarkRed); - WriteLine("{0}: ", exception.InnerException?.GetType().Name ?? exception.GetType().Name); + ErrorWriteLine("{0}: ", exception.InnerException?.GetType().Name ?? exception.GetType().Name); SetTerminalForegroundColor(ConsoleColor.Red); - WriteLine(" {0}", exception.InnerException?.Message ?? exception.Message); + ErrorWriteLine(" {0}", exception.InnerException?.Message ?? exception.Message); if (showStackTrace) { - WriteLine("Stack Trace: "); + ErrorWriteLine("Stack Trace: "); SetTerminalForegroundColor(ConsoleColor.DarkRed); - WriteLine(" {0}", stackTrace?.Trim() ?? string.Empty); + ErrorWriteLine(" {0}", stackTrace?.Trim() ?? string.Empty); } ResetColor(); @@ -165,11 +183,11 @@ public void ErrorMessage(Exception exception, bool showStackTrace = true) public void ErrorMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) { SetTerminalForegroundColor(ConsoleColor.Red); - WriteDateTime(); + WriteDateTime(isError: true); SetTerminalForegroundColor(ConsoleColor.DarkRed); - Write("Error: "); + ErrorWrite("Error: "); SetTerminalForegroundColor(ConsoleColor.Red); - WriteLine(format, args); + ErrorWriteLine(format, args); ResetColor(); } diff --git a/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs b/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs new file mode 100644 index 0000000..53204f4 --- /dev/null +++ b/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs @@ -0,0 +1,28 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using System.CommandLine; + +namespace Neo.Platform.Hosting.Middleware +{ + public delegate void PlatformCommandLineNextDelegate(ParseResult parseResult); +} diff --git a/src/Neo.Platform.Hosting/PlatformCommandLine.cs b/src/Neo.Platform.Hosting/PlatformCommandLine.cs new file mode 100644 index 0000000..953c3b9 --- /dev/null +++ b/src/Neo.Platform.Hosting/PlatformCommandLine.cs @@ -0,0 +1,56 @@ +// BSD 2-Clause License +// +// Copyright (c) 2026, Rapid Loop +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES + +using Microsoft.Extensions.Hosting; +using System; +using System.CommandLine; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.Platform.Hosting +{ + public sealed class PlatformCommandLine(ParseResult parseResult, IHost host, InvocationConfiguration invocationConfiguration) + { + public async Task InvokeAsync(CancellationToken cancellationToken = default) + { + await host.StartAsync(cancellationToken); + + var exitCode = 0; + + try + { + exitCode = await parseResult.InvokeAsync(invocationConfiguration, cancellationToken); + } + catch (Exception ex) + { + exitCode = ex.HResult; + } + finally + { + await host.StopAsync(cancellationToken); + } + + + return exitCode; + } + } +} diff --git a/src/Neo.VM/Builders/VirtualMachineBuilder.cs b/src/Neo.VM/Builder/VirtualMachineBuilder.cs similarity index 99% rename from src/Neo.VM/Builders/VirtualMachineBuilder.cs rename to src/Neo.VM/Builder/VirtualMachineBuilder.cs index 51d8c84..1082dab 100644 --- a/src/Neo.VM/Builders/VirtualMachineBuilder.cs +++ b/src/Neo.VM/Builder/VirtualMachineBuilder.cs @@ -26,7 +26,7 @@ using Neo.VM.Pipeline; using System; -namespace Neo.VM.Builders +namespace Neo.VM.Builder { public sealed class VirtualMachineBuilder { diff --git a/src/Neo.VM/Builders/VirtualMachinePipelineBuilder.cs b/src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs similarity index 99% rename from src/Neo.VM/Builders/VirtualMachinePipelineBuilder.cs rename to src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs index 78b1898..d4cabec 100644 --- a/src/Neo.VM/Builders/VirtualMachinePipelineBuilder.cs +++ b/src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs @@ -26,7 +26,7 @@ using System; using System.Collections.Generic; -namespace Neo.VM.Builders +namespace Neo.VM.Builder { /// /// Fluent builder for constructing a . diff --git a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs index 4e748b4..68616b3 100644 --- a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs +++ b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs @@ -21,7 +21,7 @@ // SERVICES using Microsoft.Extensions.DependencyInjection; -using Neo.VM.Builders; +using Neo.VM.Builder; using Neo.VM.Middleware; using System;