diff --git a/README.md b/README.md
index 291bf3a..9293398 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# NEO Platform - C# Blockchain SDK
+# NEO Platform
[](https://github.com/Rapid-Loop/neo-platform/blob/master/LICENSE)
[](https://github.com/Rapid-Loop/neo-platform/stargazers)
@@ -7,105 +7,115 @@
[](https://github.com/Rapid-Loop/neo-platform/commits/master)
[](https://github.com/Rapid-Loop/neo-platform/actions/workflows/verify.yml)
-**Official Rapid Loop fork and enterprise-enhanced rebuild of the NEO Blockchain Smart Economy Platform.**
+**Modular Neo blockchain platform for C# / .NET** — maintained by [Rapid Loop](https://github.com/Rapid-Loop).
-A modern, high-performance C# library for building on the **Neo blockchain** — designed for developers and enterprises who need reliability, scalability, and seamless integration.
+A high-performance library set for building on **Neo**: core types, virtual machine, wallets, storage, and hosting integration. Aimed at developers and enterprises that need reliable packaging and a modern .NET experience.
+
+This repository is **compatible with Neo protocol semantics**. It is Rapid Loop’s platform packaging and engineering work — **not** an “official Neo Foundation” product unless separately stated by Neo.
+
+**Neo docs:** [developers.neo.org](https://developers.neo.org/) · **Neo site:** [neo.org](https://neo.org)
---
-## 🌟 About This Project
+## About
-**neo-platform** is Rapid Loop's actively maintained fork of the Neo blockchain C# ecosystem. We are rebuilding and enhancing the Neo platform with enterprise-grade improvements for:
+**neo-platform** is Rapid Loop’s actively developed Neo C# stack: modular packages, enterprise-friendly configuration and logging hooks, and a cleaner project layout for real systems.
-- Better performance and scalability
-- Modern developer experience
-- Seamless integration with existing business systems
-- Enhanced security and tooling
+Use it when you need:
-Whether you're building decentralized applications (dApps), smart contracts, wallets, or enterprise blockchain solutions, this SDK provides the core building blocks.
+- Core blockchain types and serialization
+- Neo VM execution
+- Wallet formats (e.g. NEP-6) and account management
+- RocksDB-backed blockchain storage
+- .NET Generic Host / CLI integration
-**Official Neo Website**: [neo.org](https://neo.org)
+Led by a [neo-project](https://github.com/neo-project) contributor with core Neo development experience. Rapid Loop commercial work (integration, review, support) is separate from open-source contribution — see [Rapid-Loop](https://github.com/Rapid-Loop).
---
-## ✨ Key Features
+## Packages
-- **Full Neo Compatibility** — Core blockchain, VM, cryptography, and wallet functionality
-- **Enterprise Ready** — Robust configuration, logging, and integration tools
-- **Modular Architecture** — Clean separation across packages:
- - `Neo.Core` — Core blockchain logic
- - `Neo.VM` — Virtual Machine
- - `Neo.Cryptography` — Secure cryptographic primitives
- - `Neo.Wallet` — Wallet management
- - And more...
-- **High Performance** — Optimized for real-world usage
-- **Comprehensive Testing** — Full test suite included
-- **NuGet Ready** — Easy package distribution
+| Project | Role |
+|---------|------|
+| `Neo.Core` | Protocol types, crypto, P2P messages, serialization, VM opcodes / limits |
+| `Neo.VM` | Execution engine, stack types, middleware pipeline |
+| `Neo.Wallet` | Wallet abstractions, NEP-6 / dev wallets |
+| `Neo.IO` | I/O helpers (hashing, bloom filter) |
+| `Neo.Configuration` | Options and JSON converters for protocol / store settings |
+| `Neo.Platform.Storage` | Blockchain store, snapshots, backups (RocksDB) |
+| `Neo.Platform.Hosting` | Hosting, configuration, logging, command-line wiring |
+| `Neo.Platform.CLI` | Sample / entry CLI host |
+| `Neo.Localization` | Localized VM messages |
---
-## 📁 Repository Structure
+## Repository layout
-```bash
+```text
neo-platform/
-├── src/ # Source code (multiple projects)
-│ ├── Neo.Core/
-│ ├── Neo.VM/
-│ ├── Neo.Cryptography/
-│ ├── Neo.Wallet/
-│ └── ...
-├── tests/ # Unit and integration tests
-├── pkgs/ # Package distribution
-├── .github/workflows/ # CI/CD pipelines
-├── .images/ # Logos and assets
-└── All.slnx # Solution file
+├── src/ # Library and host projects
+├── tests/ # Unit and integration tests
+├── pkgs/ # NuGet package output
+├── .github/workflows/ # CI
+├── .images/ # Package / branding assets
+└── All.slnx # Solution
```
---
-## 🚀 Quick Start
+## Quick start
+
+### Clone
-### 1. Clone the repository
```bash
git clone https://github.com/Rapid-Loop/neo-platform.git
cd neo-platform
```
-### 2. Build the solution
+### Build
+
```bash
dotnet build All.slnx
```
-### 3. Run tests
+### Test
+
```bash
-dotnet test
+dotnet test All.slnx
```
---
-## 🛠️ Tech Stack
+## Tech stack
-- **Language**: `C# (.NET)`
-- **Build System**: `dotnet`
-- **CI/CD**: `GitHub Actions`
+- **Language:** C# (.NET)
+- **Build:** `dotnet` / `All.slnx`
+- **CI:** GitHub Actions
---
-## 📖 Documentation
+## Documentation
-- [Neo Official Documentation](https://developers.neo.org/)
-- Check individual project folders for more details.
+- [Neo developer documentation](https://developers.neo.org/)
+- Public APIs use XML documentation comments (`///`) in source under `src/`
+- Contribution guide: [CONTRIBUTING.md](CONTRIBUTING.md)
---
-## 🤝 Contributing
+## Contributing
-We welcome contributions! See our [Contributing Guidelines](CONTRIBUTING.md) (create this file if needed).
+Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md).
---
-## 📜 License
+## License
-This project is licensed under the **BSD-2-Clause License** — see the [LICENSE](LICENSE) file for details.
+**BSD-2-Clause** — see [LICENSE](LICENSE).
---
+
+## Contact
+
+- **Org:** [github.com/Rapid-Loop](https://github.com/Rapid-Loop)
+- **Website:** [rapidloop.net](https://rapidloop.net)
+- **Email:** [github@rapidloop.net](mailto:github@rapidloop.net)
diff --git a/src/Neo.Configuration/BlockchainBackupOptions.cs b/src/Neo.Configuration/BlockchainBackupOptions.cs
index 06cd81d..effd294 100644
--- a/src/Neo.Configuration/BlockchainBackupOptions.cs
+++ b/src/Neo.Configuration/BlockchainBackupOptions.cs
@@ -26,10 +26,20 @@
namespace Neo.Configuration
{
+ ///
+ /// Configuration options for blockchain store backups.
+ ///
public class BlockchainBackupOptions : JsonModel
{
+ ///
+ /// The directory path where backup files are stored.
+ /// Defaults to data/backups under the application base directory.
+ ///
public string BackupPath { get; init; } = Path.Combine(AppContext.BaseDirectory, "data", "backups");
+ ///
+ /// The maximum number of backup files to retain. Defaults to 3.
+ ///
public int MaxBackups { get; init; } = 3;
}
}
diff --git a/src/Neo.Configuration/BlockchainStoreOptions.cs b/src/Neo.Configuration/BlockchainStoreOptions.cs
index adaaf5d..5954230 100644
--- a/src/Neo.Configuration/BlockchainStoreOptions.cs
+++ b/src/Neo.Configuration/BlockchainStoreOptions.cs
@@ -26,10 +26,20 @@
namespace Neo.Configuration
{
+ ///
+ /// Configuration options for the blockchain store database.
+ ///
public class BlockchainStoreOptions : JsonModel
{
+ ///
+ /// The directory path of the blockchain database.
+ /// Defaults to data/chain under the application base directory.
+ ///
public string DatabasePath { get; init; } = Path.Combine(AppContext.BaseDirectory, "data", "chain");
+ ///
+ /// Whether to create the database if it does not already exist. Defaults to .
+ ///
public bool CreateIfMissing { get; init; } = true;
}
}
diff --git a/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs b/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs
index 5a8658e..4a3213e 100644
--- a/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs
+++ b/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs
@@ -28,8 +28,22 @@
namespace Neo.Configuration.Json.Converters
{
+ ///
+ /// Converts values to and from JSON strings.
+ /// Supports IP address and hostname forms (for example, 127.0.0.1:20333 ).
+ ///
public class JsonIPEndPointConverter : JsonConverter
{
+ ///
+ /// Reads an from a JSON string token.
+ ///
+ /// The reader positioned at the JSON value.
+ /// The type to convert.
+ /// The serializer options.
+ ///
+ /// The parsed endpoint; when the string is empty or cannot be resolved, .
+ ///
+ /// Thrown when the token is not a JSON string.
public override IPEndPoint? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
@@ -71,6 +85,12 @@ public class JsonIPEndPointConverter : JsonConverter
return default;
}
+ ///
+ /// Writes an as a JSON string, or JSON null when the value is .
+ ///
+ /// The writer to which the value is written.
+ /// The endpoint to write.
+ /// The serializer options.
public override void Write(Utf8JsonWriter writer, IPEndPoint? value, JsonSerializerOptions options)
{
if (value is null)
diff --git a/src/Neo.Configuration/Json/Converters/JsonStringAddressConverter.cs b/src/Neo.Configuration/Json/Converters/JsonStringAddressConverter.cs
index 5ac9861..5da5e09 100644
--- a/src/Neo.Configuration/Json/Converters/JsonStringAddressConverter.cs
+++ b/src/Neo.Configuration/Json/Converters/JsonStringAddressConverter.cs
@@ -28,8 +28,20 @@
namespace Neo.Configuration.Json.Converters
{
+ ///
+ /// Converts script hashes to and from NEO address JSON strings
+ /// using address version.
+ ///
public class JsonStringAddressConverter : JsonConverter
{
+ ///
+ /// Reads a from a NEO address string.
+ ///
+ /// The reader positioned at the JSON value.
+ /// The type to convert.
+ /// The serializer options.
+ /// The script hash, or when the string is empty.
+ /// Thrown when the token is not a JSON string.
public override UInt160? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
@@ -44,6 +56,12 @@ public class JsonStringAddressConverter : JsonConverter
return valueString.ToScriptHash(ProtocolSettings.Default.AddressVersion);
}
+ ///
+ /// Writes a as a NEO address string, or JSON null when the value is .
+ ///
+ /// The writer to which the value is written.
+ /// The script hash to write.
+ /// The serializer options.
public override void Write(Utf8JsonWriter writer, UInt160? value, JsonSerializerOptions options)
{
// TODO: Remove `ProtocolSettings.Default` and get from active ProtocolSettings.
diff --git a/src/Neo.Configuration/Json/Converters/JsonStringByteArrayConverter.cs b/src/Neo.Configuration/Json/Converters/JsonStringByteArrayConverter.cs
index 1d1d3d9..62994ec 100644
--- a/src/Neo.Configuration/Json/Converters/JsonStringByteArrayConverter.cs
+++ b/src/Neo.Configuration/Json/Converters/JsonStringByteArrayConverter.cs
@@ -27,8 +27,19 @@
namespace Neo.Configuration.Json.Converters
{
+ ///
+ /// Converts values to and from UTF-8 encoded JSON strings.
+ ///
public class JsonStringByteArrayConverter : JsonConverter
{
+ ///
+ /// Reads a byte array from a UTF-8 JSON string.
+ ///
+ /// The reader positioned at the JSON value.
+ /// The type to convert.
+ /// The serializer options.
+ /// The UTF-8 bytes of the string, or when the string is empty.
+ /// Thrown when the token is not a JSON string.
public override byte[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
@@ -42,6 +53,12 @@ public class JsonStringByteArrayConverter : JsonConverter
return Encoding.UTF8.GetBytes(valueString);
}
+ ///
+ /// Writes a byte array as a UTF-8 JSON string, or JSON null when the value is .
+ ///
+ /// The writer to which the value is written.
+ /// The bytes to write.
+ /// The serializer options.
public override void Write(Utf8JsonWriter writer, byte[]? value, JsonSerializerOptions options)
{
if (value is null)
diff --git a/src/Neo.Configuration/Json/Converters/JsonStringECPointConverter.cs b/src/Neo.Configuration/Json/Converters/JsonStringECPointConverter.cs
index 3cf8b9a..014d256 100644
--- a/src/Neo.Configuration/Json/Converters/JsonStringECPointConverter.cs
+++ b/src/Neo.Configuration/Json/Converters/JsonStringECPointConverter.cs
@@ -27,8 +27,21 @@
namespace Neo.Configuration.Json.Converters
{
+ ///
+ /// Converts values to and from hex-encoded JSON strings on the secp256r1 curve.
+ ///
public class JsonStringECPointConverter : JsonConverter
{
+ ///
+ /// Reads an from a hex-encoded JSON string.
+ ///
+ /// The reader positioned at the JSON value.
+ /// The type to convert.
+ /// The serializer options.
+ /// The parsed point, or when the string is empty.
+ ///
+ /// Thrown when the token is not a JSON string, or the string is not a valid secp256r1 point.
+ ///
public override ECPoint? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
@@ -45,6 +58,12 @@ public class JsonStringECPointConverter : JsonConverter
return value;
}
+ ///
+ /// Writes an as a hex-encoded JSON string, or JSON null when the value is .
+ ///
+ /// The writer to which the value is written.
+ /// The point to write.
+ /// The serializer options.
public override void Write(Utf8JsonWriter writer, ECPoint? value, JsonSerializerOptions options)
{
if (value is null)
diff --git a/src/Neo.Configuration/Json/Converters/JsonStringHexFormatConverter.cs b/src/Neo.Configuration/Json/Converters/JsonStringHexFormatConverter.cs
index 477e1a4..027dacc 100644
--- a/src/Neo.Configuration/Json/Converters/JsonStringHexFormatConverter.cs
+++ b/src/Neo.Configuration/Json/Converters/JsonStringHexFormatConverter.cs
@@ -26,8 +26,19 @@
namespace Neo.Configuration.Json.Converters
{
+ ///
+ /// Converts values to and from lowercase hexadecimal JSON strings.
+ ///
public class JsonStringHexFormatConverter : JsonConverter
{
+ ///
+ /// Reads a byte array from a hexadecimal JSON string.
+ ///
+ /// The reader positioned at the JSON value.
+ /// The type to convert.
+ /// The serializer options.
+ /// The decoded bytes, or when the string is empty.
+ /// Thrown when the token is not a JSON string.
public override byte[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
@@ -41,6 +52,12 @@ public class JsonStringHexFormatConverter : JsonConverter
return Convert.FromHexString(valueString);
}
+ ///
+ /// Writes a byte array as a lowercase hexadecimal JSON string, or JSON null when the value is .
+ ///
+ /// The writer to which the value is written.
+ /// The bytes to write.
+ /// The serializer options.
public override void Write(Utf8JsonWriter writer, byte[]? value, JsonSerializerOptions options)
{
if (value is null)
diff --git a/src/Neo.Configuration/Json/Converters/JsonStringUInt160Converter.cs b/src/Neo.Configuration/Json/Converters/JsonStringUInt160Converter.cs
index 150215f..991a92f 100644
--- a/src/Neo.Configuration/Json/Converters/JsonStringUInt160Converter.cs
+++ b/src/Neo.Configuration/Json/Converters/JsonStringUInt160Converter.cs
@@ -27,8 +27,21 @@
namespace Neo.Configuration.Json.Converters
{
+ ///
+ /// Converts values to and from hex-encoded JSON strings.
+ ///
public class JsonStringUInt160Converter : JsonConverter
{
+ ///
+ /// Reads a from a hex-encoded JSON string.
+ ///
+ /// The reader positioned at the JSON value.
+ /// The type to convert.
+ /// The serializer options.
+ /// The parsed script hash, or when the string is empty.
+ ///
+ /// Thrown when the token is not a JSON string, or the string is not a valid .
+ ///
public override UInt160? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
@@ -45,6 +58,12 @@ public class JsonStringUInt160Converter : JsonConverter
return scriptHash;
}
+ ///
+ /// Writes a as a hex-encoded JSON string, or JSON null when the value is .
+ ///
+ /// The writer to which the value is written.
+ /// The script hash to write.
+ /// The serializer options.
public override void Write(Utf8JsonWriter writer, UInt160? value, JsonSerializerOptions options)
{
if (value is null)
diff --git a/src/Neo.Configuration/Json/JsonDefaults.cs b/src/Neo.Configuration/Json/JsonDefaults.cs
index 6a34d92..504c068 100644
--- a/src/Neo.Configuration/Json/JsonDefaults.cs
+++ b/src/Neo.Configuration/Json/JsonDefaults.cs
@@ -27,8 +27,15 @@
namespace Neo.Configuration.Json
{
+ ///
+ /// Provides default used by configuration models.
+ ///
public static class JsonDefaults
{
+ ///
+ /// The default serializer options for Neo configuration JSON, including camel-case naming
+ /// and converters for enums, , , and .
+ ///
public static readonly JsonSerializerOptions SerializerOptions = new()
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
diff --git a/src/Neo.Configuration/Json/JsonModel.cs b/src/Neo.Configuration/Json/JsonModel.cs
index 2b499a7..4bcddec 100644
--- a/src/Neo.Configuration/Json/JsonModel.cs
+++ b/src/Neo.Configuration/Json/JsonModel.cs
@@ -27,23 +27,52 @@
namespace Neo.Configuration.Json
{
+ ///
+ /// Base type for configuration models that can be serialized to and deserialized from JSON.
+ ///
public abstract class JsonModel
{
protected JsonSerializerOptions _jsonSerializerOptions = JsonDefaults.SerializerOptions;
+ ///
+ /// Serializes this model to a JSON string using the specified options, or the instance defaults when omitted.
+ ///
+ /// Optional serializer options; when , instance defaults are used.
+ /// The JSON representation of this model.
[return: NotNull]
public virtual string? ToString(JsonSerializerOptions? options = default) =>
ToJson(options);
+ ///
+ /// Serializes this model to a JSON string.
+ ///
+ /// Optional serializer options; when , instance defaults are used.
+ /// The JSON representation of this model.
public virtual string ToJson(JsonSerializerOptions? options) =>
JsonSerializer.Serialize(this, options ?? _jsonSerializerOptions);
+ ///
+ /// Deserializes a JSON string into a instance.
+ ///
+ /// The model type to deserialize.
+ /// The JSON string to deserialize.
+ /// Optional serializer options; when , is used.
+ /// The deserialized model, or if deserialization yields no value.
public static TModel? FromJson(string jsonString, JsonSerializerOptions? options = default)
where TModel : notnull, JsonModel =>
JsonSerializer.Deserialize(
jsonString,
options ?? JsonDefaults.SerializerOptions);
+ ///
+ /// Deserializes the contents of a JSON file into a instance.
+ ///
+ /// The model type to deserialize.
+ /// The JSON file to read.
+ /// Optional serializer options; when , is used.
+ /// The deserialized model, or if deserialization yields no value.
+ /// Thrown when does not exist.
+ /// Thrown when the file is empty.
public static TModel? FromJson(FileInfo file, JsonSerializerOptions? options = default)
where TModel : notnull, JsonModel
{
diff --git a/src/Neo.Configuration/ProtocolSettingsOptions.cs b/src/Neo.Configuration/ProtocolSettingsOptions.cs
index 5d1103f..079355c 100644
--- a/src/Neo.Configuration/ProtocolSettingsOptions.cs
+++ b/src/Neo.Configuration/ProtocolSettingsOptions.cs
@@ -32,30 +32,71 @@
namespace Neo.Configuration
{
+ ///
+ /// JSON-bindable configuration options that map to .
+ ///
public class ProtocolSettingsOptions : JsonModel, IMap
{
+ ///
+ /// The magic number of the NEO network.
+ ///
public uint Network { get; set; }
+ ///
+ /// The address version of the NEO system.
+ ///
public byte AddressVersion { get; set; }
+ ///
+ /// The time in milliseconds between two blocks.
+ ///
public uint MillisecondsPerBlock { get; set; }
+ ///
+ /// The maximum number of transactions that can be contained in a block.
+ ///
public uint MaxTransactionsPerBlock { get; set; }
+ ///
+ /// The maximum number of transactions that can be contained in the memory pool.
+ ///
public int MemoryPoolMaxTransactions { get; set; }
+ ///
+ /// The maximum number of blocks that can be traced in a smart contract.
+ ///
public uint MaxTraceableBlocks { get; set; }
+ ///
+ /// The amount of GAS to distribute during initialization, in datoshi (1 GAS = 1e8 datoshi).
+ ///
public ulong InitialGasDistribution { get; set; }
+ ///
+ /// The number of validators in the NEO system.
+ ///
public int ValidatorsCount { get; set; }
+ ///
+ /// The default seed node endpoints, or if none are configured.
+ ///
public IPEndPoint[]? SeedList { get; set; }
+ ///
+ /// The block heights from which hard forks are activated, or if none are configured.
+ ///
public IDictionary? HardForks { get; set; }
+ ///
+ /// The public keys of the standby committee members, or if none are configured.
+ ///
public ECPoint[]? StandbyCommittee { get; set; }
+ ///
+ /// Converts these options into a instance.
+ /// Nullable collections are replaced with empty defaults.
+ ///
+ /// A new populated from this instance.
public ProtocolSettings ToObject() =>
new()
{
diff --git a/src/Neo.Core/Blockchain/Block.cs b/src/Neo.Core/Blockchain/Block.cs
index 439a857..e8d6402 100644
--- a/src/Neo.Core/Blockchain/Block.cs
+++ b/src/Neo.Core/Blockchain/Block.cs
@@ -31,8 +31,14 @@
namespace Neo.Core.Blockchain
{
+ ///
+ /// A blockchain block that includes a header and its transactions.
+ ///
public class Block : BlockHeader, IEquatable, INeoSerializable, IInventory, IVerifiable
{
+ ///
+ /// Gets the hash of this block.
+ ///
public override UInt256 Hash => this.ToArray().ToTxHash();
///
@@ -40,17 +46,32 @@ public class Block : BlockHeader, IEquatable, INeoSerializable, IInventor
///
public Transaction[] Transactions { get; set; } = [];
+ ///
+ /// Gets the inventory type for this block.
+ ///
public InventoryType InventoryType => InventoryType.Block;
+ ///
+ /// Gets the serialized size of this block in bytes.
+ ///
public override int Size =>
base.Size +
Transactions.GetSerializedSize();
+ ///
+ /// Returns a hash code for this block based on its hash.
+ ///
+ /// A hash code for the current block.
public override int GetHashCode()
{
return Hash.GetHashCode();
}
+ ///
+ /// Determines whether the specified is equal to the current instance.
+ ///
+ /// The block to compare with the current instance.
+ /// if the blocks have the same hash; otherwise, .
public bool Equals([NotNullWhen(true)] Block? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -58,6 +79,11 @@ public bool Equals([NotNullWhen(true)] Block? other)
return Hash == other.Hash;
}
+ ///
+ /// Determines whether the specified object is equal to the current block.
+ ///
+ /// The object to compare with the current instance.
+ /// if the objects are equal; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -65,6 +91,11 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as Block);
}
+ ///
+ /// Deserializes this block from the specified stream and validates the Merkle root.
+ ///
+ /// The stream to read from.
+ /// The computed Merkle root does not match the header.
public override void Deserialize(Stream reader)
{
base.Deserialize(reader);
@@ -78,6 +109,10 @@ public override void Deserialize(Stream reader)
throw new FormatException("The computed Merkle root does not match the expected value.");
}
+ ///
+ /// Serializes this block to the specified stream, recomputing the Merkle root first.
+ ///
+ /// The stream to write to.
public override void Serialize(Stream writer)
{
var txHashes = Transactions
diff --git a/src/Neo.Core/Blockchain/BlockHeader.cs b/src/Neo.Core/Blockchain/BlockHeader.cs
index 8f7cfc0..0265490 100644
--- a/src/Neo.Core/Blockchain/BlockHeader.cs
+++ b/src/Neo.Core/Blockchain/BlockHeader.cs
@@ -29,6 +29,9 @@
namespace Neo.Core.Blockchain
{
+ ///
+ /// A blockchain block header without transaction bodies.
+ ///
public class BlockHeader : IEquatable, INeoSerializable, IVerifiable
{
///
@@ -36,6 +39,9 @@ public class BlockHeader : IEquatable, INeoSerializable, IVerifiabl
///
public uint Version { get; set; }
+ ///
+ /// Gets the hash of this block header.
+ ///
public virtual UInt256 Hash => this.ToArray().ToTxHash();
///
@@ -83,6 +89,9 @@ public class BlockHeader : IEquatable, INeoSerializable, IVerifiabl
///
public Witness[] Witnesses => [Witness];
+ ///
+ /// Gets the serialized size of this block header in bytes.
+ ///
public virtual int Size =>
sizeof(uint) + // Version
UInt256.Length + // PrevHash
@@ -94,11 +103,20 @@ public class BlockHeader : IEquatable, INeoSerializable, IVerifiabl
UInt160.Length + // NextConsensus
Witnesses.GetSerializedSize();
+ ///
+ /// Returns a hash code for this header based on its hash.
+ ///
+ /// A hash code for the current header.
public override int GetHashCode()
{
return Hash.GetHashCode();
}
+ ///
+ /// Determines whether the specified is equal to the current instance.
+ ///
+ /// The header to compare with the current instance.
+ /// if the headers have the same hash; otherwise, .
public bool Equals([NotNullWhen(true)] BlockHeader? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -106,6 +124,11 @@ public bool Equals([NotNullWhen(true)] BlockHeader? other)
return Hash == other.Hash;
}
+ ///
+ /// Determines whether the specified object is equal to the current header.
+ ///
+ /// The object to compare with the current instance.
+ /// if the objects are equal; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -113,6 +136,11 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as BlockHeader);
}
+ ///
+ /// Deserializes this header from the specified stream.
+ ///
+ /// The stream to read from.
+ /// Version is not 0, or the witness count is not 1.
public virtual void Deserialize(Stream reader)
{
Version = reader.Read();
@@ -133,6 +161,10 @@ public virtual void Deserialize(Stream reader)
Witness = witnesses[0];
}
+ ///
+ /// Serializes this header to the specified stream.
+ ///
+ /// The stream to write to.
public virtual void Serialize(Stream writer)
{
writer.Write(Version);
diff --git a/src/Neo.Core/Blockchain/Interface/IInventory.cs b/src/Neo.Core/Blockchain/Interface/IInventory.cs
index 5d13a21..1fb604e 100644
--- a/src/Neo.Core/Blockchain/Interface/IInventory.cs
+++ b/src/Neo.Core/Blockchain/Interface/IInventory.cs
@@ -22,8 +22,14 @@
namespace Neo.Core.Blockchain.Interface
{
+ ///
+ /// An inventory item that can be relayed on the network (transaction, block, or extensible payload).
+ ///
public interface IInventory
{
+ ///
+ /// Gets the inventory type of this item.
+ ///
InventoryType InventoryType { get; }
}
}
diff --git a/src/Neo.Core/Blockchain/Interface/IVerifiable.cs b/src/Neo.Core/Blockchain/Interface/IVerifiable.cs
index b59fbc5..5135bd3 100644
--- a/src/Neo.Core/Blockchain/Interface/IVerifiable.cs
+++ b/src/Neo.Core/Blockchain/Interface/IVerifiable.cs
@@ -22,6 +22,9 @@
namespace Neo.Core.Blockchain.Interface
{
+ ///
+ /// An object that can be cryptographically verified via its hash and witnesses.
+ ///
public interface IVerifiable
{
///
diff --git a/src/Neo.Core/Blockchain/Signer.cs b/src/Neo.Core/Blockchain/Signer.cs
index 1bad251..09eaa88 100644
--- a/src/Neo.Core/Blockchain/Signer.cs
+++ b/src/Neo.Core/Blockchain/Signer.cs
@@ -30,6 +30,9 @@
namespace Neo.Core.Blockchain
{
+ ///
+ /// A transaction signer describing an account, witness scopes, and related permissions.
+ ///
public class Signer : INeoSerializable, IEquatable
{
///
@@ -60,8 +63,16 @@ public class Signer : INeoSerializable, IEquatable
///
public WitnessRule[] Rules { get; set; } = [];
+ ///
+ /// Gets the serialized size of this signer in bytes.
+ ///
+ /// Always thrown; size calculation is not implemented.
public int Size => throw new NotImplementedException();
+ ///
+ /// Returns a hash code for this signer.
+ ///
+ /// A hash code for the current signer.
public override int GetHashCode()
{
unchecked
@@ -78,12 +89,23 @@ public override int GetHashCode()
}
}
+ ///
+ /// Determines whether the specified object is equal to the current signer.
+ ///
+ /// The object to compare with the current instance.
+ /// if the objects are equal; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
if (obj is null) return false;
return Equals(obj as Signer);
}
+
+ ///
+ /// Determines whether the specified is equal to the current instance.
+ ///
+ /// The signer to compare with the current instance.
+ /// if the signers are equal; otherwise, .
public bool Equals([NotNullWhen(true)] Signer? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -106,6 +128,11 @@ public bool Equals([NotNullWhen(true)] Signer? other)
return true;
}
+ ///
+ /// Deserializes this signer from the specified stream.
+ ///
+ /// The stream to read from.
+ /// combines with other flags.
public void Deserialize(Stream reader)
{
Account.Deserialize(reader);
@@ -124,6 +151,10 @@ public void Deserialize(Stream reader)
Rules = reader.ReadObjects();
}
+ ///
+ /// Serializes this signer to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
Account.Serialize(writer);
diff --git a/src/Neo.Core/Blockchain/Transaction.cs b/src/Neo.Core/Blockchain/Transaction.cs
index 1f287d2..13161b2 100644
--- a/src/Neo.Core/Blockchain/Transaction.cs
+++ b/src/Neo.Core/Blockchain/Transaction.cs
@@ -30,9 +30,19 @@
namespace Neo.Core.Blockchain
{
+ ///
+ /// A NEO transaction that can be verified, inventoried, and serialized.
+ ///
public class Transaction : IEquatable, IInventory, INeoSerializable, IVerifiable
{
+ ///
+ /// The maximum allowed size of a serialized transaction in bytes.
+ ///
public const int MaxTransactionSize = 102400;
+
+ ///
+ /// The maximum number of attributes allowed on a transaction.
+ ///
public const int MaxTransactionAttributes = 16;
///
@@ -40,6 +50,9 @@ public class Transaction : IEquatable, IInventory, INeoSerializable
///
public byte Version { get; set; }
+ ///
+ /// Gets the hash of this transaction.
+ ///
public UInt256 Hash => this.ToArray().ToTxHash();
///
@@ -82,6 +95,9 @@ public class Transaction : IEquatable, IInventory, INeoSerializable
///
public Signer[] Signers { get; set; } = [];
+ ///
+ /// The witnesses of the transaction.
+ ///
public Witness[] Witnesses { get; set; } = [];
///
@@ -90,8 +106,14 @@ public class Transaction : IEquatable, IInventory, INeoSerializable
/// Note: The sender will pay the fees of the transaction.
public UInt160 Sender => Signers.FirstOrDefault()?.Account ?? UInt160.Zero;
+ ///
+ /// Gets the inventory type for this transaction.
+ ///
public InventoryType InventoryType => InventoryType.TX;
+ ///
+ /// Gets the serialized size of this transaction in bytes.
+ ///
public int Size =>
sizeof(byte) + //Version
sizeof(uint) + //Nonce
@@ -103,11 +125,20 @@ public class Transaction : IEquatable, IInventory, INeoSerializable
Script.GetSerializedSize() +
Witnesses.GetSerializedSize();
+ ///
+ /// Returns a hash code for this transaction based on its hash.
+ ///
+ /// A hash code for the current transaction.
public override int GetHashCode()
{
return Hash.GetHashCode();
}
+ ///
+ /// Determines whether the specified object is equal to the current transaction.
+ ///
+ /// The object to compare with the current instance.
+ /// if the objects are equal; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -115,6 +146,11 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as Transaction);
}
+ ///
+ /// Determines whether the specified is equal to the current instance.
+ ///
+ /// The transaction to compare with the current instance.
+ /// if the transactions have the same hash; otherwise, .
public bool Equals([NotNullWhen(true)] Transaction? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -122,6 +158,10 @@ public bool Equals([NotNullWhen(true)] Transaction? other)
return Hash == other.Hash;
}
+ ///
+ /// Deserializes this transaction from the specified stream.
+ ///
+ /// The stream to read from.
public void Deserialize(Stream reader)
{
Version = reader.Read();
@@ -135,6 +175,10 @@ public void Deserialize(Stream reader)
Witnesses = reader.ReadObjects();
}
+ ///
+ /// Serializes this transaction to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(Version);
diff --git a/src/Neo.Core/Blockchain/TransactionAttribute.cs b/src/Neo.Core/Blockchain/TransactionAttribute.cs
index cd09cd5..e966e0b 100644
--- a/src/Neo.Core/Blockchain/TransactionAttribute.cs
+++ b/src/Neo.Core/Blockchain/TransactionAttribute.cs
@@ -27,6 +27,9 @@
namespace Neo.Core.Blockchain
{
+ ///
+ /// Base type for optional transaction attributes carried on a .
+ ///
public abstract class TransactionAttribute : INeoSerializable
{
///
@@ -39,8 +42,16 @@ public abstract class TransactionAttribute : INeoSerializable
///
public abstract bool AllowMultiple { get; }
+ ///
+ /// Gets the serialized size of this attribute in bytes.
+ ///
public virtual int Size => sizeof(TransactionAttributeType);
+ ///
+ /// Deserializes this attribute from the specified stream.
+ ///
+ /// The stream to read from.
+ /// The attribute type is not defined.
public virtual void Deserialize(Stream reader)
{
var type = reader.Read();
@@ -49,6 +60,10 @@ public virtual void Deserialize(Stream reader)
throw new FormatException($"[{nameof(TransactionAttribute)}] Invalid {nameof(Type)} \'{type:d}\'.");
}
+ ///
+ /// Serializes this attribute to the specified stream.
+ ///
+ /// The stream to write to.
public virtual void Serialize(Stream writer)
{
writer.Write(Type);
diff --git a/src/Neo.Core/Blockchain/TransactionAttributeType.cs b/src/Neo.Core/Blockchain/TransactionAttributeType.cs
index 83d67fd..9f767bf 100644
--- a/src/Neo.Core/Blockchain/TransactionAttributeType.cs
+++ b/src/Neo.Core/Blockchain/TransactionAttributeType.cs
@@ -22,6 +22,9 @@
namespace Neo.Core.Blockchain
{
+ ///
+ /// Identifies the type of a .
+ ///
public enum TransactionAttributeType : byte
{
///
diff --git a/src/Neo.Core/Blockchain/Witness.cs b/src/Neo.Core/Blockchain/Witness.cs
index 7346b6b..3392568 100644
--- a/src/Neo.Core/Blockchain/Witness.cs
+++ b/src/Neo.Core/Blockchain/Witness.cs
@@ -26,11 +26,24 @@
namespace Neo.Core.Blockchain
{
+ ///
+ /// A cryptographic witness consisting of invocation and verification scripts.
+ ///
public class Witness : INeoSerializable
{
+ ///
+ /// The maximum allowed length of an invocation script in bytes.
+ ///
public const int MaxInvocationScript = 1024;
+
+ ///
+ /// The maximum allowed length of a verification script in bytes.
+ ///
public const int MaxVerificationScript = 1024;
+ ///
+ /// Gets an empty witness with no scripts.
+ ///
public static Witness Empty => new();
///
@@ -48,16 +61,27 @@ public class Witness : INeoSerializable
///
public UInt160 ScriptHash => VerificationScript.ToScriptHash();
+ ///
+ /// Gets the serialized size of this witness in bytes.
+ ///
public int Size =>
InvocationScript.GetSerializedSize() +
VerificationScript.GetSerializedSize();
+ ///
+ /// Deserializes this witness from the specified stream.
+ ///
+ /// The stream to read from.
public void Deserialize(Stream reader)
{
InvocationScript = reader.ReadDynamic();
VerificationScript = reader.ReadDynamic();
}
+ ///
+ /// Serializes this witness to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(InvocationScript);
diff --git a/src/Neo.Core/Blockchain/WitnessCondition.cs b/src/Neo.Core/Blockchain/WitnessCondition.cs
index 5a1b949..add1fb9 100644
--- a/src/Neo.Core/Blockchain/WitnessCondition.cs
+++ b/src/Neo.Core/Blockchain/WitnessCondition.cs
@@ -28,6 +28,9 @@
namespace Neo.Core.Blockchain
{
+ ///
+ /// Base type for conditions evaluated as part of a .
+ ///
public abstract class WitnessCondition : INeoSerializable
{
///
@@ -35,8 +38,16 @@ public abstract class WitnessCondition : INeoSerializable
///
public abstract WitnessConditionType Type { get; }
+ ///
+ /// Gets the serialized size of this condition in bytes.
+ ///
public virtual int Size => Unsafe.SizeOf();
+ ///
+ /// Deserializes this condition from the specified stream.
+ ///
+ /// The stream to read from.
+ /// The type byte does not match .
public void Deserialize(Stream reader)
{
var type = reader.Read();
@@ -45,6 +56,10 @@ public void Deserialize(Stream reader)
throw new FormatException($"[{nameof(WitnessCondition)}::{nameof(Type)}] Value \'{type}\' does not match \'{Type}\'.");
}
+ ///
+ /// Serializes this condition to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(Type);
diff --git a/src/Neo.Core/Blockchain/WitnessContract.cs b/src/Neo.Core/Blockchain/WitnessContract.cs
index 30cdb81..9ff080c 100644
--- a/src/Neo.Core/Blockchain/WitnessContract.cs
+++ b/src/Neo.Core/Blockchain/WitnessContract.cs
@@ -117,6 +117,13 @@ public static byte[] CreateMultiSigRedeemScript(int m, params ECPoint[] publicKe
return sb.ToArray();
}
+ ///
+ /// Determines whether the specified redeem script is a multi-signature contract script.
+ ///
+ /// The redeem script to inspect.
+ ///
+ /// if the script ends with a multi-signature system call; otherwise, .
+ ///
public static bool IsMultiSigContract(byte[] redeemScriptBytes)
{
using var sb = new ScriptBuilder();
diff --git a/src/Neo.Core/Blockchain/WitnessRule.cs b/src/Neo.Core/Blockchain/WitnessRule.cs
index 802f90b..ff9426d 100644
--- a/src/Neo.Core/Blockchain/WitnessRule.cs
+++ b/src/Neo.Core/Blockchain/WitnessRule.cs
@@ -29,6 +29,9 @@
namespace Neo.Core.Blockchain
{
+ ///
+ /// A witness rule pairing an action with a .
+ ///
public class WitnessRule : IEquatable, INeoSerializable
{
///
@@ -41,10 +44,17 @@ public class WitnessRule : IEquatable, INeoSerializable
///
public WitnessCondition Condition { get; set; } = default!;
+ ///
+ /// Gets the serialized size of this rule in bytes.
+ ///
public int Size =>
Unsafe.SizeOf() +
Condition.Size;
+ ///
+ /// Returns a hash code for this rule.
+ ///
+ /// A hash code for the current rule.
public override int GetHashCode()
{
unchecked
@@ -58,6 +68,11 @@ public override int GetHashCode()
}
}
+ ///
+ /// Determines whether the specified object is equal to the current rule.
+ ///
+ /// The object to compare with the current instance.
+ /// if the objects are equal; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -65,6 +80,11 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as WitnessRule);
}
+ ///
+ /// Determines whether the specified is equal to the current instance.
+ ///
+ /// The rule to compare with the current instance.
+ /// if the rules are equal; otherwise, .
public bool Equals([NotNullWhen(true)] WitnessRule? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -72,12 +92,20 @@ public bool Equals([NotNullWhen(true)] WitnessRule? other)
return Action == other.Action && Condition == other.Condition;
}
+ ///
+ /// Deserializes this rule from the specified stream.
+ ///
+ /// The stream to read from.
public void Deserialize(Stream reader)
{
Action = reader.Read();
Condition.Deserialize(reader);
}
+ ///
+ /// Serializes this rule to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(Action);
diff --git a/src/Neo.Core/CoreUtilities.cs b/src/Neo.Core/CoreUtilities.cs
index 5e94015..6ebea33 100644
--- a/src/Neo.Core/CoreUtilities.cs
+++ b/src/Neo.Core/CoreUtilities.cs
@@ -25,32 +25,86 @@
namespace Neo.Core
{
+ ///
+ /// Shared helpers for encoding and Unix time conversion.
+ ///
public static class CoreUtilities
{
+ ///
+ /// Gets a UTF-8 encoding that throws on invalid sequences.
+ ///
public static Encoding StrictUtf8Encoding => Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
+ ///
+ /// Converts a to Unix time in milliseconds.
+ ///
+ /// The date and time to convert.
+ /// The UTC offset of .
+ /// The Unix timestamp in milliseconds.
public static long ToUnixTimeMilliseconds(DateTime dateTime, TimeSpan offset = default) =>
new DateTimeOffset(dateTime, offset)
.ToUnixTimeMilliseconds();
+ ///
+ /// Converts a duration from the Unix epoch to milliseconds.
+ ///
+ /// The time span from the Unix epoch.
+ /// The UTC offset applied to the conversion.
+ /// The Unix timestamp in milliseconds.
public static long ToUnixTimeMilliseconds(TimeSpan timeSpan, TimeSpan offset = default) =>
new DateTimeOffset(timeSpan.Ticks, offset)
.ToUnixTimeMilliseconds();
+ ///
+ /// Converts a to Unix time in seconds.
+ ///
+ /// The date and time to convert.
+ /// The UTC offset of .
+ /// The Unix timestamp in seconds.
public static uint ToUnixTimeSeconds(DateTime dateTime, TimeSpan offset = default) =>
(uint)new DateTimeOffset(dateTime, offset)
.ToUnixTimeSeconds();
+ ///
+ /// Converts a duration from the Unix epoch to seconds.
+ ///
+ /// The time span from the Unix epoch.
+ /// The UTC offset applied to the conversion.
+ /// The Unix timestamp in seconds.
public static uint ToUnixTimeSeconds(TimeSpan timeSpan, TimeSpan offset = default) =>
(uint)new DateTimeOffset(timeSpan.Ticks, offset)
.ToUnixTimeSeconds();
+ ///
+ /// Converts a Unix timestamp in milliseconds to a .
+ ///
+ /// The Unix timestamp in milliseconds.
+ ///
+ /// to return local time; otherwise UTC.
+ ///
+ /// The converted date and time.
public static DateTime FromUnixTimeMilliseconds(ulong milliseconds, bool isLocalDateTime = false) =>
FromUnixTimeMilliseconds((long)milliseconds, isLocalDateTime);
+ ///
+ /// Converts a Unix timestamp in seconds to a .
+ ///
+ /// The Unix timestamp in seconds.
+ ///
+ /// to return local time; otherwise UTC.
+ ///
+ /// The converted date and time.
public static DateTime FromUnixTimeSeconds(uint seconds, bool isLocalDateTime = false) =>
FromUnixTimeMilliseconds(seconds * TimeSpan.MillisecondsPerSecond, isLocalDateTime);
+ ///
+ /// Converts a Unix timestamp in milliseconds to a .
+ ///
+ /// The Unix timestamp in milliseconds.
+ ///
+ /// to return local time; otherwise UTC.
+ ///
+ /// The converted date and time.
public static DateTime FromUnixTimeMilliseconds(long milliseconds, bool isLocalDateTime = false)
{
var dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(milliseconds);
diff --git a/src/Neo.Core/Cryptography/ECC/ECCurve.cs b/src/Neo.Core/Cryptography/ECC/ECCurve.cs
index f4692a9..67c9fd7 100644
--- a/src/Neo.Core/Cryptography/ECC/ECCurve.cs
+++ b/src/Neo.Core/Cryptography/ECC/ECCurve.cs
@@ -26,20 +26,38 @@
namespace Neo.Core.Cryptography.ECC
{
+ ///
+ /// Parameters and helpers for a named elliptic curve used by Neo.
+ ///
public class ECCurve
{
+ ///
+ /// The secp256r1 (NIST P-256) curve used by Neo.
+ ///
public static readonly ECCurve SecP256r1 = new(
BigInteger.Parse("115792089210356248762697446949407573530086143415290314195533631308867097853951"),
BigInteger.Parse("41058363725152142129326129780047268409114441015993725554835256314039467401291"),
ECCurveName.SecP256r1
);
+ ///
+ /// Gets the name of this curve.
+ ///
public ECCurveName Name => _curveName;
+ ///
+ /// Gets the corresponding for platform crypto APIs.
+ ///
public CECCurve ECCurveType => _curve;
+ ///
+ /// Gets the prime field modulus of the curve.
+ ///
public BigInteger P => _p;
+ ///
+ /// Gets the curve equation coefficient b .
+ ///
public BigInteger B => _b;
private readonly BigInteger _p;
diff --git a/src/Neo.Core/Cryptography/ECC/ECCurveName.cs b/src/Neo.Core/Cryptography/ECC/ECCurveName.cs
index 56bcfe2..f774905 100644
--- a/src/Neo.Core/Cryptography/ECC/ECCurveName.cs
+++ b/src/Neo.Core/Cryptography/ECC/ECCurveName.cs
@@ -22,8 +22,14 @@
namespace Neo.Core.Cryptography.ECC
{
+ ///
+ /// Identifies a supported elliptic curve.
+ ///
public enum ECCurveName : byte
{
+ ///
+ /// The NIST P-256 / secp256r1 curve.
+ ///
SecP256r1 = 0,
}
}
diff --git a/src/Neo.Core/Cryptography/ECC/ECPoint.cs b/src/Neo.Core/Cryptography/ECC/ECPoint.cs
index a77c2a9..ebdb80c 100644
--- a/src/Neo.Core/Cryptography/ECC/ECPoint.cs
+++ b/src/Neo.Core/Cryptography/ECC/ECPoint.cs
@@ -32,24 +32,52 @@
namespace Neo.Core.Cryptography.ECC
{
+ ///
+ /// An elliptic curve public key point on a supported .
+ ///
[TypeConverter(typeof(ECPointTypeConverter))]
public class ECPoint : IEquatable, IComparable, IComparable, INeoSerializable
{
+ ///
+ /// The length in bytes of an uncompressed public key encoding (including prefix).
+ ///
public const int UncompressedLength = 65;
+
+ ///
+ /// The length in bytes of a compressed public key encoding (including prefix).
+ ///
public const int CompressedLength = 33;
+ ///
+ /// Gets the X coordinate of this point.
+ ///
public BigInteger X => _x;
+ ///
+ /// Gets the Y coordinate of this point.
+ ///
public BigInteger Y => _y;
+ ///
+ /// Gets a value indicating whether this point is the point at infinity.
+ ///
public bool IsInfinity => _x == BigInteger.Zero && _y == BigInteger.Zero;
+ ///
+ /// Gets the length of the uncompressed encoding in bytes.
+ ///
public int Length => _uncompressed.Length;
+ ///
+ /// Gets the serialized size of this point in bytes.
+ ///
public int Size =>
sizeof(ECCurveName) +
_uncompressed.Length;
+ ///
+ /// Gets the curve this point belongs to.
+ ///
public ECCurve Curve => _curve;
private ECCurve _curve;
@@ -71,6 +99,12 @@ .. y.ToByteArray(true, true),
];
}
+ ///
+ /// Derives the public key point from a private key on the specified curve.
+ ///
+ /// The private key bytes.
+ /// The elliptic curve.
+ /// The corresponding public key point.
public static ECPoint FromPrivateKey(ReadOnlySpan privateKeySpan, ECCurve curve)
{
using var ecdsa = ECDsa.Create();
@@ -90,6 +124,13 @@ public static ECPoint FromPrivateKey(ReadOnlySpan privateKeySpan, ECCurve
return new(x, y, curve);
}
+ ///
+ /// Parses a hex-encoded public key on the specified curve.
+ ///
+ /// A compressed or uncompressed hex public key.
+ /// The elliptic curve.
+ /// The parsed point.
+ /// is not a valid public key encoding.
public static ECPoint Parse(string value, ECCurve curve)
{
if ((value.Length != UncompressedLength * 2 || value.Length != CompressedLength * 2) &&
@@ -112,6 +153,13 @@ public static ECPoint Parse(string value, ECCurve curve)
return new(x, y, curve);
}
+ ///
+ /// Attempts to parse a hex-encoded public key on the specified curve.
+ ///
+ /// A compressed or uncompressed hex public key.
+ /// The elliptic curve.
+ /// When this method returns, the parsed point if successful; otherwise, .
+ /// if parsing succeeded; otherwise, .
public static bool TryParse(string value, ECCurve curve, [MaybeNullWhen(false)] out ECPoint? result)
{
try
@@ -126,6 +174,11 @@ public static bool TryParse(string value, ECCurve curve, [MaybeNullWhen(false)]
}
}
+ ///
+ /// Determines whether the specified object is equal to the current point.
+ ///
+ /// The object to compare with the current instance.
+ /// if the objects are equal; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -133,17 +186,30 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as ECPoint);
}
+ ///
+ /// Returns a hash code for this point.
+ ///
+ /// A hash code for the current point.
public override int GetHashCode()
{
return _uncompressed.ToHashCode();
}
+ ///
+ /// Returns the uncompressed hex encoding of this point.
+ ///
+ /// A lower-case hex string of the uncompressed public key.
[return: MaybeNull]
public override string? ToString()
{
return Convert.ToHexStringLower(Encode(false));
}
+ ///
+ /// Determines whether the specified is equal to the current instance.
+ ///
+ /// The point to compare with the current instance.
+ /// if the points are equal; otherwise, .
public bool Equals(ECPoint? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -152,12 +218,26 @@ public bool Equals(ECPoint? other)
return _x == other._x && _y == other._y;
}
+ ///
+ /// Compares this instance to a specified object.
+ ///
+ /// An object to compare, or .
+ ///
+ /// A signed integer that indicates the relative order of this instance and .
+ ///
public int CompareTo(object? obj)
{
if (ReferenceEquals(obj, this)) return 0;
return CompareTo(obj as ECPoint);
}
+ ///
+ /// Compares this instance to another .
+ ///
+ /// An to compare.
+ ///
+ /// A signed integer that indicates the relative order of this instance and .
+ ///
public int CompareTo(ECPoint? other)
{
if (other is null) return 1;
@@ -166,6 +246,11 @@ public int CompareTo(ECPoint? other)
return _y.CompareTo(other._y);
}
+ ///
+ /// Serializes this point to the specified stream.
+ ///
+ /// The stream to write to.
+ /// The stream does not support writing.
public void Serialize(Stream writer)
{
if (writer.CanWrite == false)
@@ -175,6 +260,12 @@ public void Serialize(Stream writer)
writer.Write(Encode(false));
}
+ ///
+ /// Deserializes this point from the specified stream.
+ ///
+ /// The stream to read from.
+ /// The stream does not support reading.
+ /// The curve name is not supported.
public void Deserialize(Stream reader)
{
if (reader.CanRead == false)
@@ -193,6 +284,11 @@ public void Deserialize(Stream reader)
_y = new(_uncompressed.AsSpan()[33..UncompressedLength], true, true);
}
+ ///
+ /// Encodes this point in compressed or uncompressed form.
+ ///
+ /// for compressed encoding; otherwise uncompressed.
+ /// The encoded public key bytes.
public byte[] Encode(bool shouldCompress = true)
{
return shouldCompress ?
@@ -200,6 +296,9 @@ public byte[] Encode(bool shouldCompress = true)
_uncompressed;
}
+ ///
+ /// Determines whether two values are equal.
+ ///
public static bool operator ==(ECPoint? left, ECPoint? right)
{
if (left is null)
@@ -207,26 +306,41 @@ public byte[] Encode(bool shouldCompress = true)
return left.Equals(right);
}
+ ///
+ /// Determines whether two values are not equal.
+ ///
public static bool operator !=(ECPoint? left, ECPoint? right)
{
return !(left == right);
}
+ ///
+ /// Determines whether one is less than another.
+ ///
public static bool operator <(ECPoint? left, ECPoint? right)
{
return left is null ? right is not null : left.CompareTo(right) < 0;
}
+ ///
+ /// Determines whether one is less than or equal to another.
+ ///
public static bool operator <=(ECPoint? left, ECPoint? right)
{
return left is null || left.CompareTo(right) <= 0;
}
+ ///
+ /// Determines whether one is greater than another.
+ ///
public static bool operator >(ECPoint? left, ECPoint? right)
{
return left is not null && left.CompareTo(right) > 0;
}
+ ///
+ /// Determines whether one is greater than or equal to another.
+ ///
public static bool operator >=(ECPoint? left, ECPoint? right)
{
return left is null ? right is null : left.CompareTo(right) >= 0;
diff --git a/src/Neo.Core/Cryptography/MerkleTree.cs b/src/Neo.Core/Cryptography/MerkleTree.cs
index 4f9aa8f..356f7ba 100644
--- a/src/Neo.Core/Cryptography/MerkleTree.cs
+++ b/src/Neo.Core/Cryptography/MerkleTree.cs
@@ -28,6 +28,9 @@
namespace Neo.Core.Cryptography
{
+ ///
+ /// Builds Merkle trees over hash leaves and computes Merkle roots.
+ ///
public class MerkleTree
{
private readonly byte[] _root = [];
@@ -41,6 +44,11 @@ private MerkleTree(IEnumerable leaves)
_root = BuildTree(_leaves);
}
+ ///
+ /// Computes the Merkle root of the specified hash leaves.
+ ///
+ /// The leaf hashes; an empty array yields .
+ /// The Merkle root of .
public static UInt256 ComputeRoot(UInt256[] hashes)
{
if (hashes.Length == 0) return UInt256.Zero;
diff --git a/src/Neo.Core/Cryptography/ProtectedString.cs b/src/Neo.Core/Cryptography/ProtectedString.cs
index dca2459..041bda4 100644
--- a/src/Neo.Core/Cryptography/ProtectedString.cs
+++ b/src/Neo.Core/Cryptography/ProtectedString.cs
@@ -30,6 +30,9 @@
namespace Neo.Core.Cryptography
{
+ ///
+ /// Stores a string encrypted in memory using AES-GCM with process-local entropy.
+ ///
public class ProtectedString : IEquatable, IComparable, IComparable, IDisposable
{
// Sizes required for standard AES-GCM compliance
@@ -42,26 +45,45 @@ public class ProtectedString : IEquatable, IComparable, ICompar
private int _cachedHashCode = 0;
+ ///
+ /// Initializes an empty protected string.
+ ///
public ProtectedString() { }
+ ///
+ /// Initializes a protected string from the specified plaintext.
+ ///
+ /// The plaintext string to protect.
public ProtectedString(string plaintext)
{
var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
_protectedMemory = ProtectData(plaintextBytes);
}
+ ///
+ /// Clears the protected memory and releases resources.
+ ///
public void Dispose()
{
CryptographicOperations.ZeroMemory(_protectedMemory.Span);
GC.SuppressFinalize(this);
}
+ ///
+ /// Determines whether the specified object is equal to the current instance.
+ ///
+ /// The object to compare with the current instance.
+ /// if the objects are equal; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
return Equals(obj as ProtectedString);
}
+ ///
+ /// Returns a hash code for this instance based on the protected payload.
+ ///
+ /// A hash code for the current instance.
public override int GetHashCode()
{
if (_cachedHashCode != 0) return _cachedHashCode;
@@ -71,21 +93,44 @@ public override int GetHashCode()
(hash * 31) ^ b);
}
+ ///
+ /// Decrypts and returns the plaintext string.
+ ///
+ /// The unprotected plaintext.
public override string ToString() =>
UnprotectData(_protectedMemory.Span);
+ ///
+ /// Compares this instance to a specified object.
+ ///
+ /// An object to compare, or .
+ ///
+ /// A signed integer that indicates the relative order of this instance and .
+ ///
public int CompareTo(object? obj)
{
if (ReferenceEquals(obj, this)) return 0;
return CompareTo(obj as ProtectedString);
}
+ ///
+ /// Compares this instance to another .
+ ///
+ /// A to compare.
+ ///
+ /// A signed integer that indicates the relative order of this instance and .
+ ///
public int CompareTo(ProtectedString? other)
{
if (other is null) return 1;
return string.Compare(this, other);
}
+ ///
+ /// Determines whether the specified is equal to the current instance.
+ ///
+ /// The instance to compare with the current instance.
+ /// if the plaintext values are equal; otherwise, .
public bool Equals([NotNullWhen(true)] ProtectedString? other)
{
if (other is null) return false;
@@ -124,6 +169,11 @@ private static Memory ProtectData(byte[] data)
return result;
}
+ ///
+ /// Decrypts a protected binary payload produced by this type.
+ ///
+ /// The encrypted payload (nonce, ciphertext, and tag).
+ /// The decrypted UTF-8 string.
public static string UnprotectData(ReadOnlySpan protectedData)
{
if (protectedData.Length == 0) return string.Empty;
@@ -140,17 +190,31 @@ public static string UnprotectData(ReadOnlySpan protectedData)
return Encoding.UTF8.GetString(plaintext);
}
+ ///
+ /// Implicitly converts a plaintext string to a .
+ ///
+ /// The plaintext string.
public static implicit operator ProtectedString(string value) =>
new(value);
+ ///
+ /// Implicitly converts a to its plaintext string.
+ ///
+ /// The protected string.
public static implicit operator string(ProtectedString value) =>
value.ToString();
+ ///
+ /// Determines whether two values are equal.
+ ///
public static bool operator ==(ProtectedString a, ProtectedString b)
{
return a.Equals(b);
}
+ ///
+ /// Determines whether two values are not equal.
+ ///
public static bool operator !=(ProtectedString a, ProtectedString b) =>
!(a == b);
}
diff --git a/src/Neo.Core/Cryptography/RipeMD160.cs b/src/Neo.Core/Cryptography/RipeMD160.cs
index d0ed9c7..006d563 100644
--- a/src/Neo.Core/Cryptography/RipeMD160.cs
+++ b/src/Neo.Core/Cryptography/RipeMD160.cs
@@ -25,6 +25,9 @@
namespace Neo.Core.Cryptography
{
+ ///
+ /// Computes the RIPEMD-160 cryptographic hash for the input data.
+ ///
public class RipeMD160 : HashAlgorithm
{
private readonly byte[] _buffer;
@@ -41,6 +44,11 @@ private RipeMD160()
InitializeState();
}
+ ///
+ /// Computes the RIPEMD-160 hash of the specified data.
+ ///
+ /// The data to hash.
+ /// The 20-byte hash value.
public static byte[] HashData(byte[] data)
{
using var hasher = new RipeMD160();
@@ -50,6 +58,9 @@ public static byte[] HashData(byte[] data)
#pragma warning restore CA1850
}
+ ///
+ /// Initializes this instance for a new hash computation.
+ ///
public override void Initialize()
{
InitializeState();
diff --git a/src/Neo.Core/Cryptography/SCrypt.cs b/src/Neo.Core/Cryptography/SCrypt.cs
index e5dc78a..7cd1f82 100644
--- a/src/Neo.Core/Cryptography/SCrypt.cs
+++ b/src/Neo.Core/Cryptography/SCrypt.cs
@@ -25,11 +25,23 @@
namespace Neo.Core.Cryptography
{
+ ///
+ /// SCrypt password-based key derivation and salt generation.
+ ///
public static class SCrypt
{
///
/// Generates a derived key using SCrypt.
///
+ /// The password bytes.
+ /// The salt bytes.
+ /// CPU/memory cost parameter; must be a power of two greater than 1.
+ /// Block size parameter; must be at least 1.
+ /// Parallelization parameter; must be at least 1.
+ /// The desired derived key length in bytes.
+ /// The derived key.
+ /// or is empty.
+ /// A parameter is outside the allowed range.
public static byte[] Generate(byte[] password, byte[] salt, int n, int r, int p, int keyLength = 32)
{
ArgumentOutOfRangeException.ThrowIfZero(password.Length, nameof(password));
@@ -44,8 +56,15 @@ public static byte[] Generate(byte[] password, byte[] salt, int n, int r, int p,
}
///
- /// Convenience overload for string password.
+ /// Generates a derived key from a string password using SCrypt.
///
+ /// The password string (UTF-8 encoded).
+ /// The salt bytes.
+ /// CPU/memory cost parameter; must be a power of two greater than 1.
+ /// Block size parameter; must be at least 1.
+ /// Parallelization parameter; must be at least 1.
+ /// The desired derived key length in bytes.
+ /// The derived key.
public static byte[] Generate(string password, byte[] salt, int n = 16384, int r = 8, int p = 1, int keyLength = 32)
{
var passBytes = CoreUtilities.StrictUtf8Encoding.GetBytes(password);
@@ -55,6 +74,8 @@ public static byte[] Generate(string password, byte[] salt, int n = 16384, int r
///
/// Generates a cryptographically secure random salt.
///
+ /// The salt length in bytes.
+ /// A new random salt.
public static byte[] GenerateSalt(int length = 16)
{
var salt = new byte[length];
@@ -63,8 +84,21 @@ public static byte[] GenerateSalt(int length = 16)
}
}
+ ///
+ /// Low-level scrypt encoder helpers used by .
+ ///
internal static class ScryptEncoder
{
+ ///
+ /// Derives a key using the scrypt password-based key derivation function.
+ ///
+ /// Password bytes.
+ /// Salt bytes.
+ /// CPU/memory cost parameter (must be a power of two).
+ /// Block size parameter.
+ /// Parallelization parameter.
+ /// Length of the derived key in bytes.
+ /// The derived key.
public unsafe static byte[] CryptoScrypt(byte[] password, byte[] salt, int n, int r, int p, int keyLength = 32)
{
var ba = new byte[128 * r * p + 63];
diff --git a/src/Neo.Core/Extensions/BigIntegerExtensions.cs b/src/Neo.Core/Extensions/BigIntegerExtensions.cs
index bef70b5..5bcae81 100644
--- a/src/Neo.Core/Extensions/BigIntegerExtensions.cs
+++ b/src/Neo.Core/Extensions/BigIntegerExtensions.cs
@@ -25,8 +25,17 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for .
+ ///
public static class BigIntegerExtensions
{
+ ///
+ /// Computes the integer square root of a non-negative value (floor of the true square root).
+ ///
+ /// The non-negative value whose square root is computed.
+ /// The largest integer whose square is less than or equal to .
+ /// Thrown when is negative.
public static BigInteger Sqrt(this BigInteger value)
{
if (value < 0)
@@ -57,9 +66,13 @@ public static BigInteger Sqrt(this BigInteger value)
}
///
- /// Calculates the modular multiplicative inverse of 'a' modulo 'm'.
- /// Throws ArithmeticException if the inverse does not exist.
+ /// Calculates the modular multiplicative inverse of modulo .
///
+ /// The value for which the modular inverse is computed.
+ /// The positive modulus.
+ /// The modular multiplicative inverse of modulo .
+ /// Thrown when is not positive.
+ /// Thrown when the inverse does not exist because the values are not coprime.
public static BigInteger ModInverse(this BigInteger a, BigInteger m)
{
if (m <= 0)
diff --git a/src/Neo.Core/Extensions/ByteExtensions.cs b/src/Neo.Core/Extensions/ByteExtensions.cs
index ffd404d..123728c 100644
--- a/src/Neo.Core/Extensions/ByteExtensions.cs
+++ b/src/Neo.Core/Extensions/ByteExtensions.cs
@@ -29,11 +29,18 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for arrays.
+ ///
public static class ByteExtensions
{
///
/// Returns a new byte array containing the bitwise XOR of the two arrays.
///
+ /// The first operand.
+ /// The second operand; must be the same length as .
+ /// A new array containing the bitwise XOR of and .
+ /// Thrown when the arrays have different lengths.
public static byte[] Xor(this byte[] x, byte[] y)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(y.Length, x.Length, nameof(y));
@@ -85,23 +92,60 @@ public static T AsSerializable(this byte[] data, int startIndex = 0)
return newObject;
}
+ ///
+ /// Gets the serialized size of a byte array, including its compact-size length prefix.
+ ///
+ /// The byte array.
+ /// The number of bytes required to serialize .
public static int GetSerializedSize(this byte[] data) =>
data.Length.GetCompactSize() + data.Length;
+ ///
+ /// Computes the script hash of the byte array (Hash160).
+ ///
+ /// The data to hash.
+ /// A script hash of the data.
public static UInt160 ToScriptHash(this byte[] data) =>
new(data.ToHash160());
+ ///
+ /// Computes the RIPEMD-160 hash of the byte array.
+ ///
+ /// The data to hash.
+ /// The 20-byte RIPEMD-160 digest.
public static byte[] ToRipeMD160(this byte[] data) =>
RipeMD160.HashData(data);
+ ///
+ /// Computes the SHA-256 hash of the byte array.
+ ///
+ /// The data to hash.
+ /// The 32-byte SHA-256 digest.
public static byte[] ToSha256(this byte[] data) =>
SHA256.HashData(data);
+ ///
+ /// Computes Hash160 (SHA-256 followed by RIPEMD-160) of the byte array.
+ ///
+ /// The data to hash.
+ /// The 20-byte Hash160 digest.
public static byte[] ToHash160(this byte[] data) =>
data.ToSha256().ToRipeMD160();
+ ///
+ /// Computes a transaction-style hash of the byte array as a .
+ ///
+ /// The data to hash.
+ /// A containing the SHA-256 digest.
public static UInt256 ToTxHash(this byte[] data) =>
new(data.ToSha256());
+
+ ///
+ /// Computes a stable integer hash code over the byte array contents.
+ ///
+ /// The data to hash.
+ /// The initial hash seed.
+ /// An integer hash code derived from the array contents.
public static int ToHashCode(this byte[] data, int seed = 397) =>
data.Aggregate(seed,
(hash, b) =>
diff --git a/src/Neo.Core/Extensions/DictionaryExtensions.cs b/src/Neo.Core/Extensions/DictionaryExtensions.cs
index abf6997..e49db36 100644
--- a/src/Neo.Core/Extensions/DictionaryExtensions.cs
+++ b/src/Neo.Core/Extensions/DictionaryExtensions.cs
@@ -26,8 +26,20 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for dictionary types.
+ ///
public static class DictionaryExtensions
{
+ ///
+ /// Gets the value associated with the specified key, or adds a new value created by the factory.
+ ///
+ /// The type of dictionary keys.
+ /// The type of dictionary values.
+ /// The dictionary to query or update.
+ /// The key to look up.
+ /// A factory used to create the value when the key is missing.
+ /// The existing or newly created value for .
public static TValue GetOrAdd(this IDictionary source, TKey key, Func valueFactory)
{
if (source.TryGetValue(key, out var value) == false)
@@ -38,12 +50,28 @@ public static TValue GetOrAdd(this IDictionary sourc
return value;
}
+ ///
+ /// Computes a stable integer hash code over the dictionary entries.
+ ///
+ /// The type of dictionary keys.
+ /// The type of dictionary values.
+ /// The dictionary to hash.
+ /// The initial hash seed.
+ /// An integer hash code derived from the dictionary contents.
public static int ToHashCode(this IDictionary source, int seed = 397) =>
source.Aggregate(seed,
(hash, b) =>
unchecked((hash * 31) + ((b.Key?.GetHashCode() ?? 0) ^ (b.Value?.GetHashCode() ?? 0)))
);
+ ///
+ /// Computes a stable integer hash code over the read-only dictionary entries.
+ ///
+ /// The type of dictionary keys.
+ /// The type of dictionary values.
+ /// The dictionary to hash.
+ /// The initial hash seed.
+ /// An integer hash code derived from the dictionary contents.
public static int ToHashCode(this IReadOnlyDictionary source, int seed = 397) =>
source.Aggregate(seed,
(hash, b) =>
diff --git a/src/Neo.Core/Extensions/EnumerableExtensions.cs b/src/Neo.Core/Extensions/EnumerableExtensions.cs
index 18572f6..55d3d6b 100644
--- a/src/Neo.Core/Extensions/EnumerableExtensions.cs
+++ b/src/Neo.Core/Extensions/EnumerableExtensions.cs
@@ -25,8 +25,18 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for .
+ ///
public static class EnumerableExtensions
{
+ ///
+ /// Computes a stable integer hash code over the sequence elements.
+ ///
+ /// The type of the sequence elements.
+ /// The sequence to hash.
+ /// The initial hash seed.
+ /// An integer hash code derived from the sequence contents.
public static int ToHashCode(this IEnumerable source, int seed = 397) =>
source.Aggregate(seed,
(hash, b) =>
diff --git a/src/Neo.Core/Extensions/INeoSerializableExtensions.cs b/src/Neo.Core/Extensions/INeoSerializableExtensions.cs
index 2ce42bb..ab58498 100644
--- a/src/Neo.Core/Extensions/INeoSerializableExtensions.cs
+++ b/src/Neo.Core/Extensions/INeoSerializableExtensions.cs
@@ -26,6 +26,9 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for types.
+ ///
public static class INeoSerializableExtensions
{
///
@@ -41,6 +44,11 @@ public static byte[] ToArray(this INeoSerializable source)
return ms.ToArray();
}
+ ///
+ /// Gets the serialized size of an array of objects, including the compact-size length prefix.
+ ///
+ /// The array of serializable objects.
+ /// The number of bytes required to serialize the array.
public static int GetSerializedSize(this INeoSerializable[] source) =>
source.Length.GetCompactSize() +
source.Sum(static s => s.Size);
diff --git a/src/Neo.Core/Extensions/IntegerExtensions.cs b/src/Neo.Core/Extensions/IntegerExtensions.cs
index 7a8c2b0..3cc6cc6 100644
--- a/src/Neo.Core/Extensions/IntegerExtensions.cs
+++ b/src/Neo.Core/Extensions/IntegerExtensions.cs
@@ -25,8 +25,17 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for integer types.
+ ///
public static class IntegerExtensions
{
+ ///
+ /// Gets the number of bytes required to encode the value as a Bitcoin-style compact size integer.
+ ///
+ /// The integer type.
+ /// The value to measure.
+ /// The encoded size in bytes (1, 3, 5, or 9).
public static byte GetCompactSize(this T value)
where T : unmanaged, IBinaryInteger =>
Convert.ToUInt64(value) switch
diff --git a/src/Neo.Core/Extensions/ListExtensions.cs b/src/Neo.Core/Extensions/ListExtensions.cs
index 4222ec6..16e28f8 100644
--- a/src/Neo.Core/Extensions/ListExtensions.cs
+++ b/src/Neo.Core/Extensions/ListExtensions.cs
@@ -25,14 +25,31 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for list types.
+ ///
public static class ListExtensions
{
+ ///
+ /// Computes a stable integer hash code over the list elements.
+ ///
+ /// The type of the list elements.
+ /// The list to hash.
+ /// The initial hash seed.
+ /// An integer hash code derived from the list contents.
public static int ToHashCode(this IList source, int seed = 397) =>
source.Aggregate(seed,
(hash, b) =>
unchecked((hash * 31) ^ (b?.GetHashCode() ?? 0))
);
+ ///
+ /// Computes a stable integer hash code over the read-only list elements.
+ ///
+ /// The type of the list elements.
+ /// The list to hash.
+ /// The initial hash seed.
+ /// An integer hash code derived from the list contents.
public static int ToHashCode(this IReadOnlyList source, int seed = 397) =>
source.Aggregate(seed,
(hash, b) =>
diff --git a/src/Neo.Core/Extensions/MemoryExtensions.cs b/src/Neo.Core/Extensions/MemoryExtensions.cs
index e924f46..87a49f3 100644
--- a/src/Neo.Core/Extensions/MemoryExtensions.cs
+++ b/src/Neo.Core/Extensions/MemoryExtensions.cs
@@ -26,30 +26,54 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for and .
+ ///
public static class MemoryExtensions
{
+ ///
+ /// Gets the serialized size of a memory region of unmanaged values, including the compact-size length prefix.
+ ///
+ /// The unmanaged element type.
+ /// The memory region to measure.
+ /// The number of bytes required to serialize the memory contents.
public static int GetSerializedSize(this Memory memory)
where TSource : unmanaged =>
memory.Length.GetCompactSize() +
(memory.Length * Unsafe.SizeOf());
+ ///
+ /// Gets the serialized size of a read-only memory region of unmanaged values, including the compact-size length prefix.
+ ///
+ /// The unmanaged element type.
+ /// The memory region to measure.
+ /// The number of bytes required to serialize the memory contents.
public static int GetSerializedSize(this ReadOnlyMemory readOnlyMemory)
where TSource : unmanaged =>
readOnlyMemory.Length.GetCompactSize() +
(readOnlyMemory.Length * Unsafe.SizeOf());
///
- /// Converts a byte array to an object.
+ /// Converts a byte memory region to an object.
///
/// The type to convert to.
- /// The byte array to be converted.
- ///
+ /// The bytes to be converted.
+ /// The offset into the byte data from which to begin deserialization.
/// The converted object.
public static T? AsSerializable(this ReadOnlyMemory memory, int startIndex = 0)
where T : class?, INeoSerializable? =>
memory.ToArray()
.AsSerializable(startIndex);
+ ///
+ /// Applies an accumulator function over a memory region.
+ ///
+ /// The type of the memory elements.
+ /// The type of the accumulator value.
+ /// The memory region to aggregate.
+ /// The initial accumulator value.
+ /// An accumulator function to be invoked on each element.
+ /// The final accumulator value.
public static TAccumulate Aggregate(this Memory source, TAccumulate seed, Func func)
{
var val = seed;
@@ -65,6 +89,15 @@ public static TAccumulate Aggregate(this Memory s
return val;
}
+ ///
+ /// Applies an accumulator function over a read-only memory region.
+ ///
+ /// The type of the memory elements.
+ /// The type of the accumulator value.
+ /// The memory region to aggregate.
+ /// The initial accumulator value.
+ /// An accumulator function to be invoked on each element.
+ /// The final accumulator value.
public static TAccumulate Aggregate(this ReadOnlyMemory source, TAccumulate seed, Func func)
{
var val = seed;
@@ -78,18 +111,34 @@ public static TAccumulate Aggregate(this ReadOnlyMemory
+ /// Computes a stable integer hash code over the byte memory contents.
+ ///
+ /// The data to hash.
+ /// The initial hash seed.
+ /// An integer hash code derived from the memory contents.
public static int ToHashCode(this Memory data, int seed) =>
data.Aggregate(seed,
(hash, b) =>
unchecked((hash * 31) ^ b)
);
+ ///
+ /// Computes a stable integer hash code over the byte memory contents, seeded by the memory length.
+ ///
+ /// The data to hash.
+ /// An integer hash code derived from the memory contents.
public static int ToHashCode(this Memory data) =>
data.Aggregate(data.Length,
(hash, b) =>
(hash * 31) ^ b
);
+ ///
+ /// Computes a stable integer hash code over the read-only byte memory contents, seeded by the memory length.
+ ///
+ /// The data to hash.
+ /// An integer hash code derived from the memory contents.
public static int ToHashCode(this ReadOnlyMemory data) =>
data.Aggregate(data.Length,
(hash, b) =>
diff --git a/src/Neo.Core/Extensions/ObjectExtensions.cs b/src/Neo.Core/Extensions/ObjectExtensions.cs
index edb2a39..188bb58 100644
--- a/src/Neo.Core/Extensions/ObjectExtensions.cs
+++ b/src/Neo.Core/Extensions/ObjectExtensions.cs
@@ -24,8 +24,17 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for unmanaged object arrays.
+ ///
public static class ObjectExtensions
{
+ ///
+ /// Gets the serialized size of an unmanaged array, including its compact-size length prefix.
+ ///
+ /// The unmanaged element type.
+ /// The array to measure.
+ /// The number of bytes required to serialize the array.
public static int GetSerializedSize(this TSource[] source)
where TSource : unmanaged =>
source.Length.GetCompactSize() +
diff --git a/src/Neo.Core/Extensions/SpanExtensions.cs b/src/Neo.Core/Extensions/SpanExtensions.cs
index 6d24ebe..ebf1d86 100644
--- a/src/Neo.Core/Extensions/SpanExtensions.cs
+++ b/src/Neo.Core/Extensions/SpanExtensions.cs
@@ -28,30 +28,54 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for and .
+ ///
public static class SpanExtensions
{
+ ///
+ /// Gets the serialized size of a span of unmanaged values, including the compact-size length prefix.
+ ///
+ /// The unmanaged element type.
+ /// The span to measure.
+ /// The number of bytes required to serialize the span contents.
public static int GetSerializedSize(this Span span)
where TSource : unmanaged =>
span.Length.GetCompactSize() +
(span.Length * Unsafe.SizeOf());
+ ///
+ /// Gets the serialized size of a read-only span of unmanaged values, including the compact-size length prefix.
+ ///
+ /// The unmanaged element type.
+ /// The span to measure.
+ /// The number of bytes required to serialize the span contents.
public static int GetSerializedSize(this ReadOnlySpan readOnlySpan)
where TSource : unmanaged =>
readOnlySpan.Length.GetCompactSize() +
(readOnlySpan.Length * Unsafe.SizeOf());
///
- /// Converts a byte array to an object.
+ /// Converts a byte span to an object.
///
/// The type to convert to.
- /// The byte array to be converted.
- ///
+ /// The bytes to be converted.
+ /// The offset into the byte data from which to begin deserialization.
/// The converted object.
public static T? AsSerializable(this ReadOnlySpan span, int startIndex = 0)
where T : class?, INeoSerializable? =>
span.ToArray()
.AsSerializable(startIndex);
+ ///
+ /// Applies an accumulator function over a span.
+ ///
+ /// The type of the span elements.
+ /// The type of the accumulator value.
+ /// The span to aggregate.
+ /// The initial accumulator value.
+ /// An accumulator function to be invoked on each element.
+ /// The final accumulator value.
public static TAccumulate Aggregate(this Span source, TAccumulate seed, Func func)
{
var val = seed;
@@ -67,6 +91,15 @@ public static TAccumulate Aggregate(this Span sou
return val;
}
+ ///
+ /// Applies an accumulator function over a read-only span.
+ ///
+ /// The type of the span elements.
+ /// The type of the accumulator value.
+ /// The span to aggregate.
+ /// The initial accumulator value.
+ /// An accumulator function to be invoked on each element.
+ /// The final accumulator value.
public static TAccumulate Aggregate(this ReadOnlySpan source, TAccumulate seed, Func func)
{
var val = seed;
@@ -80,12 +113,23 @@ public static TAccumulate Aggregate(this ReadOnlySpan
+ /// Computes a stable integer hash code over the byte span contents.
+ ///
+ /// The data to hash.
+ /// The initial hash seed.
+ /// An integer hash code derived from the span contents.
public static int ToHashCode(this ReadOnlySpan data, int seed = 397) =>
data.Aggregate(seed,
(hash, b) =>
unchecked((hash * 31) ^ b)
);
+ ///
+ /// Compresses the span using LZ4, prefixing the output with the original length as a little-endian .
+ ///
+ /// The data to compress.
+ /// A span containing the original length followed by the LZ4-compressed payload.
public static Span ToLz4Compress(this ReadOnlySpan data)
{
var maxLength = LZ4Codec.MaximumOutputSize(data.Length);
@@ -98,6 +142,14 @@ public static Span ToLz4Compress(this ReadOnlySpan data)
return buffer[..(sizeof(uint) + length)];
}
+ ///
+ /// Decompresses an LZ4 payload previously produced by .
+ ///
+ /// The compressed data, beginning with a little-endian original length.
+ /// The decompressed bytes.
+ ///
+ /// Thrown when the decompressed length does not match the length prefix.
+ ///
public static Span ToLz4Decompress(this ReadOnlySpan data)
{
var length = BinaryPrimitives.ReadInt32LittleEndian(data);
diff --git a/src/Neo.Core/Extensions/StreamExtensions.cs b/src/Neo.Core/Extensions/StreamExtensions.cs
index 6da5a33..da3e8c3 100644
--- a/src/Neo.Core/Extensions/StreamExtensions.cs
+++ b/src/Neo.Core/Extensions/StreamExtensions.cs
@@ -28,8 +28,18 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for reading and writing Neo serialization formats on .
+ ///
public static class StreamExtensions
{
+ ///
+ /// Writes an integer value using Bitcoin-style compact size encoding.
+ ///
+ /// The integer type to write.
+ /// The stream to write to.
+ /// The value to encode.
+ /// Thrown when the stream does not support writing.
public static void WriteCompact(this Stream stream, T value)
where T : unmanaged, IBinaryInteger
{
@@ -58,6 +68,13 @@ public static void WriteCompact(this Stream stream, T value)
}
}
+ ///
+ /// Writes the raw bytes of an unmanaged value to the stream.
+ ///
+ /// The unmanaged type to write.
+ /// The stream to write to.
+ /// The value to write.
+ /// Thrown when the stream does not support writing.
public static void Write(this Stream stream, T value)
where T : unmanaged
{
@@ -70,6 +87,13 @@ public static void Write(this Stream stream, T value)
stream.Write(span);
}
+ ///
+ /// Writes the raw bytes of an unmanaged value to the stream by reference.
+ ///
+ /// The unmanaged type to write.
+ /// The stream to write to.
+ /// The value to write.
+ /// Thrown when the stream does not support writing.
public static void Write(this Stream stream, ref T value)
where T : unmanaged
{
@@ -82,6 +106,13 @@ public static void Write(this Stream stream, ref T value)
stream.Write(span);
}
+ ///
+ /// Writes an unmanaged array with a compact-size length prefix followed by the raw element bytes.
+ ///
+ /// The unmanaged element type.
+ /// The stream to write to.
+ /// The array to write.
+ /// Thrown when the stream does not support writing.
public static void Write(this Stream stream, T[] values)
where T : unmanaged
{
@@ -95,10 +126,23 @@ public static void Write(this Stream stream, T[] values)
stream.Write(span);
}
+ ///
+ /// Writes an unmanaged span with a compact-size length prefix followed by the raw element bytes.
+ ///
+ /// The unmanaged element type.
+ /// The stream to write to.
+ /// The span to write.
public static void Write(this Stream stream, Span values)
where T : unmanaged =>
stream.Write((ReadOnlySpan)values);
+ ///
+ /// Writes an unmanaged read-only span with a compact-size length prefix followed by the raw element bytes.
+ ///
+ /// The unmanaged element type.
+ /// The stream to write to.
+ /// The span to write.
+ /// Thrown when the stream does not support writing.
public static void Write(this Stream stream, ReadOnlySpan values)
where T : unmanaged
{
@@ -111,6 +155,12 @@ public static void Write(this Stream stream, ReadOnlySpan values)
stream.Write(span);
}
+ ///
+ /// Writes a UTF-8 string with a compact-size byte-length prefix.
+ ///
+ /// The stream to write to.
+ /// The string to write.
+ /// Thrown when the stream does not support writing.
public static void Write(this Stream stream, string value)
{
if (stream.CanWrite == false)
@@ -127,6 +177,12 @@ public static void Write(this Stream stream, string value)
stream.Write(byteSpan);
}
+ ///
+ /// Writes a character array as UTF-8 with compact-size prefixes for both byte length and character count.
+ ///
+ /// The stream to write to.
+ /// The character array to write.
+ /// Thrown when the stream does not support writing.
public static void Write(this Stream stream, char[] value)
{
if (stream.CanWrite == false)
@@ -144,6 +200,13 @@ public static void Write(this Stream stream, char[] value)
stream.Write(byteSpan);
}
+ ///
+ /// Writes an array of objects with a compact-size length prefix.
+ ///
+ /// The serializable element type.
+ /// The stream to write to.
+ /// The objects to serialize.
+ /// Thrown when the stream does not support writing.
public static void WriteObjects(this Stream stream, T[] values)
where T : INeoSerializable
{
@@ -156,6 +219,14 @@ public static void WriteObjects(this Stream stream, T[] values)
t.Serialize(stream);
}
+ ///
+ /// Reads an integer value encoded with Bitcoin-style compact size encoding.
+ ///
+ /// The integer type to read.
+ /// The stream to read from.
+ /// The decoded integer value.
+ /// Thrown when the stream does not support reading.
+ /// Thrown when the stream ends before the value can be read.
public static T ReadCompact(this Stream stream)
where T : unmanaged, IBinaryInteger
{
@@ -180,6 +251,14 @@ public static T ReadCompact(this Stream stream)
return T.CreateChecked(value); // Throws on overflow for the target type
}
+ ///
+ /// Reads the raw bytes of an unmanaged value into the provided reference.
+ ///
+ /// The unmanaged type to read.
+ /// The stream to read from.
+ /// A reference that receives the read value.
+ /// A reference to .
+ /// Thrown when the stream does not support reading.
public static ref T Read(this Stream stream, ref T result)
where T : unmanaged
{
@@ -194,6 +273,13 @@ public static ref T Read(this Stream stream, ref T result)
return ref result;
}
+ ///
+ /// Reads the raw bytes of an unmanaged value from the stream.
+ ///
+ /// The unmanaged type to read.
+ /// The stream to read from.
+ /// The value read from the stream.
+ /// Thrown when the stream does not support reading.
public static T Read(this Stream stream)
where T : unmanaged
{
@@ -209,6 +295,12 @@ public static T Read(this Stream stream)
return result;
}
+ ///
+ /// Reads a character array previously written by .
+ ///
+ /// The stream to read from.
+ /// The decoded character array.
+ /// Thrown when the stream does not support reading.
public static char[] ReadCharArray(this Stream stream)
{
if (stream.CanRead == false)
@@ -228,6 +320,12 @@ public static char[] ReadCharArray(this Stream stream)
return results;
}
+ ///
+ /// Reads a UTF-8 string previously written by .
+ ///
+ /// The stream to read from.
+ /// The decoded string.
+ /// Thrown when the stream does not support reading.
public static string ReadString(this Stream stream)
{
if (stream.CanRead == false)
@@ -242,6 +340,14 @@ public static string ReadString(this Stream stream)
return encoding.GetString(bytes);
}
+ ///
+ /// Reads an unmanaged array previously written with a compact-size length prefix.
+ ///
+ /// The unmanaged element type.
+ /// The stream to read from.
+ /// The decoded array.
+ /// Thrown when the stream does not support reading.
+ /// Thrown when is .
public static T[] ReadDynamic(this Stream stream)
where T : unmanaged
{
@@ -261,6 +367,14 @@ public static T[] ReadDynamic(this Stream stream)
return results;
}
+ ///
+ /// Reads an array of objects previously written by .
+ ///
+ /// The serializable element type.
+ /// The stream to read from.
+ /// The deserialized object array.
+ /// Thrown when the stream does not support reading.
+ /// Thrown when is .
public static T[] ReadObjects(this Stream stream)
where T : INeoSerializable
{
diff --git a/src/Neo.Core/Extensions/StringExtensions.cs b/src/Neo.Core/Extensions/StringExtensions.cs
index 2d6a531..efa87bd 100644
--- a/src/Neo.Core/Extensions/StringExtensions.cs
+++ b/src/Neo.Core/Extensions/StringExtensions.cs
@@ -25,6 +25,9 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for and character spans.
+ ///
public static class StringExtensions
{
///
@@ -42,6 +45,13 @@ public static ReadOnlySpan TrimStartIgnoreCase(this ReadOnlySpan val
return value;
}
+ ///
+ /// Pads a string on both sides so that it is centered within the specified total width.
+ ///
+ /// The string to pad; when , an empty string is returned.
+ /// The total width of the resulting string.
+ /// The character used for padding.
+ /// The centered string, or the original string when it is already at least characters.
public static string PadCenter(this string str, int totalWidth, char paddingChar)
{
if (str is null) return string.Empty;
@@ -59,6 +69,9 @@ public static string PadCenter(this string str, int totalWidth, char paddingChar
/// The address to convert.
/// The address version.
/// The converted script hash.
+ ///
+ /// Thrown when the address is not a valid Base58Check address for the specified version.
+ ///
public static UInt160 ToScriptHash(this string address, byte version)
{
var data = Base58.DecodeCheck(address);
@@ -72,6 +85,11 @@ public static UInt160 ToScriptHash(this string address, byte version)
return new(data.AsSpan(1));
}
+ ///
+ /// Gets the serialized size of a UTF-8 string, including its compact-size length prefix.
+ ///
+ /// The string to measure.
+ /// The number of bytes required to serialize the string.
public static int GetSerializedSize(this string source)
{
var byteCount = CoreUtilities.StrictUtf8Encoding.GetByteCount(source);
diff --git a/src/Neo.Core/Extensions/TryCatchExtensions.cs b/src/Neo.Core/Extensions/TryCatchExtensions.cs
index a4b7d87..0c36ae9 100644
--- a/src/Neo.Core/Extensions/TryCatchExtensions.cs
+++ b/src/Neo.Core/Extensions/TryCatchExtensions.cs
@@ -24,8 +24,19 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides fluent try/catch helpers for chaining operations with optional error handling.
+ ///
public static class TryCatchExtensions
{
+ ///
+ /// Invokes a function and returns its result, or the default value when any exception is thrown.
+ ///
+ /// The type of the receiver.
+ /// The type of the result.
+ /// The receiver passed to .
+ /// The function to invoke.
+ /// The function result, or if an exception is thrown.
public static TResult? TryCatch(this TSource obj, Func func)
{
try
@@ -38,6 +49,15 @@ public static class TryCatchExtensions
}
}
+ ///
+ /// Invokes an action and optionally handles a specific exception type, then returns the receiver.
+ ///
+ /// The type of the receiver.
+ /// The exception type to catch.
+ /// The receiver passed to the action.
+ /// The action to invoke.
+ /// An optional handler invoked when is thrown.
+ /// The original receiver.
public static TSource TryCatch(this TSource obj, Action action, Action? onError = default)
where TException : Exception
{
@@ -53,6 +73,19 @@ public static TSource TryCatch(this TSource obj, Action
+ /// Invokes a function and optionally maps a specific exception type to a result.
+ ///
+ /// The type of the receiver.
+ /// The exception type to catch.
+ /// The type of the result.
+ /// The receiver passed to the function.
+ /// The function to invoke.
+ ///
+ /// An optional handler that produces a result when is thrown.
+ /// When omitted, the exception is rethrown.
+ ///
+ /// The function result, or the value produced by .
public static TResult TryCatch(this TSource obj, Func func, Func? onError = default)
where TException : Exception
{
@@ -67,6 +100,14 @@ public static TResult TryCatch(this TSource obj, F
}
}
+ ///
+ /// Invokes an action and rethrows exceptions of type .
+ ///
+ /// The type of the receiver.
+ /// The exception type that is rethrown.
+ /// The receiver passed to the action.
+ /// The action to invoke.
+ /// The original receiver when the action completes successfully.
public static TSource TryCatchThrow(this TSource obj, Action action)
where TException : Exception
{
@@ -82,6 +123,15 @@ public static TSource TryCatchThrow(this TSource obj, Actio
}
}
+ ///
+ /// Invokes a function and rethrows exceptions of type .
+ ///
+ /// The type of the receiver.
+ /// The exception type that is rethrown.
+ /// The type of the result.
+ /// The receiver passed to the function.
+ /// The function to invoke.
+ /// The function result when it completes successfully.
public static TResult TryCatchThrow(this TSource obj, Func func)
where TException : Exception
{
@@ -95,6 +145,17 @@ public static TResult TryCatchThrow(this TSource o
}
}
+ ///
+ /// Invokes an action and, on failure, optionally wraps with a new message.
+ ///
+ /// The type of the receiver.
+ /// The exception type to catch and optionally wrap.
+ /// The receiver passed to the action.
+ /// The action to invoke.
+ ///
+ /// When provided, a new is thrown with this message and the original exception as the inner exception.
+ ///
+ /// The original receiver when the action completes successfully.
public static TSource TryCatchThrow(this TSource obj, Action action, string? errorMessage = default)
where TException : Exception, new()
{
@@ -119,6 +180,18 @@ public static TSource TryCatchThrow(this TSource obj, Actio
}
}
+ ///
+ /// Invokes a function and, on failure, optionally wraps with a new message.
+ ///
+ /// The type of the receiver.
+ /// The exception type to catch and optionally wrap.
+ /// The type of the result.
+ /// The receiver passed to the function.
+ /// The function to invoke.
+ ///
+ /// When provided, a new is thrown with this message and the original exception as the inner exception.
+ ///
+ /// The function result when it completes successfully.
public static TResult? TryCatchThrow(this TSource obj, Func func, string? errorMessage = default)
where TException : Exception
{
diff --git a/src/Neo.Core/Extensions/TypeExtensions.cs b/src/Neo.Core/Extensions/TypeExtensions.cs
index 61e7ed7..53038ff 100644
--- a/src/Neo.Core/Extensions/TypeExtensions.cs
+++ b/src/Neo.Core/Extensions/TypeExtensions.cs
@@ -25,8 +25,19 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for .
+ ///
public static class TypeExtensions
{
+ ///
+ /// Creates a new instance of the specified type using its public or non-public parameterless constructor.
+ ///
+ /// The type to instantiate.
+ /// A newly constructed instance of .
+ ///
+ /// Thrown when the type does not have a parameterless constructor.
+ ///
public static object CreateInitializedObject(this Type source)
{
// Try to get the private/public parameterless constructor.
diff --git a/src/Neo.Core/Extensions/UInt160Extensions.cs b/src/Neo.Core/Extensions/UInt160Extensions.cs
index 326b0aa..b6239da 100644
--- a/src/Neo.Core/Extensions/UInt160Extensions.cs
+++ b/src/Neo.Core/Extensions/UInt160Extensions.cs
@@ -24,6 +24,9 @@
namespace Neo.Core.Extensions
{
+ ///
+ /// Provides extension methods for .
+ ///
public static class UInt160Extensions
{
///
diff --git a/src/Neo.Core/Factory/RandomNumberFactory.cs b/src/Neo.Core/Factory/RandomNumberFactory.cs
index 5d2b031..3d1bd37 100644
--- a/src/Neo.Core/Factory/RandomNumberFactory.cs
+++ b/src/Neo.Core/Factory/RandomNumberFactory.cs
@@ -27,11 +27,24 @@
namespace Neo.Core.Factory
{
+ ///
+ /// Factory methods for generating random numbers and byte sequences.
+ ///
public static class RandomNumberFactory
{
+ ///
+ /// Returns a non-negative random less than .
+ ///
+ /// A random value in the range [0, ).
public static sbyte NextSByte() =>
NextSByte(0, sbyte.MaxValue);
+ ///
+ /// Returns a non-negative random that is less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated.
+ /// A random value in the range [0, ).
+ /// is negative.
public static sbyte NextSByte(sbyte maxValue)
{
if (maxValue < 0)
@@ -40,6 +53,13 @@ public static sbyte NextSByte(sbyte maxValue)
return NextSByte(0, maxValue);
}
+ ///
+ /// Returns a random within the specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned.
+ /// A random value in the range [ , ).
+ /// is greater than .
public static sbyte NextSByte(sbyte minValue, sbyte maxValue)
{
if (minValue == maxValue) return maxValue;
@@ -50,12 +70,28 @@ public static sbyte NextSByte(sbyte minValue, sbyte maxValue)
return (sbyte)(NextUInt32((uint)(maxValue - minValue)) + minValue);
}
+ ///
+ /// Returns a random less than .
+ ///
+ /// A random value in the range [0, ).
public static byte NextByte() =>
NextByte(0, byte.MaxValue);
+ ///
+ /// Returns a random that is less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated.
+ /// A random value in the range [0, ).
public static byte NextByte(byte maxValue) =>
NextByte(0, maxValue);
+ ///
+ /// Returns a random within the specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned.
+ /// A random value in the range [ , ).
+ /// is greater than .
public static byte NextByte(byte minValue, byte maxValue)
{
if (minValue == maxValue) return maxValue;
@@ -66,9 +102,19 @@ public static byte NextByte(byte minValue, byte maxValue)
return (byte)(NextUInt32((uint)(maxValue - minValue)) + minValue);
}
+ ///
+ /// Returns a non-negative random less than .
+ ///
+ /// A random value in the range [0, ).
public static short NextInt16() =>
NextInt16(0, short.MaxValue);
+ ///
+ /// Returns a non-negative random that is less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated.
+ /// A random value in the range [0, ).
+ /// is negative.
public static short NextInt16(short maxValue)
{
if (maxValue < 0)
@@ -77,6 +123,13 @@ public static short NextInt16(short maxValue)
return NextInt16(0, maxValue);
}
+ ///
+ /// Returns a random within the specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned.
+ /// A random value in the range [ , ).
+ /// is greater than .
public static short NextInt16(short minValue, short maxValue)
{
if (minValue == maxValue) return maxValue;
@@ -87,12 +140,28 @@ public static short NextInt16(short minValue, short maxValue)
return (short)(NextUInt32((uint)(maxValue - minValue)) + minValue);
}
+ ///
+ /// Returns a random less than .
+ ///
+ /// A random value in the range [0, ).
public static ushort NextUInt16() =>
NextUInt16(0, ushort.MaxValue);
+ ///
+ /// Returns a random that is less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated.
+ /// A random value in the range [0, ).
public static ushort NextUInt16(ushort maxValue) =>
NextUInt16(0, maxValue);
+ ///
+ /// Returns a random within the specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned.
+ /// A random value in the range [ , ).
+ /// is greater than .
public static ushort NextUInt16(ushort minValue, ushort maxValue)
{
if (minValue == maxValue) return maxValue;
@@ -103,9 +172,19 @@ public static ushort NextUInt16(ushort minValue, ushort maxValue)
return (ushort)(NextUInt32((uint)(maxValue - minValue)) + minValue);
}
+ ///
+ /// Returns a non-negative random less than .
+ ///
+ /// A random value in the range [0, ).
public static int NextInt32() =>
NextInt32(0, int.MaxValue);
+ ///
+ /// Returns a non-negative random that is less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated.
+ /// A random value in the range [0, ).
+ /// is negative.
public static int NextInt32(int maxValue)
{
ArgumentOutOfRangeException.ThrowIfNegative(maxValue);
@@ -113,6 +192,13 @@ public static int NextInt32(int maxValue)
return NextInt32(0, maxValue);
}
+ ///
+ /// Returns a random within the specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned.
+ /// A random value in the range [ , ).
+ /// is greater than .
public static int NextInt32(int minValue, int maxValue)
{
if (minValue == maxValue) return maxValue;
@@ -122,9 +208,18 @@ public static int NextInt32(int minValue, int maxValue)
return (int)NextUInt32((uint)(maxValue - minValue)) + minValue;
}
+ ///
+ /// Returns a random using cryptographic randomness.
+ ///
+ /// A random 32-bit unsigned integer.
public static uint NextUInt32() =>
BinaryPrimitives.ReadUInt32LittleEndian(NextBytes(4, true));
+ ///
+ /// Returns a random that is less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated.
+ /// A random value in the range [0, ).
public static uint NextUInt32(uint maxValue)
{
var randomProduct = (ulong)maxValue * NextUInt32();
@@ -144,6 +239,13 @@ public static uint NextUInt32(uint maxValue)
return (uint)(randomProduct >> 32);
}
+ ///
+ /// Returns a random within the specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned.
+ /// A random value in the range [ , ).
+ /// is greater than .
public static uint NextUInt32(uint minValue, uint maxValue)
{
if (minValue == maxValue) return maxValue;
@@ -153,14 +255,30 @@ public static uint NextUInt32(uint minValue, uint maxValue)
return NextUInt32(maxValue - minValue) + minValue;
}
+ ///
+ /// Returns a non-negative random less than .
+ ///
+ /// A random value in the range [0, ).
public static long NextInt64() =>
NextInt64(0L, long.MaxValue);
+ ///
+ /// Returns a non-negative random that is less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated.
+ /// A random value in the range [0, ).
public static long NextInt64(long maxValue)
{
return NextInt64(0L, maxValue);
}
+ ///
+ /// Returns a random within the specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned.
+ /// A random value in the range [ , ).
+ /// is greater than .
public static long NextInt64(long minValue, long maxValue)
{
if (minValue == maxValue) return maxValue;
@@ -170,9 +288,18 @@ public static long NextInt64(long minValue, long maxValue)
return (long)NextUInt64((ulong)(maxValue - minValue)) + minValue;
}
+ ///
+ /// Returns a random using cryptographic randomness.
+ ///
+ /// A random 64-bit unsigned integer.
public static ulong NextUInt64() =>
BinaryPrimitives.ReadUInt64LittleEndian(NextBytes(8, true));
+ ///
+ /// Returns a random that is less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated.
+ /// A random value in the range [0, ).
public static ulong NextUInt64(ulong maxValue)
{
var randomProduct = Math.BigMul(maxValue, NextUInt64(), out var lowPart);
@@ -190,6 +317,13 @@ public static ulong NextUInt64(ulong maxValue)
return randomProduct;
}
+ ///
+ /// Returns a random within the specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned.
+ /// A random value in the range [ , ).
+ /// is greater than .
public static ulong NextUInt64(ulong minValue, ulong maxValue)
{
if (minValue == maxValue) return maxValue;
@@ -199,6 +333,13 @@ public static ulong NextUInt64(ulong minValue, ulong maxValue)
return NextUInt64(maxValue - minValue) + minValue;
}
+ ///
+ /// Returns a random within the specified range.
+ ///
+ /// The inclusive lower bound of the random number returned.
+ /// The exclusive upper bound of the random number returned.
+ /// A random value in the range [ , ).
+ /// is greater than .
public static BigInteger NextBigInteger(BigInteger minValue, BigInteger maxValue)
{
if (minValue == maxValue) return maxValue;
@@ -208,6 +349,12 @@ public static BigInteger NextBigInteger(BigInteger minValue, BigInteger maxValue
return NextBigInteger(maxValue - minValue) + minValue;
}
+ ///
+ /// Returns a non-negative random that is less than the specified maximum.
+ ///
+ /// The exclusive upper bound of the random number to be generated.
+ /// A random value in the range [0, ).
+ /// is negative.
public static BigInteger NextBigInteger(BigInteger maxValue)
{
if (maxValue.Sign < 0)
@@ -236,6 +383,12 @@ public static BigInteger NextBigInteger(BigInteger maxValue)
return randomProduct >> maxValueBits;
}
+ ///
+ /// Returns a non-negative random with the specified bit length.
+ ///
+ /// The number of bits in the generated value.
+ /// A random non-negative integer with at most bits.
+ /// is negative.
public static BigInteger NextBigInteger(int sizeInBits)
{
if (sizeInBits < 0)
@@ -255,6 +408,15 @@ public static BigInteger NextBigInteger(int sizeInBits)
return new BigInteger(b);
}
+ ///
+ /// Returns a random byte array of the specified length.
+ ///
+ /// The number of random bytes to generate.
+ ///
+ /// to use ; otherwise .
+ ///
+ /// A new array filled with random bytes.
+ /// is negative.
public static byte[] NextBytes(int length, bool cryptography = false)
{
ArgumentOutOfRangeException.ThrowIfLessThan(length, 0, nameof(length));
diff --git a/src/Neo.Core/Interfaces/IMap.cs b/src/Neo.Core/Interfaces/IMap.cs
index 7d2c8b8..8c14d98 100644
--- a/src/Neo.Core/Interfaces/IMap.cs
+++ b/src/Neo.Core/Interfaces/IMap.cs
@@ -22,9 +22,17 @@
namespace Neo.Core.Interfaces
{
+ ///
+ /// Maps an instance to a destination representation of type .
+ ///
+ /// The type of the mapped result.
public interface IMap
where TDestination : notnull
{
+ ///
+ /// Converts this instance to a value.
+ ///
+ /// The mapped destination object.
TDestination ToObject();
}
}
diff --git a/src/Neo.Core/Json/LowerCaseNamingPolicy.cs b/src/Neo.Core/Json/LowerCaseNamingPolicy.cs
index 7ff7d93..9c97099 100644
--- a/src/Neo.Core/Json/LowerCaseNamingPolicy.cs
+++ b/src/Neo.Core/Json/LowerCaseNamingPolicy.cs
@@ -24,8 +24,16 @@
namespace Neo.Core.Json
{
+ ///
+ /// A JSON naming policy that converts property names to lower case.
+ ///
public class LowerCaseNamingPolicy : JsonNamingPolicy
{
+ ///
+ /// Converts the specified property name to lower case using the invariant culture.
+ ///
+ /// The property name to convert.
+ /// The lower-case property name.
public override string ConvertName(string name) =>
name.ToLowerInvariant();
}
diff --git a/src/Neo.Core/Net/Message/ArchivalNodeCapabilityMessage.cs b/src/Neo.Core/Net/Message/ArchivalNodeCapabilityMessage.cs
index 177c925..126cc26 100644
--- a/src/Neo.Core/Net/Message/ArchivalNodeCapabilityMessage.cs
+++ b/src/Neo.Core/Net/Message/ArchivalNodeCapabilityMessage.cs
@@ -26,10 +26,19 @@
namespace Neo.Core.Net.Message
{
+ ///
+ /// Advertises that the node stores full block history for archival synchronization.
+ ///
public class ArchivalNodeCapabilityMessage : NodeCapabilityMessage
{
+ ///
+ /// Gets the capability type ( ).
+ ///
public override NodeCapabilityType Type => NodeCapabilityType.ArchivalNode;
+ ///
+ /// Gets the serialized size of this capability in bytes.
+ ///
public override int Size =>
base.Size +
1;
diff --git a/src/Neo.Core/Net/Message/DisableCompressionCapabilityMessage.cs b/src/Neo.Core/Net/Message/DisableCompressionCapabilityMessage.cs
index ba4dd32..c4c0972 100644
--- a/src/Neo.Core/Net/Message/DisableCompressionCapabilityMessage.cs
+++ b/src/Neo.Core/Net/Message/DisableCompressionCapabilityMessage.cs
@@ -26,10 +26,19 @@
namespace Neo.Core.Net.Message
{
+ ///
+ /// Advertises that the node does not support P2P payload compression.
+ ///
public class DisableCompressionCapabilityMessage : NodeCapabilityMessage
{
+ ///
+ /// Gets the capability type ( ).
+ ///
public override NodeCapabilityType Type => NodeCapabilityType.DisableCompression;
+ ///
+ /// Gets the serialized size of this capability in bytes.
+ ///
public override int Size =>
base.Size +
1;
diff --git a/src/Neo.Core/Net/Message/FullNodeCapabilityMessage.cs b/src/Neo.Core/Net/Message/FullNodeCapabilityMessage.cs
index 671cc10..2da701c 100644
--- a/src/Neo.Core/Net/Message/FullNodeCapabilityMessage.cs
+++ b/src/Neo.Core/Net/Message/FullNodeCapabilityMessage.cs
@@ -25,20 +25,39 @@
namespace Neo.Core.Net.Message
{
+ ///
+ /// Advertises that the node has complete current state at a given start height.
+ ///
public class FullNodeCapabilityMessage : NodeCapabilityMessage
{
+ ///
+ /// Gets the capability type ( ).
+ ///
public override NodeCapabilityType Type => NodeCapabilityType.FullNode;
+ ///
+ /// Gets the block height at which this node reports full state.
+ ///
public uint StartHeight { get; private set; }
+ ///
+ /// Gets the serialized size of this capability in bytes.
+ ///
public override int Size =>
base.Size +
sizeof(uint);
+ ///
+ /// Initializes an empty full-node capability for deserialization.
+ ///
public FullNodeCapabilityMessage()
{
}
+ ///
+ /// Initializes a full-node capability with the specified start height.
+ ///
+ /// The reported full-state block height.
public FullNodeCapabilityMessage(uint startHeight)
{
StartHeight = startHeight;
diff --git a/src/Neo.Core/Net/Message/NodeCapabilityMessage.cs b/src/Neo.Core/Net/Message/NodeCapabilityMessage.cs
index 8e8e3e7..1edab40 100644
--- a/src/Neo.Core/Net/Message/NodeCapabilityMessage.cs
+++ b/src/Neo.Core/Net/Message/NodeCapabilityMessage.cs
@@ -34,8 +34,14 @@ namespace Neo.Core.Net.Message
///
public abstract class NodeCapabilityMessage : INeoSerializable
{
+ ///
+ /// Gets the capability type of this message.
+ ///
public abstract NodeCapabilityType Type { get; }
+ ///
+ /// Gets the serialized size of this capability in bytes.
+ ///
public virtual int Size =>
sizeof(NodeCapabilityType);
@@ -73,6 +79,10 @@ public void Deserialize(Stream reader)
DeserializeWithoutType(reader);
}
+ ///
+ /// Serializes this capability to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(Type);
diff --git a/src/Neo.Core/Net/Message/PingMessage.cs b/src/Neo.Core/Net/Message/PingMessage.cs
index eb79aa2..8afec89 100644
--- a/src/Neo.Core/Net/Message/PingMessage.cs
+++ b/src/Neo.Core/Net/Message/PingMessage.cs
@@ -33,20 +33,43 @@ namespace Neo.Core.Net.Message
///
public class PingMessage : INeoSerializable
{
+ ///
+ /// Gets the last known block index of the sender.
+ ///
public uint LastBlockIndex { get; private set; }
+ ///
+ /// Gets the Unix timestamp (seconds) when this payload was created.
+ ///
public uint Timestamp { get; private set; }
+ ///
+ /// Gets the random nonce used to correlate Ping and Pong.
+ ///
public uint Nonce { get; private set; }
+ ///
+ /// Gets the serialized size of this payload in bytes.
+ ///
public int Size =>
sizeof(uint) + // LastBlockIndex
sizeof(uint) + // Timestamp
sizeof(uint); // Nonce
+ ///
+ /// Creates a ping/pong payload with a random nonce.
+ ///
+ /// The sender's last known block index.
+ /// A new .
public static PingMessage Create(uint lastBlockIndex) =>
Create(lastBlockIndex, RandomNumberFactory.NextUInt32());
+ ///
+ /// Creates a ping/pong payload with the specified nonce.
+ ///
+ /// The sender's last known block index.
+ /// The correlation nonce.
+ /// A new .
public static PingMessage Create(uint lastBlockIndex, uint nonce) =>
new()
{
@@ -55,6 +78,10 @@ public static PingMessage Create(uint lastBlockIndex, uint nonce) =>
Nonce = nonce,
};
+ ///
+ /// Deserializes this payload from the specified stream.
+ ///
+ /// The stream to read from.
public void Deserialize(Stream reader)
{
LastBlockIndex = reader.Read();
@@ -62,6 +89,10 @@ public void Deserialize(Stream reader)
Nonce = reader.Read();
}
+ ///
+ /// Serializes this payload to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(LastBlockIndex);
diff --git a/src/Neo.Core/Net/Message/ProtocolMessage.cs b/src/Neo.Core/Net/Message/ProtocolMessage.cs
index b457bd7..764718b 100644
--- a/src/Neo.Core/Net/Message/ProtocolMessage.cs
+++ b/src/Neo.Core/Net/Message/ProtocolMessage.cs
@@ -43,12 +43,24 @@ public class ProtocolMessage : INeoSerializable
private const int CompressionMinSize = 128;
private const int CompressionThreshold = 64;
+ ///
+ /// Gets the message flags (for example, compressed).
+ ///
public ProtocolMessageFlags Flags { get; private set; }
+ ///
+ /// Gets the protocol command of this frame.
+ ///
public ProtocolMessageCommand Command { get; private set; }
+ ///
+ /// Gets the decoded payload object when known (Version, Ping, or Pong); otherwise, .
+ ///
public INeoSerializable? Message => _protocolMessage;
+ ///
+ /// Gets a value indicating whether the payload is LZ4-compressed on the wire.
+ ///
public bool Compressed => Flags.HasFlag(ProtocolMessageFlags.Compressed);
///
@@ -56,6 +68,9 @@ public class ProtocolMessage : INeoSerializable
///
public ReadOnlyMemory PayloadBytes => _compressedPayloadBytes;
+ ///
+ /// Gets the serialized size of this frame in bytes.
+ ///
public int Size =>
sizeof(ProtocolMessageFlags) +
sizeof(ProtocolMessageCommand) +
@@ -195,6 +210,10 @@ public byte[] ToArray(bool allowCompression = true)
return ms.ToArray();
}
+ ///
+ /// Deserializes this frame from the specified stream.
+ ///
+ /// The stream to read from.
public void Deserialize(Stream reader)
{
Flags = reader.Read();
@@ -205,6 +224,10 @@ public void Deserialize(Stream reader)
DecodePayload();
}
+ ///
+ /// Serializes this frame to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(Flags);
diff --git a/src/Neo.Core/Net/Message/ServerCapabilityMessage.cs b/src/Neo.Core/Net/Message/ServerCapabilityMessage.cs
index 0b0c202..a634073 100644
--- a/src/Neo.Core/Net/Message/ServerCapabilityMessage.cs
+++ b/src/Neo.Core/Net/Message/ServerCapabilityMessage.cs
@@ -25,20 +25,39 @@
namespace Neo.Core.Net.Message
{
+ ///
+ /// Advertises that the node listens for TCP peers on a specific port.
+ ///
public class ServerCapabilityMessage : NodeCapabilityMessage
{
+ ///
+ /// Gets the capability type ( ).
+ ///
public override NodeCapabilityType Type => NodeCapabilityType.TcpServer;
+ ///
+ /// Gets the TCP listen port.
+ ///
public ushort Port { get; private set; }
+ ///
+ /// Gets the serialized size of this capability in bytes.
+ ///
public override int Size =>
base.Size +
sizeof(ushort);
+ ///
+ /// Initializes an empty server capability for deserialization.
+ ///
public ServerCapabilityMessage()
{
}
+ ///
+ /// Initializes a server capability with the specified port.
+ ///
+ /// The TCP listen port.
public ServerCapabilityMessage(ushort port)
{
Port = port;
diff --git a/src/Neo.Core/Net/Message/VersionMessage.cs b/src/Neo.Core/Net/Message/VersionMessage.cs
index a127805..f135013 100644
--- a/src/Neo.Core/Net/Message/VersionMessage.cs
+++ b/src/Neo.Core/Net/Message/VersionMessage.cs
@@ -30,26 +30,56 @@
namespace Neo.Core.Net.Message
{
+ ///
+ /// Payload for the P2P Version handshake message.
+ ///
public class VersionMessage : INeoSerializable
{
+ ///
+ /// The maximum number of capabilities allowed in a version payload.
+ ///
public const int MaxCapabilities = 32;
private readonly static string s_version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0";
+ ///
+ /// Gets the network magic of the sending node.
+ ///
public uint Network { get; private set; }
+ ///
+ /// Gets the protocol version of the sending node.
+ ///
public uint Version { get; private set; } = 0u;
+ ///
+ /// Gets the Unix timestamp (seconds) when this payload was created.
+ ///
public uint Timestamp { get; private set; } = CoreUtilities.ToUnixTimeSeconds(DateTime.UtcNow);
+ ///
+ /// Gets the random nonce identifying the sending node.
+ ///
public uint Nonce { get; private set; } = RandomNumberFactory.NextUInt32();
+ ///
+ /// Gets a value indicating whether the peer allows payload compression.
+ ///
public bool AllowCompression => !Capabilities.Any(static a => a.Type == NodeCapabilityType.DisableCompression);
+ ///
+ /// Gets the user agent string of the sending node.
+ ///
public string UserAgent { get; private set; } = $"/RapidLoop:{s_version}/";
+ ///
+ /// Gets the node capabilities advertised by the sender.
+ ///
public NodeCapabilityMessage[] Capabilities { get; private set; } = [];
+ ///
+ /// Gets the serialized size of this payload in bytes.
+ ///
public int Size =>
sizeof(uint) + // Network
sizeof(uint) + // Version
@@ -58,6 +88,12 @@ public class VersionMessage : INeoSerializable
UserAgent.GetSerializedSize() + // UserAgent
Capabilities.GetSerializedSize(); // Node Capabilities
+ ///
+ /// Creates a version payload for the specified network and capabilities.
+ ///
+ /// The network magic number.
+ /// The node capabilities to advertise.
+ /// A new .
public static VersionMessage Create(uint network, params NodeCapabilityMessage[] capabilities) =>
new()
{
@@ -65,6 +101,13 @@ public static VersionMessage Create(uint network, params NodeCapabilityMessage[]
Capabilities = capabilities,
};
+ ///
+ /// Creates a version payload with an explicit node nonce.
+ ///
+ /// The network magic number.
+ /// The local node nonce.
+ /// The node capabilities to advertise.
+ /// A new .
public static VersionMessage Create(uint network, uint nonce, params NodeCapabilityMessage[] capabilities) =>
new()
{
@@ -73,6 +116,11 @@ public static VersionMessage Create(uint network, uint nonce, params NodeCapabil
Capabilities = capabilities,
};
+ ///
+ /// Deserializes this payload from the specified stream.
+ ///
+ /// The stream to read from.
+ /// Capability count exceeds the limit or capabilities are duplicated.
public void Deserialize(Stream reader)
{
Network = reader.Read();
@@ -94,6 +142,10 @@ public void Deserialize(Stream reader)
throw new FormatException("Duplicate node capabilities.");
}
+ ///
+ /// Serializes this payload to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(Network);
diff --git a/src/Neo.Core/Net/NodeCapabilityType.cs b/src/Neo.Core/Net/NodeCapabilityType.cs
index 47cafeb..2180a17 100644
--- a/src/Neo.Core/Net/NodeCapabilityType.cs
+++ b/src/Neo.Core/Net/NodeCapabilityType.cs
@@ -22,6 +22,9 @@
namespace Neo.Core.Net
{
+ ///
+ /// Identifies a node capability advertised in the P2P version handshake.
+ ///
public enum NodeCapabilityType : byte
{
#region Servers
diff --git a/src/Neo.Core/Net/NodeConnection.cs b/src/Neo.Core/Net/NodeConnection.cs
index 90808e6..ba00bba 100644
--- a/src/Neo.Core/Net/NodeConnection.cs
+++ b/src/Neo.Core/Net/NodeConnection.cs
@@ -77,16 +77,34 @@ public class NodeConnection : IAsyncDisposable
///
public event EventHandler? Disconnected;
+ ///
+ /// Gets a value indicating whether the underlying socket is connected.
+ ///
public bool Connected => _socket.Connected;
+ ///
+ /// Gets the remote endpoint of this connection, if available.
+ ///
public EndPoint? RemoteEndPoint => _socket.RemoteEndPoint;
+ ///
+ /// Gets the local endpoint of this connection, if available.
+ ///
public EndPoint? LocalEndPoint => _socket.LocalEndPoint;
+ ///
+ /// Gets the current handshake state.
+ ///
public NodeHandshakeState HandshakeState => _handshake.State;
+ ///
+ /// Gets a value indicating whether the Version/Verack handshake has completed.
+ ///
public bool IsReady => _handshake.IsReady;
+ ///
+ /// Gets the remote Version payload after handshake processing, if available.
+ ///
public VersionMessage? RemoteVersion => _handshake.RemoteVersion;
///
@@ -123,6 +141,14 @@ public uint LocalBlockIndex
private long _localBlockIndex;
private long _remoteLastBlockIndex;
+ ///
+ /// Initializes a new connection over an already-connected socket.
+ ///
+ /// The connected TCP socket.
+ /// The protocol settings for this network.
+ /// The local node nonce used for self-connection detection.
+ /// Optional local capabilities advertised in Version.
+ /// Optional logger factory; defaults to a null logger.
public NodeConnection(
Socket socket,
ProtocolSettings protocolSettings,
@@ -232,6 +258,10 @@ void OnDisconnected(object? sender, EventArgs e) =>
}
}
+ ///
+ /// Cancels outstanding work, closes the socket, and releases resources.
+ ///
+ /// A task that completes when dispose work finishes.
public async ValueTask DisposeAsync()
{
try
diff --git a/src/Neo.Core/Net/NodeHandshake.cs b/src/Neo.Core/Net/NodeHandshake.cs
index b1ef853..3171b27 100644
--- a/src/Neo.Core/Net/NodeHandshake.cs
+++ b/src/Neo.Core/Net/NodeHandshake.cs
@@ -30,15 +30,26 @@ namespace Neo.Core.Net
/// Pure Neo N3 handshake state machine (no I/O).
/// Local already sent Version; then: receive Version → send Verack → receive Verack → Ready.
///
+ /// The local network magic expected from the remote Version.
+ /// The local node nonce; matching remote nonce is treated as a self-connection.
public sealed class NodeHandshake(uint network, uint localNonce)
{
private readonly uint _network = network;
private readonly uint _localNonce = localNonce;
+ ///
+ /// Gets the current handshake state.
+ ///
public NodeHandshakeState State { get; private set; } = NodeHandshakeState.WaitingForVersion;
+ ///
+ /// Gets the remote Version payload after it has been accepted.
+ ///
public VersionMessage? RemoteVersion { get; private set; }
+ ///
+ /// Gets a value indicating whether the handshake has completed successfully.
+ ///
public bool IsReady => State == NodeHandshakeState.Ready;
///
diff --git a/src/Neo.Core/Net/NodeServerListener.cs b/src/Neo.Core/Net/NodeServerListener.cs
index f1eec4b..6d03d21 100644
--- a/src/Neo.Core/Net/NodeServerListener.cs
+++ b/src/Neo.Core/Net/NodeServerListener.cs
@@ -44,8 +44,14 @@ public class NodeServerListener : IAsyncDisposable
///
public const int DefaultBacklog = 128;
+ ///
+ /// Gets the currently tracked peer connections.
+ ///
public NodeConnection[] Clients => [.. _connections.Keys];
+ ///
+ /// Gets the protocol settings used for accepted and outbound connections.
+ ///
public ProtocolSettings ProtocolSettings => _protocolSettings;
///
@@ -61,6 +67,9 @@ public class NodeServerListener : IAsyncDisposable
_serverSocket.LocalEndPoint as IPEndPoint
?? throw new InvalidOperationException($"Listener is not bound. Call {nameof(Start)} first.");
+ ///
+ /// Gets a value indicating whether the accept loop has been started.
+ ///
public bool IsActive => _isActive != 0;
///
@@ -84,6 +93,12 @@ _serverSocket.LocalEndPoint as IPEndPoint
private int _isActive;
private int _disposed;
+ ///
+ /// Initializes a new listener for the specified endpoint.
+ ///
+ /// The local endpoint to bind when starting.
+ /// Optional protocol settings; defaults to .
+ /// Optional logger factory; defaults to a null logger.
public NodeServerListener(
IPEndPoint endPoint,
ProtocolSettings? protocolSettings = default,
@@ -100,6 +115,10 @@ public NodeServerListener(
_capabilities = CreateLocalCapabilities(endPoint);
}
+ ///
+ /// Stops listening, disconnects peers, and releases resources.
+ ///
+ /// A task that completes when dispose work finishes.
public async ValueTask DisposeAsync()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
@@ -143,11 +162,20 @@ public async ValueTask DisposeAsync()
GC.SuppressFinalize(this);
}
+ ///
+ /// Binds the listener and starts accepting connections with .
+ ///
public void Start()
{
Start(DefaultBacklog);
}
+ ///
+ /// Binds the listener and starts accepting connections.
+ ///
+ /// The maximum length of the pending connections queue.
+ /// is less than or equal to zero.
+ /// The listener has been disposed.
public void Start(int backlog)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(backlog);
diff --git a/src/Neo.Core/Net/ProtocolMessageFlags.cs b/src/Neo.Core/Net/ProtocolMessageFlags.cs
index 36af10f..f74e90c 100644
--- a/src/Neo.Core/Net/ProtocolMessageFlags.cs
+++ b/src/Neo.Core/Net/ProtocolMessageFlags.cs
@@ -24,6 +24,9 @@
namespace Neo.Core.Net
{
+ ///
+ /// Flags carried in a Neo P2P protocol message frame.
+ ///
[Flags]
public enum ProtocolMessageFlags : byte
{
diff --git a/src/Neo.Core/ProtocolSettings.cs b/src/Neo.Core/ProtocolSettings.cs
index 8121922..b06881b 100644
--- a/src/Neo.Core/ProtocolSettings.cs
+++ b/src/Neo.Core/ProtocolSettings.cs
@@ -30,6 +30,9 @@
namespace Neo.Core
{
+ ///
+ /// Network and chain protocol parameters for a NEO network.
+ ///
public class ProtocolSettings
{
///
@@ -126,6 +129,11 @@ public class ProtocolSettings
InitialGasDistribution = 52_000_000_00000000ul,
};
+ ///
+ /// Gets the highest hard fork that is active at the specified block height.
+ ///
+ /// The block height to evaluate.
+ /// The active hard fork at .
public HardFork GetActiveHardFork(long blockHeight) =>
HardForks
.OrderBy(static o => o.Key)
diff --git a/src/Neo.Core/Serialization/INeoSerializable.cs b/src/Neo.Core/Serialization/INeoSerializable.cs
index 1bfd8e9..1cf1e2f 100644
--- a/src/Neo.Core/Serialization/INeoSerializable.cs
+++ b/src/Neo.Core/Serialization/INeoSerializable.cs
@@ -24,6 +24,9 @@
namespace Neo.Core.Serialization
{
+ ///
+ /// Defines binary serialization and deserialization for Neo wire and storage types.
+ ///
public interface INeoSerializable
{
///
diff --git a/src/Neo.Core/Storage/IReadOnlyStore.cs b/src/Neo.Core/Storage/IReadOnlyStore.cs
index c897a3c..4e71c52 100644
--- a/src/Neo.Core/Storage/IReadOnlyStore.cs
+++ b/src/Neo.Core/Storage/IReadOnlyStore.cs
@@ -26,14 +26,41 @@
namespace Neo.Core.Storage
{
+ ///
+ /// A read-only key/value store of binary entries.
+ ///
public interface IReadOnlyStore : IDisposable
{
+ ///
+ /// Determines whether the store contains the specified key.
+ ///
+ /// The key to locate.
+ /// if the key exists; otherwise, .
bool ContainsKey(ReadOnlySpan key);
+ ///
+ /// Gets the value associated with the specified key.
+ ///
+ /// The key of the value to get.
+ /// The value for the key, or if not found.
byte[]? Get(ReadOnlySpan key);
+ ///
+ /// Attempts to get the value associated with the specified key.
+ ///
+ /// The key of the value to get.
+ /// When this method returns, the value if found; otherwise, .
+ /// if the key was found; otherwise, .
bool TryGet(ReadOnlySpan key, [NotNullWhen(true)] out byte[]? value);
+ ///
+ /// Seeks key/value pairs starting at the specified key or prefix.
+ ///
+ /// The key or prefix at which to begin seeking.
+ ///
+ /// to seek in reverse from the end; otherwise forward.
+ ///
+ /// An enumerable of matching key/value pairs.
IEnumerable> Seek(ReadOnlyMemory keyOrPrefix, bool seekFromEnd = false);
}
}
diff --git a/src/Neo.Core/Storage/IStore.cs b/src/Neo.Core/Storage/IStore.cs
index 795279d..f662a0f 100644
--- a/src/Neo.Core/Storage/IStore.cs
+++ b/src/Neo.Core/Storage/IStore.cs
@@ -24,16 +24,40 @@
namespace Neo.Core.Storage
{
+ ///
+ /// A mutable key/value store that supports snapshots, backups, and checkpoints.
+ ///
public interface IStore : IReadOnlyStore
{
+ ///
+ /// Creates a snapshot of the current store state.
+ ///
+ /// A disposable store snapshot.
IStoreSnapshot CreateSnapshot();
+ ///
+ /// Creates a backup handle for this store.
+ ///
+ /// A disposable backup handle.
IStoreBackup CreateBackup();
+ ///
+ /// Creates a checkpoint of the store under the specified directory.
+ ///
+ /// The directory that receives the checkpoint.
void CreateCheckpoint(string checkpointDirectory);
+ ///
+ /// Puts or replaces a value for the specified key.
+ ///
+ /// The key to write.
+ /// The value to associate with .
void Put(ReadOnlySpan key, ReadOnlySpan value);
+ ///
+ /// Deletes the value for the specified key, if present.
+ ///
+ /// The key to delete.
void Delete(ReadOnlySpan key);
}
}
diff --git a/src/Neo.Core/Storage/IStoreBackup.cs b/src/Neo.Core/Storage/IStoreBackup.cs
index fc6f5fb..645f231 100644
--- a/src/Neo.Core/Storage/IStoreBackup.cs
+++ b/src/Neo.Core/Storage/IStoreBackup.cs
@@ -24,10 +24,27 @@
namespace Neo.Core.Storage
{
+ ///
+ /// Creates, purges, and restores store backups.
+ ///
public interface IStoreBackup : IDisposable
{
+ ///
+ /// Creates a backup of the store.
+ ///
void Backup();
+
+ ///
+ /// Removes older backups, retaining the specified number of most recent ones.
+ ///
+ /// The number of backups to keep.
void Purge(uint numberOfBackupsToKeep);
+
+ ///
+ /// Restores the store from a backup path.
+ ///
+ /// The path of the backup to restore.
+ /// Optional write-ahead log path used during restore.
void Restore(string restorePath, string? walPath = null);
}
}
diff --git a/src/Neo.Core/Storage/IStoreSnapshot.cs b/src/Neo.Core/Storage/IStoreSnapshot.cs
index 5c5083c..9fb4b79 100644
--- a/src/Neo.Core/Storage/IStoreSnapshot.cs
+++ b/src/Neo.Core/Storage/IStoreSnapshot.cs
@@ -24,10 +24,19 @@
namespace Neo.Core.Storage
{
+ ///
+ /// A transactional snapshot of an .
+ ///
public interface IStoreSnapshot : IStore, IDisposable
{
+ ///
+ /// Gets the underlying store that produced this snapshot.
+ ///
IStore Store { get; }
+ ///
+ /// Commits pending changes made on this snapshot to the underlying store.
+ ///
void Commit();
}
}
diff --git a/src/Neo.Core/Text/Base58.cs b/src/Neo.Core/Text/Base58.cs
index 64c67b6..4adce51 100644
--- a/src/Neo.Core/Text/Base58.cs
+++ b/src/Neo.Core/Text/Base58.cs
@@ -28,6 +28,9 @@
namespace Neo.Core.Text
{
+ ///
+ /// Provides Base58 and Base58Check encoding and decoding.
+ ///
public static class Base58
{
private const string Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
diff --git a/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs b/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs
index e98c0f2..fa189ae 100644
--- a/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs
+++ b/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs
@@ -27,11 +27,20 @@
namespace Neo.Core.Types.Converter
{
+ ///
+ /// Converts between values and strings for design-time and configuration scenarios.
+ ///
internal class ECPointTypeConverter : TypeConverter
{
+ ///
+ /// Returns whether this converter can convert an object of the given type to an .
+ ///
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) =>
sourceType == typeof(string) && base.CanConvertFrom(context, sourceType);
+ ///
+ /// Converts a string (or other supported value) to an on secp256r1.
+ ///
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string pointString)
@@ -43,6 +52,9 @@ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type source
return base.ConvertFrom(context, culture, value);
}
+ ///
+ /// Converts an to the requested destination type (typically a compressed hex string).
+ ///
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) =>
CanConvertTo(context, destinationType) ?
value switch
diff --git a/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs b/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs
index 295601b..037ee0c 100644
--- a/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs
+++ b/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs
@@ -27,11 +27,29 @@
namespace Neo.Core.Types.Converter
{
+ ///
+ /// Converts string values to instances.
+ ///
public class IPAddressTypeConverter : TypeConverter
{
+ ///
+ /// Returns whether this converter can convert an object of the given type to an .
+ ///
+ /// An that provides a format context.
+ /// A that represents the type to convert from.
+ ///
+ /// if conversion from is supported; otherwise, the base result.
+ ///
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) =>
sourceType == typeof(string) && base.CanConvertFrom(context, sourceType);
+ ///
+ /// Converts the given object to an .
+ ///
+ /// An that provides a format context.
+ /// The to use as the current culture.
+ /// The object to convert.
+ /// An , or the base conversion result.
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string ipString)
diff --git a/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs b/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs
index 9002655..cbc6b8a 100644
--- a/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs
+++ b/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs
@@ -28,11 +28,29 @@
namespace Neo.Core.Types.Converter
{
+ ///
+ /// Converts string values to instances, including host name resolution.
+ ///
public class IPEndPointTypeConverter : TypeConverter
{
+ ///
+ /// Returns whether this converter can convert an object of the given type to an .
+ ///
+ /// An that provides a format context.
+ /// A that represents the type to convert from.
+ ///
+ /// if conversion from is supported; otherwise, the base result.
+ ///
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) =>
sourceType == typeof(string) && base.CanConvertFrom(context, sourceType);
+ ///
+ /// Converts the given object to an .
+ ///
+ /// An that provides a format context.
+ /// The to use as the current culture.
+ /// The object to convert.
+ /// An , or the base conversion result.
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string endPointString)
diff --git a/src/Neo.Core/UInt160.cs b/src/Neo.Core/UInt160.cs
index b05b696..c970ccf 100644
--- a/src/Neo.Core/UInt160.cs
+++ b/src/Neo.Core/UInt160.cs
@@ -51,6 +51,9 @@ public class UInt160 : IComparable, IComparable, IEquatable, I
[FieldOffset(8)] private ulong _value2;
[FieldOffset(16)] private uint _value3;
+ ///
+ /// Gets the serialized size of this value in bytes.
+ ///
public int Size => Length;
///
@@ -71,12 +74,26 @@ public UInt160(ReadOnlySpan value)
value.CopyTo(span);
}
+ ///
+ /// Compares this instance to a specified object.
+ ///
+ /// An object to compare, or .
+ ///
+ /// A signed integer that indicates the relative order of this instance and .
+ ///
public int CompareTo(object? obj)
{
if (ReferenceEquals(obj, this)) return 0;
return CompareTo(obj as UInt160);
}
+ ///
+ /// Compares this instance to another .
+ ///
+ /// An to compare.
+ ///
+ /// A signed integer that indicates the relative order of this instance and .
+ ///
public int CompareTo(UInt160? other)
{
if (other is null) return 1;
@@ -87,6 +104,10 @@ public int CompareTo(UInt160? other)
return _value1.CompareTo(other._value1);
}
+ ///
+ /// Deserializes this value from the specified stream.
+ ///
+ /// The stream to read from.
public void Deserialize(Stream reader)
{
_value1 = reader.Read();
@@ -94,12 +115,22 @@ public void Deserialize(Stream reader)
_value3 = reader.Read();
}
+ ///
+ /// Determines whether the specified object is equal to the current .
+ ///
+ /// The object to compare with the current instance.
+ /// if the objects are equal; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
return Equals(obj as UInt160);
}
+ ///
+ /// Determines whether the specified is equal to the current instance.
+ ///
+ /// The to compare with the current instance.
+ /// if the values are equal; otherwise, .
public bool Equals(UInt160? other)
{
if (other == null) return false;
@@ -108,6 +139,10 @@ public bool Equals(UInt160? other)
_value3 == other._value3;
}
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ /// A hash code for the current .
public override int GetHashCode()
{
unchecked
@@ -122,6 +157,10 @@ public override int GetHashCode()
}
}
+ ///
+ /// Serializes this value to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(_value1);
@@ -129,6 +168,10 @@ public void Serialize(Stream writer)
writer.Write(_value3);
}
+ ///
+ /// Returns the hex string representation of this value with a 0x prefix.
+ ///
+ /// A hex string of this .
public override string ToString()
{
return "0x" + Convert.ToHexStringLower([.. this.ToArray().Reverse()]);
@@ -173,16 +216,27 @@ public static UInt160 Parse(string value)
return new UInt160([.. Convert.FromHexString(data).Reverse()]);
}
+ ///
+ /// Implicitly converts a hex string to a .
+ ///
+ /// A hex string representation of a .
public static implicit operator UInt160(string s)
{
return Parse(s);
}
+ ///
+ /// Implicitly converts a 20-byte array to a .
+ ///
+ /// The little-endian 20-byte value.
public static implicit operator UInt160(byte[] b)
{
return new UInt160(b);
}
+ ///
+ /// Determines whether two values are equal.
+ ///
public static bool operator ==(UInt160? left, UInt160? right)
{
if (left is null || right is null)
@@ -190,6 +244,9 @@ public static implicit operator UInt160(byte[] b)
return left.Equals(right);
}
+ ///
+ /// Determines whether two values are not equal.
+ ///
public static bool operator !=(UInt160? left, UInt160? right)
{
if (left is null || right is null)
@@ -197,21 +254,33 @@ public static implicit operator UInt160(byte[] b)
return !left.Equals(right);
}
+ ///
+ /// Determines whether one is greater than another.
+ ///
public static bool operator >(UInt160 left, UInt160 right)
{
return left.CompareTo(right) > 0;
}
+ ///
+ /// Determines whether one is greater than or equal to another.
+ ///
public static bool operator >=(UInt160 left, UInt160 right)
{
return left.CompareTo(right) >= 0;
}
+ ///
+ /// Determines whether one is less than another.
+ ///
public static bool operator <(UInt160 left, UInt160 right)
{
return left.CompareTo(right) < 0;
}
+ ///
+ /// Determines whether one is less than or equal to another.
+ ///
public static bool operator <=(UInt160 left, UInt160 right)
{
return left.CompareTo(right) <= 0;
diff --git a/src/Neo.Core/UInt256.cs b/src/Neo.Core/UInt256.cs
index 9b4b2d5..37fb8a7 100644
--- a/src/Neo.Core/UInt256.cs
+++ b/src/Neo.Core/UInt256.cs
@@ -52,6 +52,9 @@ public class UInt256 : IComparable, IComparable, IEquatable, I
[FieldOffset(16)] private ulong _value3;
[FieldOffset(24)] private ulong _value4;
+ ///
+ /// Gets the serialized size of this value in bytes.
+ ///
public int Size => Length;
///
@@ -73,12 +76,26 @@ public UInt256(ReadOnlySpan value)
value.CopyTo(span);
}
+ ///
+ /// Compares this instance to a specified object.
+ ///
+ /// An object to compare, or .
+ ///
+ /// A signed integer that indicates the relative order of this instance and .
+ ///
public int CompareTo(object? obj)
{
if (ReferenceEquals(obj, this)) return 0;
return CompareTo(obj as UInt256);
}
+ ///
+ /// Compares this instance to another .
+ ///
+ /// An to compare.
+ ///
+ /// A signed integer that indicates the relative order of this instance and .
+ ///
public int CompareTo(UInt256? other)
{
if (other is null) return 1;
@@ -91,6 +108,10 @@ public int CompareTo(UInt256? other)
return _value1.CompareTo(other._value1);
}
+ ///
+ /// Deserializes this value from the specified stream.
+ ///
+ /// The stream to read from.
public void Deserialize(Stream reader)
{
_value1 = reader.Read();
@@ -99,12 +120,22 @@ public void Deserialize(Stream reader)
_value4 = reader.Read();
}
+ ///
+ /// Determines whether the specified object is equal to the current .
+ ///
+ /// The object to compare with the current instance.
+ /// if the objects are equal; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
return Equals(obj as UInt256);
}
+ ///
+ /// Determines whether the specified is equal to the current instance.
+ ///
+ /// The to compare with the current instance.
+ /// if the values are equal; otherwise, .
public bool Equals(UInt256? other)
{
if (other is null) return false;
@@ -114,6 +145,10 @@ public bool Equals(UInt256? other)
&& _value4 == other._value4;
}
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ /// A hash code for the current .
public override int GetHashCode()
{
unchecked
@@ -129,6 +164,10 @@ public override int GetHashCode()
}
}
+ ///
+ /// Serializes this value to the specified stream.
+ ///
+ /// The stream to write to.
public void Serialize(Stream writer)
{
writer.Write(_value1);
@@ -137,6 +176,10 @@ public void Serialize(Stream writer)
writer.Write(_value4);
}
+ ///
+ /// Returns the hex string representation of this value with a 0x prefix.
+ ///
+ /// A hex string of this .
public override string ToString()
{
return "0x" + Convert.ToHexStringLower([.. this.ToArray().Reverse()]);
@@ -180,16 +223,27 @@ public static UInt256 Parse(string value)
return new UInt256([.. Convert.FromHexString(data).Reverse()]);
}
+ ///
+ /// Implicitly converts a hex string to a .
+ ///
+ /// A hex string representation of a .
public static implicit operator UInt256(string s)
{
return Parse(s);
}
+ ///
+ /// Implicitly converts a 32-byte array to a .
+ ///
+ /// The little-endian 32-byte value.
public static implicit operator UInt256(byte[] b)
{
return new UInt256(b);
}
+ ///
+ /// Determines whether two values are equal.
+ ///
public static bool operator ==(UInt256? left, UInt256? right)
{
if (left is null || right is null)
@@ -197,26 +251,41 @@ public static implicit operator UInt256(byte[] b)
return left.Equals(right);
}
+ ///
+ /// Determines whether two values are not equal.
+ ///
public static bool operator !=(UInt256? left, UInt256? right)
{
return !(left == right);
}
+ ///
+ /// Determines whether one is greater than another.
+ ///
public static bool operator >(UInt256 left, UInt256 right)
{
return left.CompareTo(right) > 0;
}
+ ///
+ /// Determines whether one is greater than or equal to another.
+ ///
public static bool operator >=(UInt256 left, UInt256 right)
{
return left.CompareTo(right) >= 0;
}
+ ///
+ /// Determines whether one is less than another.
+ ///
public static bool operator <(UInt256 left, UInt256 right)
{
return left.CompareTo(right) < 0;
}
+ ///
+ /// Determines whether one is less than or equal to another.
+ ///
public static bool operator <=(UInt256 left, UInt256 right)
{
return left.CompareTo(right) <= 0;
diff --git a/src/Neo.Core/VM/Attributes/MethodDescriptorAttribute.cs b/src/Neo.Core/VM/Attributes/MethodDescriptorAttribute.cs
index 94d2286..c1f8a50 100644
--- a/src/Neo.Core/VM/Attributes/MethodDescriptorAttribute.cs
+++ b/src/Neo.Core/VM/Attributes/MethodDescriptorAttribute.cs
@@ -25,6 +25,9 @@
namespace Neo.Core.VM.Attributes
{
+ ///
+ /// Describes an interoperable service method exposed to the VM, including pricing, permissions, and type metadata.
+ ///
[AttributeUsage(
AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Event,
AllowMultiple = true
@@ -51,8 +54,14 @@ public class MethodDescriptorAttribute : Attribute
///
public required CallFlags CallFlags { get; init; } = CallFlags.None;
+ ///
+ /// Indicates whether the method is safe to invoke without changing chain state.
+ ///
public required bool Safe { get; init; }
+ ///
+ /// The return type of the interoperable service method.
+ ///
public required MethodParameterType ReturnType { get; init; } = MethodParameterType.Any;
}
}
diff --git a/src/Neo.Core/VM/Attributes/OpCodePriceAttribute.cs b/src/Neo.Core/VM/Attributes/OpCodePriceAttribute.cs
index a62ee88..017ac36 100644
--- a/src/Neo.Core/VM/Attributes/OpCodePriceAttribute.cs
+++ b/src/Neo.Core/VM/Attributes/OpCodePriceAttribute.cs
@@ -30,8 +30,14 @@ namespace Neo.Core.VM.Attributes
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class OpCodePriceAttribute : Attribute
{
+ ///
+ /// The hard fork from which this price applies.
+ ///
public required HardFork HardFork { get; init; }
+ ///
+ /// The gas cost of the opcode at the specified hard fork.
+ ///
public required long Cost { get; init; }
}
}
diff --git a/src/Neo.Core/VM/ExecutionEngineLimits.cs b/src/Neo.Core/VM/ExecutionEngineLimits.cs
index e59475a..51a39c9 100644
--- a/src/Neo.Core/VM/ExecutionEngineLimits.cs
+++ b/src/Neo.Core/VM/ExecutionEngineLimits.cs
@@ -29,6 +29,9 @@ namespace Neo.Core.VM
///
public sealed class ExecutionEngineLimits
{
+ ///
+ /// The default maximum amount of gas that can be consumed by a script execution.
+ ///
public const long MaxGas = 20_00000000;
///
diff --git a/src/Neo.Core/VM/HardFork.cs b/src/Neo.Core/VM/HardFork.cs
index 171bdf4..4e6bf5c 100644
--- a/src/Neo.Core/VM/HardFork.cs
+++ b/src/Neo.Core/VM/HardFork.cs
@@ -22,15 +22,49 @@
namespace Neo.Core.VM
{
+ ///
+ /// Identifies protocol hard forks that may change VM behavior, gas costs, or features.
+ ///
public enum HardFork : byte
{
+ ///
+ /// The initial protocol state before any named hard fork.
+ ///
Genesis = 0,
+
+ ///
+ /// The Aspidochelone hard fork.
+ ///
Aspidochelone,
+
+ ///
+ /// The Basilisk hard fork.
+ ///
Basilisk,
+
+ ///
+ /// The Cockatrice hard fork.
+ ///
Cockatrice,
+
+ ///
+ /// The Domovoi hard fork.
+ ///
Domovoi,
+
+ ///
+ /// The Echidna hard fork.
+ ///
Echidna,
+
+ ///
+ /// The Faun hard fork.
+ ///
Faun,
+
+ ///
+ /// The Gorgon hard fork.
+ ///
Gorgon,
}
}
diff --git a/src/Neo.Core/VM/Interface/IVMComponent.cs b/src/Neo.Core/VM/Interface/IVMComponent.cs
index 7eb9fd1..672c6af 100644
--- a/src/Neo.Core/VM/Interface/IVMComponent.cs
+++ b/src/Neo.Core/VM/Interface/IVMComponent.cs
@@ -24,9 +24,19 @@
namespace Neo.Core.VM.Interface
{
+ ///
+ /// Represents a disposable VM component that tracks size and reference count.
+ ///
public interface IVMComponent : IDisposable
{
+ ///
+ /// Gets the size of the component in bytes for stack and limit accounting.
+ ///
int Size { get; }
+
+ ///
+ /// Gets the current reference count of the component.
+ ///
int RefCount { get; }
}
}
diff --git a/src/Neo.Core/VM/MethodDescriptor.cs b/src/Neo.Core/VM/MethodDescriptor.cs
index 041a358..01b6824 100644
--- a/src/Neo.Core/VM/MethodDescriptor.cs
+++ b/src/Neo.Core/VM/MethodDescriptor.cs
@@ -31,6 +31,9 @@
namespace Neo.Core.VM
{
+ ///
+ /// Describes an interoperable service method that can be invoked from the VM via .
+ ///
public class MethodDescriptor : IEquatable
{
///
@@ -49,7 +52,7 @@ public class MethodDescriptor : IEquatable
public IReadOnlyList Parameters => _targetMethodInfo.GetParameters();
///
- /// the method info of the interoperable service.
+ /// The method info of the interoperable service.
///
public MethodInfo TargetMethodInfo => _targetMethodInfo;
@@ -63,6 +66,16 @@ public class MethodDescriptor : IEquatable
private readonly MethodInfo _targetMethodInfo;
private readonly Delegate _targetMethod;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The instance that owns the target method, property, or event.
+ /// The name of the method, property, or event to bind.
+ ///
+ /// Optional system method name used for the call address.
+ /// When omitted, a name of the form System.{Type}.{Method} is generated.
+ ///
+ /// Thrown when the named target cannot be found on the instance type.
public MethodDescriptor(object targetInstance, string targetMethodName, string? systemMethodName = default)
{
var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
@@ -87,6 +100,11 @@ public MethodDescriptor(object targetInstance, string targetMethodName, string?
_hash = CreateCallAddress(_systemMethodName);
}
+ ///
+ /// Determines whether this descriptor refers to the same interoperable service as another.
+ ///
+ /// The other descriptor to compare.
+ /// if both descriptors share the same hash; otherwise, .
public bool Equals(MethodDescriptor? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -94,6 +112,7 @@ public bool Equals(MethodDescriptor? other)
return _hash == other._hash;
}
+ ///
public override bool Equals(object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -101,6 +120,7 @@ public override bool Equals(object? obj)
return Equals(obj as MethodDescriptor);
}
+ ///
public override int GetHashCode()
{
checked
@@ -109,9 +129,18 @@ public override int GetHashCode()
}
}
+ ///
+ /// Creates a 32-bit call address from a system method name by hashing it with SHA-256.
+ ///
+ /// The interoperable service name to hash.
+ /// The first four little-endian bytes of the SHA-256 hash.
public static uint CreateCallAddress(string systemMethodName) =>
BinaryPrimitives.ReadUInt32LittleEndian(Encoding.ASCII.GetBytes(systemMethodName).ToSha256());
+ ///
+ /// Converts the descriptor to its interoperable service hash.
+ ///
+ /// The descriptor to convert.
public static implicit operator uint(MethodDescriptor descriptor)
{
return descriptor._hash;
diff --git a/src/Neo.Core/VM/OpCodeInst.cs b/src/Neo.Core/VM/OpCodeInst.cs
index e69d26c..bd5511c 100644
--- a/src/Neo.Core/VM/OpCodeInst.cs
+++ b/src/Neo.Core/VM/OpCodeInst.cs
@@ -36,18 +36,47 @@
namespace Neo.Core.VM
{
+ ///
+ /// Represents a single decoded instruction within a Neo VM script, including its opcode and operand.
+ ///
[DebuggerDisplay("OpCode={OpCode}, OperandSize={OperandSize}")]
public sealed class OpCodeInst : IEnumerable
{
private const int OpCodeSize = 1;
+ ///
+ /// Gets a prebuilt instruction containing only .
+ ///
public static OpCodeInst Ret => new((byte[])[(byte)OpCode.RET]);
+ ///
+ /// Gets the byte offset of this instruction within the script.
+ ///
public int Position { get; private init; }
+
+ ///
+ /// Gets the opcode of this instruction.
+ ///
public OpCode OpCode { get; private init; }
+
+ ///
+ /// Gets the operand bytes associated with this instruction.
+ ///
public ReadOnlyMemory Operand { get; private init; }
+
+ ///
+ /// Gets the total operand size in bytes, including any size prefix.
+ ///
public int OperandSize { get; private init; }
+
+ ///
+ /// Gets the size of the operand length prefix in bytes, or zero when the operand size is fixed.
+ ///
public int OperandPrefixSize { get; private init; }
+
+ ///
+ /// Gets the total size of this instruction in bytes, including the opcode and operand.
+ ///
public int Size => OperandPrefixSize > 0 ?
OpCodeSize + Operand.Length :
OpCodeSize + OperandSize;
@@ -57,6 +86,14 @@ public sealed class OpCodeInst : IEnumerable
private readonly ReadOnlyMemory _script;
+ ///
+ /// Decodes the instruction at the specified position in a script.
+ ///
+ /// The script that contains the instruction.
+ /// The byte offset at which the instruction begins.
+ /// Thrown when the script is empty.
+ /// Thrown when the opcode or operand prefix is invalid.
+ /// Thrown when the operand extends past the end of the script.
public OpCodeInst(ReadOnlyMemory script, int start = 0)
{
if (script.IsEmpty)
@@ -104,6 +141,10 @@ static OpCodeInst()
}
}
+ ///
+ /// Enumerates this instruction and all subsequent instructions in the script.
+ ///
+ /// An enumerator over instructions starting at the current position.
public IEnumerator GetEnumerator()
{
var nip = Position + OperandSize + OpCodeSize;
@@ -114,9 +155,14 @@ public IEnumerator GetEnumerator()
yield return instruct = new OpCodeInst(_script, ip);
}
+ ///
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
+ ///
+ /// Returns a human-readable representation of the instruction, including position, opcode, and decoded operand.
+ ///
+ /// A formatted instruction string.
public override string ToString()
{
var sb = new StringBuilder();
@@ -124,6 +170,15 @@ public override string ToString()
return sb.ToString();
}
+ ///
+ /// Interprets a portion of the operand as an unmanaged value of type .
+ ///
+ /// The unmanaged type to read from the operand.
+ /// The byte offset within the operand at which to begin reading.
+ /// The value read from the operand.
+ ///
+ /// Thrown when does not fit in the operand at the specified index.
+ ///
public T AsToken(uint index = 0)
where T : unmanaged
{
@@ -138,6 +193,10 @@ public T AsToken(uint index = 0)
return Unsafe.As(ref bytes[index]);
}
+ ///
+ /// Produces a short human-readable decoding of the operand for display and debugging.
+ ///
+ /// A string describing the operand, or an empty string when no decoding is available.
public string DecodeOperand()
{
var operand = Operand[OperandPrefixSize..].ToArray();
diff --git a/src/Neo.Core/VM/ScriptBuilder.cs b/src/Neo.Core/VM/ScriptBuilder.cs
index 9f93595..7c3a6d9 100644
--- a/src/Neo.Core/VM/ScriptBuilder.cs
+++ b/src/Neo.Core/VM/ScriptBuilder.cs
@@ -42,6 +42,9 @@ public class ScriptBuilder : IDisposable
///
public int Length => (int)_stream.Length;
+ ///
+ /// Releases the underlying script buffer.
+ ///
public void Dispose()
{
_stream.Dispose();
@@ -200,6 +203,12 @@ public ScriptBuilder EmitSysCall(uint api)
return Emit(OpCode.SYSCALL, BitConverter.GetBytes(api));
}
+ ///
+ /// Emits instructions that create an array from the specified items.
+ ///
+ /// The type of the array elements.
+ /// The items to place into the array; when null or empty, emits .
+ /// A reference to this instance after the emit operation has completed.
public ScriptBuilder EmitCreateArray(IReadOnlyList? list = null)
{
if (list is null || list.Count == 0)
@@ -213,6 +222,13 @@ public ScriptBuilder EmitCreateArray(IReadOnlyList? list = null)
return Emit(OpCode.PACK);
}
+ ///
+ /// Emits instructions that create a map from the specified key/value pairs.
+ ///
+ /// The type of the map keys.
+ /// The type of the map values.
+ /// The key/value pairs to place into the map.
+ /// A reference to this instance after the emit operation has completed.
public ScriptBuilder EmitCreateMap(IEnumerable> map)
where TKey : notnull
where TValue : notnull
@@ -233,6 +249,13 @@ public ScriptBuilder EmitCreateMap(IEnumerable
+ /// Emits instructions that create a map from the specified dictionary.
+ ///
+ /// The type of the map keys.
+ /// The type of the map values.
+ /// The dictionary to place into the map.
+ /// A reference to this instance after the emit operation has completed.
public ScriptBuilder EmitCreateMap(IReadOnlyDictionary map)
where TKey : notnull
where TValue : notnull
@@ -251,6 +274,12 @@ public ScriptBuilder EmitCreateMap(IReadOnlyDictionary
+ /// Emits instructions that create a struct from the specified items.
+ ///
+ /// The type of the struct elements.
+ /// The items to place into the struct.
+ /// A reference to this instance after the emit operation has completed.
public ScriptBuilder EmitCreateStruct(IReadOnlyList array)
where T : notnull
{
@@ -265,6 +294,11 @@ public ScriptBuilder EmitCreateStruct(IReadOnlyList array)
return Emit(OpCode.PACKSTRUCT);
}
+ ///
+ /// Emits one or more opcodes without operands.
+ ///
+ /// The opcodes to emit.
+ /// A reference to this instance after the emit operation has completed.
public ScriptBuilder Emit(params OpCode[] ops)
{
foreach (var op in ops)
@@ -273,6 +307,15 @@ public ScriptBuilder Emit(params OpCode[] ops)
return this;
}
+ ///
+ /// Emits a push instruction for a supported object value.
+ ///
+ ///
+ /// The value to push. Supported types include booleans, numeric types, strings, byte arrays,
+ /// enums, instances, and .
+ ///
+ /// A reference to this instance after the emit operation has completed.
+ /// Thrown when has an unsupported type.
public ScriptBuilder EmitPush(object? obj)
{
switch (obj)
@@ -332,6 +375,12 @@ public ScriptBuilder EmitPush(object? obj)
return this;
}
+ ///
+ /// Emits argument pushes followed by an for the specified API.
+ ///
+ /// The interoperable service hash to call.
+ /// The arguments to push onto the stack before the call.
+ /// A reference to this instance after the emit operation has completed.
public ScriptBuilder EmitSysCall(uint method, params object[] args)
{
for (var i = args.Length - 1; i >= 0; i--)
@@ -340,6 +389,10 @@ public ScriptBuilder EmitSysCall(uint method, params object[] args)
return EmitSysCall(method);
}
+ ///
+ /// Emits an instruction.
+ ///
+ /// A reference to this instance after the emit operation has completed.
public ScriptBuilder EmitReturn() =>
Emit(OpCode.RET);
diff --git a/src/Neo.Core/VM/SmartContract/MethodParameterType.cs b/src/Neo.Core/VM/SmartContract/MethodParameterType.cs
index b909e1b..89bdc2f 100644
--- a/src/Neo.Core/VM/SmartContract/MethodParameterType.cs
+++ b/src/Neo.Core/VM/SmartContract/MethodParameterType.cs
@@ -22,6 +22,9 @@
namespace Neo.Core.VM.SmartContract
{
+ ///
+ /// Describes the expected parameter or return type of a smart-contract method.
+ ///
public enum MethodParameterType : byte
{
///
diff --git a/src/Neo.Core/VM/Specs/GasTable.cs b/src/Neo.Core/VM/Specs/GasTable.cs
index d3a969c..5402c02 100644
--- a/src/Neo.Core/VM/Specs/GasTable.cs
+++ b/src/Neo.Core/VM/Specs/GasTable.cs
@@ -28,6 +28,9 @@
namespace Neo.Core.VM.Specs
{
+ ///
+ /// Provides opcode and interop method gas costs resolved by hard fork.
+ ///
public static class GasTable
{
private readonly static Dictionary> s_gasCosts = [];
@@ -61,8 +64,11 @@ static GasTable()
}
///
- /// Get gas cost for an under a specific .
+ /// Gets the gas cost for an under a specific .
///
+ /// The opcode whose gas cost is requested.
+ /// The hard fork used to resolve the cost; falls back to earlier forks when needed.
+ /// The gas cost for the opcode at the specified hard fork.
public static long GetGasCost(OpCode opcode, HardFork fork = HardFork.Genesis)
{
for (var current = fork; current >= HardFork.Genesis && Enum.IsDefined(current); current--)
@@ -77,6 +83,15 @@ public static long GetGasCost(OpCode opcode, HardFork fork = HardFork.Genesis)
return s_gasCosts[HardFork.Genesis][opcode]; // fallback Genesis MUST exist
}
+ ///
+ /// Gets the gas cost for an interoperable service method under a specific .
+ ///
+ /// The method descriptor whose execute price is requested.
+ /// The hard fork used to resolve the price; falls back to earlier forks when needed.
+ ///
+ /// The method's when available;
+ /// otherwise the gas cost of .
+ ///
public static long GetGasCost(MethodDescriptor method, HardFork fork = HardFork.Genesis)
{
var attributes = method.TargetMethodInfo.GetCustomAttributes();
@@ -93,8 +108,10 @@ public static long GetGasCost(MethodDescriptor method, HardFork fork = HardFork.
}
///
- /// Get all gas costs for a specific (useful for debugging).
+ /// Gets all gas costs defined for a specific (useful for debugging).
///
+ /// The hard fork used to resolve the cost table; falls back to earlier forks when needed.
+ /// A read-only dictionary of opcodes to gas costs for the resolved hard fork.
public static IReadOnlyDictionary GetAllCosts(HardFork fork = HardFork.Genesis)
{
for (var current = fork; current >= HardFork.Genesis && Enum.IsDefined(current); current--)
diff --git a/src/Neo.IO/Data/BloomFIlter.cs b/src/Neo.IO/Data/BloomFIlter.cs
index 75fbda5..474cfac 100644
--- a/src/Neo.IO/Data/BloomFIlter.cs
+++ b/src/Neo.IO/Data/BloomFIlter.cs
@@ -26,6 +26,9 @@
namespace Neo.IO.Data
{
+ ///
+ /// A probabilistic set that supports membership tests with a configurable false-positive rate.
+ ///
public class BloomFilter
{
private readonly BitArray _bits;
@@ -33,10 +36,19 @@ public class BloomFilter
private readonly int _hashCount;
private readonly uint _tweak;
+ ///
+ /// Gets the number of hash functions used by this filter.
+ ///
public int HashCount => _hashCount;
+ ///
+ /// Gets the size of the underlying bit array in bits.
+ ///
public int BitSize => _bitSize;
+ ///
+ /// Gets the tweak value mixed into hash seeds for this filter.
+ ///
public uint Tweak => _tweak;
///
@@ -62,6 +74,10 @@ public BloomFilter(int expectedElements, double falsePositiveRate = 0.01, uint t
_tweak = tweak;
}
+ ///
+ /// Adds the specified data to the filter.
+ ///
+ /// The element data to insert.
public void Add(ReadOnlySpan data)
{
for (var i = 0; i < _hashCount; i++)
@@ -73,6 +89,14 @@ public void Add(ReadOnlySpan data)
}
}
+ ///
+ /// Tests whether the specified data may be present in the filter.
+ ///
+ /// The element data to test.
+ ///
+ /// if the element is definitely not present;
+ /// otherwise if it is probably present.
+ ///
public bool Contains(ReadOnlySpan data)
{
for (var i = 0; i < _hashCount; i++)
@@ -87,17 +111,38 @@ public bool Contains(ReadOnlySpan data)
return true; // Probably present
}
+ ///
+ /// Adds the specified data to the filter.
+ ///
+ /// The element data to insert.
public void Add(byte[] data) =>
Add(data.AsSpan());
+ ///
+ /// Tests whether the specified data may be present in the filter.
+ ///
+ /// The element data to test.
+ ///
+ /// if the element is definitely not present;
+ /// otherwise if it is probably present.
+ ///
public bool Contains(byte[] data) =>
Contains(data.AsSpan());
+ ///
+ /// Estimates the false-positive probability after the specified number of insertions.
+ ///
+ /// The number of elements that have been inserted.
+ /// The approximate false-positive probability in the range [0, 1].
public double GetFalsePositiveProbability(int insertedCount)
{
return Math.Pow(1 - Math.Exp(-_hashCount * insertedCount / (double)_bitSize), _hashCount);
}
+ ///
+ /// Copies the underlying bit array into the provided buffer.
+ ///
+ /// The destination buffer that receives the bit array contents.
public void GetBits(byte[] bytes) =>
_bits.CopyTo(bytes, 0);
}
diff --git a/src/Neo.IO/Hashing/Murmur128.cs b/src/Neo.IO/Hashing/Murmur128.cs
index 52ae2fe..047bd5c 100644
--- a/src/Neo.IO/Hashing/Murmur128.cs
+++ b/src/Neo.IO/Hashing/Murmur128.cs
@@ -28,6 +28,9 @@
namespace Neo.IO.Hashing
{
+ ///
+ /// Implements the 128-bit MurmurHash3 non-cryptographic hash algorithm.
+ ///
public class Murmur128 : NonCryptographicHashAlgorithm
{
private const uint DefaultSeed = 0xdeadc0deu;
@@ -47,9 +50,21 @@ private Murmur128(uint seed = DefaultSeed) : base(16)
Reset();
}
+ ///
+ /// Computes the 128-bit MurmurHash3 of the specified data.
+ ///
+ /// The input data to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// A 16-byte array containing the hash digest.
public static byte[] Hash(byte[] data, uint seed = DefaultSeed) =>
Hash(data.AsSpan(), seed);
+ ///
+ /// Computes the 128-bit MurmurHash3 of the specified data.
+ ///
+ /// The input data to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// A 16-byte array containing the hash digest.
public static byte[] Hash(ReadOnlySpan data, uint seed = DefaultSeed)
{
var hasher = new Murmur128(seed);
@@ -57,24 +72,51 @@ public static byte[] Hash(ReadOnlySpan data, uint seed = DefaultSeed)
return hasher.GetCurrentHash();
}
+ ///
+ /// Computes the 128-bit MurmurHash3 of the UTF-8 encoding of the specified text.
+ ///
+ /// The text to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// A 16-byte array containing the hash digest.
public static byte[] Hash(string text, uint seed = DefaultSeed)
{
var bytes = Encoding.UTF8.GetBytes(text);
return Hash(bytes, seed);
}
+ ///
+ /// Computes the 128-bit MurmurHash3 of the specified data and returns it as a .
+ ///
+ /// The input data to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// The hash digest as a .
public static UInt128 HashToUInt128(byte[] data, uint seed = DefaultSeed) =>
HashToUInt128(data.AsSpan(), seed);
+ ///
+ /// Computes the 128-bit MurmurHash3 of the specified data and returns it as a .
+ ///
+ /// The input data to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// The hash digest as a .
public static UInt128 HashToUInt128(ReadOnlySpan data, uint seed = DefaultSeed) =>
BitConverter.ToUInt128(Hash(data, seed), 0);
+ ///
+ /// Computes the 128-bit MurmurHash3 of the UTF-8 encoding of the specified text and returns it as a .
+ ///
+ /// The text to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// The hash digest as a .
public static UInt128 HashToUInt128(string text, uint seed = DefaultSeed)
{
var bytes = Encoding.UTF8.GetBytes(text);
return HashToUInt128(bytes, seed);
}
+ ///
+ /// Resets the hasher to its initial state for the configured seed.
+ ///
public override void Reset()
{
_h1 = _seed;
@@ -82,6 +124,10 @@ public override void Reset()
_length = 0;
}
+ ///
+ /// Appends data to the hash computation.
+ ///
+ /// The data to include in the hash.
public override void Append(ReadOnlySpan source)
{
_length += (uint)source.Length;
diff --git a/src/Neo.IO/Hashing/Murmur32.cs b/src/Neo.IO/Hashing/Murmur32.cs
index 16ecca2..93989ec 100644
--- a/src/Neo.IO/Hashing/Murmur32.cs
+++ b/src/Neo.IO/Hashing/Murmur32.cs
@@ -26,6 +26,9 @@
namespace Neo.IO.Hashing
{
+ ///
+ /// Implements the 32-bit MurmurHash3 non-cryptographic hash algorithm.
+ ///
public sealed class Murmur32 : NonCryptographicHashAlgorithm
{
private const uint DefaultSeed = 0xdeadc0deu;
@@ -54,9 +57,21 @@ private Murmur32(uint seed = DefaultSeed) : base(sizeof(uint)) // 4 bytes outout
_hash = _seed;
}
+ ///
+ /// Computes the 32-bit MurmurHash3 of the specified data.
+ ///
+ /// The input data to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// A 4-byte array containing the hash digest.
public static byte[] Hash(byte[] data, uint seed = DefaultSeed) =>
Hash(data.AsSpan(), seed);
+ ///
+ /// Computes the 32-bit MurmurHash3 of the specified data.
+ ///
+ /// The input data to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// A 4-byte array containing the hash digest.
public static byte[] Hash(ReadOnlySpan data, uint seed = DefaultSeed)
{
var hasher = new Murmur32(seed);
@@ -64,24 +79,52 @@ public static byte[] Hash(ReadOnlySpan data, uint seed = DefaultSeed)
return hasher.GetCurrentHash();
}
+ ///
+ /// Computes the 32-bit MurmurHash3 of the UTF-8 encoding of the specified text.
+ ///
+ /// The text to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// A 4-byte array containing the hash digest.
public static byte[] Hash(string text, uint seed = DefaultSeed)
{
var bytes = Encoding.UTF8.GetBytes(text);
return Hash(bytes, seed);
}
+ ///
+ /// Computes the 32-bit MurmurHash3 of the specified data and returns it as a .
+ ///
+ /// The input data to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// The hash digest as a .
public static uint HashToUInt32(byte[] data, uint seed = DefaultSeed) =>
HashToUInt32(data.AsSpan(), seed);
+ ///
+ /// Computes the 32-bit MurmurHash3 of the specified data and returns it as a .
+ ///
+ /// The input data to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// The hash digest as a .
public static uint HashToUInt32(ReadOnlySpan data, uint seed = DefaultSeed) =>
BitConverter.ToUInt32(Hash(data, seed), 0);
+ ///
+ /// Computes the 32-bit MurmurHash3 of the UTF-8 encoding of the specified text and returns it as a .
+ ///
+ /// The text to hash.
+ /// The hash seed. Defaults to a fixed non-zero seed.
+ /// The hash digest as a .
public static uint HashToUInt32(string text, uint seed = DefaultSeed)
{
var bytes = Encoding.UTF8.GetBytes(text);
return HashToUInt32(bytes, seed);
}
+ ///
+ /// Appends data to the hash computation.
+ ///
+ /// The data to include in the hash.
public override void Append(ReadOnlySpan source)
{
_length += (uint)source.Length;
@@ -120,6 +163,9 @@ public override void Append(ReadOnlySpan source)
}
}
+ ///
+ /// Resets the hasher to its initial state.
+ ///
public override void Reset()
{
_hash = 0;
diff --git a/src/Neo.Localization/ResourceFactory.cs b/src/Neo.Localization/ResourceFactory.cs
index 1e4ebc8..54a0369 100644
--- a/src/Neo.Localization/ResourceFactory.cs
+++ b/src/Neo.Localization/ResourceFactory.cs
@@ -27,8 +27,14 @@
namespace Neo.Localization
{
+ ///
+ /// Provides a shared for Neo localization resources.
+ ///
public static class ResourceFactory
{
+ ///
+ /// Gets the default string localizer factory configured with the Resources path.
+ ///
public static readonly ResourceManagerStringLocalizerFactory Instance = Create(config => config.ResourcesPath = "Resources");
private static ResourceManagerStringLocalizerFactory Create(Action configure)
diff --git a/src/Neo.Localization/VM/VirtualMachineLocalizer.cs b/src/Neo.Localization/VM/VirtualMachineLocalizer.cs
index 9be48d2..440bd51 100644
--- a/src/Neo.Localization/VM/VirtualMachineLocalizer.cs
+++ b/src/Neo.Localization/VM/VirtualMachineLocalizer.cs
@@ -25,10 +25,19 @@
namespace Neo.Localization.VM
{
+ ///
+ /// Resolves localized virtual machine messages from resource files.
+ ///
public static class VirtualMachineLocalizer
{
private static readonly IStringLocalizer s_localizer = ResourceFactory.Instance.Create(typeof(VirtualMachineLocalizer));
+ ///
+ /// Gets a localized virtual machine message by name, optionally formatting it with the provided arguments.
+ ///
+ /// The resource key of the message, typically from .
+ /// Optional format arguments for the message template.
+ /// The localized message string.
[return: NotNullIfNotNull(nameof(s_localizer))]
public static string GetMessage(string messageName, params object[] args) =>
s_localizer[messageName, args];
diff --git a/src/Neo.Localization/VM/VirtualMachineMessageNames.cs b/src/Neo.Localization/VM/VirtualMachineMessageNames.cs
index 49a5325..eaf0826 100644
--- a/src/Neo.Localization/VM/VirtualMachineMessageNames.cs
+++ b/src/Neo.Localization/VM/VirtualMachineMessageNames.cs
@@ -22,12 +22,26 @@
namespace Neo.Localization.VM
{
+ ///
+ /// Resource keys for localized virtual machine messages.
+ ///
public static class VirtualMachineMessageNames
{
private const string MessageSuffixString = "Message";
+ ///
+ /// Resource key for the message emitted when VM execution starts.
+ ///
public const string ExecuteStartup = nameof(ExecuteStartup) + MessageSuffixString;
+
+ ///
+ /// Resource key for the message emitted when an opcode is executed.
+ ///
public const string ExecuteOpCode = nameof(ExecuteOpCode) + MessageSuffixString;
+
+ ///
+ /// Resource key for the message emitted when VM execution completes successfully.
+ ///
public const string ExecuteSuccessfully = nameof(ExecuteSuccessfully) + MessageSuffixString;
}
}
diff --git a/src/Neo.Platform.CLI/Commands/EmptyHandler.cs b/src/Neo.Platform.CLI/Commands/EmptyHandler.cs
index ccb8028..d85cfe0 100644
--- a/src/Neo.Platform.CLI/Commands/EmptyHandler.cs
+++ b/src/Neo.Platform.CLI/Commands/EmptyHandler.cs
@@ -27,8 +27,17 @@
namespace Neo.Platform.CLI.Commands
{
+ ///
+ /// A no-op command-line action that always succeeds with exit code 0.
+ ///
public class EmptyHandler : AsynchronousCommandLineAction
{
+ ///
+ /// Invokes the handler without performing any work.
+ ///
+ /// The parse result for the current command invocation.
+ /// A token that can cancel the operation.
+ /// A completed task with exit code 0.
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
index b4e2d1a..f820629 100644
--- a/src/Neo.Platform.CLI/Commands/ProgramRootCommand.cs
+++ b/src/Neo.Platform.CLI/Commands/ProgramRootCommand.cs
@@ -24,8 +24,14 @@
namespace Neo.Platform.CLI.Commands
{
+ ///
+ /// Root command for the Neo Platform CLI.
+ ///
internal class ProgramRootCommand : RootCommand
{
+ ///
+ /// Initializes the root command and registers subcommands.
+ ///
public ProgramRootCommand() : base("Neo Platform Command-line Tool")
{
var showCommand = new ShowCommand();
diff --git a/src/Neo.Platform.CLI/Commands/ShowCommand.cs b/src/Neo.Platform.CLI/Commands/ShowCommand.cs
index c1515fa..6d5ba5a 100644
--- a/src/Neo.Platform.CLI/Commands/ShowCommand.cs
+++ b/src/Neo.Platform.CLI/Commands/ShowCommand.cs
@@ -28,17 +28,33 @@
namespace Neo.Platform.CLI.Commands
{
+ ///
+ /// CLI command that displays configuration samples and related information.
+ ///
internal class ShowCommand : Command
{
+ ///
+ /// Initializes the show command.
+ ///
public ShowCommand() : base("show", "Shows many configurations")
{
}
+ ///
+ /// Handles the show command by writing sample configuration output.
+ ///
+ /// The factory used to create the command logger.
public class Handler(ILoggerFactory loggerFactory) : AsynchronousCommandLineAction
{
private readonly ILogger _logger = loggerFactory.CreateLogger();
+ ///
+ /// Invokes the show command handler.
+ ///
+ /// The parse result for the current command invocation.
+ /// A token that can cancel the operation.
+ /// A completed task with exit code 0.
public override Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken = default)
{
_logger.LogInformation("Hello World!!!!");
diff --git a/src/Neo.Platform.Hosting/BlockchainStoreNames.cs b/src/Neo.Platform.Hosting/BlockchainStoreNames.cs
index bc8c31d..0ebda99 100644
--- a/src/Neo.Platform.Hosting/BlockchainStoreNames.cs
+++ b/src/Neo.Platform.Hosting/BlockchainStoreNames.cs
@@ -24,16 +24,39 @@
namespace Neo.Platform.Hosting
{
+ ///
+ /// Configuration key names for blockchain store and backup options.
+ ///
public static class BlockchainStoreNames
{
+ ///
+ /// The root configuration section for store settings.
+ ///
public static readonly string SectionKey = "Store";
+ ///
+ /// Configuration key for .
+ ///
public static readonly string DatabasePathKey = $"{SectionKey}:{nameof(BlockchainStoreOptions.DatabasePath)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string CreateIfMissingKey = $"{SectionKey}:{nameof(BlockchainStoreOptions.CreateIfMissing)}";
+ ///
+ /// Configuration section for store backup settings.
+ ///
public static readonly string BackupSectionKey = $"{SectionKey}:Backup";
+ ///
+ /// Configuration key for .
+ ///
public static readonly string BackupPathKey = $"{BackupSectionKey}:{nameof(BlockchainBackupOptions.BackupPath)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string MaxBackupsKey = $"{BackupSectionKey}:{nameof(BlockchainBackupOptions.MaxBackups)}";
}
}
diff --git a/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs b/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs
index 43876ef..dbb7d24 100644
--- a/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs
+++ b/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs
@@ -30,6 +30,16 @@
namespace Neo.Platform.Hosting.Builder
{
+ ///
+ /// Fluent builder that wires System.CommandLine parsing with a Microsoft.Extensions host.
+ ///
+ ///
+ /// The root command to parse. When , a new is used.
+ ///
+ ///
+ /// Command-line arguments to parse when is called.
+ /// When , an empty argument list is used.
+ ///
public class PlatformCommandLineBuilder(Command? rootCommand = default, string[]? args = default)
{
internal Command Command { get; } = rootCommand ?? new RootCommand();
@@ -42,6 +52,19 @@ public class PlatformCommandLineBuilder(Command? rootCommand = default, string[]
internal void AddMiddleware(Action middleware) =>
_middleware.Add(middleware);
+ ///
+ /// Configures host creation middleware that runs after parsing.
+ ///
+ ///
+ /// Factory that creates an from unmatched tokens.
+ /// When , a new is created.
+ ///
+ /// Optional callback that further configures the host builder.
+ /// The same builder for chaining.
+ ///
+ /// The middleware stores the on the host builder properties,
+ /// registers it for DI, and enables .
+ ///
public PlatformCommandLineBuilder UseHost(
Func? hostBuilderFactory,
Action? configure = default)
@@ -68,24 +91,43 @@ public PlatformCommandLineBuilder UseHost(
return this;
}
+ ///
+ /// Enables or disables the default System.CommandLine exception handler.
+ ///
+ /// to enable the handler; otherwise, .
+ /// The same builder for chaining.
public PlatformCommandLineBuilder EnableDefaultExceptionHandler(bool value = true)
{
InvocationConfiguration.EnableDefaultExceptionHandler = value;
return this;
}
+ ///
+ /// Sets the process termination timeout used during command invocation.
+ ///
+ /// The termination timeout.
+ /// The same builder for chaining.
public PlatformCommandLineBuilder SetProcessTerminationTimeout(TimeSpan timeout)
{
InvocationConfiguration.ProcessTerminationTimeout = timeout;
return this;
}
+ ///
+ /// Enables or disables POSIX-style option bundling for the parser.
+ ///
+ /// to enable bundling; otherwise, .
+ /// The same builder for chaining.
public PlatformCommandLineBuilder EnablePosixBundling(bool value = true)
{
ParserConfiguration.EnablePosixBundling = value;
return this;
}
+ ///
+ /// Parses the configured arguments, runs middleware, builds the host, and returns a runnable command line.
+ ///
+ /// A that can start the host and invoke the command.
public PlatformCommandLine Build()
{
var parseResults = Command.Parse(args ?? [], ParserConfiguration);
diff --git a/src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs b/src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs
index ebffc0c..a7f7d03 100644
--- a/src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs
+++ b/src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs
@@ -25,8 +25,20 @@
namespace Neo.Platform.Hosting.Configuration
{
+ ///
+ /// Extension methods for adding Neo platform configuration sources.
+ ///
public static class ConfigurationBuilderExtensions
{
+ ///
+ /// Adds Neo platform defaults and NEO_ -prefixed environment variables
+ /// as a configuration source.
+ ///
+ /// The configuration builder.
+ ///
+ /// Optional key/value pairs that override platform defaults before environment variables are applied.
+ ///
+ /// The same for chaining.
public static IConfigurationBuilder AddNeoPlatformConfiguration(this IConfigurationBuilder builder, IEnumerable>? initialData = default)
{
builder.Add(new NeoEnvironmentConfigurationSource() { InitialData = initialData, });
diff --git a/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs b/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs
index 9f7a9be..2aac8b3 100644
--- a/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs
+++ b/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs
@@ -26,10 +26,21 @@
namespace Neo.Platform.Hosting.Configuration
{
+ ///
+ /// Configuration source that supplies Neo environment-related settings.
+ ///
internal class NeoEnvironmentConfigurationSource : IConfigurationSource
{
+ ///
+ /// Optional initial key/value pairs merged into the configuration provider.
+ ///
public IEnumerable>? InitialData { get; set; }
+ ///
+ /// Builds a configuration provider for this source.
+ ///
+ /// The configuration builder.
+ /// A instance.
public IConfigurationProvider Build(IConfigurationBuilder builder) =>
new NeoEnvironmentConfigurationProvider(this);
}
diff --git a/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs b/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs
index e9256c1..ad0df28 100644
--- a/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs
+++ b/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs
@@ -25,14 +25,28 @@
namespace Neo.Platform.Hosting.Factory
{
+ ///
+ /// Helper factory methods used when constructing Neo hosting environments.
+ ///
internal static class NeoHostingFactory
{
private static readonly uint s_networkSeed = 810960196u; // DEV0 Magic Code
private static readonly string s_globalString = "Global\\";
+ ///
+ /// Computes a development network magic value for the given index.
+ ///
+ /// Development network index embedded in the high nibble of the magic.
+ /// The derived network magic number.
public static uint GetDevNetwork(uint index) =>
s_networkSeed & ~(0xfu << 24) | (index << 24);
+ ///
+ /// Resolves a file path relative to a root directory when the path is not rooted.
+ ///
+ /// File name or path to resolve.
+ /// Root directory used when is relative.
+ /// A for the resolved path.
public static FileInfo ResolveFileName(string filename, string rootPath)
{
if (Path.IsPathRooted(filename))
@@ -42,6 +56,11 @@ public static FileInfo ResolveFileName(string filename, string rootPath)
return new(Path.Combine(rootPath, filename));
}
+ ///
+ /// Creates a named system mutex, generating a global random name when none is supplied.
+ ///
+ /// Optional mutex name; when null or empty a random global name is used.
+ /// A newly created that is initially owned.
public static Mutex CreateMutex(string? name = default)
{
if (string.IsNullOrEmpty(name))
diff --git a/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs b/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs
index 2adefa1..1701496 100644
--- a/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs
+++ b/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs
@@ -25,26 +25,53 @@
namespace Neo.Platform.Hosting
{
+ ///
+ /// Extension methods for checking Neo hosting environment names.
+ ///
public static class HostEnvironmentExtensions
{
+ ///
+ /// Determines whether the host is running in the localnet environment.
+ ///
+ /// The host environment.
+ /// if the environment is localnet; otherwise, .
+ /// is .
public static bool IsLocalNet(this IHostEnvironment hostEnvironment)
{
ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment));
return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.LocalNet);
}
+ ///
+ /// Determines whether the host is running in the testnet environment.
+ ///
+ /// The host environment.
+ /// if the environment is testnet; otherwise, .
+ /// is .
public static bool IsTestNet(this IHostEnvironment hostEnvironment)
{
ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment));
return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.TestNet);
}
+ ///
+ /// Determines whether the host is running in the mainnet environment.
+ ///
+ /// The host environment.
+ /// if the environment is mainnet; otherwise, .
+ /// is .
public static bool IsMainNet(this IHostEnvironment hostEnvironment)
{
ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment));
return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.MainNet);
}
+ ///
+ /// Determines whether the host is running in the privatenet environment.
+ ///
+ /// The host environment.
+ /// if the environment is privatenet; otherwise, .
+ /// is .
public static bool IsPrivateNet(this IHostEnvironment hostEnvironment)
{
ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment));
diff --git a/src/Neo.Platform.Hosting/HostingExtensions.cs b/src/Neo.Platform.Hosting/HostingExtensions.cs
index de711ab..4ed5f65 100644
--- a/src/Neo.Platform.Hosting/HostingExtensions.cs
+++ b/src/Neo.Platform.Hosting/HostingExtensions.cs
@@ -38,9 +38,22 @@
namespace Neo.Platform.Hosting
{
+ ///
+ /// Extension methods that configure Neo platform hosting defaults on .
+ ///
public static class HostingExtensions
{
-
+ ///
+ /// Registers a command action for a matching System.CommandLine command type.
+ ///
+ /// The command type expected on the parse result.
+ /// The action type resolved from DI and assigned to the command.
+ /// The host builder.
+ /// The same for chaining.
+ ///
+ /// Requires a in whose
+ /// selected command is exactly of type .
+ ///
public static IHostBuilder UseCommandAction(this IHostBuilder builder)
where TCommand : Command
where TCommandAction : CommandLineAction
@@ -61,6 +74,16 @@ parseResult.CommandResult.Command is Command command &&
return builder;
}
+ ///
+ /// Configures Neo platform host defaults for configuration, logging, lifetime, options, and type converters.
+ ///
+ /// The host builder.
+ /// The same for chaining.
+ ///
+ /// Loads JSON configuration from the application base and content root (including environment-specific files),
+ /// registers Neo platform logging, enables development service-provider validation in Development,
+ /// and applies and .
+ ///
public static IHostBuilder UseNeoPlatformConfiguration(this IHostBuilder builder)
{
// Host Configuration
@@ -163,6 +186,12 @@ public static IHostBuilder UseNeoPlatformConfiguration(this IHostBuilder builder
return builder;
}
+ ///
+ /// Binds protocol settings from configuration and registers them as a singleton
+ /// service.
+ ///
+ /// The host builder.
+ /// The same for chaining.
public static IHostBuilder UseNeoPlatformOptions(this IHostBuilder builder) =>
builder.ConfigureServices
(
@@ -174,6 +203,12 @@ public static IHostBuilder UseNeoPlatformOptions(this IHostBuilder builder) =>
}
);
+ ///
+ /// Registers as the host lifetime and optionally configures its options.
+ ///
+ /// The host builder.
+ /// Optional callback that configures .
+ /// The same for chaining.
public static IHostBuilder UseNeoPlatformLifetime(this IHostBuilder builder, Action? configure = default) =>
builder.ConfigureServices(
services =>
diff --git a/src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs b/src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs
index 201c77d..bffde18 100644
--- a/src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs
+++ b/src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs
@@ -28,8 +28,16 @@
namespace Neo.Platform.Hosting.Logging
{
+ ///
+ /// Extension methods for registering the Neo platform console logger.
+ ///
public static class LoggingBuilderExtensions
{
+ ///
+ /// Adds the Neo platform logger provider to the logging pipeline.
+ ///
+ /// The logging builder.
+ /// The same for chaining.
public static ILoggingBuilder AddNeoPlatform(this ILoggingBuilder builder)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
@@ -39,6 +47,12 @@ public static ILoggingBuilder AddNeoPlatform(this ILoggingBuilder builder)
return builder;
}
+ ///
+ /// Adds the Neo platform logger provider and configures its options.
+ ///
+ /// The logging builder.
+ /// A callback that configures .
+ /// The same for chaining.
public static ILoggingBuilder AddNeoPlatform(this ILoggingBuilder builder, Action configure)
{
builder.AddNeoPlatform();
diff --git a/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs
index 7272c7e..57447a6 100644
--- a/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs
+++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs
@@ -26,6 +26,9 @@
namespace Neo.Platform.Hosting.Logging
{
+ ///
+ /// Console logger implementation used by the Neo platform host.
+ ///
internal sealed class NeoPlatformLogger(
string name,
Func config) : ILogger
@@ -36,12 +39,15 @@ internal sealed class NeoPlatformLogger(
private readonly string _name = name;
private readonly Func _getConfig = config;
+ ///
public IDisposable? BeginScope(TState state) where TState : notnull =>
default!;
+ ///
public bool IsEnabled(LogLevel logLevel) =>
logLevel != LogLevel.None;
+ ///
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
{
if (IsEnabled(logLevel) == false)
@@ -90,12 +96,19 @@ private void WriteDateTime(bool isError = false)
Write(format, DateTimeNow);
}
+ ///
+ /// Writes a formatted message to standard output without a trailing newline.
+ ///
public static void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
var message = string.Format(format, args);
Console.Out.Write(message);
}
+
+ ///
+ /// Writes a formatted message to standard error without a trailing newline.
+ ///
public static void ErrorWrite([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
var message = string.Format(format, args);
@@ -103,18 +116,33 @@ public static void ErrorWrite([StringSyntax(StringSyntaxAttribute.CompositeForma
Console.Error.Write(message);
}
+ ///
+ /// Writes a newline to standard output.
+ ///
public static void WriteLine() =>
Write(Environment.NewLine);
+ ///
+ /// Writes a formatted message to standard output followed by a newline.
+ ///
public static void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) =>
Write(string.Format(format, args) + Environment.NewLine);
+ ///
+ /// Writes a newline to standard error.
+ ///
public static void ErrorWriteLine() =>
ErrorWrite(Environment.NewLine);
+ ///
+ /// Writes a formatted message to standard error followed by a newline.
+ ///
public static void ErrorWriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) =>
ErrorWrite(string.Format(format, args) + Environment.NewLine);
+ ///
+ /// Writes an information-level message with platform coloring.
+ ///
public void InfoMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
WriteDateTime();
@@ -125,6 +153,9 @@ public void InfoMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] st
ResetColor();
}
+ ///
+ /// Writes a warning-level message with platform coloring.
+ ///
public void WarningMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
WriteDateTime();
@@ -135,6 +166,9 @@ public void WarningMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)]
ResetColor();
}
+ ///
+ /// Writes a debug-level message with platform coloring.
+ ///
public void DebugMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
SetTerminalForegroundColor(ConsoleColor.DarkGray);
@@ -144,6 +178,9 @@ public void DebugMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] s
ResetColor();
}
+ ///
+ /// Writes a trace-level message with platform coloring.
+ ///
public void TraceMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
SetTerminalForegroundColor(ConsoleColor.DarkGray);
@@ -153,6 +190,11 @@ public void TraceMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] s
ResetColor();
}
+ ///
+ /// Writes exception details to standard error, optionally including a stack trace.
+ ///
+ /// Exception to report.
+ /// When , includes stack trace output.
public void ErrorMessage(Exception exception, bool showStackTrace = true)
{
var stackTrace = exception.InnerException?.StackTrace ?? exception.StackTrace;
@@ -174,6 +216,9 @@ public void ErrorMessage(Exception exception, bool showStackTrace = true)
ResetColor();
}
+ ///
+ /// Writes an error-level formatted message to standard error.
+ ///
public void ErrorMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
SetTerminalForegroundColor(ConsoleColor.Red);
@@ -185,16 +230,25 @@ public void ErrorMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] s
ResetColor();
}
+ ///
+ /// Sets the console foreground color.
+ ///
public static void SetTerminalForegroundColor(ConsoleColor consoleColor)
{
Console.ForegroundColor = consoleColor;
}
+ ///
+ /// Sets the console background color.
+ ///
public static void SetTerminalBackgroundColor(ConsoleColor consoleColor)
{
Console.BackgroundColor = consoleColor;
}
+ ///
+ /// Resets console colors to the default.
+ ///
public static void ResetColor()
{
Console.ResetColor();
diff --git a/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs
index 90672d6..6050a93 100644
--- a/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs
+++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs
@@ -24,15 +24,33 @@
namespace Neo.Platform.Hosting.Logging
{
+ ///
+ /// Options that control Neo platform console log formatting.
+ ///
public sealed class NeoPlatformLoggerOptions
{
+ ///
+ /// The default timestamp format string used by .
+ ///
public const string DefaultDateTimeFormatString = "yyyy-MM-dd HH:mm:ss.ffff";
+ ///
+ /// Gets or sets a value indicating whether log timestamps use UTC.
+ /// Defaults to .
+ ///
public bool UseUtcTimestamp { get; set; } = true;
+ ///
+ /// Gets or sets the .NET date/time format string used for log timestamps.
+ /// Defaults to .
+ ///
[StringSyntax(StringSyntaxAttribute.DateTimeFormat)]
public string TimestampFormat { get; set; } = DefaultDateTimeFormatString;
+ ///
+ /// Gets or sets a value indicating whether exception stack traces are written to the console.
+ /// Defaults to .
+ ///
public bool ShowExceptionStackTrace { get; set; } = true;
}
}
diff --git a/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs
index 6882d01..c109d02 100644
--- a/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs
+++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs
@@ -28,6 +28,9 @@
namespace Neo.Platform.Hosting.Logging
{
+ ///
+ /// that creates instances.
+ ///
[UnsupportedOSPlatform("browser")]
[ProviderAlias("NeoPlatform")]
internal sealed class NeoPlatformLoggerProvider : ILoggerProvider
@@ -37,6 +40,10 @@ internal sealed class NeoPlatformLoggerProvider : ILoggerProvider
private NeoPlatformLoggerOptions _currentConfig;
+ ///
+ /// Initializes a new provider that tracks option changes from the monitor.
+ ///
+ /// Options monitor for logger configuration.
public NeoPlatformLoggerProvider(
IOptionsMonitor config)
{
@@ -44,6 +51,11 @@ public NeoPlatformLoggerProvider(
_onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
}
+ ///
+ /// Creates or returns a cached logger for the specified category.
+ ///
+ /// Logger category name.
+ /// An for the category.
public ILogger CreateLogger(string categoryName) =>
_loggers.GetOrAdd(
categoryName,
@@ -54,6 +66,9 @@ public ILogger CreateLogger(string categoryName) =>
private NeoPlatformLoggerOptions GetCurrentConfig() =>
_currentConfig;
+ ///
+ /// Clears cached loggers and disposes option change subscriptions.
+ ///
public void Dispose()
{
_loggers.Clear();
diff --git a/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs b/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs
index 53204f4..16910e2 100644
--- a/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs
+++ b/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs
@@ -24,5 +24,10 @@
namespace Neo.Platform.Hosting.Middleware
{
+ ///
+ /// Continues the middleware chain
+ /// with the current .
+ ///
+ /// The parse result to pass to the next middleware.
public delegate void PlatformCommandLineNextDelegate(ParseResult parseResult);
}
diff --git a/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs b/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs
index f234eb9..e0942a1 100644
--- a/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs
+++ b/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs
@@ -22,14 +22,29 @@
namespace Neo.Platform.Hosting
{
+ ///
+ /// Well-known hosting environment name strings for Neo platform hosts.
+ ///
internal static class NeoHostingEnvironmentNames
{
+ ///
+ /// Local development network environment name.
+ ///
public static readonly string LocalNet = "localnet";
+ ///
+ /// Public test network environment name.
+ ///
public static readonly string TestNet = "testnet";
+ ///
+ /// Public main network environment name.
+ ///
public static readonly string MainNet = "mainnet";
+ ///
+ /// Private network environment name.
+ ///
public static readonly string PrivateNet = "privatenet";
}
diff --git a/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs b/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs
index e8c02ad..710e236 100644
--- a/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs
+++ b/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs
@@ -30,16 +30,34 @@
namespace Neo.Platform.Hosting
{
+ ///
+ /// Host lifetime that logs Neo platform start and shutdown status messages.
+ ///
+ /// Lifetime options, including whether status messages are suppressed.
+ /// The current hosting environment.
+ /// Application lifetime notifications used to log status.
+ /// Optional logger factory. When omitted, a null logger is used.
public class NeoPlatformLifetime(
IOptions options,
IHostEnvironment environment,
IHostApplicationLifetime applicationLifetime,
ILoggerFactory? loggerFactory = default) : IHostLifetime, IDisposable
{
+ ///
+ /// Gets the configured lifetime options.
+ ///
public NeoPlatformLifetimeOptions Options { get; } = options?.Value ??
throw new ArgumentNullException(nameof(options));
+
+ ///
+ /// Gets the hosting environment.
+ ///
public IHostEnvironment Environment { get; } = environment ??
throw new ArgumentNullException(nameof(environment));
+
+ ///
+ /// Gets the application lifetime used for start and stop notifications.
+ ///
public IHostApplicationLifetime ApplicationLifetime { get; } = applicationLifetime ??
throw new ArgumentNullException(nameof(applicationLifetime));
@@ -49,6 +67,9 @@ public class NeoPlatformLifetime(
private readonly ILogger _logger = (loggerFactory ?? NullLoggerFactory.Instance)
.CreateLogger("Neo.Platform.Hosting.Lifetime");
+ ///
+ /// Releases registrations for application started and stopping callbacks.
+ ///
public void Dispose()
{
_appStartedReg.Dispose();
@@ -57,12 +78,22 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ ///
+ /// Called when the host is stopping. This implementation performs no work.
+ ///
+ /// A token that can cancel the stop operation.
+ /// A completed task.
public Task StopAsync(CancellationToken cancellationToken)
{
// Nothing to do
return Task.CompletedTask;
}
+ ///
+ /// Registers status logging for application start and stop when messages are not suppressed.
+ ///
+ /// A token that can cancel waiting for start.
+ /// A completed task.
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
if (Options.SuppressStatusMessages == false)
diff --git a/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs b/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs
index 9a3e252..aeb9404 100644
--- a/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs
+++ b/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs
@@ -24,6 +24,13 @@
namespace Neo.Platform.Hosting
{
+ ///
+ /// Options that configure .
+ ///
+ ///
+ /// Inherits console lifetime settings such as status message suppression from
+ /// .
+ ///
public class NeoPlatformLifetimeOptions : ConsoleLifetimeOptions
{
}
diff --git a/src/Neo.Platform.Hosting/PlatformCommandLine.cs b/src/Neo.Platform.Hosting/PlatformCommandLine.cs
index 953c3b9..36cf5b4 100644
--- a/src/Neo.Platform.Hosting/PlatformCommandLine.cs
+++ b/src/Neo.Platform.Hosting/PlatformCommandLine.cs
@@ -28,8 +28,22 @@
namespace Neo.Platform.Hosting
{
+ ///
+ /// Runs a System.CommandLine parse result against a configured .
+ ///
+ /// The parsed command-line result to invoke.
+ /// The host that supplies services for the application lifetime.
+ /// Invocation settings for System.CommandLine.
public sealed class PlatformCommandLine(ParseResult parseResult, IHost host, InvocationConfiguration invocationConfiguration)
{
+ ///
+ /// Starts the host, invokes the command-line action, then stops the host.
+ ///
+ /// A token that can cancel host start, command invocation, or host stop.
+ ///
+ /// The process exit code from the command invocation, or the exception
+ /// if invocation fails.
+ ///
public async Task InvokeAsync(CancellationToken cancellationToken = default)
{
await host.StartAsync(cancellationToken);
diff --git a/src/Neo.Platform.Hosting/ProtocolSettingNames.cs b/src/Neo.Platform.Hosting/ProtocolSettingNames.cs
index a34e65b..302fd67 100644
--- a/src/Neo.Platform.Hosting/ProtocolSettingNames.cs
+++ b/src/Neo.Platform.Hosting/ProtocolSettingNames.cs
@@ -24,20 +24,69 @@
namespace Neo.Platform.Hosting
{
+ ///
+ /// Configuration key names for Neo protocol settings.
+ ///
public static class ProtocolSettingNames
{
+ ///
+ /// The root configuration section for protocol settings.
+ ///
public static readonly string SectionKey = "Protocol";
+ ///
+ /// Configuration key for .
+ ///
public static readonly string NetworkKey = $"{SectionKey}:{nameof(ProtocolSettings.Network)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string AddressVersionKey = $"{SectionKey}:{nameof(ProtocolSettings.AddressVersion)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string StandbyCommitteeKey = $"{SectionKey}:{nameof(ProtocolSettings.StandbyCommittee)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string ValidatorsCountKey = $"{SectionKey}:{nameof(ProtocolSettings.ValidatorsCount)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string HardForksKey = $"{SectionKey}:{nameof(ProtocolSettings.HardForks)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string SeedListKey = $"{SectionKey}:{nameof(ProtocolSettings.SeedList)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string MillisecondsPerBlockKey = $"{SectionKey}:{nameof(ProtocolSettings.MillisecondsPerBlock)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string MaxTransactionsPerBlockKey = $"{SectionKey}:{nameof(ProtocolSettings.MaxTransactionsPerBlock)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string MemoryPoolMaxTransactionsKey = $"{SectionKey}:{nameof(ProtocolSettings.MemoryPoolMaxTransactions)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string MaxTraceableBlocksKey = $"{SectionKey}:{nameof(ProtocolSettings.MaxTraceableBlocks)}";
+
+ ///
+ /// Configuration key for .
+ ///
public static readonly string InitialGasDistributionKey = $"{SectionKey}:{nameof(ProtocolSettings.InitialGasDistribution)}";
}
}
diff --git a/src/Neo.Platform.Storage/BlockchainStore.cs b/src/Neo.Platform.Storage/BlockchainStore.cs
index 2f20416..c0cd2e1 100644
--- a/src/Neo.Platform.Storage/BlockchainStore.cs
+++ b/src/Neo.Platform.Storage/BlockchainStore.cs
@@ -35,10 +35,19 @@
namespace Neo.Platform.Storage
{
+ ///
+ /// RocksDB-backed blockchain key/value store implementing .
+ ///
public sealed class BlockchainStore : IEnumerable>, IStore
{
+ ///
+ /// Gets the store options used to open the database.
+ ///
public BlockchainStoreOptions StoreOptions => _storeOptions.Value;
+ ///
+ /// Gets the backup options used when creating backups.
+ ///
public BlockchainBackupOptions BackupOptions => _backupOptions.Value;
internal RocksDb Database => _db;
@@ -58,6 +67,12 @@ public sealed class BlockchainStore : IEnumerable>,
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _logger;
+ ///
+ /// Opens or creates a RocksDB database at the path specified by .
+ ///
+ /// Store options, including the database path.
+ /// Optional backup options. When omitted, default backup options are used.
+ /// Optional logger factory. When omitted, a null logger is used.
public BlockchainStore(
IOptions options,
IOptions? backupOptions = default,
@@ -128,6 +143,9 @@ public BlockchainStore(
_db = RocksDb.Open(dbOptions, _storeOptions.Value.DatabasePath, s_columnFamilies);
}
+ ///
+ /// Releases the underlying RocksDB database and related native resources.
+ ///
public void Dispose()
{
_db.Dispose();
@@ -135,6 +153,13 @@ public void Dispose()
_bloomFilter.Dispose();
}
+ ///
+ /// Creates a RocksDB checkpoint in the specified directory.
+ ///
+ /// The directory that will receive the checkpoint.
+ ///
+ /// is or empty.
+ ///
public void CreateCheckpoint(string checkpointDirectory)
{
if (string.IsNullOrEmpty(checkpointDirectory))
@@ -149,9 +174,17 @@ public void CreateCheckpoint(string checkpointDirectory)
_logger.LogCheckpointMessage(logLevel, $"Created checkpoint: \'{checkpointDirectory}\'");
}
+ ///
+ /// Creates a backup handle for this store.
+ ///
+ /// An bound to this database.
public IStoreBackup CreateBackup() =>
new BlockchainStoreBackup(_db, _backupOptions, _loggerFactory);
+ ///
+ /// Creates a consistent snapshot of the current store state.
+ ///
+ /// An for transactional reads and writes.
public IStoreSnapshot CreateSnapshot()
{
var logLevel = LogLevel.Debug;
@@ -162,6 +195,11 @@ public IStoreSnapshot CreateSnapshot()
return new BlockchainStoreSnapshot(this, _db, _loggerFactory);
}
+ ///
+ /// Writes under .
+ ///
+ /// The storage key.
+ /// The value to store.
public void Put(ReadOnlySpan key, ReadOnlySpan value)
{
_db.Put(key, value);
@@ -172,6 +210,10 @@ public void Put(ReadOnlySpan key, ReadOnlySpan value)
_logger.LogWriteMessage(logLevel, $"Put key: 0x{Convert.ToHexStringLower(key)} value: 0x{Convert.ToHexStringLower(value)}");
}
+ ///
+ /// Deletes the value associated with if it may exist.
+ ///
+ /// The storage key to delete.
public void Delete(ReadOnlySpan key)
{
if (_db.KeyMayExist(key))
@@ -185,6 +227,11 @@ public void Delete(ReadOnlySpan key)
}
}
+ ///
+ /// Determines whether is present in the store.
+ ///
+ /// The storage key.
+ /// if the key exists; otherwise, .
public bool ContainsKey(ReadOnlySpan key)
{
if (_db.KeyMayExist(key))
@@ -192,6 +239,11 @@ public bool ContainsKey(ReadOnlySpan key)
return false;
}
+ ///
+ /// Gets the value for , or if it does not exist.
+ ///
+ /// The storage key.
+ /// The stored value, or when missing.
public byte[]? Get(ReadOnlySpan key)
{
if (_db.KeyMayExist(key))
@@ -210,6 +262,15 @@ public bool ContainsKey(ReadOnlySpan key)
return default;
}
+ ///
+ /// Attempts to get the value for .
+ ///
+ /// The storage key.
+ ///
+ /// When this method returns , contains the stored value;
+ /// otherwise, .
+ ///
+ /// if the key was found; otherwise, .
public bool TryGet(ReadOnlySpan key, [NotNullWhen(true)] out byte[]? value)
{
if (_db.KeyMayExist(key))
@@ -232,6 +293,14 @@ public bool TryGet(ReadOnlySpan key, [NotNullWhen(true)] out byte[]? value
return false;
}
+ ///
+ /// Iterates key/value pairs starting at or before .
+ ///
+ /// The seek key or prefix.
+ ///
+ /// When , iterates backward; otherwise, forward.
+ ///
+ /// An enumerable of key/value pairs in seek order.
public IEnumerable> Seek(ReadOnlyMemory keyOrPrefix, bool seekFromEnd = false)
{
using var iter = _db.NewIterator();
@@ -255,6 +324,10 @@ public IEnumerable> Seek(ReadOnlyMemory keyOr
}
}
+ ///
+ /// Returns a non-generic enumerator over all key/value pairs.
+ ///
+ /// An enumerator for the store contents.
public IEnumerator GetEnumerator() =>
GetEnumerator();
diff --git a/src/Neo.Platform.Storage/BlockchainStoreBackup.cs b/src/Neo.Platform.Storage/BlockchainStoreBackup.cs
index 1ae73f4..0826d9f 100644
--- a/src/Neo.Platform.Storage/BlockchainStoreBackup.cs
+++ b/src/Neo.Platform.Storage/BlockchainStoreBackup.cs
@@ -33,8 +33,14 @@
namespace Neo.Platform.Storage
{
+ ///
+ /// RocksDB backup engine wrapper that implements .
+ ///
public class BlockchainStoreBackup : IStoreBackup
{
+ ///
+ /// Gets the backup options used by this instance.
+ ///
public BlockchainBackupOptions BackupOptions => _backupOptions;
private readonly BlockchainBackupOptions _backupOptions;
@@ -45,6 +51,12 @@ public class BlockchainStoreBackup : IStoreBackup
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _logger;
+ ///
+ /// Initializes a new backup engine for the specified database.
+ ///
+ /// The open RocksDB instance to back up.
+ /// Backup options, including the backup directory path.
+ /// Optional logger factory. When omitted, a null logger is used.
public BlockchainStoreBackup(
RocksDb db,
IOptions options,
@@ -63,12 +75,18 @@ public BlockchainStoreBackup(
_backupEngine = BackupEngine.Open(new(), _backupOptions.BackupPath);
}
+ ///
+ /// Releases the underlying RocksDB backup engine.
+ ///
public void Dispose()
{
_backupEngine.Dispose();
GC.SuppressFinalize(this);
}
+ ///
+ /// Creates a new backup of the database.
+ ///
public void Backup()
{
_backupEngine.CreateNewBackup(_db);
@@ -79,6 +97,19 @@ public void Backup()
_logger.LogBackupMessage(logLevel, $"Created new backup of the store.");
}
+ ///
+ /// Restores the latest backup to the specified path.
+ ///
+ /// The directory that will receive the restored database.
+ ///
+ /// Optional WAL directory. When omitted, is used.
+ ///
+ ///
+ /// is or empty.
+ ///
+ ///
+ /// or does not exist.
+ ///
public void Restore(string restorePath, string? walPath = default)
{
if (string.IsNullOrEmpty(restorePath))
@@ -99,9 +130,17 @@ public void Restore(string restorePath, string? walPath = default)
_logger.LogRestoreMessage(logLevel, $"Restored latest backup to path \'{restorePath}\'.");
}
+ ///
+ /// Lists available backups managed by the backup engine.
+ ///
+ /// An enumerable of backup metadata.
public IEnumerable ListBackups() =>
_backupEngine.AsEnumerable();
+ ///
+ /// Deletes older backups, keeping only the newest backups up to the specified count.
+ ///
+ /// The number of most recent backups to retain.
public void Purge(uint numberOfBackupsToKeep)
{
_backupEngine.PurgeOldBackups(numberOfBackupsToKeep);
diff --git a/src/Neo.Platform.Storage/BlockchainStoreSnapshot.cs b/src/Neo.Platform.Storage/BlockchainStoreSnapshot.cs
index 93d5d5f..4640d98 100644
--- a/src/Neo.Platform.Storage/BlockchainStoreSnapshot.cs
+++ b/src/Neo.Platform.Storage/BlockchainStoreSnapshot.cs
@@ -32,8 +32,14 @@
namespace Neo.Platform.Storage
{
+ ///
+ /// Point-in-time RocksDB snapshot that stages writes in a batch until .
+ ///
public class BlockchainStoreSnapshot : IEnumerable>, IStoreSnapshot
{
+ ///
+ /// Gets the store that produced this snapshot.
+ ///
public IStore Store => _store;
private readonly IStore _store;
@@ -45,6 +51,12 @@ public class BlockchainStoreSnapshot : IEnumerable>
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _logger;
+ ///
+ /// Creates a new snapshot over the given store and RocksDB instance.
+ ///
+ /// The parent store associated with this snapshot.
+ /// The open RocksDB database.
+ /// Optional logger factory. When omitted, a null logger is used.
public BlockchainStoreSnapshot(
IStore store,
RocksDb db,
@@ -61,6 +73,9 @@ public BlockchainStoreSnapshot(
_writeBatch = new();
}
+ ///
+ /// Releases the RocksDB snapshot, read options, and write batch.
+ ///
public void Dispose()
{
_snapshot.Dispose();
@@ -69,6 +84,9 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ ///
+ /// Applies all staged write-batch changes to the database.
+ ///
public void Commit()
{
var logLevel = LogLevel.Debug;
@@ -79,6 +97,11 @@ public void Commit()
_db.Write(_writeBatch);
}
+ ///
+ /// Determines whether is present as of this snapshot.
+ ///
+ /// The storage key.
+ /// if the key exists; otherwise, .
public bool ContainsKey(ReadOnlySpan key)
{
if (_db.KeyMayExist(key))
@@ -86,12 +109,24 @@ public bool ContainsKey(ReadOnlySpan key)
return false;
}
+ ///
+ /// Creates a backup handle for the underlying store.
+ ///
+ /// An from the parent store.
public IStoreBackup CreateBackup() =>
_store.CreateBackup();
+ ///
+ /// Creates a checkpoint of the underlying store.
+ ///
+ /// The directory that will receive the checkpoint.
public void CreateCheckpoint(string checkpointDirectory) =>
_store.CreateCheckpoint(checkpointDirectory);
+ ///
+ /// Creates a nested snapshot over the same database.
+ ///
+ /// A new .
public IStoreSnapshot CreateSnapshot()
{
var logLevel = LogLevel.Debug;
@@ -102,6 +137,10 @@ public IStoreSnapshot CreateSnapshot()
return new BlockchainStoreSnapshot(this, _db, _loggerFactory);
}
+ ///
+ /// Stages deletion of in the write batch when the key may exist.
+ ///
+ /// The storage key to delete.
public void Delete(ReadOnlySpan key)
{
if (_db.KeyMayExist(key))
@@ -115,6 +154,11 @@ public void Delete(ReadOnlySpan key)
}
}
+ ///
+ /// Gets the value for as of this snapshot, or if missing.
+ ///
+ /// The storage key.
+ /// The stored value, or when missing.
public byte[]? Get(ReadOnlySpan key)
{
if (_db.KeyMayExist(key))
@@ -133,6 +177,10 @@ public void Delete(ReadOnlySpan key)
return default;
}
+ ///
+ /// Enumerates all key/value pairs visible in this snapshot.
+ ///
+ /// An enumerator over snapshot contents.
public IEnumerator> GetEnumerator()
{
using var iter = _db.NewIterator(_readOptions);
@@ -140,6 +188,11 @@ public IEnumerator> GetEnumerator()
yield return new(iter.KeyToArray(), iter.ValueToArray());
}
+ ///
+ /// Stages a put of under in the write batch.
+ ///
+ /// The storage key.
+ /// The value to store.
public void Put(ReadOnlySpan key, ReadOnlySpan value)
{
_writeBatch.Put(key, value);
@@ -150,6 +203,14 @@ public void Put(ReadOnlySpan key, ReadOnlySpan value)
_logger.LogWriteMessage(logLevel, $"Put key: {Convert.ToHexStringLower(key)} value: {Convert.ToHexStringLower(value)}");
}
+ ///
+ /// Iterates key/value pairs from this snapshot starting at or before .
+ ///
+ /// The seek key or prefix.
+ ///
+ /// When , iterates backward; otherwise, forward.
+ ///
+ /// An enumerable of key/value pairs in seek order.
public IEnumerable> Seek(ReadOnlyMemory keyOrPrefix, bool seekFromEnd = false)
{
using var iter = _db.NewIterator(_readOptions);
@@ -173,6 +234,15 @@ public IEnumerable> Seek(ReadOnlyMemory keyOr
}
}
+ ///
+ /// Attempts to get the value for as of this snapshot.
+ ///
+ /// The storage key.
+ ///
+ /// When this method returns , contains the stored value;
+ /// otherwise, .
+ ///
+ /// if the key was found; otherwise, .
public bool TryGet(ReadOnlySpan key, [NotNullWhen(true)] out byte[]? value)
{
if (_db.KeyMayExist(key))
diff --git a/src/Neo.Platform.Storage/ColumnFamilyNames.cs b/src/Neo.Platform.Storage/ColumnFamilyNames.cs
index a3dcb72..411d6ba 100644
--- a/src/Neo.Platform.Storage/ColumnFamilyNames.cs
+++ b/src/Neo.Platform.Storage/ColumnFamilyNames.cs
@@ -22,8 +22,14 @@
namespace Neo.Platform.Storage
{
+ ///
+ /// Well-known RocksDB column family names used by the blockchain store.
+ ///
public static class ColumnFamilyNames
{
+ ///
+ /// The default column family name.
+ ///
public static readonly string Default = "default";
}
}
diff --git a/src/Neo.Platform.Storage/Extensions/StorageKeyBuilderExtensions.cs b/src/Neo.Platform.Storage/Extensions/StorageKeyBuilderExtensions.cs
index 4fec16f..ff8e251 100644
--- a/src/Neo.Platform.Storage/Extensions/StorageKeyBuilderExtensions.cs
+++ b/src/Neo.Platform.Storage/Extensions/StorageKeyBuilderExtensions.cs
@@ -25,11 +25,27 @@
namespace Neo.Platform.Storage.Extensions
{
+ ///
+ /// Extension methods that append values to a .
+ ///
public static class StorageKeyBuilderExtensions
{
+ ///
+ /// Serializes and appends the resulting bytes to the builder.
+ ///
+ /// The key builder.
+ /// The serializable value to append.
+ /// The same for chaining.
public static StorageKeyBuilder Append(this StorageKeyBuilder builder, INeoSerializable data) =>
builder.Append(data.ToArray());
+ ///
+ /// Appends a prefix byte, then serializes and appends .
+ ///
+ /// The key builder.
+ /// The prefix byte to append first.
+ /// The serializable value to append after the prefix.
+ /// The same for chaining.
public static StorageKeyBuilder Append(this StorageKeyBuilder builder, byte prefix, INeoSerializable data) =>
builder.Append(prefix).Append(data.ToArray());
}
diff --git a/src/Neo.Platform.Storage/Logging/LoggingExtensions.cs b/src/Neo.Platform.Storage/Logging/LoggingExtensions.cs
index adb23de..dc3a012 100644
--- a/src/Neo.Platform.Storage/Logging/LoggingExtensions.cs
+++ b/src/Neo.Platform.Storage/Logging/LoggingExtensions.cs
@@ -25,8 +25,14 @@
namespace Neo.Platform.Storage.Logging
{
+ ///
+ /// Source-generated logging helpers for blockchain store operations.
+ ///
internal static partial class LoggerExtensions
{
+ ///
+ /// Logs a store fault message.
+ ///
[LoggerMessage(
EventId = StoreEventId.Fault,
EventName = nameof(StoreEventId.Fault),
@@ -38,6 +44,9 @@ public static partial void LogFaultMessage(
string message
);
+ ///
+ /// Logs a store fault with an exception.
+ ///
[LoggerMessage(
EventId = StoreEventId.Fault,
Message = ""
@@ -48,6 +57,9 @@ public static partial void LogFaultMessage(
Exception exception
);
+ ///
+ /// Logs a store read message.
+ ///
[LoggerMessage(
EventId = StoreEventId.Read,
EventName = nameof(StoreEventId.Read),
@@ -59,6 +71,9 @@ public static partial void LogReadMessage(
string message
);
+ ///
+ /// Logs a store write message.
+ ///
[LoggerMessage(
EventId = StoreEventId.Write,
EventName = nameof(StoreEventId.Write),
@@ -70,6 +85,9 @@ public static partial void LogWriteMessage(
string message
);
+ ///
+ /// Logs a store delete message.
+ ///
[LoggerMessage(
EventId = StoreEventId.Delete,
EventName = nameof(StoreEventId.Delete),
@@ -81,6 +99,9 @@ public static partial void LogDeleteMessage(
string message
);
+ ///
+ /// Logs a store snapshot message.
+ ///
[LoggerMessage(
EventId = StoreEventId.Snapshot,
EventName = nameof(StoreEventId.Snapshot),
@@ -92,6 +113,9 @@ public static partial void LogSnapshotMessage(
string message
);
+ ///
+ /// Logs a store commit message.
+ ///
[LoggerMessage(
EventId = StoreEventId.Commit,
EventName = nameof(StoreEventId.Commit),
@@ -103,6 +127,9 @@ public static partial void LogCommitMessage(
string message
);
+ ///
+ /// Logs a store backup message.
+ ///
[LoggerMessage(
EventId = StoreEventId.Backup,
EventName = nameof(StoreEventId.Backup),
@@ -114,6 +141,9 @@ public static partial void LogBackupMessage(
string message
);
+ ///
+ /// Logs a store restore message.
+ ///
[LoggerMessage(
EventId = StoreEventId.Restore,
EventName = nameof(StoreEventId.Restore),
@@ -125,6 +155,9 @@ public static partial void LogRestoreMessage(
string message
);
+ ///
+ /// Logs a store checkpoint message.
+ ///
[LoggerMessage(
EventId = StoreEventId.Checkpoint,
EventName = nameof(StoreEventId.Checkpoint),
diff --git a/src/Neo.Platform.Storage/Logging/StoreEventId.cs b/src/Neo.Platform.Storage/Logging/StoreEventId.cs
index b659bfa..b5f6999 100644
--- a/src/Neo.Platform.Storage/Logging/StoreEventId.cs
+++ b/src/Neo.Platform.Storage/Logging/StoreEventId.cs
@@ -22,16 +22,28 @@
namespace Neo.Platform.Storage.Logging
{
+ ///
+ /// Event id constants for blockchain store logging.
+ ///
internal static class StoreEventId
{
+ /// Event id for store fault logs.
public const int Fault = 100;
+ /// Event id for store read logs.
public const int Read = 200;
+ /// Event id for store write logs.
public const int Write = 300;
+ /// Event id for store delete logs.
public const int Delete = 400;
+ /// Event id for store snapshot logs.
public const int Snapshot = 500;
+ /// Event id for store commit logs.
public const int Commit = 600;
+ /// Event id for store restore logs.
public const int Restore = 700;
+ /// Event id for store backup logs.
public const int Backup = 800;
+ /// Event id for store checkpoint logs.
public const int Checkpoint = 900;
}
}
diff --git a/src/Neo.Platform.Storage/StorageKeyBuilder.cs b/src/Neo.Platform.Storage/StorageKeyBuilder.cs
index 81583b8..b23f1c3 100644
--- a/src/Neo.Platform.Storage/StorageKeyBuilder.cs
+++ b/src/Neo.Platform.Storage/StorageKeyBuilder.cs
@@ -35,12 +35,28 @@
namespace Neo.Platform.Storage
{
+ ///
+ /// Builds binary storage keys by appending typed values into a rented buffer.
+ ///
+ ///
+ /// Keys begin with a little-endian contract id and an optional prefix byte
+ /// ( ). Dispose the builder when finished to return pooled memory.
+ ///
public class StorageKeyBuilder : IDisposable, IComparable, IComparable, IEquatable, IEnumerable
{
+ ///
+ /// The fixed prefix length: 4-byte little-endian id plus 1 prefix byte.
+ ///
public const int PrefixLength = sizeof(int) + sizeof(byte);
+ ///
+ /// Gets the number of bytes currently written to the key.
+ ///
public int Length => _byteCount;
+ ///
+ /// Gets the maximum capacity of the key buffer, including the fixed prefix.
+ ///
public int MaxLength => _maxLength;
private readonly IMemoryOwner _memoryOwner;
@@ -72,6 +88,9 @@ private StorageKeyBuilder(ReadOnlySpan data, int maxLength = -1)
data.CopyTo(_memoryOwner.Memory.Span);
}
+ ///
+ /// Clears and returns the rented key buffer to the shared pool.
+ ///
public void Dispose()
{
_memoryOwner.Memory.Span.Clear();
@@ -79,6 +98,11 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ ///
+ /// Determines whether the specified object is a with the same key bytes.
+ ///
+ /// The object to compare.
+ /// if the objects represent the same key; otherwise, .
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -86,6 +110,11 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as StorageKeyBuilder);
}
+ ///
+ /// Determines whether another builder has the same key bytes.
+ ///
+ /// The other builder.
+ /// if the keys are equal; otherwise, .
public bool Equals([NotNullWhen(true)] StorageKeyBuilder? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -96,6 +125,14 @@ public bool Equals([NotNullWhen(true)] StorageKeyBuilder? other)
.SequenceEqual(other._memoryOwner.Memory[..other._byteCount].Span);
}
+ ///
+ /// Compares this key to another object.
+ ///
+ /// The object to compare.
+ ///
+ /// A value less than zero if this key is less than ;
+ /// zero if equal; greater than zero if greater.
+ ///
public int CompareTo(object? obj)
{
if (ReferenceEquals(obj, this)) return 0;
@@ -103,6 +140,14 @@ public int CompareTo(object? obj)
return CompareTo(obj as StorageKeyBuilder);
}
+ ///
+ /// Compares this key to another builder by length, then by byte sequence.
+ ///
+ /// The other builder.
+ ///
+ /// A value less than zero if this key is less than ;
+ /// zero if equal; greater than zero if greater.
+ ///
public int CompareTo(StorageKeyBuilder? other)
{
if (ReferenceEquals(other, this)) return 0;
@@ -116,13 +161,27 @@ public int CompareTo(StorageKeyBuilder? other)
.SequenceCompareTo(other._memoryOwner.Memory[..other._byteCount].Span);
}
+ ///
+ /// Returns a hash code for the current key bytes.
+ ///
+ /// A hash code for the key content.
public override int GetHashCode() =>
_memoryOwner.Memory[.._byteCount]
.ToHashCode();
+ ///
+ /// Returns a lowercase hexadecimal representation of the key bytes.
+ ///
+ /// The key as a hex string.
public override string ToString() =>
Convert.ToHexStringLower(_memoryOwner.Memory[.._byteCount].Span);
+ ///
+ /// Concatenates the key bytes of two builders into a new builder.
+ ///
+ /// The left-hand key.
+ /// The right-hand key.
+ /// A new builder containing the concatenated bytes.
public static StorageKeyBuilder operator +(StorageKeyBuilder a, StorageKeyBuilder b) =>
new
(
@@ -133,21 +192,52 @@ .. b._memoryOwner.Memory[..b._byteCount].Span
a._maxLength + b._maxLength
);
+ ///
+ /// Determines whether two builders have equal key bytes.
+ ///
+ /// The left-hand builder.
+ /// The right-hand builder.
+ /// if the keys are equal; otherwise, .
public static bool operator ==(StorageKeyBuilder left, StorageKeyBuilder right) =>
left.Equals(right);
+ ///
+ /// Determines whether two builders have different key bytes.
+ ///
+ /// The left-hand builder.
+ /// The right-hand builder.
+ /// if the keys differ; otherwise, .
public static bool operator !=(StorageKeyBuilder left, StorageKeyBuilder right) =>
!(left == right);
+ ///
+ /// Creates a new key builder for the specified contract id and prefix.
+ ///
+ /// The contract id written as a little-endian .
+ /// An optional prefix byte written after the id.
+ /// A new instance.
public static StorageKeyBuilder Create(int id, byte prefix = 0) =>
new(id, prefix);
+ ///
+ /// Appends a single byte to the key.
+ ///
+ /// The byte to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(byte data)
{
_memoryOwner.Memory.Span[_byteCount++] = data;
return this;
}
+ ///
+ /// Appends a 16-bit signed integer to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(short data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -158,6 +248,14 @@ public StorageKeyBuilder Append(short data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a 16-bit unsigned integer to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(ushort data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -168,6 +266,14 @@ public StorageKeyBuilder Append(ushort data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a 32-bit signed integer to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(int data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -178,6 +284,14 @@ public StorageKeyBuilder Append(int data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a 32-bit unsigned integer to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(uint data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -188,6 +302,14 @@ public StorageKeyBuilder Append(uint data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a 64-bit signed integer to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(long data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -198,6 +320,14 @@ public StorageKeyBuilder Append(long data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a 64-bit unsigned integer to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(ulong data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -208,6 +338,14 @@ public StorageKeyBuilder Append(ulong data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a 128-bit signed integer to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(Int128 data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -218,6 +356,14 @@ public StorageKeyBuilder Append(Int128 data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a 128-bit unsigned integer to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(UInt128 data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -228,6 +374,14 @@ public StorageKeyBuilder Append(UInt128 data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a double-precision floating-point value to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(double data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -238,6 +392,14 @@ public StorageKeyBuilder Append(double data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a single-precision floating-point value to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(float data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -248,6 +410,14 @@ public StorageKeyBuilder Append(float data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a half-precision floating-point value to the key.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(Half data, bool isBigEndian = false)
{
if (isBigEndian)
@@ -258,9 +428,22 @@ public StorageKeyBuilder Append(Half data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a as its unsigned byte representation.
+ ///
+ /// The value to append.
+ ///
+ /// When , writes big-endian; otherwise little-endian.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(BigInteger data, bool isBigEndian = false) =>
Append(data.ToByteArray(true, isBigEndian));
+ ///
+ /// Appends raw bytes to the key.
+ ///
+ /// The bytes to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(ReadOnlySpan data)
{
data.CopyTo(_memoryOwner.Memory.Span[_byteCount..]);
@@ -268,6 +451,12 @@ public StorageKeyBuilder Append(ReadOnlySpan data)
return this;
}
+ ///
+ /// Appends a as its four 32-bit integer parts (little-endian).
+ ///
+ /// The value to append.
+ /// Reserved for API consistency; currently unused.
+ /// This builder for chaining.
public StorageKeyBuilder Append(decimal data, bool isBigEndian = false)
{
Span span = stackalloc int[4];
@@ -280,90 +469,247 @@ public StorageKeyBuilder Append(decimal data, bool isBigEndian = false)
return this;
}
+ ///
+ /// Appends a as its 16-byte representation.
+ ///
+ /// The value to append.
+ /// Controls the byte order of the GUID encoding.
+ /// This builder for chaining.
public StorageKeyBuilder Append(Guid data, bool isBigEndian = false) =>
Append(data.ToByteArray(isBigEndian));
+ ///
+ /// Appends a sequence of bytes to the key.
+ ///
+ /// The sequence to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(ReadOnlySequence data) =>
Append(data.ToArray());
+ ///
+ /// Appends an array segment of bytes to the key.
+ ///
+ /// The segment to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(ArraySegment data) =>
Append(data.AsSpan());
+ ///
+ /// Appends a as Unix milliseconds (UTC adjusted by ).
+ ///
+ /// The timestamp to append.
+ /// Optional offset applied when converting to Unix time.
+ /// This builder for chaining.
public StorageKeyBuilder Append(DateTime stamp, TimeSpan offset = default) =>
Append((ulong)CoreUtilities.ToUnixTimeMilliseconds(stamp, offset));
+ ///
+ /// Appends a as Unix milliseconds (adjusted by ).
+ ///
+ /// The time span to append.
+ /// Optional offset applied when converting to Unix time.
+ /// This builder for chaining.
public StorageKeyBuilder Append(TimeSpan span, TimeSpan offset = default) =>
Append((ulong)CoreUtilities.ToUnixTimeMilliseconds(span, offset));
+ ///
+ /// Appends a as Unix milliseconds.
+ ///
+ /// The timestamp to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(DateTimeOffset stamp) =>
Append((ulong)stamp.ToUnixTimeMilliseconds());
+ ///
+ /// Appends a value as its time-of-day span encoding.
+ ///
+ /// The time to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(TimeOnly time) =>
Append(time.ToTimeSpan());
+ ///
+ /// Appends a combined with an optional as Unix milliseconds.
+ ///
+ /// The date to append.
+ /// The time of day to combine with the date. Defaults to midnight.
+ /// This builder for chaining.
public StorageKeyBuilder Append(DateOnly date, TimeOnly time = default) =>
Append(date.ToDateTime(time));
+ ///
+ /// Appends a sequence of characters encoded as UTF-8.
+ ///
+ /// The characters to encode and append.
+ ///
+ /// When , uses strict UTF-8 encoding; otherwise standard UTF-8.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(ReadOnlySequence data, bool isStrictUtf8 = true) =>
Append(data.ToArray(), isStrictUtf8);
+ ///
+ /// Appends characters encoded as UTF-8.
+ ///
+ /// The characters to encode and append.
+ ///
+ /// When , uses strict UTF-8 encoding; otherwise standard UTF-8.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(ReadOnlyMemory data, bool isStrictUtf8 = true) =>
Append(data.ToArray(), isStrictUtf8);
+ ///
+ /// Appends characters encoded as UTF-8.
+ ///
+ /// The characters to encode and append.
+ ///
+ /// When , uses strict UTF-8 encoding; otherwise standard UTF-8.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(ReadOnlySpan data, bool isStrictUtf8 = true) =>
Append(data.ToArray(), isStrictUtf8);
+ ///
+ /// Appends a character array encoded as UTF-8.
+ ///
+ /// The characters to encode and append.
+ ///
+ /// When , uses strict UTF-8 encoding; otherwise standard UTF-8.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(char[] data, bool isStrictUtf8 = true) =>
isStrictUtf8 ?
Append(CoreUtilities.StrictUtf8Encoding.GetBytes(data)) :
Append(Encoding.UTF8.GetBytes(data));
+ ///
+ /// Appends a single character encoded as UTF-8.
+ ///
+ /// The character to encode and append.
+ ///
+ /// When , uses strict UTF-8 encoding; otherwise standard UTF-8.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(char data, bool isStrictUtf8 = true) =>
isStrictUtf8 ?
Append(CoreUtilities.StrictUtf8Encoding.GetBytes(data.ToString())) :
Append(Encoding.UTF8.GetBytes(data.ToString()));
+ ///
+ /// Appends a signed byte as an unsigned byte.
+ ///
+ /// The value to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(sbyte data) =>
Append((byte)data);
+ ///
+ /// Appends a Boolean as a single byte (1 for , 0 for ).
+ ///
+ /// The value to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(bool data) =>
Append(data ? (byte)1 : (byte)0);
+ ///
+ /// Appends a byte array to the key.
+ ///
+ /// The bytes to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(byte[] data) =>
Append(data.AsSpan());
+ ///
+ /// Appends a memory region of bytes to the key.
+ ///
+ /// The bytes to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(ReadOnlyMemory data) =>
Append(data.Span);
+ ///
+ /// Appends the key bytes of another builder.
+ ///
+ /// The builder whose current bytes are appended.
+ /// This builder for chaining.
public StorageKeyBuilder Append(StorageKeyBuilder other) =>
Append(other._memoryOwner.Memory[..other._byteCount].Span);
+ ///
+ /// Appends a as its 20-byte representation.
+ ///
+ /// The value to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(UInt160 data) =>
Append(data.ToArray());
+ ///
+ /// Appends a as its 32-byte representation.
+ ///
+ /// The value to append.
+ /// This builder for chaining.
public StorageKeyBuilder Append(UInt256 data) =>
Append(data.ToArray());
+ ///
+ /// Appends a string encoded as UTF-8.
+ ///
+ /// The string to encode and append.
+ ///
+ /// When , uses strict UTF-8 encoding; otherwise standard UTF-8.
+ ///
+ /// This builder for chaining.
public StorageKeyBuilder Append(string data, bool isStrictUtf8 = true) =>
isStrictUtf8 ?
Append(CoreUtilities.StrictUtf8Encoding.GetBytes(data)) :
Append(Encoding.UTF8.GetBytes(data));
+ ///
+ /// Appends a prefix byte followed by raw bytes.
+ ///
+ /// The prefix byte.
+ /// The bytes to append after the prefix.
+ /// This builder for chaining.
public StorageKeyBuilder Append(byte prefix, ReadOnlySpan data) =>
Append(prefix).Append(data);
+ ///
+ /// Appends a prefix byte followed by a .
+ ///
+ /// The prefix byte.
+ /// The value to append after the prefix.
+ /// This builder for chaining.
public StorageKeyBuilder Append(byte prefix, UInt160 data) =>
Append(prefix).Append(data);
+ ///
+ /// Appends a prefix byte followed by a .
+ ///
+ /// The prefix byte.
+ /// The value to append after the prefix.
+ /// This builder for chaining.
public StorageKeyBuilder Append(byte prefix, UInt256 data) =>
Append(prefix).Append(data);
+ ///
+ /// Copies the current key bytes into a new array.
+ ///
+ /// A new array containing the key bytes written so far.
public byte[] ToArray() =>
_memoryOwner.Memory[.._byteCount].ToArray();
+ ///
+ /// Creates a new builder with a copy of the current key bytes.
+ ///
+ /// A cloned .
public StorageKeyBuilder Clone() =>
new(_memoryOwner.Memory[.._byteCount].Span, _maxLength);
+ ///
+ /// Returns an enumerator that iterates over the key bytes.
+ ///
+ /// An enumerator for the key content.
public IEnumerator GetEnumerator() =>
GetEnumerator();
diff --git a/src/Neo.Platform.Storage/StoreExtensions.cs b/src/Neo.Platform.Storage/StoreExtensions.cs
index be335de..e0619fd 100644
--- a/src/Neo.Platform.Storage/StoreExtensions.cs
+++ b/src/Neo.Platform.Storage/StoreExtensions.cs
@@ -28,16 +28,48 @@
namespace Neo.Platform.Storage
{
+ ///
+ /// Extension methods for reading and writing values from stores.
+ ///
public static class StoreExtensions
{
+ ///
+ /// Serializes and stores it under .
+ ///
+ /// The serializable value type.
+ /// The store to write to.
+ /// The storage key.
+ /// The value to serialize and store.
public static void Put(this IStore store, ReadOnlySpan key, T value)
where T : INeoSerializable =>
store.Put(key, value.ToArray());
+ ///
+ /// Reads and deserializes a value of type for .
+ ///
+ /// The serializable result type.
+ /// The store to read from.
+ /// The storage key.
+ ///
+ /// The deserialized value, or if the key is not present.
+ ///
public static TResult? Get(this IReadOnlyStore store, ReadOnlySpan key)
where TResult : class?, INeoSerializable =>
store.Get(key)?.AsSerializable();
+ ///
+ /// Attempts to read and deserialize a value of type for .
+ ///
+ /// The serializable value type.
+ /// The store to read from.
+ /// The storage key.
+ ///
+ /// When this method returns , contains the deserialized value;
+ /// otherwise, .
+ ///
+ ///
+ /// if the key was found and deserialized; otherwise, .
+ ///
public static bool TryGet(this IReadOnlyStore store, ReadOnlySpan key, [NotNullWhen(true)] out T? value)
where T : class?, INeoSerializable
{
diff --git a/src/Neo.VM/Builder/VirtualMachineBuilder.cs b/src/Neo.VM/Builder/VirtualMachineBuilder.cs
index d2ba4f2..915babe 100644
--- a/src/Neo.VM/Builder/VirtualMachineBuilder.cs
+++ b/src/Neo.VM/Builder/VirtualMachineBuilder.cs
@@ -29,6 +29,9 @@
namespace Neo.VM.Builder
{
+ ///
+ /// Fluent builder for constructing a .
+ ///
public sealed class VirtualMachineBuilder
{
private VirtualTable? _vtable;
@@ -42,15 +45,29 @@ public sealed class VirtualMachineBuilder
private VirtualMachineBuilder() { }
+ ///
+ /// Creates a new instance.
+ ///
+ /// A new builder.
public static VirtualMachineBuilder Create() =>
new();
+ ///
+ /// Configures the middleware pipeline used during execution.
+ ///
+ /// The pipeline to use.
+ /// This builder for chaining.
public VirtualMachineBuilder UsePipeline(VirtualMachinePipeline pipeline)
{
_pipeline = pipeline;
return this;
}
+ ///
+ /// Configures the middleware pipeline via a nested .
+ ///
+ /// An action that registers middleware on the pipeline builder.
+ /// This builder for chaining.
public VirtualMachineBuilder UsePipeline(Action config)
{
var pb = VirtualMachinePipelineBuilder.Create();
@@ -59,36 +76,66 @@ public VirtualMachineBuilder UsePipeline(Action c
return this;
}
+ ///
+ /// Sets the execution limits for the engine.
+ ///
+ /// The limits to apply.
+ /// This builder for chaining.
public VirtualMachineBuilder UseLimits(ExecutionEngineLimits limits)
{
_engineLimits = limits;
return this;
}
+ ///
+ /// Sets the block being persisted, used to resolve the active hard fork.
+ ///
+ /// The block under persistence.
+ /// This builder for chaining.
public VirtualMachineBuilder UseBlock(Block persistingBlock)
{
_persistingBlock = persistingBlock;
return this;
}
+ ///
+ /// Sets the protocol settings used by the engine.
+ ///
+ /// The protocol settings.
+ /// This builder for chaining.
public VirtualMachineBuilder UseProtocolSettings(ProtocolSettings protocolSettings)
{
_protocolSettings = protocolSettings;
return this;
}
+ ///
+ /// Sets the opcode dispatch table used by the engine.
+ ///
+ /// The virtual table of opcode handlers.
+ /// This builder for chaining.
public VirtualMachineBuilder UseVirtualTable(VirtualTable vtable)
{
_vtable = vtable;
return this;
}
+ ///
+ /// Sets the transaction container, which supplies the system-fee gas limit.
+ ///
+ /// The transaction to execute against.
+ /// This builder for chaining.
public VirtualMachineBuilder UseTransaction(Transaction tx)
{
_tx = tx;
return this;
}
+ ///
+ /// Builds a from the configured options.
+ /// Unspecified options use engine defaults.
+ ///
+ /// A new engine instance.
public VirtualMachineEngine Build() =>
new
(
diff --git a/src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs b/src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs
index d4cabec..f9961af 100644
--- a/src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs
+++ b/src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs
@@ -39,12 +39,18 @@ public sealed class VirtualMachinePipelineBuilder
private VirtualMachinePipelineBuilder() { }
+ ///
+ /// Creates a new instance.
+ ///
+ /// A new builder.
public static VirtualMachinePipelineBuilder Create() =>
new();
///
/// Registers a middleware instance.
///
+ /// The middleware to register.
+ /// This builder for chaining.
public VirtualMachinePipelineBuilder Use(IEngineMiddleware middleware)
{
ArgumentNullException.ThrowIfNull(middleware);
@@ -55,6 +61,8 @@ public VirtualMachinePipelineBuilder Use(IEngineMiddleware middleware)
///
/// Registers a middleware by type (requires parameterless constructor).
///
+ /// The middleware type to instantiate and register.
+ /// This builder for chaining.
public VirtualMachinePipelineBuilder UseMiddleware()
where TMiddleware : IEngineMiddleware, new()
{
@@ -64,6 +72,8 @@ public VirtualMachinePipelineBuilder UseMiddleware()
///
/// Registers multiple middleware's at once.
///
+ /// The middleware instances to register, in execution order.
+ /// This builder for chaining.
public VirtualMachinePipelineBuilder Use(IEnumerable middlewares)
{
foreach (var middleware in middlewares)
@@ -75,6 +85,7 @@ public VirtualMachinePipelineBuilder Use(IEnumerable middlewa
///
/// Builds the final optimized pipeline with separate chains for each hook.
///
+ /// A pipeline ready for use by .
public VirtualMachinePipeline Build()
{
var preExecution = BuildExecutionChain(mw => mw.PreExecution);
diff --git a/src/Neo.VM/Core/ExecutionContext.cs b/src/Neo.VM/Core/ExecutionContext.cs
index 5ef9d8b..78c95af 100644
--- a/src/Neo.VM/Core/ExecutionContext.cs
+++ b/src/Neo.VM/Core/ExecutionContext.cs
@@ -28,59 +28,68 @@
namespace Neo.VM.Core
{
+ ///
+ /// Represents a single invocation frame: script, instruction pointer, gas budget, and evaluation stack.
+ ///
public class ExecutionContext
{
///
- /// The script being executed (bytecode)
+ /// Gets the script being executed (bytecode), including a trailing RET appended at construction.
///
public ReadOnlyMemory Script => _script;
///
- /// The pointer indicating the current instruction.
+ /// Gets the pointer indicating the current instruction.
///
public int InstructionPointer { get; internal set; }
///
- /// Returns the current .
+ /// Gets the current at .
///
public OpCodeInst CurrentInstruction => new(_script[InstructionPointer..]);
///
- /// Returns the next .
+ /// Gets the next after the current instruction.
///
public OpCodeInst NextInstruction => new(_script[(InstructionPointer + CurrentInstruction.Size)..]);
///
- /// Current stack frame
+ /// Gets the current stack frame that owns the evaluation stack and locals.
///
public StackFrame Frame { get; }
///
- /// Gas remaining for this execution
+ /// Gets the amount of gas consumed by this context so far.
///
public long GasConsumed => _maxGasConsumed;
///
- /// Whether this context is currently executing
+ /// Gets a value indicating whether this context is still eligible to execute instructions.
///
public bool IsExecuting { get; internal set; } = true;
///
- /// Parent context (for nested calls)
+ /// Gets the parent context for nested calls, if any.
///
public ExecutionContext? Parent => _parentContext;
///
- /// Custom state / context data (e.g., contract storage, runtime)
+ /// Gets a dictionary of custom state objects keyed by type (for example, runtime services).
///
public Dictionary State { get; } = [];
+ ///
+ /// Gets the hard fork under which this context executes.
+ ///
public HardFork HardFork => _fork;
+ ///
+ /// Gets the block height associated with this context.
+ ///
public uint BlockHeight => _blockHeight;
///
- /// Invocation stack depth
+ /// Gets the depth of this context in the invocation chain.
///
public int Depth => _contextDepth;
@@ -94,6 +103,15 @@ public class ExecutionContext
private long _initialGasLeft;
private long _maxGasConsumed;
+ ///
+ /// Initializes a new execution context for the specified script.
+ ///
+ /// The bytecode to execute. A trailing RET is appended automatically.
+ /// The hard fork that determines gas costs and opcode behavior.
+ /// The block height associated with this execution.
+ /// The gas budget available to this context.
+ /// The invocation depth of this context.
+ /// The parent context for nested calls, or for a root context.
public ExecutionContext(byte[] script, HardFork fork = HardFork.Genesis, uint blockHeight = 0, long initialGas = 1_000000L, int depth = 0, ExecutionContext? parent = null)
{
script = [.. script, (byte)OpCode.RET];
@@ -110,16 +128,19 @@ public ExecutionContext(byte[] script, HardFork fork = HardFork.Genesis, uint bl
}
///
- /// Check if execution should continue
+ /// Determines whether execution should continue for this context.
///
+ /// if the context is active, has remaining script bytes, and has gas remaining.
public bool ShouldContinue()
{
return IsExecuting && InstructionPointer < Script.Length && _initialGasLeft > 0;
}
///
- /// Consume gas for an operation
+ /// Attempts to consume the gas cost for the specified opcode.
///
+ /// The opcode whose gas cost should be charged.
+ /// if gas was available and deducted; otherwise and execution is stopped.
public bool ConsumeGas(OpCode opcode)
{
var cost = GasTable.GetGasCost(opcode, HardFork);
@@ -136,62 +157,87 @@ public bool ConsumeGas(OpCode opcode)
}
///
- /// Push value onto current frame's evaluation stack
+ /// Pushes a value onto the current frame's evaluation stack.
///
+ /// The stack item to push.
+ /// Whether to increment the item's reference count.
+ /// Whether to increment reference counts of child items.
public void Push(VMObject item, bool addReferenceItem = true, bool addReferenceChildren = true)
{
Frame.Push(item, addReferenceItem, addReferenceChildren);
}
///
- /// Pop value from current frame's evaluation stack
+ /// Pops a value from the current frame's evaluation stack.
///
+ /// Whether to release the item's reference count.
+ /// Whether to release reference counts of child items.
+ /// The popped stack item.
public VMObject Pop(bool releaseReferenceItem = true, bool releaseReferenceChildren = true)
{
return Frame.Pop(releaseReferenceItem, releaseReferenceChildren);
}
///
- /// Peek value from current frame's evaluation stack without removing
+ /// Peeks at the top of the current frame's evaluation stack without removing it.
///
+ /// The top stack item.
public VMObject Peek()
{
return Frame.Peek();
}
///
- /// Peek value from current frame's evaluation stack without removing by an index
+ /// Peeks at an item on the evaluation stack by depth without removing it.
///
+ /// Zero-based depth from the top of the stack.
+ /// The stack item at the specified depth.
public VMObject Peek(int index)
{
return Frame.Peek(index);
}
+ ///
+ /// Inserts an item into the evaluation stack at the specified depth.
+ ///
+ /// Zero-based depth from the top of the stack.
+ /// The item to insert.
public void Insert(int index, VMObject item)
{
Frame.Insert(index, item);
}
+ ///
+ /// Swaps two items on the evaluation stack by depth.
+ ///
+ /// The first depth index.
+ /// The second depth index.
public void Swap(int fromIndex, int toIndex)
{
Frame.Swap(fromIndex, toIndex);
}
+ ///
+ /// Reverses the order of the top items on the evaluation stack.
+ ///
+ /// The number of items from the top to reverse.
public void Reverse(int n)
{
Frame.Reverse(n);
}
///
- /// Pop value from current frame's evaluation stack at an index
+ /// Removes and returns the evaluation-stack item at the specified depth.
///
+ /// Zero-based depth from the top of the stack.
+ /// The removed stack item.
public VMObject RemoveAt(int index)
{
return Frame.RemoveAt(index);
}
///
- /// Safely clear current frame's evaluation stack and release all references
+ /// Clears the current frame's evaluation stack and releases all references.
///
public void Clear()
{
@@ -199,7 +245,7 @@ public void Clear()
}
///
- /// Clean up this execution context
+ /// Releases stack resources and marks this context as no longer executing.
///
public void Cleanup()
{
diff --git a/src/Neo.VM/Core/StackFrame.cs b/src/Neo.VM/Core/StackFrame.cs
index 69a1b21..6bd09e1 100644
--- a/src/Neo.VM/Core/StackFrame.cs
+++ b/src/Neo.VM/Core/StackFrame.cs
@@ -26,40 +26,50 @@
namespace Neo.VM.Core
{
+ ///
+ /// Holds the evaluation stack, locals, static fields, and arguments for a single call frame.
+ ///
+ /// The parent frame for nested calls, or for a root frame.
public class StackFrame(StackFrame? parent = default) : IDisposable
{
///
- /// The evaluation stack for this frame
+ /// Gets the evaluation stack for this frame.
///
public Stack EvaluationStack { get; } = [];
///
- /// Local variables for this frame (indexed)
+ /// Gets the local variables for this frame (indexed).
///
public List LocalVariables { get; } = [];
+ ///
+ /// Gets the static fields associated with this frame.
+ ///
public List StaticFields { get; } = [];
///
- /// The list used to store the arguments of the current method.
+ /// Gets the list used to store the arguments of the current method.
///
public List Arguments { get; } = [];
///
- /// Alternative stack (used by some NeoVM instructions)
+ /// Gets the alternative stack (used by some NeoVM instructions).
///
public Stack AltStack { get; } = [];
///
- /// Parent frame (for nested calls)
+ /// Gets the parent frame for nested calls.
///
public StackFrame? Parent { get; } = parent;
///
- /// Whether this frame is currently executing
+ /// Gets a value indicating whether this frame is still active.
///
public bool IsActive { get; private set; } = true;
+ ///
+ /// Releases stack resources held by this frame.
+ ///
public void Dispose()
{
Cleanup();
@@ -67,8 +77,11 @@ public void Dispose()
}
///
- /// Push item onto evaluation stack with reference counting
+ /// Pushes an item onto the evaluation stack with optional reference counting.
///
+ /// The item to push.
+ /// Whether to increment the item's reference count.
+ /// Whether to increment reference counts of child items.
public void Push(VMObject item, bool addReferenceItem = true, bool addReferenceChildren = true)
{
if (addReferenceItem)
@@ -87,8 +100,12 @@ public void Push(VMObject item, bool addReferenceItem = true, bool addReferenceC
}
///
- /// Pop item from evaluation stack and release reference
+ /// Pops an item from the evaluation stack and optionally releases references.
///
+ /// Whether to release the item's reference count.
+ /// Whether to release reference counts of child items.
+ /// The popped item.
+ /// Thrown when the evaluation stack is empty.
public VMObject Pop(bool releaseReferenceItem = true, bool releaseReferenceChildren = true)
{
if (EvaluationStack.Count == 0)
@@ -112,14 +129,21 @@ public VMObject Pop(bool releaseReferenceItem = true, bool releaseReferenceChild
}
///
- /// Peek from top of evaluation stack without removing by an index
+ /// Peeks at an evaluation-stack item by depth without removing it.
///
+ /// Zero-based depth from the top of the stack.
+ /// The stack item at the specified depth.
public VMObject Peek(int index = 0)
{
var list = new List(EvaluationStack); // Copy to list (top is at the end)
return list[index];
}
+ ///
+ /// Inserts an item into the evaluation stack at the specified depth.
+ ///
+ /// Zero-based depth from the top of the stack.
+ /// The item to insert.
public void Insert(int index, VMObject item)
{
var list = new List(EvaluationStack); // Copy to list (top is at the end)
@@ -136,6 +160,11 @@ public void Insert(int index, VMObject item)
item.AddReference();
}
+ ///
+ /// Swaps two items on the evaluation stack by depth.
+ ///
+ /// The first depth index.
+ /// The second depth index.
public void Swap(int fromIndex, int toIndex)
{
if (fromIndex == toIndex) return;
@@ -151,6 +180,11 @@ public void Swap(int fromIndex, int toIndex)
list.Clear();
}
+ ///
+ /// Reverses the order of the top items on the evaluation stack.
+ ///
+ /// The number of items from the top to reverse.
+ /// Thrown when is outside the stack bounds.
public void Reverse(int n)
{
var list = new List(EvaluationStack); // Copy to list (top is at the end)
@@ -171,6 +205,11 @@ public void Reverse(int n)
}
+ ///
+ /// Removes and returns the evaluation-stack item at the specified depth.
+ ///
+ /// Zero-based depth from the top of the stack.
+ /// The removed item.
public VMObject RemoveAt(int index)
{
var list = new List(EvaluationStack); // Copy to list (top is at the end)
@@ -189,7 +228,7 @@ public VMObject RemoveAt(int index)
}
///
- /// Safely clear a evaluation stack and release all references
+ /// Clears the evaluation stack and releases all references.
///
public void Clear()
{
@@ -200,6 +239,10 @@ public void Clear()
}
}
+ ///
+ /// Ensures the local-variable list has at least the specified number of slots, filling with .
+ ///
+ /// The required number of local slots.
public void InitLocalVariables(int count)
{
while (LocalVariables.Count < count)
@@ -207,8 +250,10 @@ public void InitLocalVariables(int count)
}
///
- /// Set local variable at index (with proper ref counting)
+ /// Sets the local variable at the specified index, updating reference counts.
///
+ /// The local-variable index.
+ /// The value to store.
public void SetLocalVariable(int index, VMObject value)
{
// Expand locals list if needed
@@ -223,12 +268,21 @@ public void SetLocalVariable(int index, VMObject value)
LocalVariables[index] = value;
}
+ ///
+ /// Ensures the static-field list has at least the specified number of slots, filling with .
+ ///
+ /// The required number of static-field slots.
public void InitStaticFields(int count)
{
while (StaticFields.Count < count)
StaticFields.Add(VMNull.Instance);
}
+ ///
+ /// Sets the static field at the specified index, updating reference counts.
+ ///
+ /// The static-field index.
+ /// The value to store.
public void SetStaticField(int index, VMObject value)
{
StaticFields[index]?.Release();
@@ -238,12 +292,21 @@ public void SetStaticField(int index, VMObject value)
StaticFields[index] = value;
}
+ ///
+ /// Ensures the argument list has at least the specified number of slots, filling with .
+ ///
+ /// The required number of argument slots.
public void InitArguments(int count)
{
while (Arguments.Count < count)
Arguments.Add(VMNull.Instance);
}
+ ///
+ /// Sets the argument at the specified index, updating reference counts.
+ ///
+ /// The argument index.
+ /// The value to store.
public void SetArguments(int index, VMObject value)
{
while (Arguments.Count < index)
@@ -257,26 +320,39 @@ public void SetArguments(int index, VMObject value)
}
///
- /// Get local variable at index
+ /// Gets the local variable at the specified index, or if out of range.
///
+ /// The local-variable index.
+ /// The local value, or null when the index is invalid.
public VMObject GetLocal(int index)
{
return (index >= 0 && index < LocalVariables.Count) ? LocalVariables[index] : VMNull.Instance;
}
+ ///
+ /// Gets the static field at the specified index, or if out of range.
+ ///
+ /// The static-field index.
+ /// The static-field value, or null when the index is invalid.
public VMObject GetStaticFields(int index)
{
return (index >= 0 && index < StaticFields.Count) ? StaticFields[index] : VMNull.Instance;
}
+ ///
+ /// Gets the argument at the specified index, or if out of range.
+ ///
+ /// The argument index.
+ /// The argument value, or null when the index is invalid.
public VMObject GetArguments(int index)
{
return (index >= 0 && index < Arguments.Count) ? Arguments[index] : VMNull.Instance;
}
///
- /// Checks if this stack frame contains any circular references
+ /// Determines whether this stack frame contains any circular references among its stack items.
///
+ /// if a cycle is detected; otherwise .
public bool HasCircularReference()
{
var visited = new HashSet(ReferenceEqualityComparer.Instance);
@@ -284,7 +360,7 @@ public bool HasCircularReference()
}
///
- /// Clean up this frame (release all references)
+ /// Releases all references held by this frame and marks it inactive.
///
public void Cleanup()
{
diff --git a/src/Neo.VM/Core/VirtualTable.cs b/src/Neo.VM/Core/VirtualTable.cs
index b7005d7..42f5383 100644
--- a/src/Neo.VM/Core/VirtualTable.cs
+++ b/src/Neo.VM/Core/VirtualTable.cs
@@ -26,20 +26,44 @@
namespace Neo.VM.Core
{
+ ///
+ /// Represents an opcode handler invoked by the engine for a single instruction.
+ ///
+ /// The execution engine.
+ /// The instruction being executed.
public delegate void VTableFunc(VirtualMachineEngine engine, OpCodeInst instruction);
+ ///
+ /// Dispatch table that maps values to handler methods.
+ /// Methods named after opcodes are auto-bound in the constructor via reflection.
+ ///
public partial class VirtualTable
{
+ ///
+ /// Gets the default virtual table with standard NeoVM opcode handlers.
+ ///
public static readonly VirtualTable Default = new();
+ ///
+ /// Gets the array of handlers indexed by opcode byte value.
+ ///
public VTableFunc[] Functions { get; protected set; } = new VTableFunc[byte.MaxValue];
+ ///
+ /// Gets or sets the handler for the specified opcode.
+ ///
+ /// The opcode to look up.
+ /// The registered handler for .
public VTableFunc this[OpCode opCode]
{
get => Functions[(byte)opCode];
protected set => Functions[(byte)opCode] = value;
}
+ ///
+ /// Initializes a new virtual table, filling undefined opcodes with
+ /// and binding public methods whose names match members.
+ ///
public VirtualTable()
{
Array.Fill(Functions, InvalidOpcode);
@@ -52,6 +76,12 @@ public VirtualTable()
}
+ ///
+ /// Default handler for opcodes that have no registered implementation.
+ ///
+ /// The execution engine.
+ /// The undefined instruction.
+ /// Always thrown for undefined opcodes.
[DoesNotReturn]
public static void InvalidOpcode(VirtualMachineEngine engine, OpCodeInst instruction)
{
diff --git a/src/Neo.VM/Extensions/DictionaryExtensions.cs b/src/Neo.VM/Extensions/DictionaryExtensions.cs
index f727678..c79ff76 100644
--- a/src/Neo.VM/Extensions/DictionaryExtensions.cs
+++ b/src/Neo.VM/Extensions/DictionaryExtensions.cs
@@ -26,8 +26,18 @@
namespace Neo.VM.Extensions
{
+ ///
+ /// Extension methods for converting dictionaries to VM stack items.
+ ///
public static class DictionaryExtensions
{
+ ///
+ /// Converts a dictionary into a by mapping each key and value to a .
+ ///
+ /// The dictionary key type.
+ /// The dictionary value type.
+ /// The dictionary to convert.
+ /// A containing the converted entries.
public static VMMap ToStackItem(this IDictionary source) =>
new(
source.Select(static kv => new KeyValuePair(
diff --git a/src/Neo.VM/Extensions/ExecutionContextExtensions.cs b/src/Neo.VM/Extensions/ExecutionContextExtensions.cs
index 9ceabe2..3b5eed4 100644
--- a/src/Neo.VM/Extensions/ExecutionContextExtensions.cs
+++ b/src/Neo.VM/Extensions/ExecutionContextExtensions.cs
@@ -25,11 +25,16 @@
namespace Neo.VM.Extensions
{
+ ///
+ /// Extension methods for inspecting gas usage across an chain.
+ ///
public static class ExecutionContextExtensions
{
///
- /// Returns the total GasConsumed across this context and all its parents (call stack).
+ /// Returns the total across this context and all its parents (call stack).
///
+ /// The leaf context, or .
+ /// The summed gas consumed along the parent chain.
public static long GetTotalGasConsumed(this ExecutionContext? context)
{
var total = 0L;
@@ -45,9 +50,11 @@ public static long GetTotalGasConsumed(this ExecutionContext? context)
}
///
- /// Returns a list of GasConsumed values from bottom (root) to top (current).
+ /// Returns a list of values from bottom (root) to top (current).
/// Useful for debugging / logging the full call stack gas usage.
///
+ /// The leaf context, or .
+ /// Gas-consumed values ordered from root to leaf.
public static List GetGasConsumedChain(this ExecutionContext? context)
{
var chain = new List();
diff --git a/src/Neo.VM/Extensions/LoggerExtensions.cs b/src/Neo.VM/Extensions/LoggerExtensions.cs
index 7b6e861..3a60c07 100644
--- a/src/Neo.VM/Extensions/LoggerExtensions.cs
+++ b/src/Neo.VM/Extensions/LoggerExtensions.cs
@@ -26,8 +26,14 @@
namespace Neo.VM.Extensions
{
+ ///
+ /// Source-generated logging helpers for virtual machine execution events.
+ ///
internal static partial class LoggerExtensions
{
+ ///
+ /// Logs a VM execute message.
+ ///
[LoggerMessage(
EventId = VirtualMachineEventId.Execute,
EventName = nameof(VirtualMachineEventId.Execute),
@@ -39,6 +45,9 @@ public static partial void LogExecuteMessage(
string message
);
+ ///
+ /// Logs a VM load message.
+ ///
[LoggerMessage(
EventId = VirtualMachineEventId.Load,
EventName = nameof(VirtualMachineEventId.Load),
@@ -50,6 +59,9 @@ public static partial void LogLoadMessage(
string message
);
+ ///
+ /// Logs a VM fault message.
+ ///
[LoggerMessage(
EventId = VirtualMachineEventId.Fault,
EventName = nameof(VirtualMachineEventId.Fault),
@@ -61,6 +73,9 @@ public static partial void LogFaultMessage(
string message
);
+ ///
+ /// Logs a VM fault with an exception.
+ ///
[LoggerMessage(
EventId = VirtualMachineEventId.Fault,
Message = ""
@@ -71,6 +86,9 @@ public static partial void LogFaultMessage(
Exception exception
);
+ ///
+ /// Logs a VM break (debugger breakpoint) message.
+ ///
[LoggerMessage(
EventId = VirtualMachineEventId.Break,
EventName = nameof(VirtualMachineEventId.Break),
diff --git a/src/Neo.VM/Extensions/ObjectExtensions.cs b/src/Neo.VM/Extensions/ObjectExtensions.cs
index 6be44ef..3414281 100644
--- a/src/Neo.VM/Extensions/ObjectExtensions.cs
+++ b/src/Neo.VM/Extensions/ObjectExtensions.cs
@@ -29,8 +29,19 @@
namespace Neo.VM.Extensions
{
+ ///
+ /// Extension methods for converting CLR values to stack items.
+ ///
public static class ObjectExtensions
{
+ ///
+ /// Converts a CLR value into the corresponding representation.
+ /// Supported primitives become integer, boolean, or byte-string items; sequences become arrays;
+ /// other reference types become wrappers.
+ ///
+ /// The source value type.
+ /// The value to convert; becomes .
+ /// A stack item suitable for evaluation-stack operations.
public static VMObject ToStackItem(this TSource? source) =>
source switch
{
diff --git a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs
index bfdfc72..54a86ba 100644
--- a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs
@@ -27,8 +27,17 @@
namespace Neo.VM.Extensions
{
+ ///
+ /// Dependency-injection helpers for registering the Neo VM engine and middleware.
+ ///
public static class ServiceCollectionExtensions
{
+ ///
+ /// Registers a scoped built with any registered middleware.
+ /// When a is registered, it is placed first in the pipeline.
+ ///
+ /// The service collection.
+ /// The same service collection for chaining.
public static IServiceCollection AddExecutionEngine(this IServiceCollection services)
{
// Register VirtualMachineEngine using the built pipeline
@@ -54,6 +63,12 @@ public static IServiceCollection AddExecutionEngine(this IServiceCollection serv
return services;
}
+ ///
+ /// Registers a middleware type as a singleton .
+ ///
+ /// The middleware implementation type.
+ /// The service collection.
+ /// The same service collection for chaining.
public static IServiceCollection AddEngineMiddleware(this IServiceCollection services)
where TMiddleware : class, IEngineMiddleware
{
@@ -62,6 +77,11 @@ public static IServiceCollection AddEngineMiddleware(this IServiceC
return services;
}
+ ///
+ /// Registers the as a scoped service.
+ ///
+ /// The service collection.
+ /// The same service collection for chaining.
public static IServiceCollection AddEngineMiddlewareDebugger(this IServiceCollection services)
{
services.AddScoped();
@@ -69,6 +89,11 @@ public static IServiceCollection AddEngineMiddlewareDebugger(this IServiceCollec
return services;
}
+ ///
+ /// Registers the as a scoped .
+ ///
+ /// The service collection.
+ /// The same service collection for chaining.
public static IServiceCollection AddEngineMiddlewareLogger(this IServiceCollection services)
{
services.AddScoped();
@@ -76,6 +101,13 @@ public static IServiceCollection AddEngineMiddlewareLogger(this IServiceCollecti
return services;
}
+ ///
+ /// Registers one or more middleware types as singleton implementations.
+ ///
+ /// The service collection.
+ /// Types that implement .
+ /// The same service collection for chaining.
+ /// Thrown when a type does not implement .
public static IServiceCollection AddEngineMiddleware(this IServiceCollection services,
params Type[] middlewareTypes)
{
diff --git a/src/Neo.VM/Logging/VirtualMachineEventId.cs b/src/Neo.VM/Logging/VirtualMachineEventId.cs
index eb45e24..5c09d77 100644
--- a/src/Neo.VM/Logging/VirtualMachineEventId.cs
+++ b/src/Neo.VM/Logging/VirtualMachineEventId.cs
@@ -22,34 +22,58 @@
namespace Neo.VM.Logging
{
+ ///
+ /// Well-known event IDs used by LoggerMessage sources in Neo.VM.
+ ///
public static class VirtualMachineEventId
{
+ /// Event ID for unrecoverable VM faults.
public const int Fault = 100;
+ /// Event ID for engine or context creation.
public const int Create = 200;
+ /// Event ID for script or context load operations.
public const int Load = 201;
+ /// Event ID for paired pre/post lifecycle messages.
public const int PrePost = 202;
+ /// Event ID for post-lifecycle messages.
public const int Post = 203;
+ /// Event ID for debugger breakpoint stops.
public const int Break = 204;
+ /// Event ID for general execution progress messages.
public const int Execute = 205;
+ /// Event ID for gas burn operations.
public const int Burn = 300;
+ /// Event ID for interop or contract calls.
public const int Call = 301;
+ /// Event ID for runtime notify events.
public const int Notify = 302;
+ /// Event ID for runtime log events.
public const int Log = 303;
+ /// Event ID for block persist start.
public const int Persist = 400;
+ /// Event ID for block persist completion.
public const int PostPersist = 401;
+ /// Event ID for storage put operations.
public const int StoragePut = 500;
+ /// Event ID for storage get operations.
public const int StorageGet = 501;
+ /// Event ID for storage find/query operations.
public const int StorageFind = 502;
+ /// Event ID for storage delete operations.
public const int StorageDelete = 503;
+ /// Event ID for iterator advance operations.
public const int IteratorNext = 600;
+ /// Event ID for iterator value retrieval.
public const int IteratorGet = 601;
+ /// Event ID for storage read-path diagnostics.
public const int ReadStorage = 700;
+ /// Event ID for storage update-path diagnostics.
public const int UpdateStorage = 701;
}
}
diff --git a/src/Neo.VM/Middleware/DebuggerMiddleware.cs b/src/Neo.VM/Middleware/DebuggerMiddleware.cs
index d7453cc..7f70bca 100644
--- a/src/Neo.VM/Middleware/DebuggerMiddleware.cs
+++ b/src/Neo.VM/Middleware/DebuggerMiddleware.cs
@@ -30,10 +30,22 @@
namespace Neo.VM.Middleware
{
+ ///
+ /// Middleware that supports script-offset breakpoints and single-step execution.
+ /// When a breakpoint is hit or step mode advances, the engine enters .
+ ///
+ /// Service provider used to resolve the .
+ /// Logger for debug messages.
public sealed class DebuggerMiddleware(IServiceProvider sp, ILogger logger) : IEngineMiddleware
{
+ ///
+ /// Raised when a breakpoint or step stop occurs.
+ ///
public event EventHandler? OnBreakpoint;
+ ///
+ /// Gets or sets whether the debugger pauses before each distinct instruction pointer.
+ ///
public bool StepMode { get; set; }
private static readonly decimal s_factor = 1_00000000m;
@@ -43,12 +55,20 @@ public sealed class DebuggerMiddleware(IServiceProvider sp, ILogger _breakpoints = []; // script offset breakpoints
private int _lastStepPosition = -1;
+ ///
+ /// Registers a breakpoint at the specified script offset.
+ ///
+ /// The instruction pointer value at which to break.
public void AddBreakpoint(int scriptOffset) =>
_breakpoints.Add(scriptOffset);
+ ///
+ /// Resumes execution by clearing the state.
+ ///
public void Continue() =>
Engine.State = VMState.NONE;
+ ///
public void PreExecution(ExecutionDelegate next)
{
if (logger.IsEnabled(LogLevel.Debug))
@@ -61,6 +81,7 @@ public void PreExecution(ExecutionDelegate next)
next();
}
+ ///
public void PostExecution(ExecutionDelegate next)
{
StepMode = false;
@@ -68,6 +89,7 @@ public void PostExecution(ExecutionDelegate next)
next();
}
+ ///
public void PreExecute(ExecutionContext? context, ExecuteDelegate next)
{
if (context is null)
@@ -102,14 +124,22 @@ public void PreExecute(ExecutionContext? context, ExecuteDelegate next)
next(context);
}
+ ///
public void PostExecute(ExecutionContext? context, ExecuteDelegate next)
{
next(context);
}
}
+ ///
+ /// Event arguments for debugger breakpoint notifications.
+ ///
+ /// The execution context at the break site.
public class DebuggerEventArgs(ExecutionContext context) : EventArgs
{
+ ///
+ /// Gets the execution context where the breakpoint was hit.
+ ///
public ExecutionContext Context => context;
}
}
diff --git a/src/Neo.VM/Middleware/ExecuteLoggerMiddleware.cs b/src/Neo.VM/Middleware/ExecuteLoggerMiddleware.cs
index 7720941..574443a 100644
--- a/src/Neo.VM/Middleware/ExecuteLoggerMiddleware.cs
+++ b/src/Neo.VM/Middleware/ExecuteLoggerMiddleware.cs
@@ -29,17 +29,24 @@
namespace Neo.VM.Middleware
{
+ ///
+ /// Middleware that logs VM startup, per-opcode execution (at trace level), and completion.
+ ///
+ /// Service provider used to resolve the .
+ /// Logger that receives execution messages.
public sealed class ExecuteLoggerMiddleware(IServiceProvider sp, ILogger logger) : IEngineMiddleware
{
private static readonly decimal s_factor = 1_00000000m;
private VirtualMachineEngine Engine => sp.GetRequiredService();
+ ///
public void PostExecute(ExecutionContext? context, ExecuteDelegate next)
{
next(context);
}
+ ///
public void PreExecution(ExecutionDelegate next)
{
var logLevel = LogLevel.Information;
@@ -61,6 +68,7 @@ public void PreExecution(ExecutionDelegate next)
next();
}
+ ///
public void PreExecute(ExecutionContext? context, ExecuteDelegate next)
{
var logLevel = LogLevel.Trace;
@@ -81,6 +89,7 @@ public void PreExecute(ExecutionContext? context, ExecuteDelegate next)
next(context);
}
+ ///
public void PostExecution(ExecutionDelegate next)
{
var logLevel = LogLevel.Information;
diff --git a/src/Neo.VM/Middleware/IEngineMiddleware.cs b/src/Neo.VM/Middleware/IEngineMiddleware.cs
index df723a8..9d0c531 100644
--- a/src/Neo.VM/Middleware/IEngineMiddleware.cs
+++ b/src/Neo.VM/Middleware/IEngineMiddleware.cs
@@ -24,30 +24,47 @@
namespace Neo.VM.Middleware
{
+ ///
+ /// Represents a middleware handler invoked around individual opcode execution.
+ ///
+ /// The current execution context, or when none is active.
public delegate void ExecuteDelegate(ExecutionContext? context);
+ ///
+ /// Represents a middleware handler invoked around the entire VM run.
+ ///
public delegate void ExecutionDelegate();
+ ///
+ /// Defines middleware hooks that participate in the .
+ /// Implementations must invoke the provided next delegate to continue the chain.
+ ///
public interface IEngineMiddleware
{
///
- /// Called when VM starts execution
+ /// Called when VM starts execution.
///
+ /// The next handler in the pre-execution chain.
void PreExecution(ExecutionDelegate next);
///
- /// Called when VM finishes execution (HALT, FAULT, etc.)
+ /// Called when VM finishes execution (HALT, FAULT, etc.).
///
+ /// The next handler in the post-execution chain.
void PostExecution(ExecutionDelegate next);
///
- /// Called before each opcode is executed
+ /// Called before each opcode is executed.
///
+ /// The current execution context, if any.
+ /// The next handler in the pre-execute chain.
void PreExecute(ExecutionContext? context, ExecuteDelegate next);
///
- /// Called after each opcode is executed
+ /// Called after each opcode is executed.
///
+ /// The current execution context, if any.
+ /// The next handler in the post-execute chain.
void PostExecute(ExecutionContext? context, ExecuteDelegate next);
}
}
diff --git a/src/Neo.VM/Types/VMArray.cs b/src/Neo.VM/Types/VMArray.cs
index 5c1186e..c53689b 100644
--- a/src/Neo.VM/Types/VMArray.cs
+++ b/src/Neo.VM/Types/VMArray.cs
@@ -30,24 +30,56 @@
namespace Neo.VM.Types
{
+ ///
+ /// Represents an ordered collection of items on the evaluation stack.
+ ///
+ /// The initial items in the array.
+ /// Whether the array is immutable after construction.
public class VMArray(IEnumerable items, bool isReadonly) : VMObject, IEquatable, IEnumerable, IEnumerable, ICollection, IReadOnlyCollection, IList, IReadOnlyList
{
+ ///
public override VMObjectType Type => VMObjectType.Array;
+ ///
+ /// Gets the number of items in the array.
+ ///
public int Count => Array.Count;
+ ///
+ /// Gets a value indicating whether the array is read-only.
+ ///
public bool IsReadOnly => _isReadOnly;
private bool _isReadOnly = isReadonly;
+ ///
+ /// The underlying list of items.
+ ///
protected readonly List Array = [.. items];
+ ///
+ /// Initializes an empty mutable array.
+ ///
public VMArray() : this(false) { }
+ ///
+ /// Initializes an empty array with the specified mutability.
+ ///
+ /// Whether the array is immutable.
public VMArray(bool isReadonly) : this([], isReadonly) { }
+ ///
+ /// Initializes a mutable array with the specified items.
+ ///
+ /// The initial items.
public VMArray(IEnumerable items) : this(items, false) { }
+ ///
+ /// Gets or sets the item at the specified index.
+ ///
+ /// The zero-based index of the item.
+ /// The item at the specified index.
+ /// Thrown when setting a value on a read-only array.
public VMObject this[int index]
{
get => Array[index];
@@ -58,6 +90,7 @@ public VMObject this[int index]
}
}
+ ///
protected override void Dispose(bool disposing)
{
if (disposing)
@@ -67,11 +100,13 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
+ ///
public override int GetHashCode()
{
return AsSpan().ToHashCode(RefCount ^ 397);
}
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -79,6 +114,11 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as VMArray);
}
+ ///
+ /// Determines whether this array is equal to another array.
+ ///
+ /// The other array.
+ /// if the arrays are equal; otherwise .
public bool Equals(VMArray? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -101,9 +141,11 @@ public bool Equals(VMArray? other)
#region IEnumerable
+ ///
public IEnumerator GetEnumerator() =>
Array.GetEnumerator();
+ ///
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
@@ -111,23 +153,42 @@ IEnumerator IEnumerable.GetEnumerator() =>
#region ICollection
+ ///
+ /// Adds an item to the end of the array.
+ ///
+ /// The item to add.
+ /// Thrown when the array is read-only.
public void Add(VMObject item)
{
if (_isReadOnly) throw new InvalidOperationException();
Array.Add(item);
}
+ ///
+ /// Removes all items from the array.
+ ///
+ /// Thrown when the array is read-only.
public void Clear()
{
if (_isReadOnly) throw new InvalidOperationException();
Array.Clear();
}
+ ///
+ /// Determines whether the array contains the specified item.
+ ///
+ /// The item to locate.
+ /// if the item is found; otherwise .
public bool Contains(VMObject item)
{
return Array.Contains(item);
}
+ ///
+ /// Copies the items of the array to a destination array starting at the specified index.
+ ///
+ /// The destination array.
+ /// The zero-based index in at which copying begins.
public void CopyTo(VMObject[] array, int arrayIndex)
{
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex, nameof(arrayIndex));
@@ -136,6 +197,12 @@ public void CopyTo(VMObject[] array, int arrayIndex)
Array.CopyTo(array, arrayIndex);
}
+ ///
+ /// Removes the first occurrence of the specified item from the array.
+ ///
+ /// The item to remove.
+ /// if the item was removed; otherwise .
+ /// Thrown when the array is read-only.
public bool Remove(VMObject item)
{
if (_isReadOnly) throw new InvalidOperationException();
@@ -146,17 +213,33 @@ public bool Remove(VMObject item)
#region IList
+ ///
+ /// Returns the zero-based index of the first occurrence of the specified item.
+ ///
+ /// The item to locate.
+ /// The index of the item, or -1 if not found.
public int IndexOf(VMObject item)
{
return Array.IndexOf(item);
}
+ ///
+ /// Inserts an item into the array at the specified index.
+ ///
+ /// The zero-based index at which to insert.
+ /// The item to insert.
+ /// Thrown when the array is read-only.
public void Insert(int index, VMObject item)
{
if (_isReadOnly) throw new InvalidOperationException();
Array.Insert(index, item);
}
+ ///
+ /// Removes the item at the specified index.
+ ///
+ /// The zero-based index of the item to remove.
+ /// Thrown when the array is read-only.
public void RemoveAt(int index)
{
if (_isReadOnly) throw new InvalidOperationException();
@@ -168,6 +251,10 @@ public void RemoveAt(int index)
internal override IEnumerable GetChildren() =>
[.. Array];
+ ///
+ /// Reverses the order of the items in the array.
+ ///
+ /// Thrown when the array is read-only.
public void Reverse()
{
if (_isReadOnly) throw new InvalidOperationException();
@@ -178,12 +265,14 @@ public void Reverse()
internal void SetAsReadOnly() =>
_isReadOnly = true;
+ ///
public override VMObject Clone()
{
var objectMap = new Dictionary(ReferenceEqualityComparer.Instance);
return Clone(objectMap);
}
+ ///
protected override VMObject CloneCore(Dictionary objectMap)
{
if (objectMap.TryGetValue(this, out var thisItem)) return thisItem;
@@ -204,17 +293,20 @@ protected override VMObject CloneCore(Dictionary objectMap)
return clone;
}
+ ///
public override bool GetBoolean()
{
return Array.Count > 0;
}
+ ///
[DoesNotReturn]
public override BigInteger GetInteger()
{
throw new InvalidOperationException();
}
+ ///
protected override ReadOnlySpan ComputeSpan(HashSet visited)
{
var result = new List();
diff --git a/src/Neo.VM/Types/VMBoolean.cs b/src/Neo.VM/Types/VMBoolean.cs
index d809cad..5e740ae 100644
--- a/src/Neo.VM/Types/VMBoolean.cs
+++ b/src/Neo.VM/Types/VMBoolean.cs
@@ -28,12 +28,22 @@
namespace Neo.VM.Types
{
+ ///
+ /// Represents a boolean stack item.
+ ///
+ /// The boolean value.
public class VMBoolean(bool value) : VMObject, IEquatable
{
+ ///
public override VMObjectType Type => VMObjectType.Boolean;
private readonly bool _value = value;
+ ///
+ /// Determines whether this boolean is equal to another boolean stack item.
+ ///
+ /// The other boolean.
+ /// if the values are equal; otherwise .
public bool Equals(VMBoolean? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -42,6 +52,7 @@ public bool Equals(VMBoolean? other)
return _value == other._value;
}
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -49,17 +60,20 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as VMBoolean);
}
+ ///
public override int GetHashCode()
{
return _value ? 1 : 0;
}
+ ///
[return: MaybeNull]
public override string? ToString()
{
return _value.ToString();
}
+ ///
public override VMObject Clone()
{
var clone = new VMBoolean(_value);
@@ -67,16 +81,19 @@ public override VMObject Clone()
return clone;
}
+ ///
public override bool GetBoolean()
{
return _value;
}
+ ///
public override BigInteger GetInteger()
{
return _value ? BigInteger.One : BigInteger.Zero;
}
+ ///
protected override ReadOnlySpan ComputeSpan(HashSet visited)
{
return _value ? [1] : [0];
diff --git a/src/Neo.VM/Types/VMBuffer.cs b/src/Neo.VM/Types/VMBuffer.cs
index 115c932..84cd6e0 100644
--- a/src/Neo.VM/Types/VMBuffer.cs
+++ b/src/Neo.VM/Types/VMBuffer.cs
@@ -32,15 +32,26 @@
namespace Neo.VM.Types
{
+ ///
+ /// Represents a mutable byte buffer stack item backed by pooled memory.
+ ///
public class VMBuffer : VMObject, IEquatable
{
+ ///
public override VMObjectType Type => VMObjectType.Buffer;
+ ///
+ /// Gets the length of the buffer in bytes.
+ ///
public int Length => _byteCount;
private readonly IMemoryOwner _memoryOwner;
private readonly int _byteCount;
+ ///
+ /// Initializes a zero-filled buffer of the specified size.
+ ///
+ /// The buffer size in bytes.
public VMBuffer(int size)
{
ArgumentOutOfRangeException.ThrowIfNegative(size);
@@ -48,6 +59,10 @@ public VMBuffer(int size)
_memoryOwner = MemoryPool.Shared.Rent(_byteCount);
}
+ ///
+ /// Initializes a buffer by copying the specified data.
+ ///
+ /// The source bytes.
public VMBuffer(ReadOnlySpan data)
{
_byteCount = data.Length;
@@ -55,6 +70,10 @@ public VMBuffer(ReadOnlySpan data)
data.TryCopyTo(_memoryOwner.Memory.Span);
}
+ ///
+ /// Initializes a buffer by copying the specified data.
+ ///
+ /// The source bytes.
public VMBuffer(Span data)
{
_byteCount = data.Length;
@@ -62,6 +81,10 @@ public VMBuffer(Span data)
data.TryCopyTo(_memoryOwner.Memory.Span);
}
+ ///
+ /// Initializes a buffer by copying the specified data.
+ ///
+ /// The source bytes.
public VMBuffer(byte[] data)
{
_byteCount = data.Length;
@@ -69,6 +92,10 @@ public VMBuffer(byte[] data)
data.AsMemory().TryCopyTo(_memoryOwner.Memory);
}
+ ///
+ /// Initializes a buffer by copying the contents of a .
+ ///
+ /// The source byte array stack item.
public VMBuffer(VMByteArray source)
{
_byteCount = source.Length;
@@ -76,6 +103,10 @@ public VMBuffer(VMByteArray source)
source.AsSpan().TryCopyTo(_memoryOwner.Memory.Span);
}
+ ///
+ /// Initializes a buffer by copying the specified memory.
+ ///
+ /// The source memory.
public VMBuffer(Memory source)
{
_byteCount = source.Length;
@@ -83,6 +114,11 @@ public VMBuffer(Memory source)
source.TryCopyTo(_memoryOwner.Memory);
}
+ ///
+ /// Determines whether this buffer is equal to another buffer.
+ ///
+ /// The other buffer.
+ /// if the contents are equal; otherwise .
public bool Equals([NotNullWhen(true)] VMBuffer? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -94,6 +130,7 @@ public bool Equals([NotNullWhen(true)] VMBuffer? other)
.SequenceEqual(other._memoryOwner.Memory[..other._byteCount].Span);
}
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -101,18 +138,21 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as VMBuffer);
}
+ ///
protected override void Dispose(bool disposing)
{
_memoryOwner.Dispose();
base.Dispose(disposing);
}
+ ///
public override int GetHashCode()
{
return _memoryOwner.Memory[.._byteCount]
.ToHashCode(RefCount ^ 397);
}
+ ///
public override string ToString()
{
foreach (var v in _memoryOwner.Memory[.._byteCount].Span)
@@ -124,17 +164,28 @@ public override string ToString()
return CoreUtilities.StrictUtf8Encoding.GetString(_memoryOwner.Memory[.._byteCount].Span);
}
+ ///
+ /// Copies a range of bytes from this buffer into another buffer.
+ ///
+ /// The destination buffer.
+ /// The starting index in this buffer.
+ /// The starting index in the destination buffer.
+ /// The number of bytes to copy.
public void CopyTo(VMBuffer dstBuffer, int startIndex, int dstIndex, int count)
{
_memoryOwner.Memory.Slice(startIndex, count)
.CopyTo(dstBuffer._memoryOwner.Memory[dstIndex..]);
}
+ ///
+ /// Reverses the order of bytes in this buffer.
+ ///
public void Reverse()
{
_memoryOwner.Memory[.._byteCount].Span.Reverse();
}
+ ///
public override VMObject Clone()
{
var clone = new VMBuffer(_memoryOwner.Memory[.._byteCount].ToArray());
@@ -142,11 +193,13 @@ public override VMObject Clone()
return clone;
}
+ ///
public override bool GetBoolean()
{
return !_memoryOwner.Memory[.._byteCount].IsEmpty;
}
+ ///
public override BigInteger GetInteger()
{
var span = _memoryOwner.Memory[.._byteCount].Span;
@@ -158,17 +211,27 @@ public override BigInteger GetInteger()
return new(span);
}
+ ///
protected override ReadOnlySpan ComputeSpan(HashSet visited)
{
return _memoryOwner.Memory[.._byteCount].Span;
}
+ ///
+ /// Gets or sets the byte at the specified index.
+ ///
+ /// The zero-based byte index.
+ /// The byte at the specified index.
public byte this[int index]
{
get => _memoryOwner.Memory[.._byteCount].Span[index];
set => _memoryOwner.Memory[.._byteCount].Span[index] = value;
}
+ ///
+ /// Returns the buffer contents as a lowercase hexadecimal string.
+ ///
+ /// The hex representation of the buffer.
public string ToHexString()
{
return Convert.ToHexStringLower(_memoryOwner.Memory[.._byteCount].Span);
@@ -195,9 +258,15 @@ public string ToHexString()
return a.Equals(b);
}
+ ///
+ /// Inequality comparison.
+ ///
public static bool operator !=(VMBuffer a, VMBuffer b) =>
!(a == b);
+ ///
+ /// Converts a buffer to a using up to bytes.
+ ///
public static implicit operator BigInteger(VMBuffer value)
{
return new BigInteger(value._memoryOwner.Memory[..value._byteCount]
diff --git a/src/Neo.VM/Types/VMByteArray.cs b/src/Neo.VM/Types/VMByteArray.cs
index 8e52255..9571d7f 100644
--- a/src/Neo.VM/Types/VMByteArray.cs
+++ b/src/Neo.VM/Types/VMByteArray.cs
@@ -32,15 +32,26 @@
namespace Neo.VM.Types
{
+ ///
+ /// Represents an immutable byte-string stack item (Neo ByteString ).
+ ///
public class VMByteArray : VMObject, IEquatable
{
+ ///
public override VMObjectType Type => VMObjectType.ByteString;
+ ///
+ /// Gets the length of the byte string in bytes.
+ ///
public int Length => _byteCount;
private readonly IMemoryOwner _memoryOwner;
private readonly int _byteCount;
+ ///
+ /// Initializes a new byte string by copying the specified data.
+ ///
+ /// The source bytes.
public VMByteArray(byte[] data)
{
_byteCount = data.Length;
@@ -48,6 +59,10 @@ public VMByteArray(byte[] data)
data.AsMemory().TryCopyTo(_memoryOwner.Memory);
}
+ ///
+ /// Initializes a new byte string by copying the specified memory.
+ ///
+ /// The source memory.
public VMByteArray(Memory data)
{
_byteCount = data.Length;
@@ -55,6 +70,10 @@ public VMByteArray(Memory data)
data.TryCopyTo(_memoryOwner.Memory);
}
+ ///
+ /// Initializes a new byte string by copying the specified span.
+ ///
+ /// The source bytes.
public VMByteArray(ReadOnlySpan data)
{
_byteCount = data.Length;
@@ -62,6 +81,10 @@ public VMByteArray(ReadOnlySpan data)
data.TryCopyTo(_memoryOwner.Memory.Span);
}
+ ///
+ /// Initializes a new byte string by copying the specified span.
+ ///
+ /// The source bytes.
public VMByteArray(Span data)
{
_byteCount = data.Length;
@@ -69,6 +92,7 @@ public VMByteArray(Span data)
data.TryCopyTo(_memoryOwner.Memory.Span);
}
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -76,18 +100,21 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as VMByteArray);
}
+ ///
public override int GetHashCode()
{
return _memoryOwner.Memory[.._byteCount]
.ToHashCode(RefCount ^ 397);
}
+ ///
protected override void Dispose(bool disposing)
{
_memoryOwner.Dispose();
base.Dispose(disposing);
}
+ ///
public override string ToString()
{
foreach (var v in _memoryOwner.Memory[.._byteCount].Span)
@@ -99,6 +126,7 @@ public override string ToString()
return CoreUtilities.StrictUtf8Encoding.GetString(_memoryOwner.Memory[.._byteCount].Span);
}
+ ///
public override VMObject Clone()
{
var clone = new VMByteArray(_memoryOwner.Memory[.._byteCount].ToArray());
@@ -106,11 +134,13 @@ public override VMObject Clone()
return clone;
}
+ ///
public override bool GetBoolean()
{
return !_memoryOwner.Memory[.._byteCount].IsEmpty;
}
+ ///
public override BigInteger GetInteger()
{
var span = _memoryOwner.Memory[.._byteCount].Span;
@@ -121,6 +151,7 @@ public override BigInteger GetInteger()
return new(span);
}
+ ///
protected override ReadOnlySpan ComputeSpan(HashSet visited)
{
return _memoryOwner.Memory[.._byteCount].Span;
@@ -135,11 +166,20 @@ public byte this[int index]
set => _memoryOwner.Memory[.._byteCount].Span[index] = value;
}
+ ///
+ /// Returns the byte string as a lowercase hexadecimal string.
+ ///
+ /// The hex representation of the data.
public string ToHexString()
{
return Convert.ToHexStringLower(_memoryOwner.Memory[.._byteCount].Span);
}
+ ///
+ /// Determines whether this byte string is equal to another.
+ ///
+ /// The other byte string.
+ /// if the contents are equal; otherwise .
public bool Equals([NotNullWhen(true)] VMByteArray? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -172,6 +212,9 @@ .. b._memoryOwner.Memory[..b._byteCount].Span
return a.Equals(b);
}
+ ///
+ /// Inequality comparison.
+ ///
public static bool operator !=(VMByteArray a, VMByteArray b) =>
!(a == b);
}
diff --git a/src/Neo.VM/Types/VMInteger.cs b/src/Neo.VM/Types/VMInteger.cs
index abcffa9..a930d43 100644
--- a/src/Neo.VM/Types/VMInteger.cs
+++ b/src/Neo.VM/Types/VMInteger.cs
@@ -28,14 +28,26 @@
namespace Neo.VM.Types
{
+ ///
+ /// Represents an arbitrary-precision integer stack item, limited to bytes.
+ ///
public class VMInteger : VMObject, IEquatable
{
+ ///
+ /// The maximum allowed size of the integer in bytes.
+ ///
public const int MaxSize = 32;
+ ///
public override VMObjectType Type => VMObjectType.Integer;
private readonly BigInteger _value = BigInteger.Zero;
+ ///
+ /// Initializes a new integer stack item.
+ ///
+ /// The integer value.
+ /// Thrown when exceeds bytes.
public VMInteger(BigInteger value)
{
if (value.GetByteCount() > MaxSize)
@@ -43,11 +55,13 @@ public VMInteger(BigInteger value)
_value = value;
}
+ ///
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -55,11 +69,13 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as VMInteger);
}
+ ///
public override int GetHashCode()
{
return _value.GetHashCode();
}
+ ///
public override VMObject Clone()
{
var clone = new VMInteger(_value);
@@ -67,21 +83,29 @@ public override VMObject Clone()
return clone;
}
+ ///
public override bool GetBoolean()
{
return _value != BigInteger.Zero;
}
+ ///
public override BigInteger GetInteger()
{
return _value;
}
+ ///
protected override ReadOnlySpan ComputeSpan(HashSet visited)
{
return _value.ToByteArray();
}
+ ///
+ /// Determines whether this integer is equal to another integer stack item.
+ ///
+ /// The other integer.
+ /// if the values are equal; otherwise .
public bool Equals([NotNullWhen(true)] VMInteger? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -90,6 +114,9 @@ public bool Equals([NotNullWhen(true)] VMInteger? other)
return _value == other._value;
}
+ ///
+ /// Adds two integer stack items.
+ ///
public static VMInteger operator +(VMInteger a, VMInteger b)
{
var result = new VMInteger(a._value + b._value);
@@ -97,6 +124,9 @@ public bool Equals([NotNullWhen(true)] VMInteger? other)
return result;
}
+ ///
+ /// Subtracts two integer stack items.
+ ///
public static VMInteger operator -(VMInteger a, VMInteger b)
{
var result = new VMInteger(a._value - b._value);
@@ -104,6 +134,9 @@ public bool Equals([NotNullWhen(true)] VMInteger? other)
return result;
}
+ ///
+ /// Multiplies two integer stack items.
+ ///
public static VMInteger operator *(VMInteger a, VMInteger b)
{
var result = new VMInteger(a._value * b._value);
@@ -111,6 +144,10 @@ public bool Equals([NotNullWhen(true)] VMInteger? other)
return result;
}
+ ///
+ /// Divides two integer stack items.
+ ///
+ /// Thrown when the divisor is zero.
public static VMInteger operator /(VMInteger a, VMInteger b)
{
if (b._value == BigInteger.Zero)
@@ -121,6 +158,9 @@ public bool Equals([NotNullWhen(true)] VMInteger? other)
return result;
}
+ ///
+ /// Computes the remainder of dividing two integer stack items.
+ ///
public static VMInteger operator %(VMInteger a, VMInteger b)
{
var result = new VMInteger(a._value % b._value);
@@ -128,31 +168,55 @@ public bool Equals([NotNullWhen(true)] VMInteger? other)
return result;
}
+ ///
+ /// Negates an integer stack item.
+ ///
public static VMInteger operator -(VMInteger a) =>
new(-a._value);
// Comparison operators
+ ///
+ /// Determines whether two integer stack items are equal.
+ ///
public static bool operator ==(VMInteger a, VMInteger b)
{
if (ReferenceEquals(a, b)) return true;
return a.Equals(b);
}
+ ///
+ /// Determines whether two integer stack items are not equal.
+ ///
public static bool operator !=(VMInteger a, VMInteger b) =>
!(a == b);
+ ///
+ /// Determines whether the first integer is greater than the second.
+ ///
public static bool operator >(VMInteger a, VMInteger b) =>
a._value > b._value;
+ ///
+ /// Determines whether the first integer is less than the second.
+ ///
public static bool operator <(VMInteger a, VMInteger b) =>
a._value < b._value;
+ ///
+ /// Determines whether the first integer is greater than or equal to the second.
+ ///
public static bool operator >=(VMInteger a, VMInteger b) =>
a._value >= b._value;
+ ///
+ /// Determines whether the first integer is less than or equal to the second.
+ ///
public static bool operator <=(VMInteger a, VMInteger b) =>
a._value <= b._value;
+ ///
+ /// Converts a to a .
+ ///
public static implicit operator BigInteger(VMInteger value)
{
return value.GetInteger();
diff --git a/src/Neo.VM/Types/VMInteropInterface.cs b/src/Neo.VM/Types/VMInteropInterface.cs
index 25d00fd..8b0ecf0 100644
--- a/src/Neo.VM/Types/VMInteropInterface.cs
+++ b/src/Neo.VM/Types/VMInteropInterface.cs
@@ -30,20 +30,46 @@
namespace Neo.VM.Types
{
+ ///
+ /// Represents an interop interface stack item that wraps a CLR object.
+ ///
+ /// The underlying object.
+ /// A display name for the interface type.
public class VMInteropInterface(object obj, string name) : VMObject, IEquatable
{
+ ///
public override VMObjectType Type => VMObjectType.Interop;
private readonly object _underlyingObject = obj;
private readonly string _interfaceName = name;
+ ///
+ /// Gets the underlying CLR object.
+ ///
public object UnderlyingObject => _underlyingObject;
+
+ ///
+ /// Gets the interface name associated with this wrapper.
+ ///
public string InterfaceName => _interfaceName;
+ ///
+ /// Initializes a new interop interface wrapping a default .
+ ///
public VMInteropInterface() : this(new()) { }
+ ///
+ /// Initializes a new interop interface wrapping the specified object.
+ /// The interface name defaults to the object's type name.
+ ///
+ /// The object to wrap.
public VMInteropInterface(object obj) : this(obj, obj.GetType().Name) { }
+ ///
+ /// Determines whether this interop item is equal to another.
+ ///
+ /// The other interop item.
+ /// if the underlying objects and names match; otherwise .
public bool Equals(VMInteropInterface? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -53,6 +79,7 @@ public bool Equals(VMInteropInterface? other)
string.Equals(_interfaceName, other._interfaceName);
}
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -60,17 +87,20 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as VMInteropInterface);
}
+ ///
public override int GetHashCode()
{
return HashCode.Combine(RefCount, _underlyingObject, _interfaceName);
}
+ ///
protected override void Dispose(bool disposing)
{
// Do not dispose the underlying object - it's managed externally
base.Dispose(disposing);
}
+ ///
public override VMObject Clone()
{
var clone = new VMInteropInterface(_underlyingObject, _interfaceName);
@@ -78,6 +108,7 @@ public override VMObject Clone()
return clone;
}
+ ///
protected override ReadOnlySpan ComputeSpan(HashSet visited)
{
var ptr = nint.Zero;
@@ -107,6 +138,7 @@ protected override ReadOnlySpan ComputeSpan(HashSet visited)
}
}
+ ///
[DoesNotReturn]
public override BigInteger GetInteger()
{
@@ -114,11 +146,18 @@ public override BigInteger GetInteger()
throw new InvalidOperationException($"Cannot convert InteropInterface ({_interfaceName}) to integer");
}
+ ///
public override bool GetBoolean()
{
return _underlyingObject != null;
}
+ ///
+ /// Casts the underlying object to the specified type.
+ ///
+ /// The target type.
+ /// The underlying object as .
+ /// Thrown when the underlying object is not assignable to .
public T CastTo()
{
if (_underlyingObject is T t)
@@ -127,6 +166,13 @@ public T CastTo()
throw new InvalidCastException($"This {_interfaceName} can't be casted to type {typeof(T)}.");
}
+ ///
+ /// Invokes a public method on the underlying object by name.
+ ///
+ /// The expected return type (must be a reference type).
+ /// The name of the method to invoke.
+ /// The method arguments.
+ /// The method result cast to , or if the method is missing or returns a different type.
public TResult? CallMethod(string methodName, params object[] args)
where TResult : class?
{
diff --git a/src/Neo.VM/Types/VMMap.cs b/src/Neo.VM/Types/VMMap.cs
index cf7d2ef..d556c93 100644
--- a/src/Neo.VM/Types/VMMap.cs
+++ b/src/Neo.VM/Types/VMMap.cs
@@ -31,27 +31,54 @@
namespace Neo.VM.Types
{
+ ///
+ /// Represents a key/value map stack item. Keys are limited to bytes.
+ ///
public class VMMap : VMObject, IEquatable, IDictionary, IReadOnlyDictionary
{
+ ///
+ /// The maximum allowed size of a map key in bytes.
+ ///
public const int MaxKeySize = 64;
+ ///
public override VMObjectType Type => VMObjectType.Map;
+ ///
+ /// Gets the number of key/value pairs in the map.
+ ///
public int Count => _map.Count;
+ ///
+ /// Gets a collection containing the keys of the map.
+ ///
public ICollection Keys => _map.Keys;
+ ///
+ /// Gets a collection containing the values of the map.
+ ///
public ICollection Values => _map.Values;
+ ///
+ /// Gets a value indicating whether the map is read-only.
+ ///
public bool IsReadOnly => _isReadOnly;
+ ///
IEnumerable IReadOnlyDictionary.Keys => Keys;
+ ///
IEnumerable IReadOnlyDictionary.Values => Values;
private readonly Dictionary _map = [];
private bool _isReadOnly = false;
+ ///
+ /// Gets or sets the value associated with the specified key.
+ ///
+ /// The key of the value to get or set.
+ /// The value associated with the specified key.
+ /// Thrown when the key exceeds .
public VMObject this[VMObject key]
{
get
@@ -63,13 +90,25 @@ public VMObject this[VMObject key]
set => Add(key, value);
}
+ ///
+ /// Initializes an empty mutable map.
+ ///
public VMMap() { }
+ ///
+ /// Initializes an empty map with the specified mutability.
+ ///
+ /// Whether the map is immutable.
public VMMap(bool isReadOnly)
{
_isReadOnly = isReadOnly;
}
+ ///
+ /// Initializes a map from the specified key/value pairs.
+ ///
+ /// The initial entries.
+ /// Whether the map is immutable after construction.
public VMMap(IEnumerable> items, bool isReadOnly = false)
{
foreach (var kvp in items)
@@ -78,6 +117,11 @@ public VMMap(IEnumerable> items, bool isReadOnl
_isReadOnly = isReadOnly;
}
+ ///
+ /// Initializes a map by pairing consecutive items as key/value pairs.
+ ///
+ /// A flat list of alternating keys and values.
+ /// Whether the map is immutable after construction.
public VMMap(IList items, bool isReadOnly = false)
{
for (var i = 0; i < items.Count - 2; i += 2)
@@ -86,6 +130,7 @@ public VMMap(IList items, bool isReadOnly = false)
_isReadOnly = isReadOnly;
}
+ ///
protected override void Dispose(bool disposing)
{
if (disposing)
@@ -95,6 +140,11 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
+ ///
+ /// Determines whether this map is equal to another map.
+ ///
+ /// The other map.
+ /// if the maps are equal; otherwise .
public bool Equals(VMMap? other)
{
if (ReferenceEquals(other, this)) return true;
@@ -118,6 +168,7 @@ public bool Equals(VMMap? other)
return false;
}
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -125,11 +176,19 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as VMMap);
}
+ ///
public override int GetHashCode()
{
return AsSpan().ToHashCode(RefCount * 397);
}
+ ///
+ /// Adds or replaces the value for the specified key.
+ ///
+ /// The key to add.
+ /// The value to associate with the key.
+ /// Thrown when the key exceeds .
+ /// Thrown when the map is read-only.
public void Add(VMObject key, VMObject value)
{
if (key.Size > MaxKeySize)
@@ -141,6 +200,12 @@ public void Add(VMObject key, VMObject value)
_map[key] = value;
}
+ ///
+ /// Determines whether the map contains the specified key.
+ ///
+ /// The key to locate.
+ /// if the key is present; otherwise .
+ /// Thrown when the key exceeds .
public bool ContainsKey(VMObject key)
{
if (key.Size > MaxKeySize)
@@ -149,6 +214,13 @@ public bool ContainsKey(VMObject key)
return key is not VMNull && _map.ContainsKey(key);
}
+ ///
+ /// Removes the entry with the specified key.
+ ///
+ /// The key of the entry to remove.
+ /// if the entry was removed; otherwise .
+ /// Thrown when the key exceeds .
+ /// Thrown when the map is read-only.
public bool Remove(VMObject key)
{
if (key.Size > MaxKeySize)
@@ -164,6 +236,13 @@ public bool Remove(VMObject key)
return _map.Remove(key);
}
+ ///
+ /// Attempts to get the value associated with the specified key.
+ ///
+ /// The key to locate.
+ /// When this method returns, the value associated with the key, if found.
+ /// if the key was found; otherwise .
+ /// Thrown when the key exceeds .
public bool TryGetValue(VMObject key, [MaybeNullWhen(false)] out VMObject value)
{
if (key.Size > MaxKeySize)
@@ -172,11 +251,19 @@ public bool TryGetValue(VMObject key, [MaybeNullWhen(false)] out VMObject value)
return _map.TryGetValue(key, out value);
}
+ ///
+ /// Adds the specified key/value pair to the map.
+ ///
+ /// The pair to add.
public void Add(KeyValuePair item)
{
Add(item.Key, item.Value);
}
+ ///
+ /// Removes all entries from the map.
+ ///
+ /// Thrown when the map is read-only.
public void Clear()
{
if (_isReadOnly)
@@ -184,6 +271,12 @@ public void Clear()
_map.Clear();
}
+ ///
+ /// Determines whether the map contains the specified key/value pair.
+ ///
+ /// The pair to locate.
+ /// if the pair is present; otherwise .
+ /// Thrown when the key exceeds .
public bool Contains(KeyValuePair item)
{
if (item.Key.Size > MaxKeySize)
@@ -192,6 +285,11 @@ public bool Contains(KeyValuePair item)
return ContainsKey(item.Key) && _map.Values.Any(a => a == item.Value);
}
+ ///
+ /// Copies the map entries to an array starting at the specified index.
+ ///
+ /// The destination array.
+ /// The zero-based index in at which copying begins.
public void CopyTo(KeyValuePair[] array, int arrayIndex)
{
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex, nameof(arrayIndex));
@@ -200,6 +298,12 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex)
_map.ToArray().CopyTo(array, arrayIndex);
}
+ ///
+ /// Removes the entry with the specified key from the map.
+ ///
+ /// The pair whose key identifies the entry to remove.
+ /// if the entry was removed; otherwise .
+ /// Thrown when the key exceeds .
public bool Remove(KeyValuePair item)
{
if (item.Key.Size > MaxKeySize)
@@ -217,21 +321,25 @@ internal override IEnumerable GetChildren()
}
}
+ ///
public IEnumerator> GetEnumerator() =>
_map.GetEnumerator();
+ ///
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
internal void SetAsReadOnly() =>
_isReadOnly = true;
+ ///
public override VMObject Clone()
{
var objectMap = new Dictionary(ReferenceEqualityComparer.Instance);
return Clone(objectMap);
}
+ ///
protected override VMObject CloneCore(Dictionary objectMap)
{
if (objectMap.TryGetValue(this, out var thisItem)) return thisItem;
@@ -249,6 +357,7 @@ protected override VMObject CloneCore(Dictionary objectMap)
return clone;
}
+ ///
protected override ReadOnlySpan ComputeSpan(HashSet visited)
{
var result = new List();
@@ -262,9 +371,11 @@ protected override ReadOnlySpan ComputeSpan(HashSet visited)
return result.ToArray();
}
+ ///
public override bool GetBoolean() =>
_map.Count > 0;
+ ///
[DoesNotReturn]
public override BigInteger GetInteger()
{
diff --git a/src/Neo.VM/Types/VMNull.cs b/src/Neo.VM/Types/VMNull.cs
index 4973a5a..13bdc15 100644
--- a/src/Neo.VM/Types/VMNull.cs
+++ b/src/Neo.VM/Types/VMNull.cs
@@ -27,18 +27,27 @@
namespace Neo.VM.Types
{
+ ///
+ /// Represents a null stack item. All null values compare equal.
+ ///
public class VMNull : VMObject, IEquatable
{
+ ///
public override VMObjectType Type => VMObjectType.Any;
+ ///
+ /// Gets a new null stack item instance.
+ ///
public static VMNull Instance => new();
+ ///
protected override void Dispose(bool disposing)
{
// Never dispose the singleton instance
base.Dispose(disposing);
}
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -46,39 +55,55 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as VMNull);
}
+ ///
+ /// Determines whether this null item is equal to another null item.
+ ///
+ /// The other null item.
+ /// Always for any comparison.
public bool Equals([NotNullWhen(true)] VMNull? other)
{
return true;
}
+ ///
public override int GetHashCode()
{
return 0;
}
+ ///
public override VMObject Clone()
{
return this;
}
+ ///
public override bool GetBoolean()
{
return false;
}
+ ///
public override BigInteger GetInteger()
{
return BigInteger.Zero;
}
+ ///
public override string ToString()
{
return "\0";
}
+ ///
+ /// Always returns ; all null stack items are equal.
+ ///
public static bool operator ==(VMNull left, VMNull right) =>
true;
+ ///
+ /// Always returns ; all null stack items are equal.
+ ///
public static bool operator !=(VMNull left, VMNull right) =>
false;
diff --git a/src/Neo.VM/Types/VMObject.cs b/src/Neo.VM/Types/VMObject.cs
index c569ff2..550b284 100644
--- a/src/Neo.VM/Types/VMObject.cs
+++ b/src/Neo.VM/Types/VMObject.cs
@@ -32,12 +32,25 @@
namespace Neo.VM.Types
{
+ ///
+ /// Base type for all NeoVM stack items.
+ /// Provides equality, cloning, byte serialization, reference counting, and conversion helpers.
+ ///
public abstract class VMObject : IVMComponent, IEquatable
{
+ ///
+ /// Gets the NeoVM stack-item type of this object.
+ ///
public virtual VMObjectType Type => VMObjectType.Any;
+ ///
+ /// Gets the byte length of this object's serialized representation.
+ ///
public int Size => AsSpan().Length;
+ ///
+ /// Gets the current reference count for this stack item.
+ ///
public int RefCount => _refCount;
private bool _disposed = false;
@@ -45,6 +58,7 @@ public abstract class VMObject : IVMComponent, IEquatable
#region IEquatable
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
if (ReferenceEquals(obj, this)) return true;
@@ -52,6 +66,11 @@ public override bool Equals([NotNullWhen(true)] object? obj)
return Equals(obj as VMObject);
}
+ ///
+ /// Determines whether this object is equal to another stack item.
+ ///
+ /// The other stack item.
+ /// if the items are equal; otherwise .
public bool Equals([NotNullWhen(true)] VMObject? other)
{
if (other is null) return false;
@@ -61,6 +80,7 @@ public bool Equals([NotNullWhen(true)] VMObject? other)
return AsSpan().SequenceEqual(other.AsSpan());
}
+ ///
public override int GetHashCode()
{
return AsSpan().ToHashCode(RefCount ^ 397);
@@ -70,6 +90,10 @@ public override int GetHashCode()
#region IDisposable
+ ///
+ /// Releases managed and unmanaged resources used by this instance.
+ ///
+ /// when called from ; otherwise .
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
@@ -82,12 +106,18 @@ protected virtual void Dispose(bool disposing)
}
}
+ ///
+ /// Releases resources used by this stack item.
+ ///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
+ ///
+ /// Finalizer that releases unmanaged resources if was not called.
+ ///
~VMObject()
{
Dispose(false);
@@ -116,6 +146,10 @@ internal void Release()
#endregion
+ ///
+ /// Determines whether this object graph contains a circular reference.
+ ///
+ /// if a cycle is detected; otherwise .
public bool HasCircularReference()
{
var visited = new HashSet(ReferenceEqualityComparer.Instance);
@@ -126,8 +160,9 @@ internal virtual IEnumerable GetChildren() =>
[];
///
- /// Returns a deep clone of this object
+ /// Returns a deep clone of this object.
///
+ /// A new stack item with the same value.
public abstract VMObject Clone();
///
@@ -150,8 +185,9 @@ protected virtual VMObject CloneCore(Dictionary