Skip to content

Commit

Permalink
feat: Add GenesisBlockPath option to GenesisOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
s2quake committed Aug 29, 2024
1 parent 4e09152 commit 503fe37
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 40 deletions.
4 changes: 4 additions & 0 deletions sdk/node/Libplanet.Node.Executable/appsettings-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,10 @@
"type": "string",
"description": "The timestamp of the genesis block.",
"format": "date-time"
},
"GenesisBlockPath": {
"type": "string",
"description": "The path of the genesis block."
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
}
},
"Swarm": {
"IsEnabled": true,
"AppProtocolVersion": "200210/AB2da648b9154F2cCcAFBD85e0Bc3d51f97330Fc/MEUCIQCBr..8VdITFe9nMTobl4akFid.s8G2zy2pBidAyRXSeAIgER77qX+eywjgyth6QYi7rQw5nK3KXO6cQ6ngUh.CyfU=/ZHU5OnRpbWVzdGFtcHUxMDoyMDI0LTA3LTMwZQ=="
"IsEnabled": true
},
"Validator": {
"IsEnabled": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public static ILibplanetNodeBuilder AddLibplanetNode(
services.AddOptions<GenesisOptions>()
.Bind(configuration.GetSection(GenesisOptions.Position));
services.AddSingleton<IConfigureOptions<GenesisOptions>, GenesisOptionsConfigurator>();
services.AddSingleton<IValidateOptions<GenesisOptions>, GenesisOptionsValidator>();

services.AddOptions<StoreOptions>()
.Bind(configuration.GetSection(StoreOptions.Position));
Expand Down
13 changes: 11 additions & 2 deletions sdk/node/Libplanet.Node/Options/GenesisOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,22 @@ public sealed class GenesisOptions : OptionsBase<GenesisOptions>
public const string Position = "Genesis";

[PrivateKey]
[Description("The key of the genesis block.")]
[Description(
$"The PrivateKey used to generate the genesis block. " +
$"This property cannot be used with {nameof(GenesisBlockPath)}.")]
public string GenesisKey { get; set; } = string.Empty;

[PublicKeyArray]
[Description("Public keys of the validators.")]
[Description(
$"Public keys of the validators. This property cannot be used with " +
$"{nameof(GenesisBlockPath)}.")]
public string[] Validators { get; set; } = [];

[Description("The timestamp of the genesis block.")]
public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.MinValue;

[Description(
$"The path of the genesis block, which can be a file path or a URI." +
$"This property cannot be used with {nameof(GenesisKey)}.")]
public string GenesisBlockPath { get; set; } = string.Empty;
}
29 changes: 16 additions & 13 deletions sdk/node/Libplanet.Node/Options/GenesisOptionsConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,24 @@ internal sealed class GenesisOptionsConfigurator(
{
protected override void OnConfigure(GenesisOptions options)
{
if (options.GenesisKey == string.Empty)
if (options.GenesisBlockPath == string.Empty)
{
var privateKey = new PrivateKey();
options.GenesisKey = ByteUtil.Hex(privateKey.ByteArray);
logger.LogWarning(
"Genesis key is not set. A new private key is generated:{PrivateKey}",
options.GenesisKey);
}
if (options.GenesisKey == string.Empty)
{
var privateKey = new PrivateKey();
options.GenesisKey = ByteUtil.Hex(privateKey.ByteArray);
logger.LogWarning(
"Genesis key is not set. A new private key is generated:{PrivateKey}",
options.GenesisKey);
}

if (options.Validators.Length == 0)
{
var privateKey = PrivateKey.FromString(nodeOptions.Value.PrivateKey);
options.Validators = [privateKey.PublicKey.ToHex(compress: false)];
logger.LogWarning(
"Validators are not set. Use the node's private key as a validator.");
if (options.Validators.Length == 0)
{
var privateKey = PrivateKey.FromString(nodeOptions.Value.PrivateKey);
options.Validators = [privateKey.PublicKey.ToHex(compress: false)];
logger.LogWarning(
"Validators are not set. Use the node's private key as a validator.");
}
}
}
}
43 changes: 43 additions & 0 deletions sdk/node/Libplanet.Node/Options/GenesisOptionsValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.Extensions.Options;

namespace Libplanet.Node.Options;

internal sealed class GenesisOptionsValidator : OptionsValidatorBase<GenesisOptions>
{
protected override void OnValidate(string? name, GenesisOptions options)
{
if (options.GenesisBlockPath != string.Empty)
{
if (options.GenesisKey != string.Empty)
{
var message = $"{nameof(options.GenesisKey)} cannot be used with " +
$"{nameof(options.GenesisBlockPath)}.";
throw new OptionsValidationException(
optionsName: name ?? string.Empty,
optionsType: typeof(GenesisOptions),
failureMessages: [message]);
}

if (options.Validators.Length > 0)
{
var message = $"{nameof(options.Validators)} cannot be used with " +
$"{nameof(options.GenesisBlockPath)}.";
throw new OptionsValidationException(
optionsName: name ?? string.Empty,
optionsType: typeof(GenesisOptions),
failureMessages: [message]);
}

if (!Uri.TryCreate(options.GenesisBlockPath, UriKind.Absolute, out _)
&& !File.Exists(options.GenesisBlockPath))
{
var message = $"{nameof(options.GenesisBlockPath)} must be a Uri or a existing " +
$"file path.";
throw new OptionsValidationException(
optionsName: name ?? string.Empty,
optionsType: typeof(GenesisOptions),
failureMessages: [message]);
}
}
}
}
92 changes: 69 additions & 23 deletions sdk/node/Libplanet.Node/Services/BlockChainService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Numerics;
using System.Security.Cryptography;
using Bencodex;
using Bencodex.Types;
using Libplanet.Action;
using Libplanet.Action.Loader;
Expand All @@ -25,6 +27,7 @@ namespace Libplanet.Node.Services;

internal sealed class BlockChainService : IBlockChainService, IActionRenderer
{
private static readonly Codec _codec = new();
private readonly SynchronizationContext _synchronizationContext;
private readonly ILogger<BlockChainService> _logger;
private readonly BlockChain _blockChain;
Expand Down Expand Up @@ -93,36 +96,14 @@ private static BlockChain CreateBlockChain(
IRenderer[] renderers,
IActionLoader[] actionLoaders)
{
var genesisKey = PrivateKey.FromString(genesisOptions.GenesisKey);
var (store, stateStore) = CreateStore(storeOptions);
var actionLoader = new AggregateTypedActionLoader(actionLoaders);
var actionEvaluator = new ActionEvaluator(
policyActionsRegistry: new(),
stateStore,
actionLoader);
var validators = genesisOptions.Validators.Select(PublicKey.FromHex)
.Select(item => new Validator(item, new BigInteger(1000)))
.ToArray();
var validatorSet = new ValidatorSet(validators: [.. validators]);
var nonce = 0L;
IAction[] actions =
[
new Initialize(
validatorSet: validatorSet,
states: ImmutableDictionary.Create<Address, IValue>()),
];

var transaction = Transaction.Create(
nonce: nonce,
privateKey: genesisKey,
genesisHash: null,
actions: [.. actions.Select(item => item.PlainValue)],
timestamp: DateTimeOffset.MinValue);
var transactions = ImmutableList.Create(transaction);
var genesisBlock = BlockChain.ProposeGenesisBlock(
privateKey: genesisKey,
transactions: transactions,
timestamp: DateTimeOffset.MinValue);
var genesisBlock = CreateGenesisBlock(genesisOptions);
var policy = new BlockPolicy(
blockInterval: TimeSpan.FromSeconds(10),
getMaxTransactionsPerBlock: _ => int.MaxValue,
Expand Down Expand Up @@ -176,4 +157,69 @@ private static (IStore, IStateStore) CreateStore(StoreOptions storeOptions)
return (store, stateStore);
}
}

private static Block CreateGenesisBlock(GenesisOptions genesisOptions)
{
if (genesisOptions.GenesisKey != string.Empty)
{
var genesisKey = PrivateKey.FromString(genesisOptions.GenesisKey);
var validatorKeys = genesisOptions.Validators.Select(PublicKey.FromHex).ToArray();
return CreateGenesisBlock(genesisKey, validatorKeys);
}

if (genesisOptions.GenesisBlockPath != string.Empty)
{
return genesisOptions.GenesisBlockPath switch
{
{ } path when Uri.TryCreate(path, UriKind.Absolute, out var uri)
=> LoadGenesisBlockFromUrl(uri),
{ } path => LoadGenesisBlock(path),
_ => throw new NotSupportedException(),
};
}

throw new UnreachableException("Genesis block path is not set.");
}

private static Block CreateGenesisBlock(PrivateKey genesisKey, PublicKey[] validatorKeys)
{
var validators = validatorKeys
.Select(item => new Validator(item, new BigInteger(1000)))
.ToArray();
var validatorSet = new ValidatorSet(validators: [.. validators]);
var nonce = 0L;
IAction[] actions =
[
new Initialize(
validatorSet: validatorSet,
states: ImmutableDictionary.Create<Address, IValue>()),
];

var transaction = Transaction.Create(
nonce: nonce,
privateKey: genesisKey,
genesisHash: null,
actions: [.. actions.Select(item => item.PlainValue)],
timestamp: DateTimeOffset.MinValue);
var transactions = ImmutableList.Create(transaction);
return BlockChain.ProposeGenesisBlock(
privateKey: genesisKey,
transactions: transactions,
timestamp: DateTimeOffset.MinValue);
}

private static Block LoadGenesisBlock(string genesisBlockPath)
{
var rawBlock = File.ReadAllBytes(Path.GetFullPath(genesisBlockPath));
var blockDict = (Dictionary)new Codec().Decode(rawBlock);
return BlockMarshaler.UnmarshalBlock(blockDict);
}

private static Block LoadGenesisBlockFromUrl(Uri genesisBlockUri)
{
using var client = new HttpClient();
var rawBlock = client.GetByteArrayAsync(genesisBlockUri).Result;
var blockDict = (Dictionary)_codec.Decode(rawBlock);
return BlockMarshaler.UnmarshalBlock(blockDict);
}
}

0 comments on commit 503fe37

Please sign in to comment.