diff --git a/.circleci/config.yml b/.circleci/config.yml index 1dc565a..5434238 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 jobs: build: environment: - BUILD_VERSION: 1.0.9 + BUILD_VERSION: 1.0.6.1 docker: - image: mcr.microsoft.com/dotnet/sdk:3.1 steps: @@ -24,7 +24,7 @@ jobs: command: dotnet nuget add source -n graphlinq -u $GITHUB_USERNAME -p $GITHUB_TOKEN "https://nuget.pkg.github.com/GraphLinq/index.json" --store-password-in-clear-text - run: name: Publish nuget - command: dotnet nuget push "nuget_build/GraphLinq.Engine.$BUILD_VERSION.nupkg" --api-key $GITHUB_TOKEN --source "graphlinq" --no-service-endpoint + command: dotnet nuget push "nuget_build/GraphLinq.Engine.$BUILD_VERSION.nupkg" --api-key $GITHUB_TOKEN --source "graphlinq" --no-service-endpoint --skip-duplicate workflows: version: 2 build-master: diff --git a/.gitignore b/.gitignore index b4df8d9..0ca2817 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ msbuild.wrn obj/ bin/ -.env \ No newline at end of file +.env +.env.dist \ No newline at end of file diff --git a/API/Controllers/GraphsController.cs b/API/Controllers/GraphsController.cs index 41b1abb..0102bb1 100644 --- a/API/Controllers/GraphsController.cs +++ b/API/Controllers/GraphsController.cs @@ -8,6 +8,9 @@ using System.Linq; using System.Collections.Generic; using NodeBlock.Engine.Storage.Redis; +using NodeBlock.Engine.Storage; +using Newtonsoft.Json; +using Renci.SshNet.Compression; namespace NodeBlock.Engine.API.Controllers { @@ -51,7 +54,7 @@ public async Task DeployAndInitGraph([FromBody] Graph graph) if (debugGraph != null) { graph.UniqueHash = debugGraph.graph.UniqueHash; - RedisStorage.RemoveGraphLogs(graph.UniqueHash); + StorageManager.GetStorage().RemoveGraphLogs(graph.UniqueHash); } } @@ -68,12 +71,23 @@ public IActionResult CompressGraphsNodes([FromBody] GraphRaw raw) { try { - var compressed = GraphCompression.CompressGraphData(raw.JsonData); - return Ok(new + if(Environment.GetEnvironmentVariable("USE_SHA_JSON") == "true") { - compressed, - hash = GraphCompression.GetUniqueGraphHash(raw.WalletIdentifier, compressed) - }); + var compressed = GraphCompression.CompressGraphData(raw.JsonData); + return Ok(new + { + compressed, + hash = GraphCompression.GetUniqueGraphHash(raw.WalletIdentifier, compressed) + }); + } + else + { + return Ok(new + { + compressed = raw.JsonData, + hash = GraphCompression.GetUniqueGraphHash(raw.WalletIdentifier, raw.JsonData) + }); ; + } } catch (Exception error) { @@ -115,11 +129,23 @@ public IActionResult GetGraphInfos([FromBody] Graph raw) if (graph == null) return StatusCode(404); + var hostedApiEndpoints = new List(); + if(graph.graph.HasHostedAPI()) + { + foreach(var endpoint in graph.graph.GetHostedAPI().HostedAPI.Endpoints) + { + hostedApiEndpoints.Add(endpoint.Key); + } + } + return Ok(new { state = graph.currentGraphState, loadedAt = graph.LoadedAt, - stoppedAt = graph.StoppedAt + stoppedAt = graph.StoppedAt, + cycleCount = graph.graph.CycleCountSinceStart, + hostedApi = graph.graph.HasHostedAPI(), + hostedApiEndpoints = hostedApiEndpoints, }); } catch (Exception error) @@ -164,7 +190,7 @@ public IActionResult GetGraphLogs([FromBody] Graph raw) { try { - var logs = Storage.Redis.RedisStorage.GetLogsForGraph(raw.UniqueHash); + var logs = StorageManager.GetStorage().GetLogsForGraph(raw.UniqueHash); return Ok(new { logs = logs @@ -178,7 +204,7 @@ public IActionResult GetGraphLogs([FromBody] Graph raw) } [HttpGet("healthcheck")] - public IActionResult HealthCheck([FromBody] Graph raw) + public IActionResult HealthCheck() { try { @@ -193,5 +219,150 @@ public IActionResult HealthCheck([FromBody] Graph raw) return StatusCode(500); } } + + [HttpGet("metrics")] + public IActionResult Metrics() + { + try + { + var graphs = GraphsContainer.GetGraphs(); + + return Ok(new + { + engine = new + { + started_at = GraphsContainer.StartAt + }, + metrics = new + { + total_graphs = graphs.Count + } + }); + } + catch (Exception error) + { + logger.Error(error); + return StatusCode(500); + } + } + + [HttpGet("graph_list")] + public IActionResult GraphList() + { + try + { + var graphs = GraphsContainer.GetGraphs(); + return Ok(new + { + graphs = graphs.Select(x => new + { + name = x.Value.graph.Name, + hash = x.Value.graph.UniqueHash, + wallet_identifier = x.Value.walletIdentifier, + state = x.Value.currentGraphState, + loaded_at = x.Value.LoadedAt + }) + }); ; + } + catch (Exception error) + { + logger.Error(error); + return StatusCode(500); + } + } + + [HttpPost("trace")] + public IActionResult GetGraphTraces([FromBody] Graph raw) + { + try + { + var graph = GraphsContainer.GetRunningGraphByHash(raw.UniqueHash); + if (graph == null) + { + return StatusCode(404); + } + var traces = graph.graph.PreviousCycles.Select(x => new + { + start_at = x.Timestamp, + trace_start_node = x.StartNode.FriendlyName, + execution_duration = x.Trace.GetExecutionTime(), + execution_success = x.Trace.GetExecutionSuccess(), + execution_exception = x.Trace.Exception == null ? string.Empty : x.Trace.Exception.ToString(), + stack = x.Trace.Stack.Select(y => new + { + id = y.NodeId, + execution_node_duration = y.ExecutionTime, + node_type = y.Node.NodeType, + execution_node_exception = y.ExecutionException == null ? string.Empty : y.ExecutionException.ToString(), + parameters = y.Parameters.Select(z => new + { + key = z.Key, + value = z.Value + }) + }) + }); + + return Ok(new + { + traces = traces + }); + } + catch (Exception error) + { + logger.Error(error); + return StatusCode(500); + } + } + + + [HttpPost("functions")] + public IActionResult GetGraphFunctions([FromBody] Graph raw) + { + try + { + var graph = GraphsContainer.GetRunningGraphByHash(raw.UniqueHash); + if (graph == null) + { + return StatusCode(404); + } + return Ok(new + { + functions = graph.graph.GetFunctions().Select(x => new + { + id = x.Id, + name = x.InParameters["name"].GetValue().ToString(), + in_parameters = x.GetFunctionInParameters() + }) + }); + } + catch (Exception error) + { + logger.Error(error); + return StatusCode(500); + } + } + + [HttpPost("functions/call")] + public IActionResult CallGraphFunctions([FromBody] GraphCallFunction raw) + { + try + { + var graph = GraphsContainer.GetRunningGraphByHash(raw.UniqueHash); + if (graph == null) + { + return StatusCode(404); + } + var fnResult = graph.graph.CallFunctionWithNewCycle(raw.FunctionName, raw.FunctionCallParameters); + return Ok(new + { + result = fnResult + }); + } + catch (Exception error) + { + logger.Error(error); + return StatusCode(500); + } + } } } diff --git a/API/Controllers/HostedAPIController.cs b/API/Controllers/HostedAPIController.cs index e1c48c5..ea9ce24 100644 --- a/API/Controllers/HostedAPIController.cs +++ b/API/Controllers/HostedAPIController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; +using NodeBlock.Engine.HostedAPI; using System; using System.Collections.Generic; using System.IO; @@ -16,7 +17,83 @@ public class HostedAPIController : ControllerBase { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); - [HttpPost("{graphId}/{*graphEndpoint}")] + [HttpGet("id/{graphId}/web/{*graphEndpoint}")] + public async Task RequestHostedGraphPublicPage(string graphId, string graphEndpoint) + { + try + { + var graphContext = GraphsContainer.GetRunningGraphByHash(graphId); + if (graphContext == null) + return BadRequest(new { success = false, message = string.Format("Graph {0} not loaded in the GraphLinq engine", graphId) }); + + if (!graphContext.graph.HasHostedAPI()) + return BadRequest(new { success = false, message = string.Format("Graph {0} doesn't have a hosted API", graphId) }); + + var graphHostedApi = graphContext.graph.GetHostedAPI().HostedAPI; + if(graphEndpoint != null) + { + if (!graphHostedApi.Endpoints.ContainsKey(graphEndpoint)) + return BadRequest(new { success = false, message = string.Format("Graph {0} doesn't have this endpoint available", graphId) }); + } + + HostedEndpoint endpoint = null; + if (graphEndpoint != null) + { + endpoint = graphHostedApi.Endpoints[graphEndpoint]; + } + else + { + endpoint = graphHostedApi.Endpoints[""]; + } + + var context = await endpoint.OnRequest(HttpContext, string.Empty); + if(endpoint.ContentType == "application/json" && !context.Body.Trim().StartsWith("{")) + { + // Since it's not api we need to switch the content to html + context.ResponseFormatType = HostedAPI.RequestContext.ResponseFormatTypeEnum.HTML; + } + else + { + switch(endpoint.ContentType) + { + case "text/html": + context.ResponseFormatType = HostedAPI.RequestContext.ResponseFormatTypeEnum.HTML; + break; + + case "application/javascript": + context.ResponseFormatType = HostedAPI.RequestContext.ResponseFormatTypeEnum.JS; + break; + + case "text/css": + context.ResponseFormatType = HostedAPI.RequestContext.ResponseFormatTypeEnum.CSS; + break; + } + } + if (context == null) return BadRequest(); + + switch (context.ResponseFormatType) + { + case HostedAPI.RequestContext.ResponseFormatTypeEnum.JSON: + return Content(context.Body, "application/json"); + + case HostedAPI.RequestContext.ResponseFormatTypeEnum.JS: + return Content(context.Body, "application/javascript"); + + case HostedAPI.RequestContext.ResponseFormatTypeEnum.CSS: + return Content(context.Body, "text/css"); + + default: + return Content(context.Body, "text/html"); + } + } + catch (Exception ex) + { + logger.Error(ex); + return BadRequest("Unknown error"); + } + } + + [HttpPost("id/{graphId}/{*graphEndpoint}")] public async Task RequestHostedGraphAPI(string graphId, string graphEndpoint) { try @@ -57,5 +134,81 @@ public async Task RequestHostedGraphAPI(string graphId, string gr return BadRequest("Unknown error"); } } + + [HttpGet("name/{graphName}/{*graphEndpoint}")] + public async Task RequestNamedHostedGraphPublicPage(string graphName, string graphEndpoint) + { + try + { + var graphContext = GraphsContainer.GetRunningGraphByName(graphName); + if (graphContext == null) + return BadRequest(new { success = false, message = string.Format("Graph {0} not loaded in the GraphLinq engine", graphName) }); + + if (!graphContext.graph.HasHostedAPI()) + return BadRequest(new { success = false, message = string.Format("Graph {0} doesn't have a hosted API", graphName) }); + + var graphHostedApi = graphContext.graph.GetHostedAPI().HostedAPI; + if (graphEndpoint != null) + { + if (!graphHostedApi.Endpoints.ContainsKey(graphEndpoint)) + return BadRequest(new { success = false, message = string.Format("Graph {0} doesn't have this endpoint available", graphName) }); + } + + HostedEndpoint endpoint = null; + if (graphEndpoint != null) + { + endpoint = graphHostedApi.Endpoints[graphEndpoint]; + } + else + { + endpoint = graphHostedApi.Endpoints[""]; + } + + var context = await endpoint.OnRequest(HttpContext, string.Empty); + if (endpoint.ContentType == "application/json" && !context.Body.Trim().StartsWith("{")) + { + // Since it's not api we need to switch the content to html + context.ResponseFormatType = HostedAPI.RequestContext.ResponseFormatTypeEnum.HTML; + } + else + { + switch (endpoint.ContentType) + { + case "text/html": + context.ResponseFormatType = HostedAPI.RequestContext.ResponseFormatTypeEnum.HTML; + break; + + case "application/javascript": + context.ResponseFormatType = HostedAPI.RequestContext.ResponseFormatTypeEnum.JS; + break; + + case "text/css": + context.ResponseFormatType = HostedAPI.RequestContext.ResponseFormatTypeEnum.CSS; + break; + } + } + if (context == null) return BadRequest(); + + switch (context.ResponseFormatType) + { + case HostedAPI.RequestContext.ResponseFormatTypeEnum.JSON: + return Content(context.Body, "application/json"); + + case HostedAPI.RequestContext.ResponseFormatTypeEnum.JS: + return Content(context.Body, "application/javascript"); + + case HostedAPI.RequestContext.ResponseFormatTypeEnum.CSS: + return Content(context.Body, "text/css"); + + default: + return Content(context.Body, "text/html"); + } + } + catch (Exception ex) + { + logger.Error(ex); + return BadRequest("Unknown error"); + } + } } } diff --git a/API/Controllers/WalletsController.cs b/API/Controllers/WalletsController.cs index 21dc0c9..910c658 100644 --- a/API/Controllers/WalletsController.cs +++ b/API/Controllers/WalletsController.cs @@ -3,6 +3,11 @@ using System.Threading.Tasks; using NodeBlock.Engine.API.Services; using NodeBlock.Engine.API.Entities; +using NodeBlock.Engine.Interop.Plugin; +using NodeBlock.Engine.Storage.MariaDB; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.EntityFrameworkCore; +using System.Reflection; namespace NodeBlock.Engine.API.Controllers { @@ -29,5 +34,12 @@ public async Task GetWalletInformations([FromBody] Wallet walletP return Ok(wallet); } + + [HttpPost("create")] + public async Task GenerateNewPersonalWallet([FromBody] ManagedWallet walletParam) + { + var wallet = await _walletService.GenerateNewManagedWallet(walletParam.WalletId, walletParam.WalletName); + return Ok(wallet); + } } } diff --git a/API/Entities/GraphCallFunction.cs b/API/Entities/GraphCallFunction.cs new file mode 100644 index 0000000..4ae9257 --- /dev/null +++ b/API/Entities/GraphCallFunction.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.API.Entities +{ + public class GraphCallFunction : Graph + { + public string FunctionName { get; set; } + public Dictionary FunctionCallParameters { get; set; } + } +} diff --git a/API/Entities/ManagedWallet.cs b/API/Entities/ManagedWallet.cs new file mode 100644 index 0000000..ff56d17 --- /dev/null +++ b/API/Entities/ManagedWallet.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.API.Entities +{ + public class ManagedWallet + { + public int WalletId { get; set;} + public string WalletName { get; set; } + } +} diff --git a/API/Services/GraphService.cs b/API/Services/GraphService.cs index 5f3e116..e07e81b 100644 --- a/API/Services/GraphService.cs +++ b/API/Services/GraphService.cs @@ -24,7 +24,16 @@ public async Task InitGraph(Graph graph) try { var hash = graph.UniqueHash ?? GraphCompression.GetUniqueGraphHash(graph.WalletIdentifier, graph.RawBytes); - var decompressedRaw = GraphCompression.DecompressGraphData(graph.RawBytes); + + var decompressedRaw = string.Empty; + if (Environment.GetEnvironmentVariable("USE_SHA_JSON") == "true") + { + decompressedRaw = GraphCompression.DecompressGraphData(graph.RawBytes); + } + else + { + decompressedRaw = graph.RawBytes; + } var loadedGraph = BlockGraph.LoadGraph(decompressedRaw, hash, graph.RawBytes); loadedGraph.Debug = graph.Debug; diff --git a/API/Services/WalletService.cs b/API/Services/WalletService.cs index 7d31328..e0ce7bb 100644 --- a/API/Services/WalletService.cs +++ b/API/Services/WalletService.cs @@ -2,12 +2,15 @@ using System.Linq; using System.Threading.Tasks; using NodeBlock.Engine.API.Entities; +using NodeBlock.Engine.Interop.Plugin; +using NodeBlock.Engine.Interop.Entities; namespace NodeBlock.Engine.API.Services { public interface IWalletService { Task GetWalletInformations(int identifierId); + Task GenerateNewManagedWallet(int walletId, string name); } public class WalletService : IWalletService @@ -21,5 +24,10 @@ public async Task GetWalletInformations(int identifierId) { return await Task.Run(() => wallets.Find(x => x.IdentifierId == identifierId)); } + + public async Task GenerateNewManagedWallet(int walletId, string name) + { + return await Task.Run(() => EthereumPluginBridge.CreateOrGetWallet(walletId, name)); + } } } diff --git a/Attributes/ExportableObject.cs b/Attributes/ExportableObject.cs new file mode 100644 index 0000000..7261b79 --- /dev/null +++ b/Attributes/ExportableObject.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Attributes +{ + public class ExportableObject : Attribute + { + public string Name { get; } + + public ExportableObject(string name) + { + Name = name; + } + } +} diff --git a/Attributes/NodeDefinition.cs b/Attributes/NodeDefinition.cs index 4d0cbde..7efe1dd 100644 --- a/Attributes/NodeDefinition.cs +++ b/Attributes/NodeDefinition.cs @@ -20,5 +20,6 @@ public NodeDefinition(string nodeName, string friendlyName, string nodeType, str public string NodeType { get; } public string GroupName { get; } public int BlockLimitPerGraph { get; } + public string CustomIcon { get; set; } } } diff --git a/Attributes/NodeIDEParametersAttribute.cs b/Attributes/NodeIDEParametersAttribute.cs new file mode 100644 index 0000000..f2d8db4 --- /dev/null +++ b/Attributes/NodeIDEParametersAttribute.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Attributes +{ + public class NodeIDEParametersAttribute : Attribute + { + public bool Hidden = false; + public bool IsSecretInput = false; + public bool IsScriptInput = false; + public string ScriptType = "lua"; + } +} diff --git a/Attributes/NodeSpecialActionAttribute.cs b/Attributes/NodeSpecialActionAttribute.cs new file mode 100644 index 0000000..c3e7c2a --- /dev/null +++ b/Attributes/NodeSpecialActionAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Attributes +{ + public class NodeSpecialActionAttribute : Attribute + { + public NodeSpecialActionAttribute(string text, string type, string parameter) + { + this.Text = text; + this.Type = type; + this.Parameter = parameter; + } + + public string Text { get; } + public string Type { get; } + public string Parameter { get; } + } +} diff --git a/Attributes/NodeTimeout.cs b/Attributes/NodeTimeout.cs new file mode 100644 index 0000000..1c7a6a5 --- /dev/null +++ b/Attributes/NodeTimeout.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Attributes +{ + public class NodeTimeout : Attribute + { + public NodeTimeout(int customTimeout) + { + this.CustomTimeout = customTimeout; + } + + public int CustomTimeout { get; } + } +} diff --git a/BlockGraph.cs b/BlockGraph.cs index d0b76a8..b5fe9a1 100644 --- a/BlockGraph.cs +++ b/BlockGraph.cs @@ -1,11 +1,14 @@ using Newtonsoft.Json; using NodeBlock.Engine.Attributes; using NodeBlock.Engine.Encoding; +using NodeBlock.Engine.Generics; using NodeBlock.Engine.Interop; using NodeBlock.Engine.Interop.Plugin; using NodeBlock.Engine.Nodes; using NodeBlock.Engine.Nodes.API; using NodeBlock.Engine.Nodes.Encoding; +using NodeBlock.Engine.Nodes.Functions; +using NodeBlock.Engine.Storage; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -19,7 +22,8 @@ namespace NodeBlock.Engine { public class BlockGraph { - private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + private const uint MAX_FAILED_CYCLE = 20; + public string Name { get; } public Dictionary Nodes { get; set; } @@ -32,17 +36,27 @@ public class BlockGraph public GraphContextWrapper currentContext = null; public Dictionary MemoryVariables = new Dictionary(); - - private Task queueTask; - public ConcurrentQueue PendingCycles = new ConcurrentQueue(); public GraphExecutionCycle currentCycle; + public StackLimitList PreviousCycles = new StackLimitList(20); - private CancellationTokenSource cancelCycleToken; public bool IsRunning = false; public bool Debug = false; - + public int CycleCountSinceStart = 0; + public long StartedAt = 0; public DateTime? RotateLastUpdate; + // Events + public static event EventHandler OnNewGraphLoaded; + public event EventHandler OnGraphStarted; + public event EventHandler OnGraphStopped; + public event EventHandler OnNewCycle; + + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + private Dictionary queueTaskCycleThreads = new Dictionary(); + private Dictionary> pendingCyclesQueues = new Dictionary>(); + private CancellationTokenSource cancelCycleToken; + private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + public BlockGraph(string name = "", Node entryPoint = null, bool createEntryPoint = true) { GraphManager.InitGraphEngine(); @@ -62,54 +76,90 @@ public BlockGraph(string name = "", Node entryPoint = null, bool createEntryPoin // Insert the given entry point this.AddNode(entryPoint); } - this.runQueueTask(); + if (OnNewGraphLoaded != null) OnNewGraphLoaded(this, this); + } + + public void AddCycleToQueue(string queue, GraphExecutionCycle executionCycle) + { + if(!this.pendingCyclesQueues.ContainsKey(queue)) + { + this.pendingCyclesQueues.Add(queue, new ConcurrentQueue()); + this.runQueueTask(queue); + } + this.pendingCyclesQueues[queue].Enqueue(executionCycle); } - private void runQueueTask() + private void runQueueTask(string queueName) { this.cancelCycleToken = new CancellationTokenSource(); - queueTask = new Task(async () => + var task = new Task(async () => { - while(!this.cancelCycleToken.IsCancellationRequested) + int failedCycleCount = 0; + while (!this.cancelCycleToken.IsCancellationRequested) { - if (this.PendingCycles.Count <= 0) + if (this.pendingCyclesQueues[queueName].Count <= 0) { - await Task.Delay(50); + await Task.Delay(150); } GraphExecutionCycle pendingCycle = null; if (this.cancelCycleToken.IsCancellationRequested) return; - while (this.PendingCycles.TryDequeue(out pendingCycle)) + while (this.pendingCyclesQueues[queueName].TryDequeue(out pendingCycle)) { - if (!this.IsRunning) return; - currentCycle = pendingCycle; - if (this.cancelCycleToken.IsCancellationRequested) return; - Task cycleTask = new Task(() => - { - pendingCycle.Execute(); - }); try { - Task timeoutTask = Task.Delay((1000 * 60) * 5); - cycleTask.Start(); - var taskResult = await Task.WhenAny(cycleTask, timeoutTask); - if (timeoutTask == taskResult) + await semaphoreSlim.WaitAsync(); + if (!this.IsRunning) return; + currentCycle = pendingCycle; + if (this.cancelCycleToken.IsCancellationRequested) return; + + // Add the cycle to the previous cycle list + this.PreviousCycles.Push(pendingCycle); + + Task cycleTask = new Task(() => { - this.AppendLog("error", string.Format("Timeout occured on last cycle from graph hash: {0}", this.UniqueHash)); - logger.Error("Timeout exceeded for the cycle, skipping .."); - cycleTask.Dispose(); + pendingCycle.Execute(); + }); + try + { + Task timeoutTask = Task.Delay(pendingCycle.GetCycleMaxExecutionTime()); + cycleTask.Start(); + this.CycleCountSinceStart++; + var taskResult = await Task.WhenAny(cycleTask, timeoutTask); + + if (timeoutTask == taskResult) + { + this.AppendLog("error", string.Format("Timeout occured on last cycle from graph hash: {0}", this.UniqueHash)); + logger.Error("Timeout exceeded for the cycle, skipping .."); + cycleTask.Dispose(); + } + } + catch (Exception ex) + { + logger.Error(ex, "Error when executing the cycle"); + failedCycleCount++; } } - catch(Exception ex) + catch (Exception ex2) + { + logger.Error(ex2, "Error when executing the cycle"); + failedCycleCount++; + } + finally { - logger.Error(ex, "Error when executing the cycle"); + currentCycle = null; + semaphoreSlim.Release(); + //if(failedCycleCount >= MAX_FAILED_CYCLE) + //{ + // failedCycleCount = 0; + // this.Stop(false); + //} } - - currentCycle = null; } } return; }); - queueTask.Start(); + queueTaskCycleThreads.Add(queueName, task); + task.Start(); } public bool Stop(bool force = false) @@ -140,8 +190,8 @@ public bool Stop(bool force = false) try { - this.queueTask.Dispose(); - this.cancelCycleToken.Cancel(); + this.queueTaskCycleThreads.ToList().ForEach(x => x.Value.Dispose()); + if(this.cancelCycleToken != null) this.cancelCycleToken.Cancel(); } catch(Exception ex) { @@ -197,8 +247,18 @@ public static BlockGraph LoadGraph(string graphJson, if (string.IsNullOrEmpty(graph.UniqueHash) || string.IsNullOrEmpty(graph.CompressedRaw)) throw new Exception("UniqueHash or CompressedRaw from BlockGraph cannot be null."); + + foreach (var rawDep in graphSchema.RawDeps) + { + var blockGraphDep = JsonConvert.DeserializeObject(rawDep); + foreach (var n in blockGraphDep.Nodes) + { + graphSchema.Nodes.Add(n); + } + } + // Load nodes - foreach(var nodeSchema in graphSchema.Nodes) + foreach (var nodeSchema in graphSchema.Nodes) { Node node = null; var typeFromSchema = NodeBlockExporter.GetNodes().FirstOrDefault(x => x.NodeType == nodeSchema.Type); @@ -215,7 +275,9 @@ public static BlockGraph LoadGraph(string graphJson, if (!graph.Nodes.ContainsKey(nodeSchema.Id)) continue; var node = graph.Nodes[nodeSchema.Id]; if (nodeSchema.OutNode != null) - node.OutNode = graph.Nodes[nodeSchema.OutNode]; + { + if(graph.Nodes.ContainsKey(nodeSchema.OutNode)) node.OutNode = graph.Nodes[nodeSchema.OutNode]; + } foreach (var parameter in nodeSchema.InParameters) { var nodeParam = node.InParameters[parameter.Name]; @@ -224,11 +286,16 @@ public static BlockGraph LoadGraph(string graphJson, } foreach (var parameter in nodeSchema.OutParameters) { + if (!node.OutParameters.ContainsKey(parameter.Name)) + continue; var nodeParam = node.OutParameters[parameter.Name]; nodeParam.Id = parameter.Id; if(parameter.ValueIsReference && parameter.Value != null && parameter.Value.ToString() != "") { - nodeParam.Value = graph.Nodes[(string)parameter.Value]; + if(graph.Nodes.ContainsKey((string)parameter.Value)) + { + nodeParam.Value = graph.Nodes[(string)parameter.Value]; + } } else { @@ -252,6 +319,7 @@ public static BlockGraph LoadGraph(string graphJson, foreach (var parameter in nodeSchema.OutParameters) { + if (!node.OutParameters.ContainsKey(parameter.Name)) continue; var nodeParam = node.OutParameters[parameter.Name]; if (parameter.Assignment != string.Empty) { @@ -260,6 +328,15 @@ public static BlockGraph LoadGraph(string graphJson, } } + //foreach(var rawDep in graphSchema.RawDeps) + //{ + // var blockGraphDep = LoadGraph(rawDep, "0x0", rawDep); + // foreach (var n in blockGraphDep.Nodes) + // { + // graph.Nodes.Add(n.Key, n.Value); + // } + //} + return graph; } @@ -312,6 +389,7 @@ public string GetId() public void Start(GraphContextWrapper context) { + this.StartedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); this.currentContext = context; this.MemoryVariables = new Dictionary(); GraphsContainer.UpdateStorageStateGraph(context, Enums.GraphStateEnum.STARTED); @@ -321,6 +399,7 @@ public void Start(GraphContextWrapper context) { try { + currentCycle = new GraphExecutionCycle(this, DateTimeOffset.Now.ToUnixTimeSeconds(), x.Value, new Dictionary()); x.Value.SetupConnector(); } catch (Exception ex) @@ -348,10 +427,11 @@ public void Start(GraphContextWrapper context) } IsRunning = true; - foreach (var x in this.Nodes.ToList().FindAll(x => x.Value.IsEventNode)) + foreach (var x in this.Nodes.ToList().FindAll(x => x.Value.IsEventNode).OrderByDescending(x => x.Value.GetType() == typeof(OnGraphStartNode))) { try { + currentCycle = new GraphExecutionCycle(this, DateTimeOffset.Now.ToUnixTimeSeconds(), x.Value, new Dictionary()); x.Value.SetupEvent(); } catch (Exception ex) @@ -376,11 +456,11 @@ public BigInteger GetGraphGasExecutionTotal() return total; } - public void AddCycle(Node startNode, Dictionary parameters = null) + public void AddCycle(Node startNode, Dictionary parameters = null, string cycleQueueInstance = "main") { if (startNode.LastCycleAt + startNode.NodeCycleLimit > DateTimeOffset.Now.ToUnixTimeMilliseconds()) return; startNode.LastCycleAt = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - this.PendingCycles.Enqueue(new GraphExecutionCycle(this, DateTimeOffset.Now.ToUnixTimeSeconds(), startNode, parameters)); + this.AddCycleToQueue(cycleQueueInstance, new GraphExecutionCycle(this, DateTimeOffset.Now.ToUnixTimeSeconds(), startNode, parameters)); } public GraphExecutionCycle GetCurrentCycle() @@ -409,21 +489,42 @@ public bool CheckLogRotate() public void AppendLog(string type, string message) { - //logger.Debug("[{0}] {1}", type, message); + if(Environment.GetEnvironmentVariable("graph_env") == "dev") + { + logger.Debug("[{0}] {1}", type, message); + } if (CheckLogRotate()) { - var currentLogs = Storage.Redis.RedisStorage.GetLogsForGraph(this.UniqueHash); + var currentLogs = StorageManager.GetStorage().GetLogsForGraph(this.UniqueHash); if (currentLogs.Count > 100) currentLogs.RemoveRange(0, 50); currentLogs.Add(new Storage.Redis.Entities.LogEntry() { Type = type, Message = message, Timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds() }); - Storage.Redis.RedisStorage.SaveLogsEntries(this.UniqueHash, currentLogs); + StorageManager.GetStorage().SaveLogsEntries(this.UniqueHash, currentLogs); } else - { - Storage.Redis.RedisStorage.AppendLogForGraph(this.UniqueHash, type, message); + { + StorageManager.GetStorage().AppendLogForGraph(this.UniqueHash, type, message); } } + public Dictionary CallFunctionWithNewCycle(string name, Dictionary parameters) + { + + var functionNode = this.Nodes.FirstOrDefault(x => x.Value.NodeType == "FunctionNode" && + x.Value.InParameters["name"].GetValue().ToString() == name).Value as FunctionNode; + if (functionNode == null) return null; + semaphoreSlim.Wait(); + //TODO: Verify if there is no cycle in pending to execute the function + currentCycle = new GraphExecutionCycle(this, DateTimeOffset.Now.ToUnixTimeSeconds(), functionNode, new Dictionary()); + functionNode.CallParameters = parameters; + functionNode.Execute(); + semaphoreSlim.Release(); + + this.PreviousCycles.Push(currentCycle); + this.currentCycle = null; + return functionNode.Context.ReturnValues; + } + public bool HasHostedAPI() { return this.Nodes.ToList().FindAll(x => x.Value.NodeType == typeof( @@ -434,5 +535,10 @@ public ExposeAPIBlockNode GetHostedAPI() { return this.Nodes.ToList().FirstOrDefault(x => x.Value.NodeType == typeof(ExposeAPIBlockNode).Name).Value as ExposeAPIBlockNode; } + + public List GetFunctions() + { + return this.Nodes.ToList().FindAll(x => x.Value.NodeType == typeof(FunctionNode).Name).Select(x => x.Value as FunctionNode).ToList(); + } } } diff --git a/Debugging/GraphTrace.cs b/Debugging/GraphTrace.cs index 3490b07..e232855 100644 --- a/Debugging/GraphTrace.cs +++ b/Debugging/GraphTrace.cs @@ -32,6 +32,16 @@ public void SetException(Exception exception) this.Exception = exception; } + public long GetExecutionTime() + { + return this.Stack.Select(x => x.ExecutionTime).Sum(); + } + + public bool GetExecutionSuccess() + { + return (this.Stack.FindAll(x => x.ExecutionException != null).Count > 0 ? false : true); + } + public override string ToString() { return "Total execution time : " + this.Stack.Select(x => x.ExecutionTime).Sum() + "ms\n" + diff --git a/Debugging/TraceItem.cs b/Debugging/TraceItem.cs index 9a369f0..7f813cb 100644 --- a/Debugging/TraceItem.cs +++ b/Debugging/TraceItem.cs @@ -10,8 +10,8 @@ public class TraceItem [JsonIgnore] public Node Node { get; } - private string NodeId; - private Dictionary Parameters; + public string NodeId; + public Dictionary Parameters; public long ExecutionTime; public Exception ExecutionException; diff --git a/Generics/StackLimitList.cs b/Generics/StackLimitList.cs new file mode 100644 index 0000000..b187cfc --- /dev/null +++ b/Generics/StackLimitList.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Generics +{ + public class StackLimitList : LinkedList + { + private int m_maxItems; + public int MaxItems + { + get + { + return this.m_maxItems; + } + set + { + while (base.Count > value) + { + base.RemoveFirst(); + } + this.m_maxItems = value; + } + } + + public StackLimitList(int num) + { + this.m_maxItems = num; + } + + public T Peek() + { + return base.Last.Value; + } + + public T Pop() + { + LinkedListNode last = base.Last; + base.RemoveLast(); + return last.Value; + } + + public void Push(T value) + { + LinkedListNode node = new LinkedListNode(value); + base.AddLast(node); + if (base.Count > this.m_maxItems) + { + base.RemoveFirst(); + } + } + } +} diff --git a/GraphExecutionCycle.cs b/GraphExecutionCycle.cs index 77adb5c..0b9ee01 100644 --- a/GraphExecutionCycle.cs +++ b/GraphExecutionCycle.cs @@ -15,10 +15,11 @@ public class GraphExecutionCycle public BlockGraph Graph { get; } public long Timestamp { get; } public Node StartNode { get; } + public bool DebugTraceEnabled = false; public List ExecutedNodesInCycle; public Debugging.GraphTrace Trace; - public Dictionary StartNodeInstanciatedParameters; + public Dictionary InstanciateParametersForCycle; public FunctionContext CurrentFunctionContext { get; set; } public GraphExecutionCycle(BlockGraph graph, long timestamp, Node startNode, Dictionary parameters = null) @@ -29,12 +30,12 @@ public GraphExecutionCycle(BlockGraph graph, long timestamp, Node startNode, Dic ExecutedNodesInCycle = new List(); this.Trace = new Debugging.GraphTrace(this); this.AddExecutedNode(StartNode); - this.StartNodeInstanciatedParameters = parameters != null ? parameters : this.StartNode.InstanciateParametersForCycle(); + this.InstanciateParametersForCycle = parameters != null ? parameters : this.StartNode.InstanciatedParametersForCycle(); } public void Execute() { - this.StartNodeInstanciatedParameters.ToList().ForEach(x => + this.InstanciateParametersForCycle.ToList().ForEach(x => { this.StartNode.OutParameters[x.Key].Value = x.Value.Value; }); @@ -42,6 +43,8 @@ public void Execute() this.StartNode.BeginCycle(); BigInteger usedGas = this.GetCycleExecutedGasPrice(); this.Graph.currentContext.AddCycleCost(decimal.Parse(usedGas.ToString())); + if(DebugTraceEnabled) + this.Graph.AppendLog("debug", this.Trace.ToString()); } public BigInteger GetCycleExecutedGasPrice() @@ -56,6 +59,17 @@ public BigInteger GetCycleExecutedGasPrice() return total; } + public int GetCycleMaxExecutionTime() + { + var baseTime = 1000 * 60; + var maxTimeout = 0; + foreach(var nodeWithTimeout in this.Graph.Nodes.Where(x => x.Value.CustomTimeout > 0)) + { + if (nodeWithTimeout.Value.CustomTimeout > maxTimeout) maxTimeout = (int)nodeWithTimeout.Value.CustomTimeout; + } + return baseTime + maxTimeout; + } + public TraceItem AddExecutedNode(Node node) { this.ExecutedNodesInCycle.Add(node); diff --git a/GraphsContainer.cs b/GraphsContainer.cs index ecf3afe..8aa2733 100644 --- a/GraphsContainer.cs +++ b/GraphsContainer.cs @@ -14,6 +14,7 @@ using Microsoft.EntityFrameworkCore; using MySQL.Data.EntityFrameworkCore; using NodeBlock.Engine.Storage.MariaDB.Entities; +using NodeBlock.Engine.Storage; namespace NodeBlock.Engine { @@ -49,7 +50,7 @@ public void InitContext(bool engineInit = false) { int totalThreads = Process.GetCurrentProcess().Threads.Count; - logger.Info(string.Format("New graph loaded of {0} bytes (hash: {1}, currentState: {2}, {3} threads runnings)", + logger.Info(string.Format("New graph loaded of {0} bytes (hash: {1}, currentState: {2}, {3} threads running)", graph.RawGraphData.Length, graph.UniqueHash, currentGraphState.ToString(), Convert.ToString(totalThreads))); if (engineInit || currentGraphState == GraphStateEnum.STARTING) @@ -63,8 +64,8 @@ public void InitContext(bool engineInit = false) { if (graph.IsRunning) { - graph.AppendLog("warn", string.Format("Graph hash {0} started his execution.", graph.UniqueHash)); - logger.Info("Graph hash {0} started his execution.", graph.UniqueHash); + graph.AppendLog("warn", string.Format("Graph hash {0} started its execution.", graph.UniqueHash)); + logger.Info("Graph hash {0} started its execution.", graph.UniqueHash); } } } @@ -91,6 +92,8 @@ public void RemoveCycleCost(decimal cost) public static class GraphsContainer { + public static long StartAt = 0; + private static bool running = true; private static readonly Dictionary _graphs = new Dictionary(); private static readonly object mutex = new object(); @@ -99,6 +102,7 @@ public static class GraphsContainer static GraphsContainer() { + StartAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); services = new ServiceCollection() .AddScoped(provider => provider.GetService()) .AddDbContextPool(options => @@ -110,9 +114,14 @@ static GraphsContainer() } + public static ServiceProvider GetServiceProvider() + { + return services; + } + public static bool InitActiveGraphs() { - var graphStorages = RedisStorage.GetGraphStorages(); + var graphStorages = StorageManager.GetStorage().GetGraphStorages(); logger.Info("Starting back the context of {0} graph(s) from last execution", graphStorages.Count); graphStorages.ForEach(x => @@ -153,6 +162,9 @@ public static void InitConsumingGraphCosts() if (cost > 0) { decimal decimalAmount = cost / decimal.Parse(Environment.GetEnvironmentVariable("factor_decimal")); + string precision = decimalAmount.ToString("N8"); + decimalAmount = decimal.Parse(precision); + using (var scope = services.CreateScope()) { var context = scope.ServiceProvider.GetService(); @@ -199,7 +211,7 @@ public static void UpdateAliveStorage() { var lists = _graphs.Where(x => !x.Value.graph.Debug && x.Value.currentGraphState == GraphStateEnum.STARTED || x.Value.currentGraphState == GraphStateEnum.RESTARTING).ToList().Select(x => x.Value.graph).ToList(); - RedisStorage.SaveListActiveGraphs(lists); + StorageManager.GetStorage().SaveListActiveGraphs(lists); } public static void UpdateStorageStateGraph(GraphContextWrapper context, GraphStateEnum newState) @@ -207,7 +219,7 @@ public static void UpdateStorageStateGraph(GraphContextWrapper context, GraphSta try { context.currentGraphState = newState; - var graphStorage = RedisStorage.SetGraphStorage(context.graph, + var graphStorage = StorageManager.GetStorage().SetGraphStorage(context.graph, context.walletIdentifier, newState); if (context.graph != null && context.graph.IsRunning && newState == GraphStateEnum.STOPPED) @@ -233,6 +245,15 @@ public static GraphContextWrapper GetRunningGraphByHash(string hash) return null; } + public static GraphContextWrapper GetRunningGraphByName(string name) + { + lock (mutex) + { + return _graphs.ToList().FirstOrDefault(x => x.Value.graph.Name == name).Value; + } + return null; + } + public static GraphContextWrapper GetWalletDebugGraph(int walletId) { lock (mutex) @@ -246,10 +267,10 @@ public static void UpdateGraphInStorage(GraphContextWrapper graphContext, bool e { try { - var graphStorage = RedisStorage.SetGraphStorage(graphContext.graph, + var graphStorage = StorageManager.GetStorage().SetGraphStorage(graphContext.graph, graphContext.walletIdentifier, graphContext.currentGraphState); - // to avoid redis update on each started graph at engine init + // to avoid Redis update on each started graph at engine init if (!engineInit) UpdateAliveStorage(); } @@ -297,6 +318,7 @@ public static bool AddNewGraph(BlockGraph graph, { try { + logger.Info("Starting graph " + graph.UniqueHash + " ..."); // lock once to check if the graph is already loaded lock (mutex) { @@ -314,7 +336,7 @@ public static bool AddNewGraph(BlockGraph graph, { if (_graphs.ContainsKey(graph.UniqueHash)) return; - //create and save the graph in the hashmap + //create and save the graph in the hash map var wrapper = new GraphContextWrapper(graph, walletIdentifier, initialState); _graphs.Add(graph.UniqueHash, wrapper); @@ -335,6 +357,9 @@ public static bool AddNewGraph(BlockGraph graph, } } - + public static Dictionary GetGraphs() + { + return _graphs; + } } } diff --git a/HostedAPI/HostedEndpoint.cs b/HostedAPI/HostedEndpoint.cs index a39243b..e993f3f 100644 --- a/HostedAPI/HostedEndpoint.cs +++ b/HostedAPI/HostedEndpoint.cs @@ -9,22 +9,57 @@ namespace NodeBlock.Engine.HostedAPI { public class HostedEndpoint { - public HostedEndpoint(HostedGraphAPI hostedGraphAPI, string route) + public HostedEndpoint(HostedGraphAPI hostedGraphAPI, string route,string contentType, string servedFile = "") { HostedGraphAPI = hostedGraphAPI; Route = route; + ContentType = contentType; + ServedFile = servedFile; + + + if(this.Route.EndsWith(".js")) + { + this.ContentType = "application/javascript"; + } else if (this.Route.EndsWith(".css")) + { + this.ContentType = "text/css"; + } } public HostedGraphAPI HostedGraphAPI { get; } public string Route { get; set; } + public string ContentType { get; } + public string ServedFile { get; } public OnEndpointRequestNode EventsNode { get; set; } + public int CacheTTL = -1; + public int CustomTimeout = 10000; + public long LastResponseTime = -1; + public string LastResponseCache = string.Empty; + public async Task OnRequest(HttpContext context, string rawBody) { - var requestContext = new RequestContext(context, rawBody); - if (EventsNode == null) return null; - EventsNode.OnRequest(requestContext); - var result = await requestContext.AwaitResponse(); + var requestContext = new RequestContext(context, rawBody, this.HostedGraphAPI.Graph, this.CustomTimeout); + if (EventsNode == null) + { + if (this.ServedFile == string.Empty) return null; + requestContext.Body = this.ServedFile; + requestContext.Complete(true); + return requestContext; + } + var timestamp = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds(); + if(timestamp < LastResponseTime + this.CacheTTL && this.CacheTTL != -1 && this.LastResponseCache != string.Empty) + { + requestContext.Body = this.LastResponseCache; + requestContext.Complete(true); + } + else + { + EventsNode.OnRequest(requestContext); + var result = await requestContext.AwaitResponse(); + this.LastResponseCache = requestContext.Body; + this.LastResponseTime = timestamp; + } return requestContext; } } diff --git a/HostedAPI/HostedGraphAPI.cs b/HostedAPI/HostedGraphAPI.cs index 38ae3f1..193d502 100644 --- a/HostedAPI/HostedGraphAPI.cs +++ b/HostedAPI/HostedGraphAPI.cs @@ -18,7 +18,7 @@ public string URL { get { - return Environment.GetEnvironmentVariable("hosted_api_base_url") + "/api/" + Graph.UniqueHash; + return Environment.GetEnvironmentVariable("hosted_api_base_url") + "/hostedAPI/id/" + Graph.UniqueHash + "/web"; } } } diff --git a/HostedAPI/RequestContext.cs b/HostedAPI/RequestContext.cs index 783e1ff..09e9ae5 100644 --- a/HostedAPI/RequestContext.cs +++ b/HostedAPI/RequestContext.cs @@ -11,11 +11,16 @@ public class RequestContext { public enum ResponseFormatTypeEnum { - JSON = 1 + JSON = 1, + HTML = 2, + JS = 3, + CSS = 4 } - public RequestContext(HttpContext context, string rawBody) + public RequestContext( HttpContext context, string rawBody, BlockGraph graph, int customTimeout) { + this.graph = graph; + this.customTimeout = customTimeout; Context = context; RawBody = rawBody; this.parseRawBody(); @@ -29,6 +34,8 @@ public RequestContext(HttpContext context, string rawBody) private Task timeoutTask = null; public string Body { get; set; } public ResponseFormatTypeEnum ResponseFormatType = ResponseFormatTypeEnum.JSON; + private readonly BlockGraph graph; + private readonly int customTimeout; private void parseRawBody() { @@ -38,7 +45,7 @@ private void parseRawBody() public async Task AwaitResponse() { - timeoutTask = Task.Delay(10000); + timeoutTask = Task.Delay(this.customTimeout); var result = await Task.WhenAny(completedTask.Task, timeoutTask); if(result == timeoutTask) { diff --git a/Interop/BlockGraphSchema.cs b/Interop/BlockGraphSchema.cs index de49764..5864168 100644 --- a/Interop/BlockGraphSchema.cs +++ b/Interop/BlockGraphSchema.cs @@ -12,9 +12,13 @@ public class BlockGraphSchema [JsonProperty(PropertyName = "name")] public string Name { get; set; } + [JsonProperty(PropertyName = "nodes")] public List Nodes { get; set; } + [JsonProperty(PropertyName = "rawDeps")] + public ListRawDeps { get; set; } + public BlockGraphSchema() { } public BlockGraphSchema(BlockGraph graph) { diff --git a/Interop/Entities/ManagedWalletBridgeObject.cs b/Interop/Entities/ManagedWalletBridgeObject.cs new file mode 100644 index 0000000..91fcf08 --- /dev/null +++ b/Interop/Entities/ManagedWalletBridgeObject.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Interop.Entities +{ + public class ManagedWalletBridgeObject + { + public int Id { get; set; } + public int WalletId { get; set; } + public string Name { get; set; } + public string PublicKey { get; set; } + public string PrivateKey { get; set; } + public string PrivateKeyUnencrypted { get; set; } + public string Password { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } +} diff --git a/Interop/NodeSchema.cs b/Interop/NodeSchema.cs index dd0d3cd..88def7f 100644 --- a/Interop/NodeSchema.cs +++ b/Interop/NodeSchema.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using NodeBlock.Engine.Attributes; using System; using System.Collections.Generic; using System.Text; @@ -31,6 +32,9 @@ public class NodeSchema [JsonProperty(PropertyName = "out_parameters")] public List OutParameters; + [JsonProperty(PropertyName = "gas_cost")] + public long GasCost; + public NodeSchema() { } public NodeSchema(Node node) @@ -39,6 +43,14 @@ public NodeSchema(Node node) this.Id = node.Id; this.Type = node.NodeType.ToString(); this.CanBeExecuted = node.CanBeExecuted; + if (node.GetType().GetCustomAttributes(typeof(NodeGasConfiguration), true).Length > 0) + { + this.GasCost = (long)(node.GetType().GetCustomAttributes(typeof(NodeGasConfiguration), true)[0] as NodeGasConfiguration).BlockGasPrice; + } + else + { + this.GasCost = 0; + } this.CanExecute = node.CanExecute; this.InParameters = new List(); this.OutParameters = new List(); diff --git a/Interop/Plugin/EthereumPluginBridge.cs b/Interop/Plugin/EthereumPluginBridge.cs new file mode 100644 index 0000000..56134d3 --- /dev/null +++ b/Interop/Plugin/EthereumPluginBridge.cs @@ -0,0 +1,30 @@ +using NodeBlock.Engine.Interop.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Interop.Plugin +{ + public class EthereumPluginBridge + { + public static ManagedWalletBridgeObject CreateOrGetWallet(int walletId, string name) + { + var method = PluginManager.GetExportedMethod("ManagedEthereumWallet.GetOrCreateManagedWallet"); + var managedWallet = method.Invoke(null, new object[] { walletId, name }); + var entity = managedWallet.GetType().GetProperty("ManagedWalletEntity").GetGetMethod().Invoke(managedWallet, new object[] { }); + var entityType = entity.GetType(); + return new ManagedWalletBridgeObject() + { + Id = (int)entityType.GetProperty("Id").GetGetMethod().Invoke(entity, new object[] { }), + WalletId = (int)entityType.GetProperty("WalletId").GetGetMethod().Invoke(entity, new object[] { }), + Name = (string)entityType.GetProperty("Name").GetGetMethod().Invoke(entity, new object[] { }), + PublicKey = (string)entityType.GetProperty("PublicKey").GetGetMethod().Invoke(entity, new object[] { }), + PrivateKey = (string)entityType.GetProperty("PrivateKey").GetGetMethod().Invoke(entity, new object[] { }), + PrivateKeyUnencrypted = (string)managedWallet.GetType().GetMethod("GetPrivateKey").Invoke(managedWallet, new object[] { }), + Password = (string)entityType.GetProperty("Password").GetGetMethod().Invoke(entity, new object[] { }), + CreatedAt = (DateTime?)entityType.GetProperty("CreatedAt").GetGetMethod().Invoke(entity, new object[] { }), + UpdatedAt = (DateTime?)entityType.GetProperty("UpdatedAt").GetGetMethod().Invoke(entity, new object[] { }), + }; + } + } +} diff --git a/Interop/Plugin/PluginManager.cs b/Interop/Plugin/PluginManager.cs index 8a4d952..20a9ee1 100644 --- a/Interop/Plugin/PluginManager.cs +++ b/Interop/Plugin/PluginManager.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; +using NodeBlock.Engine.Attributes; namespace NodeBlock.Engine.Interop.Plugin { @@ -11,8 +12,15 @@ public class PluginManager { private static bool _pluginLoaded = false; private static List _plugins = new List(); + private static Dictionary _exportableObjects = new Dictionary(); private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + public static BasePlugin FetchPluginByName(string name) + { + return _plugins.FirstOrDefault(x => x.GetType().FullName.Contains(name)); + } + public static void LoadPlugins() { if (_pluginLoaded) return; @@ -25,20 +33,32 @@ public static void LoadPlugins() { try { + logger.Info("Loading plugin " + pluginDll); + var assembly = Assembly.LoadFile(pluginDll); var basePluginType = assembly.GetTypes().ToList().FirstOrDefault(x => x.IsSubclassOf(typeof(BasePlugin))); if (basePluginType == null) continue; var plugin = Activator.CreateInstance(basePluginType) as BasePlugin; plugin.Load(); + var nodeCount = 0; foreach (var type in assembly.GetTypes()) { + foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) + { + if (method.GetCustomAttributes(typeof(Attributes.ExportableObject), true).Length == 0) continue; + var eo = method.GetCustomAttributes(typeof(Attributes.ExportableObject), true).FirstOrDefault() as ExportableObject; + _exportableObjects.Add(eo.Name, method); + } + if (type.GetCustomAttributes(typeof(Attributes.NodeDefinition), true).Length == 0) continue; var instance = Activator.CreateInstance(type, string.Empty, null) as Node; NodeBlockExporter.AddNodeType(instance); + nodeCount++; } _plugins.Add(plugin); + logger.Info("Plugin " + plugin.GetType().FullName + " loaded with " + nodeCount + " nodes and " + _exportableObjects.Count + " exportables objects"); } catch(Exception ex) { @@ -48,5 +68,10 @@ public static void LoadPlugins() _pluginLoaded = true; } + + public static MethodInfo GetExportedMethod(string name) + { + return _exportableObjects[name]; + } } } diff --git a/Node.cs b/Node.cs index d2e0d33..23d931f 100644 --- a/Node.cs +++ b/Node.cs @@ -6,6 +6,7 @@ using NodeBlock.Engine.Debugging; using Nethereum.JsonRpc.Client.Streaming; using NodeBlock.Engine.Nodes.Functions; +using NodeBlock.Engine.Attributes; namespace NodeBlock.Engine { @@ -24,6 +25,8 @@ public abstract class Node : ICloneable public string NodeGroupName { get; set; } public string NodeBlockType { get; set; } public string NodeDescription { get; set; } + public string CustomIcon { get; set; } + public bool IsCustomBlock = false; public Dictionary InParameters { get; set; } public Dictionary OutParameters { get; set; } public Node OutNode { get; set; } @@ -34,6 +37,10 @@ public abstract class Node : ICloneable public long LastCycleAt; public TraceItem CurrentTraceItem = null; private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + public long CustomTimeout = 0; + public string GasCost = "0"; + public List SpecialActionsAttributes = new List(); + public NodeIDEParametersAttribute IDEParameters { get; set; } public Node(string id, BlockGraph graph, string nodeType) { @@ -47,6 +54,7 @@ public Node(string id, BlockGraph graph, string nodeType) this.FriendlyName = nodeDefinition.FriendlyName; this.NodeBlockType = nodeDefinition.NodeType; this.NodeGroupName = nodeDefinition.GroupName; + this.CustomIcon = nodeDefinition.CustomIcon; } if (this.GetType().GetCustomAttributes(typeof(Attributes.NodeCycleLimit), true).Length > 0) @@ -67,6 +75,34 @@ public Node(string id, BlockGraph graph, string nodeType) } } + if (this.GetType().GetCustomAttributes(typeof(Attributes.NodeTimeout), true).Length > 0) + { + var nodeTimeout = (this.GetType().GetCustomAttributes(typeof(Attributes.NodeTimeout), true)[0] as Attributes.NodeTimeout); + if (nodeTimeout != null) + { + this.CustomTimeout = nodeTimeout.CustomTimeout; + } + } + + if (this.GetType().GetCustomAttributes(typeof(Attributes.NodeIDEParametersAttribute), true).Length > 0) + { + var nodeIdeParameters = (this.GetType().GetCustomAttributes(typeof(Attributes.NodeIDEParametersAttribute), true)[0] as Attributes.NodeIDEParametersAttribute); + if (nodeIdeParameters != null) + { + this.IDEParameters = nodeIdeParameters; + } + } + + if (this.GetType().GetCustomAttributes(typeof(NodeGasConfiguration), true).Length > 0) + { + this.GasCost = ((this.GetType().GetCustomAttributes(typeof(NodeGasConfiguration), true)[0] as NodeGasConfiguration).BlockGasPrice).ToString(); + } + else + { + this.GasCost = "0"; + } + + this.SpecialActionsAttributes = this.GetType().GetCustomAttributes(typeof(Attributes.NodeSpecialActionAttribute), true).Select(x => x as Attributes.NodeSpecialActionAttribute).ToList(); this.InParameters = new Dictionary(); this.OutParameters = new Dictionary(); @@ -98,6 +134,12 @@ public bool Execute(Node executedFromNode = null) if(cycle != null) { traceItem = cycle.AddExecutedNode(this); + if(cycle.ExecutedNodesInCycle.Count > 500) + { + this.Graph.AppendLog("error", "Max stack limit reached, stopping the graph to avoid memory leaks"); + this.Graph.Stop(); + return false; + } } if (this.NodeType != typeof(EntryPointNode).Name && this.NodeType != typeof(FunctionNode).Name) @@ -105,7 +147,7 @@ public bool Execute(Node executedFromNode = null) if (!this.CanBeExecuted) return false; } - //stop the execution if the state isnt started + //stop the execution if the state isn't started if (!(this.Graph.currentContext.currentGraphState == Enums.GraphStateEnum.STARTED)) { this.Graph.Stop(); @@ -153,14 +195,14 @@ public bool Next() } } - public Dictionary InstanciateParametersForCycle() + public Dictionary InstanciatedParametersForCycle() { - var instanciatedCycleParameters = new Dictionary(); + var InstanciatedCycleParameters = new Dictionary(); this.OutParameters.ToList().ForEach(x => { - instanciatedCycleParameters.Add(x.Key, x.Value.Clone() as NodeParameter); + InstanciatedCycleParameters.Add(x.Key, x.Value.Clone() as NodeParameter); }); - return instanciatedCycleParameters; + return InstanciatedCycleParameters; } public virtual void SetupEvent() diff --git a/NodeBlock.Engine.csproj b/NodeBlock.Engine.csproj index 968e2e1..a22df5b 100644 --- a/NodeBlock.Engine.csproj +++ b/NodeBlock.Engine.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 GraphLinq.Engine 1.0.0 @@ -14,6 +14,7 @@ + @@ -24,6 +25,8 @@ + + diff --git a/NodeParameter.cs b/NodeParameter.cs index b076aff..09a709a 100644 --- a/NodeParameter.cs +++ b/NodeParameter.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Globalization; using System.Text; namespace NodeBlock.Engine @@ -61,12 +62,53 @@ public object GetValue() } return this.Node.ComputeParameterValue(this, this.Value); } - catch(Exception ex) + catch(Exception) { return null; } } + public Node GetNode() + { + try + { + if (this.IsIn) + { + if (this.Assignments != null) + { + return this.Assignments.Node; + } + } + return null; + } + catch (Exception) + { + return null; + } + } + + public double GetValueAsDouble() + { + if(this.GetValue().GetType() != typeof(double) && + this.GetValue().GetType() != typeof(int) + && this.GetValue().GetType() != typeof(long) + && this.GetValue().GetType() != typeof(float)) { + + return double.Parse(this.GetValue().ToString(), CultureInfo.InvariantCulture); + } + else + { + if(this.GetValue().GetType() != typeof(double)) + { + return Convert.ToDouble(this.GetValue()); + } + else + { + return (double)this.GetValue(); + } + } + } + public object Clone() { return this.MemberwiseClone(); diff --git a/Nodes/API/AddAPIEndpointNode.cs b/Nodes/API/AddAPIEndpointNode.cs index 6ff9abd..c0bc166 100644 --- a/Nodes/API/AddAPIEndpointNode.cs +++ b/Nodes/API/AddAPIEndpointNode.cs @@ -15,6 +15,7 @@ public AddAPIEndpointNode(string id, BlockGraph graph) { this.InParameters.Add("hostedAPI", new NodeParameter(this, "hostedAPI", typeof(HostedGraphAPI), true)); this.InParameters.Add("path", new NodeParameter(this, "path", typeof(string), true)); + this.InParameters.Add("file", new NodeParameter(this, "file", typeof(string), true)); //this.InParameters.Add("method", new NodeParameter(this, "method", typeof(string), true)); this.OutParameters.Add("endpoint", new NodeParameter(this, "endpoint", typeof(HostedEndpoint), false)); @@ -23,11 +24,29 @@ public AddAPIEndpointNode(string id, BlockGraph graph) public override bool CanExecute => true; public override bool CanBeExecuted => true; + public string ContentType = "application/json"; + public override bool OnExecution() { var hostedAPI = this.InParameters["hostedAPI"].GetValue() as HostedGraphAPI; if (hostedAPI == null) return false; - var endpoint = new HostedEndpoint(hostedAPI, this.InParameters["path"].GetValue().ToString()); + if(this.InParameters["path"].GetValue().ToString().StartsWith("/web") || this.InParameters["path"].GetValue().ToString().StartsWith("web")) + { + this.Graph.AppendLog("debug", "The endpoint can't start with 'web', it will create conflict with public web page system"); + return false; + } + + // Check if this is a static endpoint + var fileContent = string.Empty; + if (this.InParameters.ContainsKey("file")) + { + if (this.InParameters["file"].GetValue() != null) + { + fileContent = this.InParameters["file"].GetValue().ToString(); + } + } + + var endpoint = new HostedEndpoint(hostedAPI, this.InParameters["path"].GetValue().ToString(), ContentType, fileContent); hostedAPI.Endpoints.Add(endpoint.Route, endpoint); this.Graph.AppendLog("debug", "New endpoint registered " + endpoint.Route); this.OutParameters["endpoint"].SetValue(endpoint); diff --git a/Nodes/API/EndpointCacheResponseNode.cs b/Nodes/API/EndpointCacheResponseNode.cs new file mode 100644 index 0000000..11507fa --- /dev/null +++ b/Nodes/API/EndpointCacheResponseNode.cs @@ -0,0 +1,37 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.HostedAPI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.API +{ + [NodeDefinition("EndpointCacheResponseNode", "Add Endpoint Cache", NodeTypeEnum.Function, "Hosted API")] + [NodeGraphDescription("Add a cache for the response of the endpoint")] + public class EndpointCacheResponseNode : Node + { + public EndpointCacheResponseNode(string id, BlockGraph graph) + : base(id, graph, typeof(EndpointCacheResponseNode).Name) + { + this.InParameters.Add("endpoint", new NodeParameter(this, "endpoint", typeof(object), true)); + this.InParameters.Add("ttl", new NodeParameter(this, "ttl", typeof(int), true)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var endpoint = this.InParameters["endpoint"].GetValue() as HostedEndpoint; + if (endpoint == null) return false; + var ttl = int.Parse(this.InParameters["ttl"].GetValue().ToString()); + if(ttl < 10) + { + this.Graph.AppendLog("error", "The cache TTL need to be >= 10"); + return false; + } + endpoint.CacheTTL = ttl; + return true; + } + } +} diff --git a/Nodes/API/ExposeAPIBlockNode.cs b/Nodes/API/ExposeAPIBlockNode.cs index 26d4ed6..33e5e47 100644 --- a/Nodes/API/ExposeAPIBlockNode.cs +++ b/Nodes/API/ExposeAPIBlockNode.cs @@ -25,6 +25,7 @@ public ExposeAPIBlockNode(string id, BlockGraph graph) public override void SetupConnector() { this.HostedAPI = new HostedGraphAPI(this.Graph); + this.Graph.AppendLog("debug", "New hosted API registered " + this.HostedAPI.URL); this.Next(); } diff --git a/Nodes/API/OnEndpointRequestNode.cs b/Nodes/API/OnEndpointRequestNode.cs index 8201aa8..c9c98a9 100644 --- a/Nodes/API/OnEndpointRequestNode.cs +++ b/Nodes/API/OnEndpointRequestNode.cs @@ -19,7 +19,7 @@ public OnEndpointRequestNode(string id, BlockGraph graph) this.OutParameters.Add("requestContext", new NodeParameter(this, "requestContext", typeof(RequestContext), false)); } - public override bool CanBeExecuted => false; + public override bool CanBeExecuted => true; public override bool CanExecute => true; @@ -32,9 +32,18 @@ public override void SetupEvent() public void OnRequest(RequestContext requestContext) { - var parameters = this.InstanciateParametersForCycle(); + var parameters = this.InstanciatedParametersForCycle(); parameters["requestContext"].SetValue(requestContext); - this.Graph.AddCycle(this, parameters); + this.Graph.AddCycle(this, parameters, "api"); + } + + public override bool OnExecution() + { + var endpoint = this.InParameters["endpoint"].GetValue() as HostedEndpoint; + if (endpoint == null) return false; + endpoint.EventsNode = this; + + return false; } public override void BeginCycle() diff --git a/Nodes/API/PublicWebpage/CSSFileNode.cs b/Nodes/API/PublicWebpage/CSSFileNode.cs new file mode 100644 index 0000000..d467238 --- /dev/null +++ b/Nodes/API/PublicWebpage/CSSFileNode.cs @@ -0,0 +1,22 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.API.PublicWebpage +{ + [NodeDefinition("CSSFileNode", "CSS File", NodeTypeEnum.Variable, "Public Web Page")] + [NodeGraphDescription("A string that contain a CSS file")] + [NodeIDEParameters(IsScriptInput = true, ScriptType = "css")] + public class CSSFileNode : Node + { + public CSSFileNode(string id, BlockGraph graph) + : base(id, graph, typeof(CSSFileNode).Name) + { + this.OutParameters.Add("css", new NodeParameter(this, "css", typeof(string), true)); + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + } +} \ No newline at end of file diff --git a/Nodes/API/PublicWebpage/HTMLPageNode.cs b/Nodes/API/PublicWebpage/HTMLPageNode.cs new file mode 100644 index 0000000..dd1c5d9 --- /dev/null +++ b/Nodes/API/PublicWebpage/HTMLPageNode.cs @@ -0,0 +1,22 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.API.PublicWebpage +{ + [NodeDefinition("HTMLPageNode", "HTML Page", NodeTypeEnum.Variable, "Public Web Page")] + [NodeGraphDescription("A string that contain a HTML Page")] + [NodeIDEParameters(IsScriptInput = true, ScriptType = "html")] + public class HTMLPageNode : Node + { + public HTMLPageNode(string id, BlockGraph graph) + : base(id, graph, typeof(HTMLPageNode).Name) + { + this.OutParameters.Add("html", new NodeParameter(this, "html", typeof(string), true)); + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + } +} diff --git a/Nodes/API/PublicWebpage/JavascriptFileNode.cs b/Nodes/API/PublicWebpage/JavascriptFileNode.cs new file mode 100644 index 0000000..5e148a4 --- /dev/null +++ b/Nodes/API/PublicWebpage/JavascriptFileNode.cs @@ -0,0 +1,22 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.API.PublicWebpage +{ + [NodeDefinition("JavascriptFileNode", "Javascript File", NodeTypeEnum.Variable, "Public Web Page")] + [NodeGraphDescription("A string that contain a Javascript file")] + [NodeIDEParameters(IsScriptInput = true, ScriptType = "javascript")] + public class JavascriptFileNode : Node + { + public JavascriptFileNode(string id, BlockGraph graph) + : base(id, graph, typeof(JavascriptFileNode).Name) + { + this.OutParameters.Add("javascript", new NodeParameter(this, "javascript", typeof(string), true)); + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + } +} diff --git a/Nodes/API/PublicWebpage/ProcessTemplateNode.cs b/Nodes/API/PublicWebpage/ProcessTemplateNode.cs new file mode 100644 index 0000000..12f4279 --- /dev/null +++ b/Nodes/API/PublicWebpage/ProcessTemplateNode.cs @@ -0,0 +1,40 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.HostedAPI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.API.PublicWebpage +{ + [NodeDefinition("ProcessTemplateNode", "Process Template", NodeTypeEnum.Function, "Hosted API")] + [NodeGraphDescription("Retrive all the vars from the running graph and replace by them in the web page template")] + public class ProcessTemplateNode : Node + { + public ProcessTemplateNode(string id, BlockGraph graph) + : base(id, graph, typeof(ProcessTemplateNode).Name) + { + this.InParameters.Add("html", new NodeParameter(this, "html", typeof(string), true)); + + this.OutParameters.Add("htmlProcessed", new NodeParameter(this, "htmlProcessed", typeof(string), false)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var html = this.InParameters["html"].GetValue().ToString(); + + var varsToReplace = Utils.StringUtils.ExtractTextWithinDoubleCurlyBraces(html); + foreach(var v in varsToReplace) + { + if (!this.Graph.MemoryVariables.ContainsKey(v)) continue; + var memoryVariable = this.Graph.MemoryVariables[v]; + html = html.Replace("{{" + v + "}}", memoryVariable.ToString()); + } + this.OutParameters["htmlProcessed"].SetValue(html); + + return true; + } + } +} diff --git a/Nodes/API/SetCustomResponseTimeoutNode.cs b/Nodes/API/SetCustomResponseTimeoutNode.cs new file mode 100644 index 0000000..b38af74 --- /dev/null +++ b/Nodes/API/SetCustomResponseTimeoutNode.cs @@ -0,0 +1,37 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.HostedAPI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.API +{ + [NodeDefinition("SetCustomResponseTimeoutNode", "Set Custom Response Timeout", NodeTypeEnum.Function, "Hosted API")] + [NodeGraphDescription("Set a custom timeout for a endpoint response")] + public class SetCustomResponseTimeoutNode : Node + { + public SetCustomResponseTimeoutNode(string id, BlockGraph graph) + : base(id, graph, typeof(SetCustomResponseTimeoutNode).Name) + { + this.InParameters.Add("endpoint", new NodeParameter(this, "endpoint", typeof(object), true)); + this.InParameters.Add("timeInMs", new NodeParameter(this, "timeInMs", typeof(int), true)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var endpoint = this.InParameters["endpoint"].GetValue() as HostedEndpoint; + if (endpoint == null) return false; + var ttl = int.Parse(this.InParameters["timeInMs"].GetValue().ToString()); + if (ttl > 60000) + { + this.Graph.AppendLog("error", "The timeout need to be =< 60000"); + return false; + } + endpoint.CustomTimeout = ttl; + return true; + } + } +} diff --git a/Nodes/Arbitrage/StartArbitrageBotNode.cs b/Nodes/Arbitrage/StartArbitrageBotNode.cs new file mode 100644 index 0000000..1f6e470 --- /dev/null +++ b/Nodes/Arbitrage/StartArbitrageBotNode.cs @@ -0,0 +1,38 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Nodes.Bot; +using System.Globalization; + +namespace NodeBlock.Engine.Nodes.Arbitrage +{ + [NodeDefinition("StartArbitrageBotNode", "Start Arbitrage Bot", NodeTypeEnum.Function, "Dextools")] + [NodeGraphDescription("Start the Arbitrage bot")] + public class StartArbitrageBotNode : Node + { + public StartArbitrageBotNode(string id, BlockGraph graph) + : base(id, graph, typeof(StartArbitrageBotNode).Name) + { + CanBeSerialized = false; + InParameters.Add("privateKey", new NodeParameter(this, "privateKey", typeof(string), true)); + InParameters.Add("amountEthToSwap", new NodeParameter(this, "amountEthToSwap", typeof(double), true)); + InParameters.Add("amountGlqToSwap", new NodeParameter(this, "amountGlqToSwap", typeof(double), true)); + InParameters.Add("botDeadlineInSeconds", new NodeParameter(this, "botDeadlineInSeconds", typeof(int), true)); + OutParameters.Add("arbitrageBotId", new NodeParameter(this, "arbitrageBotId", typeof(string), false)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var privateKey = InParameters["privateKey"].GetValue().ToString(); + var amountEthToSwap = double.Parse(InParameters["amountEthToSwap"].GetValue().ToString(), CultureInfo.InvariantCulture); + var amountGlqToSwap = double.Parse(InParameters["amountGlqToSwap"].GetValue().ToString(), CultureInfo.InvariantCulture); + var secondsToRetryAttempt = int.Parse(InParameters["botDeadlineInSeconds"].GetValue().ToString()); + var botId = ArbitrageBotManager.Instance.StartBot(privateKey, amountEthToSwap, amountGlqToSwap, secondsToRetryAttempt, Graph); + OutParameters["arbitrageBotId"].SetValue(botId); + if (botId == null) return false; + ArbitrageBotManager.Instance.StartKeepAlive(botId, Graph); + return true; + } + } +} diff --git a/Nodes/Arbitrage/StopArbitrageBotNode.cs b/Nodes/Arbitrage/StopArbitrageBotNode.cs new file mode 100644 index 0000000..04f5e9e --- /dev/null +++ b/Nodes/Arbitrage/StopArbitrageBotNode.cs @@ -0,0 +1,31 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Nodes.Bot; + +namespace NodeBlock.Engine.Nodes.Arbitrage +{ + [NodeDefinition("StopArbitrageBotNode", "Stop Arbitrage Bot", NodeTypeEnum.Function, "Dextools")] + [NodeGraphDescription("Stop the Arbitrage bot")] + public class StopArbitrageBotNode : Node + { + + public StopArbitrageBotNode(string id, BlockGraph graph) + : base(id, graph, typeof(StopArbitrageBotNode).Name) + { + CanBeSerialized = false; + + InParameters.Add("arbitrageBotId", new NodeParameter(this, "arbitrageBotId", typeof(string), true)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var arbitrageBotId = InParameters["arbitrageBotId"].GetValue().ToString(); + ArbitrageBotManager.Instance.StopBot(arbitrageBotId, Graph); + + return true; + } + + } +} diff --git a/Nodes/Array/AddArrayElementNode.cs b/Nodes/Array/AddArrayElementNode.cs index 092d4f7..1f8c8c8 100644 --- a/Nodes/Array/AddArrayElementNode.cs +++ b/Nodes/Array/AddArrayElementNode.cs @@ -14,6 +14,7 @@ public AddArrayElementNode(string id, BlockGraph graph) { this.InParameters.Add("array", new NodeParameter(this, "array", typeof(List), true)); this.InParameters.Add("element", new NodeParameter(this, "element", typeof(object), true)); + this.InParameters.Add("sizeLimit", new NodeParameter(this, "sizeLimit", typeof(int), true)); } public List Array { get; set; } @@ -32,6 +33,14 @@ public override bool OnExecution() } else { + if (this.InParameters["sizeLimit"].GetValue() != null) + { + var sizeLimit = int.Parse(this.InParameters["sizeLimit"].GetValue().ToString()); + if(array.Count > sizeLimit) + { + array.RemoveAt(0); + } + } array.Add(this.InParameters["element"].GetValue()); } return true; diff --git a/Nodes/Array/AddDictionaryEntryNode.cs b/Nodes/Array/AddDictionaryEntryNode.cs new file mode 100644 index 0000000..03d9054 --- /dev/null +++ b/Nodes/Array/AddDictionaryEntryNode.cs @@ -0,0 +1,30 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Array +{ + [NodeDefinition("AddDictionaryEntry", "Add Dictionary Entry", NodeTypeEnum.Function, "Dictionary")] + [NodeGraphDescription("Add a entry with a key")] + public class AddDictionaryEntry : Node + { + public AddDictionaryEntry(string id, BlockGraph graph) + : base(id, graph, typeof(AddDictionaryEntry).Name) + { + this.InParameters.Add("dictionary", new NodeParameter(this, "dictionary", typeof(object), true)); + this.InParameters.Add("key", new NodeParameter(this, "key", typeof(string), true)); + this.InParameters.Add("element", new NodeParameter(this, "element", typeof(object), true)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var array = this.InParameters["dictionary"].GetValue() as Dictionary; + array[this.InParameters["key"].GetValue().ToString()] = this.InParameters["element"].GetValue(); + return true; + } + } +} diff --git a/Nodes/Array/CreateDictionaryNode.cs b/Nodes/Array/CreateDictionaryNode.cs new file mode 100644 index 0000000..2bf53c5 --- /dev/null +++ b/Nodes/Array/CreateDictionaryNode.cs @@ -0,0 +1,31 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Array +{ + [NodeDefinition("CreateDictionaryNode", "Create Dictionary", NodeTypeEnum.Function, "Dictionary")] + [NodeGraphDescription("An Dictionary can store multiple variable in it with a key")] + + public class CreateDictionaryNode : Node + { + public CreateDictionaryNode(string id, BlockGraph graph) + : base(id, graph, typeof(CreateDictionaryNode).Name) + { + this.OutParameters.Add("dictionary", new NodeParameter(this, "dictionary", typeof(object), true)); + } + + public Dictionary Array { get; set; } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + this.Array = new Dictionary(); + this.OutParameters["dictionary"].SetValue(this.Array); + return true; + } + } +} diff --git a/Nodes/Array/EachElementArrayNode.cs b/Nodes/Array/EachElementArrayNode.cs index 2af46c9..a7e6f19 100644 --- a/Nodes/Array/EachElementArrayNode.cs +++ b/Nodes/Array/EachElementArrayNode.cs @@ -10,7 +10,7 @@ namespace NodeBlock.Engine.Nodes.Array public class EachElementArrayNode : Node { public EachElementArrayNode(string id, BlockGraph graph) - : base(id, graph, typeof(GetArrayElementAtIndexNode).Name) + : base(id, graph, typeof(EachElementArrayNode).Name) { this.InParameters = new Dictionary() { diff --git a/Nodes/Array/GetDictionaryEntryNode.cs b/Nodes/Array/GetDictionaryEntryNode.cs new file mode 100644 index 0000000..cd48e1a --- /dev/null +++ b/Nodes/Array/GetDictionaryEntryNode.cs @@ -0,0 +1,37 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Array +{ + [NodeDefinition("GetDictionaryEntryNode", "Get Dictionary Entry", NodeTypeEnum.Function, "Dictionary")] + [NodeGraphDescription("Get a entry with a key")] + public class GetDictionaryEntryNode : Node + { + public GetDictionaryEntryNode(string id, BlockGraph graph) + : base(id, graph, typeof(GetDictionaryEntryNode).Name) + { + this.InParameters.Add("dictionary", new NodeParameter(this, "dictionary", typeof(object), true)); + this.InParameters.Add("key", new NodeParameter(this, "key", typeof(string), true)); + + this.OutParameters = new Dictionary() + { + { "entry", new NodeParameter(this, "entry", typeof(object), false, null, "", true) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "entry") + { + var array = this.InParameters["dictionary"].GetValue() as Dictionary; + return array[this.InParameters["key"].GetValue().ToString()]; + } + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/Array/HasKeyInDictionaryNode.cs b/Nodes/Array/HasKeyInDictionaryNode.cs new file mode 100644 index 0000000..970a5d9 --- /dev/null +++ b/Nodes/Array/HasKeyInDictionaryNode.cs @@ -0,0 +1,48 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Array +{ + [NodeDefinition("HasKeyInDictionaryNode", "Has Key In Dictionary", NodeTypeEnum.Condition, "Dictionary")] + [NodeGraphDescription("Check if a key exist in the dictionary")] + public class HasKeyInDictionaryNode : Node + { + public HasKeyInDictionaryNode(string id, BlockGraph graph) + : base(id, graph, typeof(HasKeyInDictionaryNode).Name) + { + this.InParameters = new Dictionary() + { + { "dictionary", new NodeParameter(this, "dictionary", typeof(object), true) }, + { "key", new NodeParameter(this, "key", typeof(string), true) } + }; + this.OutParameters = new Dictionary() + { + { "true", new NodeParameter(this, "true", typeof(Node), false) }, + { "false", new NodeParameter(this, "false", typeof(Node), false) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var array = this.InParameters["dictionary"].GetValue() as Dictionary; + var key = this.InParameters["key"].GetValue().ToString(); + + + if (array.ContainsKey(key)) + { + if (this.OutParameters["true"].Value == null) return true; + return (this.OutParameters["true"].Value as Node).Execute(); + } + else + { + if (this.OutParameters["false"].Value == null) return true; + return (this.OutParameters["false"].Value as Node).Execute(); + } + } + } +} diff --git a/Nodes/Bot/ArbitrageBotManager.cs b/Nodes/Bot/ArbitrageBotManager.cs new file mode 100644 index 0000000..54280c6 --- /dev/null +++ b/Nodes/Bot/ArbitrageBotManager.cs @@ -0,0 +1,19 @@ +using NodeBlock.Engine.Nodes.Dextools; +using System; + +namespace NodeBlock.Engine.Nodes.Bot +{ + public class ArbitrageBotManager : BotManagerBase + { + private static readonly Lazy _instance = new Lazy(() => new ArbitrageBotManager()); + public static ArbitrageBotManager Instance => _instance.Value; + + private ArbitrageBotManager() : base("arbitrage_api_base_url") { } + + public string StartBot(string privateKey, double amountEthToSwap, double amountGlqToSwap, int secondsToRetryAttempt, BlockGraph graph) + { + var payload = new { privateKey, amountEthToSwap, amountGlqToSwap, secondsToRetryAttempt }; + return base.StartBot(payload, graph); + } + } +} diff --git a/Nodes/Bot/BotManagerBase.cs b/Nodes/Bot/BotManagerBase.cs new file mode 100644 index 0000000..3f7d66f --- /dev/null +++ b/Nodes/Bot/BotManagerBase.cs @@ -0,0 +1,113 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net.Http; +using System.Timers; + +namespace NodeBlock.Engine.Nodes.Dextools +{ + public abstract class BotManagerBase + { + protected static readonly HttpClient _httpClient = new HttpClient(); + protected readonly string _baseUrl; + protected readonly double _intervalInSeconds; + + protected readonly List _activeBots; + protected readonly ConcurrentDictionary _keepAliveTimers; + + protected BotManagerBase(string baseUrlEnvVar) + { + _baseUrl = Environment.GetEnvironmentVariable(baseUrlEnvVar) ?? throw new InvalidOperationException("Base URL not set."); + _activeBots = new List(); + _keepAliveTimers = new ConcurrentDictionary(); + double.TryParse(Environment.GetEnvironmentVariable("keep_alive_interval"), out _intervalInSeconds); + } + + public string StartBot(object payload, BlockGraph graph) + { + try + { + var requestUrl = $"{_baseUrl}/start-bot"; + var jsonPayload = System.Text.Json.JsonSerializer.Serialize(payload); + var content = new StringContent(jsonPayload, System.Text.Encoding.UTF8, "application/json"); + var response = _httpClient.PostAsync(requestUrl, content); + var jsonObject = JObject.Parse(response.Result.Content.ReadAsStringAsync().Result); + + if (jsonObject.ContainsKey("id")) + { + var botId = jsonObject["id"]?.ToString(); + _activeBots.Add(botId); + graph.AppendLog("info", "Bot started"); + return botId; + } + + throw new Exception(jsonObject.ToString()); + } + catch (Exception ex) + { + graph.AppendLog("error", $"Bot error : {ex.Message}"); + return null; + } + } + + public void StartKeepAlive(string botId, BlockGraph graph) + { + if (!_activeBots.Contains(botId)) + { + graph.AppendLog("error", $"Cannot start KeepAlive for inactive bot ID: {botId}"); + return; + } + + var timer = new Timer(_intervalInSeconds); + timer.Elapsed += (sender, args) => KeepAlive(botId, graph); + timer.AutoReset = true; + timer.Start(); + + _keepAliveTimers.TryAdd(botId, timer); + } + + protected void KeepAlive(string botId, BlockGraph graph) + { + try + { + var requestUrl = $"{_baseUrl}/keep-alive/{botId}"; + var response = _httpClient.PostAsync(requestUrl, null); + var jsonObject = JObject.Parse(response.Result.Content.ReadAsStringAsync().Result); + graph.AppendLog("info", $"Bot info : {jsonObject}"); + } + catch (Exception ex) + { + Console.WriteLine($"Exception in KeepAlive for bot ID {botId}: {ex.Message}"); + } + } + + public void StopBot(string botId, BlockGraph graph) + { + try + { + if (_activeBots.Contains(botId)) + { + var requestUrl = $"{_baseUrl}/stop-bot/{botId}"; + _httpClient.DeleteAsync(requestUrl).Wait(); + + if (_keepAliveTimers.TryRemove(botId, out var timer)) + { + timer.Stop(); + timer.Dispose(); + } + + _activeBots.Remove(botId); + } + else + { + graph.AppendLog("error", $"Bot ID {botId} is not active or does not exist."); + } + } + catch (Exception ex) + { + graph.AppendLog("error", $"Exception while stopping the bot: {ex.Message}"); + } + } + } +} diff --git a/Nodes/Bot/TrendingBotManager.cs b/Nodes/Bot/TrendingBotManager.cs new file mode 100644 index 0000000..cd2c7f3 --- /dev/null +++ b/Nodes/Bot/TrendingBotManager.cs @@ -0,0 +1,19 @@ +using NodeBlock.Engine.Nodes.Dextools; +using System; + +namespace NodeBlock.Engine.Nodes.Bot +{ + public class TrendingBotManager : BotManagerBase + { + private static readonly Lazy _instance = new Lazy(() => new TrendingBotManager()); + public static TrendingBotManager Instance => _instance.Value; + + private TrendingBotManager() : base("sniperbot_api_base_url") { } + + public string StartBot(string privateKey, double amountEthToSwapForOneToken, double maxTokensAmount, string dextoolsApiKey, BlockGraph graph) + { + var payload = new { privateKey, amountEthToSwapForOneToken, maxTokensAmount, dextoolsApiKey }; + return base.StartBot(payload, graph); + } + } +} diff --git a/Nodes/Branch/ExecutionTimeIntervalNode.cs b/Nodes/Branch/ExecutionTimeIntervalNode.cs index 81ca00a..845ade5 100644 --- a/Nodes/Branch/ExecutionTimeIntervalNode.cs +++ b/Nodes/Branch/ExecutionTimeIntervalNode.cs @@ -29,8 +29,8 @@ public ExecutionTimeIntervalNode(string id, BlockGraph graph) public override bool OnExecution() { - var interval = int.Parse(this.InParameters["intervalInSeconds"].GetValue().ToString()) * 1000; - if (interval < 1000) interval = 1000; + var interval = int.Parse(this.InParameters["intervalInSeconds"].GetValue().ToString()); + if (interval < 1) interval = 1; if (willTickAt == 0) { willTickAt = DateTimeOffset.Now.ToUnixTimeSeconds() + interval; diff --git a/Nodes/Common/GetGraphRunningSinceNode.cs b/Nodes/Common/GetGraphRunningSinceNode.cs new file mode 100644 index 0000000..095bd8b --- /dev/null +++ b/Nodes/Common/GetGraphRunningSinceNode.cs @@ -0,0 +1,58 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Common +{ + [NodeDefinition("GetGraphRunningSinceNode", "Get Graph Running Since Time", NodeTypeEnum.Function, "Common")] + [NodeGraphDescription("Get the time since the graph has been started")] + public class GetGraphRunningSinceNode : Node + { + public GetGraphRunningSinceNode(string id, BlockGraph graph) + : base(id, graph, typeof(GetGraphRunningSinceNode).Name) + { + this.InParameters = new Dictionary(); + + this.OutParameters.Add("time", new NodeParameter(this, "time", typeof(string), false)); + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "time") + { + return ConvertToHumanText(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - this.Graph.StartedAt); + } + return base.ComputeParameterValue(parameter, value); + } + + public string ConvertToHumanText(long unixTimestampSeconds) + { + TimeSpan timeSpan = TimeSpan.FromSeconds(unixTimestampSeconds); + + string result = ""; + + if (timeSpan.Days > 0) + { + result += timeSpan.Days + "d "; + } + + if (timeSpan.Hours > 0) + { + result += timeSpan.Hours + "h "; + } + + if (timeSpan.Minutes > 0) + { + result += timeSpan.Minutes + "m "; + } + + result += timeSpan.Seconds + "s"; + + return result.TrimEnd(); + } + } +} diff --git a/Nodes/CustomEvent/CustomEventNode.cs b/Nodes/CustomEvent/CustomEventNode.cs new file mode 100644 index 0000000..cd0cb0c --- /dev/null +++ b/Nodes/CustomEvent/CustomEventNode.cs @@ -0,0 +1,53 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Timers; + +namespace NodeBlock.Engine.Nodes.CustomEvent +{ + [NodeDefinition("CustomEventNode", "Custom Event", NodeTypeEnum.Event, "Common")] + [NodeGraphDescription("Listen for custom event from the graph")] + public class CustomEventNode : Node + { + public CustomEventNode(string id, BlockGraph graph) + : base(id, graph, typeof(CustomEventNode).Name) + { + this.IsEventNode = true; + + this.InParameters.Add("eventName", new NodeParameter(this, "eventName", typeof(string), true)); + + this.OutParameters.Add("eventData", new NodeParameter(this, "eventData", typeof(object), false)); + } + + public override bool CanBeExecuted => false; + + public override bool CanExecute => true; + + private Timer timer { get; set; } + + public override void SetupEvent() + { + + } + + public override void BeginCycle() + { + this.Next(); + } + + public void OnTriggerEvent(object data) + { + var instanciatedParameters = this.InstanciatedParametersForCycle(); + instanciatedParameters["eventData"].SetValue(data); + this.Graph.AddCycle(this, instanciatedParameters); + } + + public override void OnStop() + { + + } + } +} diff --git a/Nodes/CustomEvent/TriggerCustomEventNode.cs b/Nodes/CustomEvent/TriggerCustomEventNode.cs new file mode 100644 index 0000000..1d8b9de --- /dev/null +++ b/Nodes/CustomEvent/TriggerCustomEventNode.cs @@ -0,0 +1,40 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Nodes.Functions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NodeBlock.Engine.Nodes.CustomEvent +{ + [NodeDefinition("TriggerCustomEventNode", "Trigger Custom Event", NodeTypeEnum.Function, "Common")] + [NodeGraphDescription("Trigger a custom event")] + public class TriggerCustomEventNode : Node + { + public TriggerCustomEventNode(string id, BlockGraph graph) + : base(id, graph, typeof(TriggerCustomEventNode).Name) + { + this.InParameters.Add("eventName", new NodeParameter(this, "eventName", typeof(string), true)); + this.InParameters.Add("data", new NodeParameter(this, "data", typeof(object), true)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var customEventNode = this.Graph.Nodes.FirstOrDefault(x => x.Value.NodeType == "CustomEventNode" && + x.Value.InParameters["eventName"].GetValue().ToString() == this.InParameters["eventName"].GetValue().ToString()).Value as CustomEventNode; + if (customEventNode == null) + { + this.Graph.AppendLog("warn", "Custom event " + this.InParameters["eventName"].GetValue().ToString() + " doesnt exist in the graph"); + return false; + } + + customEventNode.OnTriggerEvent(this.InParameters["data"].GetValue()); + + return true; + } + } +} diff --git a/Nodes/Date/TimeStampMsToDateNode.cs b/Nodes/Date/TimeStampMsToDateNode.cs new file mode 100644 index 0000000..41fc8a5 --- /dev/null +++ b/Nodes/Date/TimeStampMsToDateNode.cs @@ -0,0 +1,38 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Date +{ + [NodeDefinition("TimeStampMsToDateNode", "Millisecond Timestamp to Date", NodeTypeEnum.Function, "Time")] + [NodeGraphDescription("Convert a Timestamp with Milliseconds to Date")] + public class TimeStampMsToDateNode : Node + { + public TimeStampMsToDateNode(string id, BlockGraph graph) + : base(id, graph, typeof(TimeStampMsToDateNode).Name) + { + this.InParameters = new Dictionary() + { + { "timestamp", new NodeParameter(this, "timestamp", typeof(long), true) } + }; + + this.OutParameters.Add("date", new NodeParameter(this, "date", typeof(object), false)); + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "date") + { + var timestamp = long.Parse(this.InParameters["timestamp"].GetValue().ToString()); + System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc); + dtDateTime = dtDateTime.AddMilliseconds(timestamp).ToLocalTime(); + return dtDateTime; + } + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/Dextools/StartTrendingBotNode.cs b/Nodes/Dextools/StartTrendingBotNode.cs new file mode 100644 index 0000000..8f8a0e5 --- /dev/null +++ b/Nodes/Dextools/StartTrendingBotNode.cs @@ -0,0 +1,38 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Nodes.Bot; +using System.Globalization; + +namespace NodeBlock.Engine.Nodes.Dextools +{ + [NodeDefinition("StartTrendingBotNode", "Start Trending Bot", NodeTypeEnum.Function, "Dextools")] + [NodeGraphDescription("Start the trending bot")] + public class StartTrendingBotNode : Node + { + public StartTrendingBotNode(string id, BlockGraph graph) + : base(id, graph, typeof(StartTrendingBotNode).Name) + { + this.CanBeSerialized = false; + this.InParameters.Add("privateKey", new NodeParameter(this, "privateKey", typeof(string), true)); + this.InParameters.Add("amountEthToSwapForOneToken", new NodeParameter(this, "amountEthToSwapForOneToken", typeof(double), true)); + this.InParameters.Add("maxTokensAmount", new NodeParameter(this, "maxTokensAmount", typeof(double), true)); + this.InParameters.Add("dextoolsApiKey", new NodeParameter(this, "dextoolsApiKey", typeof(string), true)); + this.OutParameters.Add("trendingBotId", new NodeParameter(this, "trendingBotId", typeof(string), false)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var privateKey = this.InParameters["privateKey"].GetValue().ToString(); + var dextoolsApiKey = this.InParameters["dextoolsApiKey"].GetValue().ToString(); + var amountEthToSwapForOneToken = double.Parse(this.InParameters["amountEthToSwapForOneToken"].GetValue().ToString(), CultureInfo.InvariantCulture); + var maxTokensAmount = double.Parse(this.InParameters["maxTokensAmount"].GetValue().ToString(), CultureInfo.InvariantCulture); + var botId = TrendingBotManager.Instance.StartBot(privateKey, amountEthToSwapForOneToken, maxTokensAmount, dextoolsApiKey, Graph); + this.OutParameters["trendingBotId"].SetValue(botId); + if (botId == null) return false; + TrendingBotManager.Instance.StartKeepAlive(botId, Graph); + return true; + } + } +} diff --git a/Nodes/Dextools/StopTrendingBotNode.cs b/Nodes/Dextools/StopTrendingBotNode.cs new file mode 100644 index 0000000..ae1cfb4 --- /dev/null +++ b/Nodes/Dextools/StopTrendingBotNode.cs @@ -0,0 +1,37 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Nodes.Bot; + +namespace NodeBlock.Engine.Nodes.Dextools +{ + [NodeDefinition("StopTrendingBotNode", "Stop Trending Bot", NodeTypeEnum.Function, "Dextools")] + [NodeGraphDescription("Stop the trending bot")] + public class StopTrendingBotNode : Node + { + + public StopTrendingBotNode(string id, BlockGraph graph) + : base(id, graph, typeof(StopTrendingBotNode).Name) + { + this.CanBeSerialized = false; + + this.InParameters.Add("trendingBotId", new NodeParameter(this, "trendingBotId", typeof(string), true)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var trendingBotId = this.InParameters["trendingBotId"].GetValue().ToString(); + + if (string.IsNullOrEmpty(trendingBotId)) + { + this.Graph.AppendLog("error", "Trending bot id is empty"); + return false; + } + + TrendingBotManager.Instance.StopBot(trendingBotId, Graph); + this.Graph.AppendLog("info", $"Trending bot {trendingBotId} stopped"); + return true; + } + } +} diff --git a/Nodes/Encoding/ConvertLastBlockOutputToJsonNode.cs b/Nodes/Encoding/ConvertLastBlockOutputToJsonNode.cs index 6009013..b69cc0c 100644 --- a/Nodes/Encoding/ConvertLastBlockOutputToJsonNode.cs +++ b/Nodes/Encoding/ConvertLastBlockOutputToJsonNode.cs @@ -4,6 +4,7 @@ using System.Text; using System.Linq; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace NodeBlock.Engine.Nodes.Encoding { @@ -31,11 +32,12 @@ public override bool OnExecution() { if(this.LastExecutionFrom.CanBeSerialized) { - this.OutParameters["json"].Value = JsonConvert.SerializeObject(this.LastExecutionFrom.OutParameters.Values.ToList().Select(x => new + var jo = new JObject(); + this.LastExecutionFrom.OutParameters.Values.ToList().ForEach(x => { - key = x.Name, - value = x.GetValue() - })); + jo.Add(new JProperty(x.Name, x.GetValue())); + }); + this.OutParameters["json"].Value = jo.ToString(); } else { diff --git a/Nodes/Encoding/JSON/MergeJSONNode.cs b/Nodes/Encoding/JSON/MergeJSONNode.cs new file mode 100644 index 0000000..fde2394 --- /dev/null +++ b/Nodes/Encoding/JSON/MergeJSONNode.cs @@ -0,0 +1,41 @@ +using Newtonsoft.Json.Linq; +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Encoding.JSON +{ + [NodeDefinition("MergeJSONNode", "Merge JSON", NodeTypeEnum.Function, "JSON")] + [NodeGraphDescription("Merge two JSON into one")] + public class MergeJSONNode : Node + { + public MergeJSONNode(string id, BlockGraph graph) + : base(id, graph, typeof(MergeJSONNode).Name) + { + this.InParameters.Add("json1", new NodeParameter(this, "json1", typeof(string), true)); + this.InParameters.Add("json2", new NodeParameter(this, "json2", typeof(string), true)); + + + this.OutParameters.Add("mergedJson", new NodeParameter(this, "mergedJson", typeof(string), true)); + } + + public override bool CanExecute => true; + + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var json1 = this.InParameters["json1"].GetValue().ToString(); + var json2 = this.InParameters["json2"].GetValue().ToString(); + var jo1 = JObject.Parse(json1); + var jo2 = JObject.Parse(json2); + jo1.Merge(jo2, new JsonMergeSettings + { + MergeArrayHandling = MergeArrayHandling.Union + }); + this.OutParameters["mergedJson"].SetValue(jo1.ToString()); + return true; + } + } +} diff --git a/Nodes/Functions/AddFunctionResultNode.cs b/Nodes/Functions/AddFunctionResultNode.cs index 2c99ff8..71ca374 100644 --- a/Nodes/Functions/AddFunctionResultNode.cs +++ b/Nodes/Functions/AddFunctionResultNode.cs @@ -24,7 +24,7 @@ public override bool OnExecution() { var context = this.Graph.currentCycle.CurrentFunctionContext; var name = this.InParameters["name"].GetValue().ToString(); - var value = this.InParameters["value"].GetValue().ToString(); + var value = this.InParameters["value"].GetValue(); if(context.ReturnValues.ContainsKey(name)) { context.ReturnValues[name] = value; diff --git a/Nodes/Functions/CallFunctionNode.cs b/Nodes/Functions/CallFunctionNode.cs index 304d181..ed0737b 100644 --- a/Nodes/Functions/CallFunctionNode.cs +++ b/Nodes/Functions/CallFunctionNode.cs @@ -24,7 +24,6 @@ public CallFunctionNode(string id, BlockGraph graph) public override bool OnExecution() { - var functions = this.Graph.Nodes.ToList().FindAll(x => x.Value.NodeType == "FunctionNode"); var functionNode = this.Graph.Nodes.FirstOrDefault(x => x.Value.NodeType == "FunctionNode" && x.Value.InParameters["name"].GetValue().ToString() == this.InParameters["name"].GetValue().ToString()).Value as FunctionNode; if(functionNode == null) diff --git a/Nodes/Functions/FunctionNode.cs b/Nodes/Functions/FunctionNode.cs index 121defe..4ccc9ee 100644 --- a/Nodes/Functions/FunctionNode.cs +++ b/Nodes/Functions/FunctionNode.cs @@ -26,5 +26,32 @@ public override bool OnExecution() this.Graph.currentCycle.CurrentFunctionContext = this.Context; return true; } + + public List GetFunctionInParameters() + { + var parameters = new List(); + + this.dissectRequiredParameters(this, parameters); + + return parameters; + } + + private void dissectRequiredParameters(Node fromNode, List parameters) + { + if (fromNode.OutNode == null) return; + foreach (var inParametersNode in fromNode.OutNode.InParameters) + { + if(inParametersNode.Value.GetNode() != null) + { + if (typeof(GetFunctionParameterNode) == inParametersNode.Value.GetNode().GetType()) + { + var paramNode = inParametersNode.Value.GetNode() as GetFunctionParameterNode; + parameters.Add(paramNode.InParameters["name"].GetValue().ToString()); + } + } + } + + this.dissectRequiredParameters(fromNode.OutNode, parameters); + } } } diff --git a/Nodes/GetTimestampMsNode.cs b/Nodes/GetTimestampMsNode.cs new file mode 100644 index 0000000..c60979a --- /dev/null +++ b/Nodes/GetTimestampMsNode.cs @@ -0,0 +1,33 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes +{ + [NodeDefinition("GetTimestampMsNode", "Get Milliseconds Timestamp", NodeTypeEnum.Function, "Time")] + [NodeGraphDescription("Return the current milliseconds timestamp of the engine localtime")] + public class GetTimestampMsNode : Node + { + public GetTimestampMsNode(string id, BlockGraph graph) + : base(id, graph, typeof(GetTimestampMsNode).Name) + { + this.OutParameters = new Dictionary() + { + { "timestamp", new NodeParameter(this, "timestamp", typeof(long), false) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "timestamp") + { + return DateTimeOffset.Now.ToUnixTimeMilliseconds(); + } + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/GetTimestampMsOffsetNode.cs b/Nodes/GetTimestampMsOffsetNode.cs new file mode 100644 index 0000000..89fe023 --- /dev/null +++ b/Nodes/GetTimestampMsOffsetNode.cs @@ -0,0 +1,87 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes +{ + [NodeDefinition("GetTimestampMsOffsetNode", "Get Milliseconds Timestamp Offset", NodeTypeEnum.Function, "Time")] + [NodeGraphDescription("Return offset timestamp with milliseconds of the engine localtime")] + public class GetTimestampMsOffsetNode : Node + { + public GetTimestampMsOffsetNode(string id, BlockGraph graph) + : base(id, graph, typeof(GetTimestampMsOffsetNode).Name) + { + + this.InParameters = new Dictionary() + { + { "offset", new NodeParameter(this, "offset", typeof(string), true) } + }; + + this.OutParameters = new Dictionary() + { + { "timestamp", new NodeParameter(this, "timestamp", typeof(long), false) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "timestamp") + { + string offset = this.InParameters["offset"].GetValue().ToString(); + string durats = string.Empty; + int duration = 0; + string period = string.Empty; + + for (int i = 0; i < offset.Length; i++) + { + if (Char.IsDigit(offset[i])) + { + durats += offset[i]; + } + else if (Char.IsLetter(offset[i])) + { + period += offset[i]; + } + else + { + // + } + } + + if (durats.Length > 0) + { + duration = int.Parse(durats); + } + else + { + return false; + } + + if (period.Length > 0) + { + if (period == "w") { duration = duration * 7; } + + switch (period) + { + case "h": return DateTimeOffset.Now.AddHours(-duration).ToUnixTimeMilliseconds(); + case "d": return DateTimeOffset.Now.AddDays(-duration).ToUnixTimeMilliseconds(); + case "w": return DateTimeOffset.Now.AddDays(-duration).ToUnixTimeMilliseconds(); + case "m": return DateTimeOffset.Now.AddMonths(-duration).ToUnixTimeMilliseconds(); + case "y": return DateTimeOffset.Now.AddYears(-duration).ToUnixTimeMilliseconds(); + default: return false; + } + } + else + { + return false; + } + + } + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/GetTimestampOffsetNode.cs b/Nodes/GetTimestampOffsetNode.cs new file mode 100644 index 0000000..685c7f1 --- /dev/null +++ b/Nodes/GetTimestampOffsetNode.cs @@ -0,0 +1,87 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes +{ + [NodeDefinition("GetTimestampOffsetNode", "Get Timestamp Offset", NodeTypeEnum.Function, "Time")] + [NodeGraphDescription("Return offset timestamp of the engine localtime")] + public class GetTimestampOffsetNode : Node + { + public GetTimestampOffsetNode(string id, BlockGraph graph) + : base(id, graph, typeof(GetTimestampOffsetNode).Name) + { + + this.InParameters = new Dictionary() + { + { "offset", new NodeParameter(this, "offset", typeof(string), true) } + }; + + this.OutParameters = new Dictionary() + { + { "timestamp", new NodeParameter(this, "timestamp", typeof(long), false) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "timestamp") + { + string offset = this.InParameters["offset"].GetValue().ToString(); + string durats = string.Empty; + int duration = 0; + string period = string.Empty; + + for (int i = 0; i < offset.Length; i++) + { + if (Char.IsDigit(offset[i])) + { + durats += offset[i]; + } + else if (Char.IsLetter(offset[i])) + { + period += offset[i]; + } + else + { + // + } + } + + if (durats.Length > 0) + { + duration = int.Parse(durats); + } + else + { + return false; + } + + if (period.Length > 0) + { + if (period == "w") { duration = duration * 7; } + + switch (period) + { + case "h": return DateTimeOffset.Now.AddHours(-duration).ToUnixTimeSeconds(); + case "d": return DateTimeOffset.Now.AddDays(-duration).ToUnixTimeSeconds(); + case "w": return DateTimeOffset.Now.AddHours(duration).ToUnixTimeSeconds(); + case "m": return DateTimeOffset.Now.AddMonths(-duration).ToUnixTimeSeconds(); + case "y": return DateTimeOffset.Now.AddYears(-duration).ToUnixTimeSeconds(); + default: return false; + } + } + else + { + return false; + } + + } + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/HTTP/DeleteHTTPNode.cs b/Nodes/HTTP/DeleteHTTPNode.cs index 4d02954..3ecb7dc 100644 --- a/Nodes/HTTP/DeleteHTTPNode.cs +++ b/Nodes/HTTP/DeleteHTTPNode.cs @@ -50,9 +50,9 @@ public override bool OnExecution() var responseString = requestUrl.Result.Content.ReadAsStringAsync(); responseString.Wait(1000); this.OutParameters["result"].Value = responseString.Result; - } + } } - catch (Exception ex) + catch (Exception) { if (this.OutParameters["exception"].Value != null) { diff --git a/Nodes/HTTP/GetHTTPNode.cs b/Nodes/HTTP/GetHTTPNode.cs index 05c43ee..8a626fb 100644 --- a/Nodes/HTTP/GetHTTPNode.cs +++ b/Nodes/HTTP/GetHTTPNode.cs @@ -51,9 +51,9 @@ public override bool OnExecution() var responseString = requestUrl.Result.Content.ReadAsStringAsync(); responseString.Wait(1000); this.OutParameters["result"].Value = responseString.Result; - } + } } - catch(Exception ex) + catch(Exception) { if (this.OutParameters["exception"].Value != null) { diff --git a/Nodes/HTTP/PostHTTPNode.cs b/Nodes/HTTP/PostHTTPNode.cs index 2417819..51b20e6 100644 --- a/Nodes/HTTP/PostHTTPNode.cs +++ b/Nodes/HTTP/PostHTTPNode.cs @@ -53,9 +53,9 @@ public override bool OnExecution() var responseString = requestUrl.Result.Content.ReadAsStringAsync(); responseString.Wait(1000); this.OutParameters["result"].Value = responseString.Result; - } + } } - catch (Exception ex) + catch (Exception) { if (this.OutParameters["exception"].Value != null) { diff --git a/Nodes/HTTP/PutHTTPNode.cs b/Nodes/HTTP/PutHTTPNode.cs index a40e7cd..86f10b2 100644 --- a/Nodes/HTTP/PutHTTPNode.cs +++ b/Nodes/HTTP/PutHTTPNode.cs @@ -54,9 +54,9 @@ public override bool OnExecution() var responseString = requestUrl.Result.Content.ReadAsStringAsync(); responseString.Wait(1000); this.OutParameters["result"].Value = responseString.Result; - } + } } - catch (Exception ex) + catch (Exception) { if (this.OutParameters["exception"].Value != null) { diff --git a/Nodes/Math/RandNode.cs b/Nodes/Math/RandNode.cs new file mode 100644 index 0000000..734d493 --- /dev/null +++ b/Nodes/Math/RandNode.cs @@ -0,0 +1,43 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Math +{ + [NodeDefinition("RandNode", "Random Number", NodeTypeEnum.Function, "Math")] + [NodeGraphDescription("Get a random number")] + public class RandNode : Node + { + public RandNode(string id, BlockGraph graph) + : base(id, graph, typeof(RandNode).Name) + { + this.InParameters = new Dictionary() + { + { "min", new NodeParameter(this, "min", typeof(int), true) }, + { "max", new NodeParameter(this, "max", typeof(int), true) } + }; + + this.OutParameters = new Dictionary() + { + { "number", new NodeParameter(this, "number", typeof(int), false, null, "", true) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "number") + { + var min = int.Parse(this.InParameters["min"].GetValue().ToString()); + var max = int.Parse(this.InParameters["max"].GetValue().ToString()); + var rand = new Random(); + + return rand.Next(min, max); + } + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/PrintNode.cs b/Nodes/PrintNode.cs index 2416cb9..a99e32e 100644 --- a/Nodes/PrintNode.cs +++ b/Nodes/PrintNode.cs @@ -5,7 +5,7 @@ namespace NodeBlock.Engine.Nodes { - [NodeDefinition("PrintNode", "Print", NodeTypeEnum.Function, "Log")] + [NodeDefinition("PrintNode", "Print", NodeTypeEnum.Function, "Log", CustomIcon = "print")] [NodeGraphDescription("Display a message in the console logs")] [NodeGasConfiguration("10000000000000")] public class PrintNode : Node { @@ -23,6 +23,7 @@ public PrintNode(string id, BlockGraph graph) public override bool OnExecution() { + if (this.InParameters["message"].GetValue().ToString().Trim() == string.Empty) return false; this.Graph.AppendLog("info", this.InParameters["message"].GetValue().ToString()); return true; } diff --git a/Nodes/SecretStringNode.cs b/Nodes/SecretStringNode.cs new file mode 100644 index 0000000..fb3ba83 --- /dev/null +++ b/Nodes/SecretStringNode.cs @@ -0,0 +1,22 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes +{ + [NodeDefinition("SecretStringNode", "Secret String", NodeTypeEnum.Variable, "Base Variable")] + [NodeGraphDescription("A string that value are hidden in the IDE")] + [NodeIDEParameters(IsSecretInput = true)] + public class SecretStringNode : Node + { + public SecretStringNode(string id, BlockGraph graph) + : base(id, graph, typeof(SecretStringNode).Name) + { + this.OutParameters.Add("value", new NodeParameter(this, "value", typeof(string), true)); + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + } +} diff --git a/Nodes/Storage/GetKeyItemNode.cs b/Nodes/Storage/GetKeyItemNode.cs index f774649..e6d2cd6 100644 --- a/Nodes/Storage/GetKeyItemNode.cs +++ b/Nodes/Storage/GetKeyItemNode.cs @@ -1,4 +1,5 @@ using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Storage; using NodeBlock.Engine.Storage.Redis; using System; using System.Collections.Generic; @@ -33,7 +34,7 @@ public override object ComputeParameterValue(NodeParameter parameter, object val { if (parameter.Name == "value") { - var v = RedisStorage.GetGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString()); + var v = StorageManager.GetStorage().GetGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString()); return v; } diff --git a/Nodes/Storage/GetWalletKeyItemNode.cs b/Nodes/Storage/GetWalletKeyItemNode.cs new file mode 100644 index 0000000..26239f0 --- /dev/null +++ b/Nodes/Storage/GetWalletKeyItemNode.cs @@ -0,0 +1,43 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Storage; +using NodeBlock.Engine.Storage.Redis; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Storage +{ + [NodeDefinition("GetWalletKeyItemNode", "Get Wallet Key Item", NodeTypeEnum.Function, "Storage")] + [NodeGraphDescription("Return a specific key from the Redis storage allocated for the wallet context")] + [NodeGasConfiguration("100000000000000")] + public class GetWalletKeyItemNode : Node + { + public GetWalletKeyItemNode(string id, BlockGraph graph) + : base(id, graph, typeof(GetWalletKeyItemNode).Name) + { + this.InParameters = new Dictionary() + { + { "key", new NodeParameter(this, "key", typeof(string), true) } + }; + + this.OutParameters = new Dictionary() + { + { "value", new NodeParameter(this, "value", typeof(string), true) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "value") + { + var v = StorageManager.GetStorage().GetWalletGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString()); + return v; + } + + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/Storage/KeyItemExistNode.cs b/Nodes/Storage/KeyItemExistNode.cs index 393b718..415858f 100644 --- a/Nodes/Storage/KeyItemExistNode.cs +++ b/Nodes/Storage/KeyItemExistNode.cs @@ -1,4 +1,5 @@ using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Storage; using NodeBlock.Engine.Storage.Redis; using System; using System.Collections.Generic; @@ -30,8 +31,7 @@ public KeyItemExistNode(string id, BlockGraph graph) public override bool OnExecution() { - // return RedisStorage.GetGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString()); - if (RedisStorage.GraphKeyItemExist(this.Graph, this.InParameters["key"].GetValue().ToString())) + if (StorageManager.GetStorage().GraphKeyItemExist(this.Graph, this.InParameters["key"].GetValue().ToString())) { if (this.OutParameters["true"].Value == null) return true; return (this.OutParameters["true"].Value as Node).Execute(); diff --git a/Nodes/Storage/KeyWalletItemExistNode.cs b/Nodes/Storage/KeyWalletItemExistNode.cs new file mode 100644 index 0000000..1f01f93 --- /dev/null +++ b/Nodes/Storage/KeyWalletItemExistNode.cs @@ -0,0 +1,46 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Storage; +using NodeBlock.Engine.Storage.Redis; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Storage +{ + [NodeDefinition("KeyWalletItemExistNode", "Is Key Wallet Item Exist", NodeTypeEnum.Function, "Storage")] + [NodeGraphDescription("Check if a specific key from the Redis storage exist in the wallet context storage")] + public class KeyWalletItemExistNode : Node + { + public KeyWalletItemExistNode(string id, BlockGraph graph) + : base(id, graph, typeof(KeyWalletItemExistNode).Name) + { + this.InParameters = new Dictionary() + { + { "key", new NodeParameter(this, "key", typeof(string), true) }, + }; + + this.OutParameters = new Dictionary() + { + { "true", new NodeParameter(this, "true", typeof(Node), false) }, + { "false", new NodeParameter(this, "false", typeof(Node), false) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + if (StorageManager.GetStorage().GraphWalletKeyItemExist(this.Graph, this.InParameters["key"].GetValue().ToString())) + { + if (this.OutParameters["true"].Value == null) return true; + return (this.OutParameters["true"].Value as Node).Execute(); + } + else + { + if (this.OutParameters["false"].Value == null) return true; + return (this.OutParameters["false"].Value as Node).Execute(); + } + } + } +} diff --git a/Nodes/Storage/SaveKeyItemNode.cs b/Nodes/Storage/SaveKeyItemNode.cs index ee2e9b6..dd6471b 100644 --- a/Nodes/Storage/SaveKeyItemNode.cs +++ b/Nodes/Storage/SaveKeyItemNode.cs @@ -1,4 +1,5 @@ using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Storage; using NodeBlock.Engine.Storage.Redis; using System; using System.Collections.Generic; @@ -33,7 +34,7 @@ public SaveKeyItemNode(string id, BlockGraph graph) public override bool OnExecution() { var value = this.InParameters["value"].GetValue().ToString(); - RedisStorage.SetGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString(), value); + StorageManager.GetStorage().SetGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString(), value); return true; } } diff --git a/Nodes/Storage/SaveWalletKeyItemNode.cs b/Nodes/Storage/SaveWalletKeyItemNode.cs new file mode 100644 index 0000000..9cdd5fd --- /dev/null +++ b/Nodes/Storage/SaveWalletKeyItemNode.cs @@ -0,0 +1,40 @@ +using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Storage; +using NodeBlock.Engine.Storage.Redis; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Storage +{ + [NodeDefinition("SaveWalletKeyItemNode", "Save Wallet Key Item", NodeTypeEnum.Function, "Storage")] + [NodeGraphDescription("Save a specific key in the Redis storage allocated for the wallet context")] + [NodeGasConfiguration("1000000000000000")] + public class SaveWalletKeyItemNode : Node + { + public SaveWalletKeyItemNode(string id, BlockGraph graph) + : base(id, graph, typeof(SaveWalletKeyItemNode).Name) + { + this.InParameters = new Dictionary() + { + { "key", new NodeParameter(this, "key", typeof(string), true) }, + { "value", new NodeParameter(this, "value", typeof(object), true) } + }; + + this.OutParameters = new Dictionary() + { + + }; + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var value = this.InParameters["value"].GetValue().ToString(); + StorageManager.GetStorage().SetGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString(), value); + return true; + } + } +} diff --git a/Nodes/Text/StartWithNode.cs b/Nodes/Text/StartWithNode.cs new file mode 100644 index 0000000..a86a813 --- /dev/null +++ b/Nodes/Text/StartWithNode.cs @@ -0,0 +1,44 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Text +{ + [NodeDefinition("StartWithNode", "String Start With", NodeTypeEnum.Condition, "String")] + [NodeGraphDescription("Trigger different node path on a condition based on characters at the start of a string")] + public class StartWithNode : Node + { + public StartWithNode(string id, BlockGraph graph) + : base(id, graph, typeof(StartWithNode).Name) + { + this.InParameters = new Dictionary() + { + { "text", new NodeParameter(this, "text", typeof(string), true) }, + { "startText", new NodeParameter(this, "startText", typeof(string), true) } + }; + this.OutParameters = new Dictionary() + { + { "true", new NodeParameter(this, "true", typeof(Node), false) }, + { "false", new NodeParameter(this, "false", typeof(Node), false) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + if (this.InParameters["text"].GetValue().ToString().StartsWith(this.InParameters["startText"].GetValue().ToString())) + { + if (this.OutParameters["true"].Value == null) return true; + return (this.OutParameters["true"].Value as Node).Execute(); + } + else + { + if (this.OutParameters["false"].Value == null) return true; + return (this.OutParameters["false"].Value as Node).Execute(); + } + } + } +} diff --git a/Nodes/Text/StringContainsMultiNode.cs b/Nodes/Text/StringContainsMultiNode.cs new file mode 100644 index 0000000..ee6199b --- /dev/null +++ b/Nodes/Text/StringContainsMultiNode.cs @@ -0,0 +1,45 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Text +{ + [NodeDefinition("StringContainsMultiNode", "String Contains Multiple", NodeTypeEnum.Condition, "String")] + [NodeGraphDescription("Check if a string contains any item in array")] + [NodeIDEParameters(Hidden = false)] + public class StringContainsMultiNode : Node + { + public StringContainsMultiNode(string id, BlockGraph graph) + : base(id, graph, typeof(StringContainsMultiNode).Name) + { + this.InParameters.Add("string", new NodeParameter(this, "string", typeof(string), true)); + this.InParameters.Add("searchItems", new NodeParameter(this, "searchItems", typeof(string), true)); + + this.OutParameters = new Dictionary() + { + { "true", new NodeParameter(this, "true", typeof(Node), false) }, + { "false", new NodeParameter(this, "false", typeof(Node), false) } + }; + } + + public override bool CanBeExecuted => true; + + public override bool CanExecute => false; + + public override bool OnExecution() + { + var original = this.InParameters["string"].GetValue().ToString(); + var items = this.InParameters["searchItems"].GetValue().ToString().Split(","); + foreach (var item in items) + { + if (original.Contains(item)) + { + return (this.OutParameters["true"].Value as Node).Execute(); + } + } + + return (this.OutParameters["false"].Value as Node).Execute(); + } + } +} diff --git a/Nodes/Text/StringGetAllMatchUsingRegexNode.cs b/Nodes/Text/StringGetAllMatchUsingRegexNode.cs new file mode 100644 index 0000000..94c23d7 --- /dev/null +++ b/Nodes/Text/StringGetAllMatchUsingRegexNode.cs @@ -0,0 +1,48 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace NodeBlock.Engine.Nodes.Text +{ + [NodeDefinition("StringGetAllMatchUsingRegexNode", "String Get All Match Using Regex", NodeTypeEnum.Function, "String")] + [NodeGraphDescription("Get All Matches in a string using regular expression")] + [NodeIDEParameters(Hidden = false)] + public class StringGetAllMatchUsingRegexNode : Node + { + public StringGetAllMatchUsingRegexNode(string id, BlockGraph graph) + : base(id, graph, typeof(StringGetAllMatchUsingRegexNode).Name) + { + this.InParameters.Add("string", new NodeParameter(this, "string", typeof(string), true)); + this.InParameters.Add("regex", new NodeParameter(this, "regex", typeof(string), true)); + + this.OutParameters = new Dictionary() + { + { "each", new NodeParameter(this, "each", typeof(Node), false) }, + { "item", new NodeParameter(this, "item", typeof(object), false) } + }; + } + + public override bool CanBeExecuted => true; + + public override bool CanExecute => true; + + public override bool OnExecution() + { + var original = this.InParameters["string"].GetValue().ToString(); + var regex = this.InParameters["regex"].GetValue().ToString(); + + var eachNode = this.OutParameters["each"].Value as Node; + + var array = Regex.Matches(original, regex); + + foreach (var obj in array) + { + this.OutParameters["item"].SetValue(obj); + eachNode.Execute(); + } + return true; + } + } +} diff --git a/Nodes/Text/StringGetMatchUsingRegexNode.cs b/Nodes/Text/StringGetMatchUsingRegexNode.cs new file mode 100644 index 0000000..eedc323 --- /dev/null +++ b/Nodes/Text/StringGetMatchUsingRegexNode.cs @@ -0,0 +1,41 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace NodeBlock.Engine.Nodes.Text +{ + [NodeDefinition("StringGetMatchUsingRegexNode", "String Get Match Using Regex", NodeTypeEnum.Function, "String")] + [NodeGraphDescription("Extract match in string using regular expression")] + [NodeIDEParameters(Hidden = false)] + public class StringGetMatchUsingRegexNode : Node + { + public StringGetMatchUsingRegexNode(string id, BlockGraph graph) + : base(id, graph, typeof(StringGetMatchUsingRegexNode).Name) + { + this.InParameters.Add("string", new NodeParameter(this, "string", typeof(string), true)); + this.InParameters.Add("regex", new NodeParameter(this, "regex", typeof(string), true)); + + this.OutParameters = new Dictionary() + { + { "returnText", new NodeParameter(this, "returnText", typeof(string), false) } + }; + } + + public override bool CanBeExecuted => false; + + public override bool CanExecute => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "returnText") + { + var original = this.InParameters["string"].GetValue().ToString(); + var regex = this.InParameters["regex"].GetValue().ToString(); + return Regex.Match(original, regex).Value; + } + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/Text/StringMatchesRegexNode.cs b/Nodes/Text/StringMatchesRegexNode.cs new file mode 100644 index 0000000..855087e --- /dev/null +++ b/Nodes/Text/StringMatchesRegexNode.cs @@ -0,0 +1,46 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace NodeBlock.Engine.Nodes.Text +{ + [NodeDefinition("StringMatchesRegexNode", "String Matches Regex", NodeTypeEnum.Condition, "String")] + [NodeGraphDescription("Check if a string matches a regular expression")] + [NodeIDEParameters(Hidden = false)] + public class StringMatchesRegexNode : Node + { + public StringMatchesRegexNode(string id, BlockGraph graph) + : base(id, graph, typeof(StringMatchesRegexNode).Name) + { + this.InParameters.Add("string", new NodeParameter(this, "string", typeof(string), true)); + this.InParameters.Add("regex", new NodeParameter(this, "regex", typeof(string), true)); + + this.OutParameters = new Dictionary() + { + { "true", new NodeParameter(this, "true", typeof(Node), false) }, + { "false", new NodeParameter(this, "false", typeof(Node), false) } + }; + } + + public override bool CanBeExecuted => true; + + public override bool CanExecute => false; + + public override bool OnExecution() + { + var original = this.InParameters["string"].GetValue().ToString(); + var regex = this.InParameters["regex"].GetValue().ToString(); + + Regex r = new Regex(@regex); + + if (r.Match(original).Success) + { + return (this.OutParameters["true"].Value as Node).Execute(); + } + + return (this.OutParameters["false"].Value as Node).Execute(); + } + } +} diff --git a/Nodes/Text/StringReplaceUsingRegexNode.cs b/Nodes/Text/StringReplaceUsingRegexNode.cs new file mode 100644 index 0000000..38681aa --- /dev/null +++ b/Nodes/Text/StringReplaceUsingRegexNode.cs @@ -0,0 +1,43 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace NodeBlock.Engine.Nodes.Text +{ + [NodeDefinition("StringReplaceUsingRegexNode", "String Replace Using Regex", NodeTypeEnum.Function, "String")] + [NodeGraphDescription("Replace match in string using regular expression")] + [NodeIDEParameters(Hidden = false)] + public class StringReplaceUsingRegexNode : Node + { + public StringReplaceUsingRegexNode(string id, BlockGraph graph) + : base(id, graph, typeof(StringReplaceUsingRegexNode).Name) + { + this.InParameters.Add("string", new NodeParameter(this, "string", typeof(string), true)); + this.InParameters.Add("regex", new NodeParameter(this, "regex", typeof(string), true)); + this.InParameters.Add("replace", new NodeParameter(this, "replace", typeof(string), true)); + + this.OutParameters = new Dictionary() + { + { "returnText", new NodeParameter(this, "returnText", typeof(string), false) } + }; + } + + public override bool CanBeExecuted => false; + + public override bool CanExecute => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "returnText") + { + var original = this.InParameters["string"].GetValue().ToString(); + var regex = this.InParameters["regex"].GetValue().ToString(); + var replace = this.InParameters["replace"].GetValue().ToString(); + return Regex.Replace(original, regex, replace); + } + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/Text/StringSplitNode.cs b/Nodes/Text/StringSplitNode.cs new file mode 100644 index 0000000..078160d --- /dev/null +++ b/Nodes/Text/StringSplitNode.cs @@ -0,0 +1,46 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Text +{ + [NodeDefinition("StringSplitNode", "String Split", NodeTypeEnum.Function, "String")] + [NodeGraphDescription("Split String By Character")] + [NodeIDEParameters(Hidden = false)] + public class StringSplitNode : Node + { + public StringSplitNode(string id, BlockGraph graph) + : base(id, graph, typeof(StringSplitNode).Name) + { + this.InParameters.Add("original", new NodeParameter(this, "original", typeof(string), true)); + this.InParameters.Add("splitUsing", new NodeParameter(this, "splitUsing", typeof(string), true)); + + this.OutParameters = new Dictionary() + { + { "each", new NodeParameter(this, "each", typeof(Node), false) }, + { "item", new NodeParameter(this, "item", typeof(object), false) }, + }; + } + + public override bool CanBeExecuted => true; + + public override bool CanExecute => true; + + public override bool OnExecution() + { + + + if (this.OutParameters["each"].Value == null) return true; + var array = this.InParameters["original"].GetValue().ToString().Split(this.InParameters["splitUsing"].GetValue().ToString()); + + var eachNode = this.OutParameters["each"].Value as Node; + foreach (var obj in array) + { + this.OutParameters["item"].SetValue(obj); + eachNode.Execute(); + } + return true; + } + } +} diff --git a/Nodes/Text/SubstringNode.cs b/Nodes/Text/SubstringNode.cs new file mode 100644 index 0000000..01cd848 --- /dev/null +++ b/Nodes/Text/SubstringNode.cs @@ -0,0 +1,42 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.Text +{ + [NodeDefinition("SubstringNode", "Substring", NodeTypeEnum.Function, "String")] + [NodeGraphDescription("Substring a text")] + public class SubstringNode : Node + { + public SubstringNode(string id, BlockGraph graph) + : base(id, graph, typeof(SubstringNode).Name) + { + this.InParameters = new Dictionary() + { + { "input", new NodeParameter(this, "input", typeof(string), true) }, + { "startIndex", new NodeParameter(this, "startIndex", typeof(int), true) } + }; + + this.OutParameters = new Dictionary() + { + { "string", new NodeParameter(this, "string", typeof(string), false, null, "", true) } + }; + } + + public override bool CanExecute => false; + public override bool CanBeExecuted => false; + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "string") + { + var input = this.InParameters["input"].GetValue().ToString(); + var startIndex = int.Parse(this.InParameters["startIndex"].GetValue().ToString()); + + return input.Substring(startIndex); + } + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/TimerNode.cs b/Nodes/TimerNode.cs index 7c4159b..fcee667 100644 --- a/Nodes/TimerNode.cs +++ b/Nodes/TimerNode.cs @@ -7,7 +7,7 @@ namespace NodeBlock.Engine.Nodes { - [NodeDefinition("TimerNode", "Timer", NodeTypeEnum.Event, "Time")] + [NodeDefinition("TimerNode", "Timer", NodeTypeEnum.Event, "Time", CustomIcon = "timer")] [NodeGasConfiguration("10000000000000")] [NodeGraphDescription("Start a timer that will init a new execution cycle, from in parameter specified time.")] public class TimerNode : Node @@ -18,6 +18,7 @@ public TimerNode(string id, BlockGraph graph) this.IsEventNode = true; this.InParameters.Add("intervalInSeconds", new NodeParameter(this, "intervalInSeconds", typeof(int), true)); + this.InParameters.Add("triggerAtStart", new NodeParameter(this, "triggerAtStart", typeof(bool), true)); } public override bool CanBeExecuted => false; @@ -38,7 +39,13 @@ public override void SetupEvent() timer.Enabled = true; timer.Start(); - this.Graph.AddCycle(this); + if(this.InParameters.ContainsKey("triggerAtStart")) + { + if (bool.Parse(this.InParameters["triggerAtStart"].GetValue().ToString())) + { + this.Graph.AddCycle(this); + } + } } private void Timer_Elapsed(object sender, ElapsedEventArgs e) diff --git a/Nodes/Vars/GetVariable.cs b/Nodes/Vars/GetVariable.cs index 176c1a2..eea0331 100644 --- a/Nodes/Vars/GetVariable.cs +++ b/Nodes/Vars/GetVariable.cs @@ -5,7 +5,7 @@ namespace NodeBlock.Engine.Nodes.Vars { - [NodeDefinition("GetVariable", "Get variable", NodeTypeEnum.Function, "Base Variable")] + [NodeDefinition("GetVariable", "Get variable", NodeTypeEnum.Variable, "Base Variable")] [NodeGraphDescription("Return the value of the variable pre computed from a Set variable block")] public class GetVariable : Node { @@ -21,7 +21,8 @@ public GetVariable(string id, BlockGraph graph) this.OutParameters = new Dictionary() { - { "value", new NodeParameter(this, "value", typeof(object), true) } + { "value", new NodeParameter(this, "value", typeof(object), true) }, + { "directName", new NodeParameter(this, "directName", typeof(string), true) } }; } @@ -32,7 +33,14 @@ public override object ComputeParameterValue(NodeParameter parameter, object val { if (parameter.Name == "value") { - return this.Graph.MemoryVariables[this.InParameters["variableName"].GetValue().ToString()]; + if(this.InParameters["variableName"].GetValue() == null) + { + return this.Graph.MemoryVariables[this.OutParameters["directName"].GetValue().ToString()]; + } + else + { + return this.Graph.MemoryVariables[this.InParameters["variableName"].GetValue().ToString()]; + } } return base.ComputeParameterValue(parameter, value); diff --git a/Nodes/WaitNode.cs b/Nodes/WaitNode.cs new file mode 100644 index 0000000..c0af5cc --- /dev/null +++ b/Nodes/WaitNode.cs @@ -0,0 +1,34 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NodeBlock.Engine.Nodes +{ + [NodeDefinition("WaitNode", "Wait", NodeTypeEnum.Function, "Time", CustomIcon = "timer")] + [NodeGraphDescription("Wait a amount of time before executing")] + public class WaitNode : Node + { + public WaitNode(string id, BlockGraph graph) + : base(id, graph, typeof(WaitNode).Name) + { + this.InParameters = new Dictionary() + { + { "timeInMs", new NodeParameter(this, "timeInMs", typeof(int), true) }, + + }; + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + System.Threading.Thread.Sleep(int.Parse(this.InParameters["timeInMs"].GetValue().ToString())); + return true; + + } + } +} diff --git a/Nodes/WebSocket/WebSocketClientCloseNode.cs b/Nodes/WebSocket/WebSocketClientCloseNode.cs new file mode 100644 index 0000000..3910942 --- /dev/null +++ b/Nodes/WebSocket/WebSocketClientCloseNode.cs @@ -0,0 +1,34 @@ +using System.Net.WebSockets; +using System.Threading.Tasks; +using NodeBlock.Engine; +using NodeBlock.Engine.Attributes; + +namespace NodeBlock.Engine.Nodes.WebSocket +{ + [NodeDefinition("WebSocketClientCloseNode", "WebSocket Client Close", NodeTypeEnum.Function, "WebSocket")] + [NodeGraphDescription("Close a WebSocket client connection")] + public class WebSocketClientCloseNode : Node + { + public WebSocketClientCloseNode(string id, BlockGraph graph) + : base(id, graph, typeof(WebSocketClientCloseNode).Name) + { + this.InParameters.Add("webSocketClient", new NodeParameter(this, "webSocketClient", typeof(WebSocketClientConnectorNode), true)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var webSocketClient = this.InParameters["webSocketClient"].GetValue() as WebSocketClientConnectorNode; + + if (webSocketClient.WebSocketClient.State == System.Net.WebSockets.WebSocketState.Open) + { + webSocketClient.WebSocketClient.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Closing from node", System.Threading.CancellationToken.None).GetAwaiter().GetResult(); + return true; + } + + return false; + } + } +} diff --git a/Nodes/WebSocket/WebSocketClientConnectorNode.cs b/Nodes/WebSocket/WebSocketClientConnectorNode.cs new file mode 100644 index 0000000..4ae258c --- /dev/null +++ b/Nodes/WebSocket/WebSocketClientConnectorNode.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using NodeBlock.Engine; +using NodeBlock.Engine.Attributes; + + +namespace NodeBlock.Engine.Nodes.WebSocket +{ + [NodeDefinition("WebSocketClientConnectorNode", "WebSocket Client Connector", NodeTypeEnum.Connector, "WebSocket")] + [NodeGraphDescription("Create a WebSocket client that connects to a WebSocket server")] + public class WebSocketClientConnectorNode : Node + { + public WebSocketClientConnectorNode(string id, BlockGraph graph) + : base(id, graph, typeof(WebSocketClientConnectorNode).Name) + { + this.CanBeSerialized = false; + + this.InParameters.Add("uri", new NodeParameter(this, "uri", typeof(string), true)); + this.OutParameters.Add("webSocketClient", new NodeParameter(this, "webSocketClient", typeof(WebSocketClientConnectorNode), true)); + this.OutParameters.Add("onError", new NodeParameter(this, "onError", typeof(Node), false)); + } + + public event EventHandler OnDataReceived; + public event EventHandler OnClose; + + public string Uri { get; set; } + public ClientWebSocket WebSocketClient { get; set; } + + public override bool CanBeExecuted => false; + public override bool CanExecute => true; + + public override void SetupConnector() + { + Uri = this.InParameters["uri"].GetValue().ToString(); + WebSocketClient = new ClientWebSocket(); + + bool connected = ConnectToWebSocketServer(); + + if (connected) + { + Task.Run(() => ReceiveDataAsync(WebSocketClient)); + this.Next(); + } + else + { + TriggerOnErrorNode(); + } + } + + private bool ConnectToWebSocketServer() + { + try + { + WebSocketClient.ConnectAsync(new Uri(Uri), CancellationToken.None).GetAwaiter().GetResult(); + return true; + } + catch + { + return false; + } + } + + private void TriggerOnErrorNode() + { + if (this.OutParameters["onError"].Value == null) return; + (this.OutParameters["onError"].Value as Node).Execute(); + } + + private async Task ReceiveDataAsync(ClientWebSocket clientWebSocket) + { + var buffer = new byte[1024]; + + while (clientWebSocket.State == WebSocketState.Open) + { + var result = await clientWebSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + if (result.MessageType == WebSocketMessageType.Text) + { + var data = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count); + OnDataReceived?.Invoke(this, data); + } + } + if (clientWebSocket.State == WebSocketState.Closed) + { + OnClose?.Invoke(this, EventArgs.Empty); + } + } + + public override object ComputeParameterValue(NodeParameter parameter, object value) + { + if (parameter.Name == "webSocketClient") + { + return this; + } + + return base.ComputeParameterValue(parameter, value); + } + } +} diff --git a/Nodes/WebSocket/WebSocketClientOnDisconnectNode.cs b/Nodes/WebSocket/WebSocketClientOnDisconnectNode.cs new file mode 100644 index 0000000..31e457c --- /dev/null +++ b/Nodes/WebSocket/WebSocketClientOnDisconnectNode.cs @@ -0,0 +1,44 @@ +using NodeBlock.Engine.Attributes; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Nodes.WebSocket +{ + [NodeDefinition("WebSocketClientOnDisconnectNode", "On WebSocket Client Disconnect", NodeTypeEnum.Event, "WebSocket")] + [NodeGraphDescription("Triggered when the WebSocket client is disconnected from the server")] + public class WebSocketClientOnDisconnectNode : Node + { + public WebSocketClientOnDisconnectNode(string id, BlockGraph graph) + : base(id, graph, typeof(WebSocketClientOnDisconnectNode).Name) + { + this.IsEventNode = true; + + this.InParameters.Add("webSocketClient", new NodeParameter(this, "webSocketClient", typeof(WebSocketClientConnectorNode), true)); + } + + public override bool CanBeExecuted => false; + + public override bool CanExecute => true; + + public override void SetupEvent() + { + WebSocketClientConnectorNode clientNode = this.InParameters["webSocketClient"].GetValue() as WebSocketClientConnectorNode; + + clientNode.OnClose += ClientNode_OnClose; + } + + private void ClientNode_OnClose(object sender, EventArgs e) + { + WebSocketClientConnectorNode clientNode = this.InParameters["webSocketClient"].GetValue() as WebSocketClientConnectorNode; + clientNode.OnClose -= ClientNode_OnClose; + var instanciatedParameters = this.InstanciatedParametersForCycle(); + this.Graph.AddCycle(this, instanciatedParameters); + } + + public override void BeginCycle() + { + this.Next(); + } + } +} diff --git a/Nodes/WebSocket/WebSocketReceiveDataEventNode.cs b/Nodes/WebSocket/WebSocketReceiveDataEventNode.cs new file mode 100644 index 0000000..7e6f2fb --- /dev/null +++ b/Nodes/WebSocket/WebSocketReceiveDataEventNode.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using NodeBlock.Engine; +using NodeBlock.Engine.Attributes; + +namespace NodeBlock.Engine.Nodes.WebSocket +{ + [NodeDefinition("WebSocketReceiveDataEventNode", "WebSocket Receive Data Event", NodeTypeEnum.Event, "WebSocket")] + [NodeGraphDescription("Trigger events when data is received from the WebSocket server")] + public class WebSocketReceiveDataEventNode : Node + { + public WebSocketReceiveDataEventNode(string id, BlockGraph graph) + : base(id, graph, typeof(WebSocketReceiveDataEventNode).Name) + { + this.IsEventNode = true; + + this.InParameters.Add("webSocketClient", new NodeParameter(this, "webSocketClient", typeof(WebSocketClientConnectorNode), true)); + + this.OutParameters.Add("data", new NodeParameter(this, "data", typeof(string), false)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => false; + + public override void SetupEvent() + { + WebSocketClientConnectorNode webSocketClientNode = this.InParameters["webSocketClient"].GetValue() as WebSocketClientConnectorNode; + webSocketClientNode.OnDataReceived += WebSocketClientNode_OnDataReceived; + } + + public override void OnStop() + { + WebSocketClientConnectorNode webSocketClientNode = this.InParameters["webSocketClient"].GetValue() as WebSocketClientConnectorNode; + webSocketClientNode.OnDataReceived -= WebSocketClientNode_OnDataReceived; + } + + private void WebSocketClientNode_OnDataReceived(object sender, string data) + { + var instanciatedParameters = this.InstanciatedParametersForCycle(); + instanciatedParameters["data"].SetValue(data); + this.Graph.AddCycle(this, instanciatedParameters); + } + + public override void BeginCycle() + { + this.Next(); + } + } +} diff --git a/Nodes/WebSocket/WebSocketSendDataNode.cs b/Nodes/WebSocket/WebSocketSendDataNode.cs new file mode 100644 index 0000000..b567987 --- /dev/null +++ b/Nodes/WebSocket/WebSocketSendDataNode.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using NodeBlock.Engine; +using NodeBlock.Engine.Attributes; + +namespace NodeBlock.Engine.Nodes.WebSocket +{ + [NodeDefinition("WebSocketSendDataNode", "WebSocket Send Data", NodeTypeEnum.Function, "WebSocket")] + [NodeGraphDescription("Send data to a connected WebSocket server")] + public class WebSocketSendDataNode : Node + { + public WebSocketSendDataNode(string id, BlockGraph graph) + : base(id, graph, typeof(WebSocketSendDataNode).Name) + { + this.InParameters.Add("webSocketClient", new NodeParameter(this, "webSocketClient", typeof(WebSocketClientConnectorNode), true)); + this.InParameters.Add("data", new NodeParameter(this, "data", typeof(string), true)); + } + + public override bool CanExecute => true; + public override bool CanBeExecuted => true; + + public override bool OnExecution() + { + var webSocketClientNode = this.InParameters["webSocketClient"].GetValue() as WebSocketClientConnectorNode; + var data = this.InParameters["data"].GetValue().ToString(); + + return SendData(webSocketClientNode.WebSocketClient, data); + } + + private bool SendData(ClientWebSocket webSocketClient, string data) + { + try + { + var buffer = System.Text.Encoding.UTF8.GetBytes(data); + var sendBuffer = new ArraySegment(buffer); + webSocketClient.SendAsync(sendBuffer, WebSocketMessageType.Text, true, CancellationToken.None).GetAwaiter().GetResult(); + return true; + } + catch (Exception ex) + { + // Handle sending errors here + return false; + } + } + } +} diff --git a/README.md b/README.md index e69de29..95cde25 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,109 @@ + + + + + + +[![Contributors][contributors-shield]][contributors-url] +[![Forks][forks-shield]][forks-url] +[![Stargazers][stars-shield]][stars-url] +[![Issues][issues-shield]][issues-url] + + +
+
+ + Logo + + +

GraphLinq.Engine

+ +

+ The Base Engine running graphs over GraphLinq Protocol (.NET core 3.0) +
+ Explore the docs » +
+
+ Report Bug + · + Request Feature +

+
+ + + +
+ Table of Contents +
    +
  1. + About The Project + +
  2. +
  3. Contributing
  4. +
  5. Contact
  6. +
  7. Acknowledgments
  8. +
+
+ + +## About The Project + +The Base Engine running graphs over GraphLinq Protocol (.NET core 3.0) + +

(back to top)

+ +### Built With + +.NET core 3.0 + +

(back to top)

+ + +## Contributing + +If you have a suggestion that would make this repository better, please fork the repo and create a pull request. You can also simply open an issue. Don't forget to give the project a star! Thanks again! + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +

(back to top)

+ + +## Contact + +GraphLinq Protocol - [@graphlinq_proto](https://twitter.com/graphlinq_proto) + +Project Home: [https://graphlinq.io](https://graphlinq.io) + +

(back to top)

+ + +## Acknowledgments + + + + + +Made with [contributors-img](https://contrib.rocks). + +

(back to top)

+ + + + + +[contributors-shield]: https://img.shields.io/github/contributors/GraphLinq/GraphLinq.Documentation.svg?style=for-the-badge +[contributors-url]: https://github.com/GraphLinq/GraphLinq.Documentation/graphs/contributors +[forks-shield]: https://img.shields.io/github/forks/GraphLinq/GraphLinq.Documentation.svg?style=for-the-badge +[forks-url]: https://github.com/GraphLinq/GraphLinq.Documentation/network/members +[stars-shield]: https://img.shields.io/github/stars/GraphLinq/GraphLinq.Documentation.svg?style=for-the-badge +[stars-url]: https://github.com/GraphLinq/GraphLinq.Documentation/stargazers +[issues-shield]: https://img.shields.io/github/issues/GraphLinq/GraphLinq.Documentation.svg?style=for-the-badge +[issues-url]: https://github.com/GraphLinq/GraphLinq.Documentation/issues diff --git a/Storage/Redis/Entities/LogEntry.cs b/Storage/Redis/Entities/LogEntry.cs index e9bd9cf..e4b3abc 100644 --- a/Storage/Redis/Entities/LogEntry.cs +++ b/Storage/Redis/Entities/LogEntry.cs @@ -13,5 +13,7 @@ public class LogEntry public string Message { get; set; } [JsonProperty("timestamp")] public long Timestamp { get; set; } + [JsonProperty("graph_hash")] + public string GraphHash { get; internal set; } } } diff --git a/Storage/Redis/RedisStorage.cs b/Storage/Redis/RedisStorage.cs index b93dc2e..9f68c78 100644 --- a/Storage/Redis/RedisStorage.cs +++ b/Storage/Redis/RedisStorage.cs @@ -15,6 +15,11 @@ public class RedisStorage { private static ConnectionMultiplexer muxer; + public static ConnectionMultiplexer GetMuxer() + { + return muxer; + } + static RedisStorage() { muxer = ConnectionMultiplexer.Connect(Environment.GetEnvironmentVariable("redis_master_addr") + ":" + Environment.GetEnvironmentVariable("redis_master_port") + ",password=" + @@ -58,6 +63,13 @@ public static void SetGraphKeyItem(BlockGraph graph, string key, string value) conn.StringSet(isolatedKey, value); } + public static void SetWalletGraphKeyItem(BlockGraph graph, string key, string value) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var isolatedKey = graph.currentContext.walletIdentifier + "/" + key; + conn.StringSet(isolatedKey, value); + } + public static string GetGraphKeyItem(BlockGraph graph, string key) { var conn = muxer.GetDatabase(GetGraphDatabaseId()); @@ -65,6 +77,13 @@ public static string GetGraphKeyItem(BlockGraph graph, string key) return conn.StringGet(isolatedKey); } + public static string GetWalletGraphKeyItem(BlockGraph graph, string key) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var isolatedKey = graph.currentContext.walletIdentifier + "/" + key; + return conn.StringGet(isolatedKey); + } + public static bool GraphKeyItemExist(BlockGraph graph, string key) { var conn = muxer.GetDatabase(GetGraphDatabaseId()); @@ -72,6 +91,13 @@ public static bool GraphKeyItemExist(BlockGraph graph, string key) return conn.KeyExists(isolatedKey); } + public static bool GraphWalletKeyItemExist(BlockGraph graph, string key) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var isolatedKey = graph.currentContext.walletIdentifier + "/" + key; + return conn.KeyExists(isolatedKey); + } + public static void SaveListActiveGraphs(List graphs) { var rawStates = JsonConvert.SerializeObject(graphs.Where(x => x.currentContext != null).Select(x => new ActiveGraphStorage() diff --git a/Storage/StorageAbstraction/IStorageAbstraction.cs b/Storage/StorageAbstraction/IStorageAbstraction.cs new file mode 100644 index 0000000..d110f94 --- /dev/null +++ b/Storage/StorageAbstraction/IStorageAbstraction.cs @@ -0,0 +1,26 @@ +using NodeBlock.Engine.Enums; +using NodeBlock.Engine.Storage.Redis.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Storage.StorageAbstraction +{ + public interface IStorageAbstraction + { + GraphStorage SetGraphStorage(BlockGraph graph, int walletIdentifier, GraphStateEnum stateGraph); + GraphStorage GetGraphStorage(string hash); + void SetGraphKeyItem(BlockGraph graph, string key, string value); + void SetWalletGraphKeyItem(BlockGraph graph, string key, string value); + string GetGraphKeyItem(BlockGraph graph, string key); + string GetWalletGraphKeyItem(BlockGraph graph, string key); + bool GraphKeyItemExist(BlockGraph graph, string key); + bool GraphWalletKeyItemExist(BlockGraph graph, string key); + void SaveListActiveGraphs(List graphs); + List GetGraphStorages(); + void AppendLogForGraph(string hash, string type, string message); + void SaveLogsEntries(string hash, List logs); + List GetLogsForGraph(string graphId); + void RemoveGraphLogs(string hashGraph); + } +} diff --git a/Storage/StorageAbstraction/LocalStorage/LocalStorage.cs b/Storage/StorageAbstraction/LocalStorage/LocalStorage.cs new file mode 100644 index 0000000..27dea9d --- /dev/null +++ b/Storage/StorageAbstraction/LocalStorage/LocalStorage.cs @@ -0,0 +1,157 @@ +using NodeBlock.Engine.Enums; +using NodeBlock.Engine.Storage.Redis.Entities; +using System; +using System.Collections.Generic; +using System.Text; +using LiteDB; +using Newtonsoft.Json; +using System.Linq; +using NodeBlock.Engine.Encoding; + +namespace NodeBlock.Engine.Storage.StorageAbstraction.LocalStorage +{ + public class LocalStorage : IStorageAbstraction + { + public class KeyValue + { + public string Key { get; set; } + public string Value { get; set; } + } + + private LiteDatabase _db; + + public LocalStorage(string dbFilePath) + { + _db = new LiteDatabase(dbFilePath); + } + + public void AppendLogForGraph(string hash, string type, string message) + { + var logStore = _db.GetCollection("graphLogs"); + var logs = logStore.Find(x => x.GraphHash == hash).ToList(); + + var logEntry = new LogEntry() + { + GraphHash = hash, + Type = type, + Message = message, + Timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds() + }; + + logs.Add(logEntry); + logStore.Upsert(logEntry); + } + + public string GetGraphKeyItem(BlockGraph graph, string key) + { + var keyValueStore = _db.GetCollection("graphKeyItems"); + var isolatedKey = graph.GetId() + "/" + key; + var result = keyValueStore.FindOne(x => x.Key == isolatedKey); + return result?.Value; + } + + public GraphStorage GetGraphStorage(string hash) + { + var keyValueStore = _db.GetCollection("graphs"); + return keyValueStore.FindOne(x => x.StoredHash == hash); + } + + public List GetGraphStorages() + { + var graphStore = _db.GetCollection("graphs"); + var activeGraphsStore = _db.GetCollection("activeGraphs"); + var activeGraphs = activeGraphsStore.FindAll().ToList(); + + return graphStore.Find(x => activeGraphs.Any(ag => ag.Hash == x.StoredHash)).ToList(); + } + + public List GetLogsForGraph(string graphId) + { + var logStore = _db.GetCollection("graphLogs"); + return logStore.Find(x => x.GraphHash == graphId).ToList(); + } + + public string GetWalletGraphKeyItem(BlockGraph graph, string key) + { + var keyValueStore = _db.GetCollection("walletGraphKeyItems"); + var isolatedKey = graph.currentContext.walletIdentifier + "/" + key; + var result = keyValueStore.FindOne(x => x.Key == isolatedKey); + return result?.Value; + } + + public bool GraphKeyItemExist(BlockGraph graph, string key) + { + var keyValueStore = _db.GetCollection("graphKeyItems"); + var isolatedKey = graph.GetId() + "/" + key; + return keyValueStore.Exists(x => x.Key == isolatedKey); + } + + public bool GraphWalletKeyItemExist(BlockGraph graph, string key) + { + var keyValueStore = _db.GetCollection("walletGraphKeyItems"); + var isolatedKey = graph.currentContext.walletIdentifier + "/" + key; + return keyValueStore.Exists(x => x.Key == isolatedKey); + } + + public void RemoveGraphLogs(string hashGraph) + { + var logStore = _db.GetCollection("graphLogs"); + logStore.DeleteMany(x => x.GraphHash == hashGraph); + } + + public void SaveListActiveGraphs(List graphs) + { + var activeGraphsStore = _db.GetCollection("activeGraphs"); + activeGraphsStore.DeleteMany(_ => true); + activeGraphsStore.InsertBulk(graphs.Where(x => x.currentContext != null).Select(x => new ActiveGraphStorage() + { + Hash = x.UniqueHash, + GraphLastState = x.currentContext.currentGraphState + })); + } + + public void SaveLogsEntries(string hash, List logs) + { + var logStore = _db.GetCollection("graphLogs"); + logStore.DeleteMany(x => x.GraphHash == hash); + logs.ForEach(log => log.GraphHash = hash); + logStore.InsertBulk(logs); + } + + public void SetGraphKeyItem(BlockGraph graph, string key, string value) + { + var keyValueStore = _db.GetCollection("graphKeyItems"); + var isolatedKey = graph.GetId() + "/" + key; + keyValueStore.Upsert(new KeyValue { Key = isolatedKey, Value = value }); + } + + public GraphStorage SetGraphStorage(BlockGraph graph, int walletIdentifier, GraphStateEnum stateGraph) + { + string hash = GraphCompression.GetUniqueGraphHash(walletIdentifier, graph.CompressedRaw); + var storage = new GraphStorage() + { + StoredHash = hash, + CompressedBytes = graph.CompressedRaw, + WalletIdentifierOwner = walletIdentifier, + StateGraph = stateGraph + }; + + var keyValueStore = _db.GetCollection("graphs"); + keyValueStore.Upsert(storage); + + return storage; + } + + public void SetWalletGraphKeyItem(BlockGraph graph, string key, string value) + { + var keyValueStore = _db.GetCollection("walletGraphKeyItems"); + var isolatedKey = graph.currentContext.walletIdentifier + "/" + key; + keyValueStore.Upsert(new KeyValue { Key = isolatedKey, Value = value }); + } + + public void Dispose() + { + _db.Dispose(); + } + } +} diff --git a/Storage/StorageAbstraction/Redis/RedisStorage.cs b/Storage/StorageAbstraction/Redis/RedisStorage.cs new file mode 100644 index 0000000..7201db1 --- /dev/null +++ b/Storage/StorageAbstraction/Redis/RedisStorage.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Text; +using StackExchange.Redis; +using NodeBlock.Engine.Storage.Redis.Entities; +using NodeBlock.Engine.Encoding; +using Newtonsoft.Json; +using NodeBlock.Engine.Enums; +using System.Linq; +using System.Threading.Tasks; + +namespace NodeBlock.Engine.Storage.StorageAbstraction.Redis +{ + public class RedisStorage : IStorageAbstraction + { + private ConnectionMultiplexer muxer; + + public ConnectionMultiplexer GetMuxer() + { + return muxer; + } + + public RedisStorage() + { + muxer = ConnectionMultiplexer.Connect(Environment.GetEnvironmentVariable("redis_master_addr") + ":" + Environment.GetEnvironmentVariable("redis_master_port") + ",password=" + + Environment.GetEnvironmentVariable("redis_master_passw") + ",connectTimeout=60000,syncTimeout=30000,asyncTimeout=30000"); ; + } + public GraphStorage SetGraphStorage(BlockGraph graph, int walletIdentifier, GraphStateEnum stateGraph) + { + string hash = GraphCompression.GetUniqueGraphHash(walletIdentifier, graph.CompressedRaw); + var storage = new GraphStorage() + { + StoredHash = hash, + CompressedBytes = graph.CompressedRaw, + WalletIdentifierOwner = walletIdentifier, + StateGraph = stateGraph + }; + + var conn = muxer.GetDatabase(0); + conn.StringSet(string.Format("graphs/{0}", hash), JsonConvert.SerializeObject(storage)); + return storage; + } + + public int GetGraphDatabaseId() + { + return Environment.GetEnvironmentVariable("graph_env") == "prod" ? 0 : 1; + } + + public GraphStorage GetGraphStorage(string hash) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + string rawContent = conn.StringGet(string.Format("graphs/{0}", hash)); + if (rawContent == null) { return null; } + + var storage = JsonConvert.DeserializeObject(rawContent); + return storage; + } + + public void SetGraphKeyItem(BlockGraph graph, string key, string value) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var isolatedKey = graph.GetId() + "/" + key; + conn.StringSet(isolatedKey, value); + } + + public void SetWalletGraphKeyItem(BlockGraph graph, string key, string value) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var isolatedKey = graph.currentContext.walletIdentifier + "/" + key; + conn.StringSet(isolatedKey, value); + } + + public string GetGraphKeyItem(BlockGraph graph, string key) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var isolatedKey = graph.GetId() + "/" + key; + return conn.StringGet(isolatedKey); + } + + public string GetWalletGraphKeyItem(BlockGraph graph, string key) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var isolatedKey = graph.currentContext.walletIdentifier + "/" + key; + return conn.StringGet(isolatedKey); + } + + public bool GraphKeyItemExist(BlockGraph graph, string key) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var isolatedKey = graph.GetId() + "/" + key; + return conn.KeyExists(isolatedKey); + } + + public bool GraphWalletKeyItemExist(BlockGraph graph, string key) + { + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var isolatedKey = graph.currentContext.walletIdentifier + "/" + key; + return conn.KeyExists(isolatedKey); + } + + public void SaveListActiveGraphs(List graphs) + { + var rawStates = JsonConvert.SerializeObject(graphs.Where(x => x.currentContext != null).Select(x => new ActiveGraphStorage() + { + Hash = x.UniqueHash, + GraphLastState = x.currentContext.currentGraphState + }).ToList()); + + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + conn.StringSet("graphs/active", rawStates); + } + + public List GetGraphStorages() + { + var graphsAlive = new List(); + var conn = muxer.GetDatabase(GetGraphDatabaseId()); + var rawActive = conn.StringGet("graphs/active"); + if (string.IsNullOrEmpty(rawActive)) { return graphsAlive; } + + ActiveGraphStorage[] lists = JsonConvert.DeserializeObject (rawActive); + List liveHashs = lists.Select(x => x.Hash).ToList(); + var transaction = conn.CreateTransaction(); + + var tasks = new List>(); + liveHashs.ForEach(x => tasks.Add(transaction.StringGetAsync(string.Format("graphs/{0}", x)))); + transaction.Execute(); + + tasks.ForEach(x => + { + if (!x.Result.IsNullOrEmpty) + { + var storage = JsonConvert.DeserializeObject(x.Result); + graphsAlive.Add(storage); + } + }); + + return graphsAlive; + } + + public void AppendLogForGraph(string hash, string type, string message) + { + var key = "graph/logs/" + hash; + var conn = muxer.GetDatabase(1); + var currentValue = conn.StringGet(key); + var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + var logs = new List(); + if (string.IsNullOrEmpty(currentValue)) + { + logs.Add(new LogEntry() + { + Type = type, + Message = message, + Timestamp = timestamp + }); + } + else + { + logs = JsonConvert.DeserializeObject>(currentValue); + logs.Add(new LogEntry() + { + Type = type, + Message = message, + Timestamp = timestamp + }); + } + conn.StringSet(key, JsonConvert.SerializeObject(logs)); + } + + public void SaveLogsEntries(string hash, List logs) + { + var key = "graph/logs/" + hash; + var conn = muxer.GetDatabase(1); + conn.StringSet(key, JsonConvert.SerializeObject(logs)); + } + + public List GetLogsForGraph(string graphId) + { + var pattern = "graph/logs/" + graphId; + var conn = muxer.GetDatabase(1); + var raw = conn.StringGet(pattern); + if (!raw.IsNullOrEmpty) + { + return JsonConvert.DeserializeObject>(raw); + } + return new List() { }; + } + + + public void RemoveGraphLogs(string hashGraph) + { + var conn = muxer.GetDatabase(1); + conn.KeyDelete("graph/logs/" + hashGraph); + } + } +} diff --git a/Storage/StorageManager.cs b/Storage/StorageManager.cs new file mode 100644 index 0000000..b189991 --- /dev/null +++ b/Storage/StorageManager.cs @@ -0,0 +1,49 @@ +using NodeBlock.Engine.Storage.StorageAbstraction; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NodeBlock.Engine.Storage +{ + public static class StorageManager + { + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + private static IStorageAbstraction _storage; + + + public static IStorageAbstraction GetStorage() + { + if(_storage == null) + { + switch (Environment.GetEnvironmentVariable("storage_engine")) + { + case "local_storage": + logger.Info("Using LocalStorage for the storage engine"); + if (!System.IO.Directory.Exists("./data")) + { + System.IO.Directory.CreateDirectory("./data"); + } + _storage = new StorageAbstraction.LocalStorage.LocalStorage("./data/graphs.db"); + break; + + case "redis": + logger.Info("Using RedisStorage for the storage engine"); + _storage = new StorageAbstraction.Redis.RedisStorage(); + break; + + // Set local storage as default + default: + logger.Info("No storage engine set in the env vars, using local_storage by default"); + if (!System.IO.Directory.Exists("./data")) + { + System.IO.Directory.CreateDirectory("./data"); + } + _storage = new StorageAbstraction.LocalStorage.LocalStorage("./data/graphs.db"); + break; + } + } + + return _storage; + } + } +} diff --git a/Utils/StringUtils.cs b/Utils/StringUtils.cs new file mode 100644 index 0000000..0565193 --- /dev/null +++ b/Utils/StringUtils.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace NodeBlock.Engine.Utils +{ + public class StringUtils + { + public static List ExtractTextWithinDoubleCurlyBraces(string input) + { + List extractedText = new List(); + + // Regular expression pattern to match "{{any_text}}" + string pattern = @"\{\{(.+?)\}\}"; + + // Use Regex to find matches in the input string + MatchCollection matches = Regex.Matches(input, pattern); + + // Iterate through the matches and add them to the list + foreach (Match match in matches) + { + extractedText.Add(match.Groups[1].Value); + } + + return extractedText; + } + } +} diff --git a/img/logo.png b/img/logo.png new file mode 100644 index 0000000..0f38ba9 Binary files /dev/null and b/img/logo.png differ diff --git a/img/project-logo-full.png b/img/project-logo-full.png new file mode 100644 index 0000000..0d20f85 Binary files /dev/null and b/img/project-logo-full.png differ diff --git a/img/project-logo-mini.png b/img/project-logo-mini.png new file mode 100644 index 0000000..9b4f88d Binary files /dev/null and b/img/project-logo-mini.png differ diff --git a/img/screenshot.png b/img/screenshot.png new file mode 100644 index 0000000..3b58bcb Binary files /dev/null and b/img/screenshot.png differ