Skip to content

Commit

Permalink
Implement diff query (#2479)
Browse files Browse the repository at this point in the history
  • Loading branch information
Atralupus authored May 23, 2024
1 parent a448c59 commit 9b88a82
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug NineChronicles.Headless",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}/NineChronicles.Headless.Executable/bin/Debug/net6/NineChronicles.Headless.Executable.dll",
"cwd": "${workspaceFolder}/NineChronicles.Headless.Executable",
"stopAtEntry": false,
"console": "integratedTerminal",
"preLaunchTask": "build",
"args": [
"--config",
"appsettings.local.json"
]
}
]
}
41 changes: 41 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj"
],
"problemMatcher": "$msCompile"
}
]
}
12 changes: 12 additions & 0 deletions NineChronicles.Headless/GraphTypes/Diff/DiffGraphType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using GraphQL.Types;

namespace NineChronicles.Headless.GraphTypes.Diff;

public class DiffGraphType : UnionGraphType
{
public DiffGraphType()
{
Type<RootStateDiffType>();
Type<StateDiffType>();
}
}
6 changes: 6 additions & 0 deletions NineChronicles.Headless/GraphTypes/Diff/IDiffType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace NineChronicles.Headless.GraphTypes.Diff;

public interface IDiffType
{
string Path { get; }
}
33 changes: 33 additions & 0 deletions NineChronicles.Headless/GraphTypes/Diff/RootStateDiffType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using GraphQL.Types;

namespace NineChronicles.Headless.GraphTypes.Diff;

public class RootStateDiffType : ObjectGraphType<RootStateDiffType.Value>
{
public class Value : IDiffType
{
public string Path { get; }
public StateDiffType.Value[] Diffs { get; }

public Value(string path, StateDiffType.Value[] diffs)
{
Path = path;
Diffs = diffs;
}
}

public RootStateDiffType()
{
Name = "RootStateDiff";

Field<NonNullGraphType<StringGraphType>>(
"Path",
description: "The path to the root state difference."
);

Field<NonNullGraphType<ListGraphType<NonNullGraphType<StateDiffType>>>>(
"Diffs",
description: "List of state differences under this root."
);
}
}
48 changes: 48 additions & 0 deletions NineChronicles.Headless/GraphTypes/Diff/StateDiffType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Bencodex;
using Bencodex.Types;
using GraphQL.Types;
using Libplanet.Common;

namespace NineChronicles.Headless.GraphTypes.Diff;

public class StateDiffType : ObjectGraphType<StateDiffType.Value>
{
public class Value : IDiffType
{
public string Path { get; }
public IValue BaseState { get; }
public IValue? ChangedState { get; }

public Value(string path, IValue baseState, IValue? changedState)
{
Path = path;
BaseState = baseState;
ChangedState = changedState;
}
}

public StateDiffType()
{
Name = "StateDiff";

Field<NonNullGraphType<StringGraphType>>(
"Path",
description: "The path of the state difference."
);

Field<NonNullGraphType<StringGraphType>>(
"BaseState",
description: "The base state before changes.",
resolve: context => ByteUtil.Hex(new Codec().Encode(context.Source.BaseState))
);

Field<StringGraphType>(
"ChangedState",
description: "The state after changes.",
resolve: context =>
context.Source.ChangedState is null
? null
: ByteUtil.Hex(new Codec().Encode(context.Source.ChangedState))
);
}
}
90 changes: 90 additions & 0 deletions NineChronicles.Headless/GraphTypes/StandaloneQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
using Nekoyume.Model;
using Nekoyume.Module;
using NineChronicles.Headless.GraphTypes.States;
using NineChronicles.Headless.GraphTypes.Diff;
using System.Security.Cryptography;
using System.Text;
using static NineChronicles.Headless.NCActionUtils;
using Transaction = Libplanet.Types.Tx.Transaction;

Expand Down Expand Up @@ -72,6 +75,93 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi
}
);

