diff --git a/.circleci/config.yml b/.circleci/config.yml index d10c6bf..5434238 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 jobs: build: environment: - BUILD_VERSION: 1.0.29 + 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/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/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/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/BlockGraph.cs b/BlockGraph.cs index 7b625c1..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; @@ -21,7 +24,6 @@ public class BlockGraph { private const uint MAX_FAILED_CYCLE = 20; - private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); public string Name { get; } public Dictionary Nodes { get; set; } @@ -34,24 +36,27 @@ public class BlockGraph public GraphContextWrapper currentContext = null; public Dictionary MemoryVariables = new Dictionary(); - - private Dictionary queueTaskCycleThreads = new Dictionary(); - private Dictionary> pendingCyclesQueues = new Dictionary>(); 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(); @@ -90,7 +95,7 @@ private void runQueueTask(string queueName) var task = new Task(async () => { int failedCycleCount = 0; - while(!this.cancelCycleToken.IsCancellationRequested) + while (!this.cancelCycleToken.IsCancellationRequested) { if (this.pendingCyclesQueues[queueName].Count <= 0) { @@ -102,10 +107,14 @@ private void runQueueTask(string queueName) { try { - + 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(() => { pendingCycle.Execute(); @@ -114,7 +123,9 @@ private void runQueueTask(string queueName) { 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)); @@ -136,6 +147,7 @@ private void runQueueTask(string queueName) finally { currentCycle = null; + semaphoreSlim.Release(); //if(failedCycleCount >= MAX_FAILED_CYCLE) //{ // failedCycleCount = 0; @@ -179,7 +191,7 @@ public bool Stop(bool force = false) try { this.queueTaskCycleThreads.ToList().ForEach(x => x.Value.Dispose()); - this.cancelCycleToken.Cancel(); + if(this.cancelCycleToken != null) this.cancelCycleToken.Cancel(); } catch(Exception ex) { @@ -235,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); @@ -306,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; } @@ -358,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); @@ -367,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) @@ -398,6 +431,7 @@ public void Start(GraphContextWrapper context) { try { + currentCycle = new GraphExecutionCycle(this, DateTimeOffset.Now.ToUnixTimeSeconds(), x.Value, new Dictionary()); x.Value.SetupEvent(); } catch (Exception ex) @@ -461,18 +495,36 @@ public void AppendLog(string type, string 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( @@ -483,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 f4264ea..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() diff --git a/GraphsContainer.cs b/GraphsContainer.cs index cb657d9..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) @@ -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 => @@ -117,7 +121,7 @@ public static ServiceProvider GetServiceProvider() 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 => @@ -207,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) @@ -215,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) @@ -241,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) @@ -254,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(); } @@ -305,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) { @@ -322,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); @@ -343,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 8436b2a..e993f3f 100644 --- a/HostedAPI/HostedEndpoint.cs +++ b/HostedAPI/HostedEndpoint.cs @@ -9,24 +9,44 @@ 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; + 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) { 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/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/PluginManager.cs b/Interop/Plugin/PluginManager.cs index c41c72c..20a9ee1 100644 --- a/Interop/Plugin/PluginManager.cs +++ b/Interop/Plugin/PluginManager.cs @@ -33,6 +33,8 @@ 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; diff --git a/Node.cs b/Node.cs index c73c403..23d931f 100644 --- a/Node.cs +++ b/Node.cs @@ -25,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; } @@ -36,6 +38,7 @@ public abstract class Node : ICloneable 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; } @@ -51,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) @@ -89,6 +93,15 @@ public Node(string id, BlockGraph graph, string nodeType) } } + 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(); @@ -121,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) @@ -128,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(); @@ -176,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 1b08bea..09a709a 100644 --- a/NodeParameter.cs +++ b/NodeParameter.cs @@ -62,7 +62,26 @@ 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; } 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/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 b2df655..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,11 +32,20 @@ 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, "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() { this.Next(); 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/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/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/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/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/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/GetTimestampMsOffsetNode.cs b/Nodes/GetTimestampMsOffsetNode.cs index 34cc62b..89fe023 100644 --- a/Nodes/GetTimestampMsOffsetNode.cs +++ b/Nodes/GetTimestampMsOffsetNode.cs @@ -63,10 +63,13 @@ public override object ComputeParameterValue(NodeParameter parameter, object val 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; diff --git a/Nodes/GetTimestampOffsetNode.cs b/Nodes/GetTimestampOffsetNode.cs index ddcee03..685c7f1 100644 --- a/Nodes/GetTimestampOffsetNode.cs +++ b/Nodes/GetTimestampOffsetNode.cs @@ -63,10 +63,13 @@ public override object ComputeParameterValue(NodeParameter parameter, object val 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; 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 2107386..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 { 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 index 279b953..26239f0 100644 --- a/Nodes/Storage/GetWalletKeyItemNode.cs +++ b/Nodes/Storage/GetWalletKeyItemNode.cs @@ -1,4 +1,5 @@ using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Storage; using NodeBlock.Engine.Storage.Redis; using System; using System.Collections.Generic; @@ -32,7 +33,7 @@ public override object ComputeParameterValue(NodeParameter parameter, object val { if (parameter.Name == "value") { - var v = RedisStorage.GetWalletGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString()); + var v = StorageManager.GetStorage().GetWalletGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString()); return v; } 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 index 413b1fe..1f01f93 100644 --- a/Nodes/Storage/KeyWalletItemExistNode.cs +++ b/Nodes/Storage/KeyWalletItemExistNode.cs @@ -1,4 +1,5 @@ using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Storage; using NodeBlock.Engine.Storage.Redis; using System; using System.Collections.Generic; @@ -11,7 +12,7 @@ namespace NodeBlock.Engine.Nodes.Storage public class KeyWalletItemExistNode : Node { public KeyWalletItemExistNode(string id, BlockGraph graph) - : base(id, graph, typeof(KeyItemExistNode).Name) + : base(id, graph, typeof(KeyWalletItemExistNode).Name) { this.InParameters = new Dictionary() { @@ -30,8 +31,7 @@ public KeyWalletItemExistNode(string id, BlockGraph graph) public override bool OnExecution() { - // return RedisStorage.GetGraphKeyItem(this.Graph, this.InParameters["key"].GetValue().ToString()); - if (RedisStorage.GraphWalletKeyItemExist(this.Graph, this.InParameters["key"].GetValue().ToString())) + 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(); 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 index 3c15bba..9cdd5fd 100644 --- a/Nodes/Storage/SaveWalletKeyItemNode.cs +++ b/Nodes/Storage/SaveWalletKeyItemNode.cs @@ -1,4 +1,5 @@ using NodeBlock.Engine.Attributes; +using NodeBlock.Engine.Storage; using NodeBlock.Engine.Storage.Redis; using System; using System.Collections.Generic; @@ -32,7 +33,7 @@ public SaveWalletKeyItemNode(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/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 index b7eba83..ee6199b 100644 --- a/Nodes/Text/StringContainsMultiNode.cs +++ b/Nodes/Text/StringContainsMultiNode.cs @@ -7,6 +7,7 @@ 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) @@ -32,7 +33,7 @@ public override bool OnExecution() var items = this.InParameters["searchItems"].GetValue().ToString().Split(","); foreach (var item in items) { - if (original == item) + if (original.Contains(item)) { return (this.OutParameters["true"].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 index ef167d3..855087e 100644 --- a/Nodes/Text/StringMatchesRegexNode.cs +++ b/Nodes/Text/StringMatchesRegexNode.cs @@ -8,6 +8,7 @@ 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) 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 index ff2e135..078160d 100644 --- a/Nodes/Text/StringSplitNode.cs +++ b/Nodes/Text/StringSplitNode.cs @@ -7,6 +7,7 @@ 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) 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/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