Field<NonNullGraphType<ListGraphType<NonNullGraphType<DiffGraphType>>>>(
name: "diffs",
description: "This field allows you to query the diffs between two blocks." +
" `baseIndex` is the reference block index, and changedIndex is the block index from which to check" +
" what changes have occurred relative to `baseIndex`." +
" Both indices must not be higher than the current block on the chain nor lower than the genesis block index (0)." +
" The difference between the two blocks must be greater than zero for a valid comparison and less than ten for performance reasons.",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<LongGraphType>>
{
Name = "baseIndex",
Description = "The index of the reference block from which the state is retrieved."
},
new QueryArgument<NonNullGraphType<LongGraphType>>
{
Name = "changedIndex",
Description = "The index of the target block for comparison."
}
),
resolve: context =>
{
if (!(standaloneContext.BlockChain is BlockChain blockChain))
{
throw new ExecutionError(
$"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"
);
}

var baseIndex = context.GetArgument<long>("baseIndex");
var changedIndex = context.GetArgument<long>("changedIndex");

var blockInterval = Math.Abs(changedIndex - baseIndex);
if (blockInterval >= 10 || blockInterval == 0)
{
throw new ExecutionError(
"Interval between baseIndex and changedIndex should not be greater than 10 or zero"
);
}

var baseBlockStateRootHash = blockChain[baseIndex].StateRootHash.ToString();
var changedBlockStateRootHash = blockChain[changedIndex].StateRootHash.ToString();

var baseStateRootHash = HashDigest<SHA256>.FromString(baseBlockStateRootHash);
var targetStateRootHash = HashDigest<SHA256>.FromString(
changedBlockStateRootHash
);

var stateStore = standaloneContext.StateStore;
var baseTrieModel = stateStore.GetStateRoot(baseStateRootHash);
var targetTrieModel = stateStore.GetStateRoot(targetStateRootHash);

IDiffType[] diffs = baseTrieModel
.Diff(targetTrieModel)
.Select(x =>
{
if (x.TargetValue is not null)
{
var baseSubTrieModel = stateStore.GetStateRoot(new HashDigest<SHA256>((Binary)x.SourceValue));
var targetSubTrieModel = stateStore.GetStateRoot(new HashDigest<SHA256>((Binary)x.TargetValue));
var subDiff = baseSubTrieModel
.Diff(targetSubTrieModel)
.Select(diff =>
{
return new StateDiffType.Value(
Encoding.Default.GetString(diff.Path.ByteArray.ToArray()),
diff.SourceValue,
diff.TargetValue);
}).ToArray();
return (IDiffType)new RootStateDiffType.Value(
Encoding.Default.GetString(x.Path.ByteArray.ToArray()),
subDiff
);
}
else
{
return new StateDiffType.Value(
Encoding.Default.GetString(x.Path.ByteArray.ToArray()),
x.SourceValue,
x.TargetValue
);
}
}).ToArray();

return diffs;
}
);

Field<ByteStringType>(
name: "state",
arguments: new QueryArguments(
Expand Down
1 change: 1 addition & 0 deletions NineChronicles.Headless/NineChroniclesNodeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ internal void ConfigureContext(StandaloneContext standaloneContext)
standaloneContext.NineChroniclesNodeService = this;
standaloneContext.BlockChain = Swarm.BlockChain;
standaloneContext.Store = Store;
standaloneContext.StateStore = NodeService.StateStore;
standaloneContext.Swarm = Swarm;
standaloneContext.CurrencyFactory =
new CurrencyFactory(() => standaloneContext.BlockChain.GetWorldState(standaloneContext.BlockChain.Tip.Hash));
Expand Down
8 changes: 8 additions & 0 deletions NineChronicles.Headless/StandaloneContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class StandaloneContext
private BlockChain? _blockChain;
private IKeyStore? _keyStore;
private IStore? _store;
private IStateStore? _stateStore;
private Swarm? _swarm;

public BlockChain BlockChain
Expand Down Expand Up @@ -64,6 +65,13 @@ public IStore Store
internal set => _store = value;
}

public IStateStore StateStore
{
get => _stateStore ??
throw new InvalidOperationException($"{nameof(StateStore)} property is not set yet.");
internal set => _stateStore = value;
}

public Swarm Swarm
{
get => _swarm ??
Expand Down

0 comments on commit 9b88a82

Please sign in to comment.