From 229519a091a2ff3f656e251da3bc07a88afcb408 Mon Sep 17 00:00:00 2001
From: daniel <4954577+jaensen@users.noreply.github.com>
Date: Thu, 24 Oct 2024 03:11:36 +0200
Subject: [PATCH 1/5] feature: add initial version of a pathfinder for circles
v2
---
.../TestConversionUtils.cs | 15 +-
Circles.Index.Query.Tests/TestSelect.cs | 15 ++
Circles.Index.Rpc/Circles.Index.Rpc.csproj | 1 +
Circles.Index.Rpc/CirclesRpcModule.cs | 12 +
Circles.Index.Rpc/ICirclesRpcModule.cs | 6 +
Circles.Index/Circles.Index.csproj | 4 +-
Circles.Pathfinder/Circles.Pathfinder.csproj | 13 +
Circles.Pathfinder/DTOs/FlowRequest.cs | 8 +
Circles.Pathfinder/DTOs/MaxFlowResponse.cs | 13 +
Circles.Pathfinder/DTOs/TransferPathStep.cs | 9 +
Circles.Pathfinder/Data/LoadGraph.cs | 54 +++++
Circles.Pathfinder/Edges/CapacityEdge.cs | 18 ++
Circles.Pathfinder/Edges/Edge.cs | 13 +
Circles.Pathfinder/Edges/FlowEdge.cs | 20 ++
Circles.Pathfinder/Edges/TrustEdge.cs | 11 +
Circles.Pathfinder/Graphs/BalanceGraph.cs | 38 +++
Circles.Pathfinder/Graphs/CapacityGraph.cs | 63 +++++
Circles.Pathfinder/Graphs/FlowGraph.cs | 225 ++++++++++++++++++
Circles.Pathfinder/Graphs/GraphExtensions.cs | 119 +++++++++
Circles.Pathfinder/Graphs/GraphFactory.cs | 148 ++++++++++++
Circles.Pathfinder/Graphs/GraphTraversal.cs | 138 +++++++++++
Circles.Pathfinder/Graphs/IGraph.cs | 11 +
Circles.Pathfinder/Graphs/TrustGraph.cs | 41 ++++
Circles.Pathfinder/IPathfinder.cs | 8 +
Circles.Pathfinder/Nodes/AvatarNode.cs | 8 +
Circles.Pathfinder/Nodes/BalanceNode.cs | 17 ++
Circles.Pathfinder/Nodes/Node.cs | 15 ++
Circles.Pathfinder/V2Pathfinder.cs | 209 ++++++++++++++++
Circles.sln | 6 +
arm64.Dockerfile | 1 +
x64.Dockerfile | 1 +
x64.debug.Dockerfile | 2 +
x64.debug.spaceneth.Dockerfile | 2 +
33 files changed, 1249 insertions(+), 15 deletions(-)
create mode 100644 Circles.Pathfinder/Circles.Pathfinder.csproj
create mode 100644 Circles.Pathfinder/DTOs/FlowRequest.cs
create mode 100644 Circles.Pathfinder/DTOs/MaxFlowResponse.cs
create mode 100644 Circles.Pathfinder/DTOs/TransferPathStep.cs
create mode 100644 Circles.Pathfinder/Data/LoadGraph.cs
create mode 100644 Circles.Pathfinder/Edges/CapacityEdge.cs
create mode 100644 Circles.Pathfinder/Edges/Edge.cs
create mode 100644 Circles.Pathfinder/Edges/FlowEdge.cs
create mode 100644 Circles.Pathfinder/Edges/TrustEdge.cs
create mode 100644 Circles.Pathfinder/Graphs/BalanceGraph.cs
create mode 100644 Circles.Pathfinder/Graphs/CapacityGraph.cs
create mode 100644 Circles.Pathfinder/Graphs/FlowGraph.cs
create mode 100644 Circles.Pathfinder/Graphs/GraphExtensions.cs
create mode 100644 Circles.Pathfinder/Graphs/GraphFactory.cs
create mode 100644 Circles.Pathfinder/Graphs/GraphTraversal.cs
create mode 100644 Circles.Pathfinder/Graphs/IGraph.cs
create mode 100644 Circles.Pathfinder/Graphs/TrustGraph.cs
create mode 100644 Circles.Pathfinder/IPathfinder.cs
create mode 100644 Circles.Pathfinder/Nodes/AvatarNode.cs
create mode 100644 Circles.Pathfinder/Nodes/BalanceNode.cs
create mode 100644 Circles.Pathfinder/Nodes/Node.cs
create mode 100644 Circles.Pathfinder/V2Pathfinder.cs
diff --git a/Circles.Index.Query.Tests/TestConversionUtils.cs b/Circles.Index.Query.Tests/TestConversionUtils.cs
index b517207..d49d085 100644
--- a/Circles.Index.Query.Tests/TestConversionUtils.cs
+++ b/Circles.Index.Query.Tests/TestConversionUtils.cs
@@ -1,3 +1,4 @@
+using System.Numerics;
using Circles.Index.Utils;
namespace Circles.Index.Query.Tests;
@@ -37,18 +38,6 @@ public class TestConversionUtils
[Test]
public void TestConvertCirclesToStaticCircles()
{
- var inflation = 0.07m;
- var year = 4;
- var baseAmount = 8m;
- var inflatedAmount = baseAmount;
- for (int i = 0; i < year; i++)
- {
- inflatedAmount *= 1 + inflation;
- }
-
-
-
- decimal circlesBalance = 20;
- decimal staticCirclesBalance = ConversionUtils.CirclesToStaticCircles(circlesBalance, DateTime.Now);
+ decimal staticCirclesBalance = ConversionUtils.CirclesToCrc(0.13151319940322485m);
}
}
\ No newline at end of file
diff --git a/Circles.Index.Query.Tests/TestSelect.cs b/Circles.Index.Query.Tests/TestSelect.cs
index 8ffaa67..4de85ec 100644
--- a/Circles.Index.Query.Tests/TestSelect.cs
+++ b/Circles.Index.Query.Tests/TestSelect.cs
@@ -1,4 +1,5 @@
using System.Data;
+using System.Numerics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
@@ -368,4 +369,18 @@ public void JsonSerialization_Deserialization_Complex()
Assert.That(deserializedOrderBy.Column, Is.EqualTo(orderBy.Column));
Assert.That(deserializedOrderBy.SortOrder, Is.EqualTo(orderBy.SortOrder));
}
+
+ [Test]
+ public void B()
+ {
+ var t = BigInteger.Parse("238208892873504508097789456176502153024265176387")
+ * (
+ BigInteger.Parse("31556952")
+ - BigInteger.Parse("4142484904995219")
+ )
+ + BigInteger.Parse("238208892873504508097789456176502153024265176387")
+ * BigInteger.Parse("4142484904995219");
+
+
+ }
}
\ No newline at end of file
diff --git a/Circles.Index.Rpc/Circles.Index.Rpc.csproj b/Circles.Index.Rpc/Circles.Index.Rpc.csproj
index 3d37fc0..b4070e2 100644
--- a/Circles.Index.Rpc/Circles.Index.Rpc.csproj
+++ b/Circles.Index.Rpc/Circles.Index.Rpc.csproj
@@ -15,6 +15,7 @@
+
diff --git a/Circles.Index.Rpc/CirclesRpcModule.cs b/Circles.Index.Rpc/CirclesRpcModule.cs
index 8a07dee..19b179d 100644
--- a/Circles.Index.Rpc/CirclesRpcModule.cs
+++ b/Circles.Index.Rpc/CirclesRpcModule.cs
@@ -3,6 +3,10 @@
using Circles.Index.Query;
using Circles.Index.Query.Dto;
using Circles.Index.Utils;
+using Circles.Pathfinder;
+using Circles.Pathfinder.Data;
+using Circles.Pathfinder.DTOs;
+using Circles.Pathfinder.Graphs;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
@@ -343,6 +347,14 @@ public ResultWrapper circles_events(Address? address, long? from
eventTypes, filterPredicates, sortAscending));
}
+ public async Task> circlesV2_findPath(FlowRequest flowRequest)
+ {
+ var loadGraph = new LoadGraph(_indexerContext.Settings.IndexDbConnectionString);
+ var graphFactory = new GraphFactory();
+ var pathfinder = new V2Pathfinder(loadGraph, graphFactory);
+ return ResultWrapper.Success(await pathfinder.ComputeMaxFlow(flowRequest));
+ }
+
private string[] GetTokenExposureIds(Address address)
{
var selectTokenExposure = new Select(
diff --git a/Circles.Index.Rpc/ICirclesRpcModule.cs b/Circles.Index.Rpc/ICirclesRpcModule.cs
index 9c0c7c4..40ce281 100644
--- a/Circles.Index.Rpc/ICirclesRpcModule.cs
+++ b/Circles.Index.Rpc/ICirclesRpcModule.cs
@@ -1,5 +1,6 @@
using Circles.Index.Common;
using Circles.Index.Query.Dto;
+using Circles.Pathfinder.DTOs;
using Nethermind.Core;
using Nethermind.JsonRpc;
using Nethermind.JsonRpc.Modules;
@@ -59,4 +60,9 @@ public interface ICirclesRpcModule : IRpcModule
IsImplemented = true)]
ResultWrapper circles_events(Address? address, long? fromBlock, long? toBlock = null,
string[]? eventTypes = null, FilterPredicateDto[]? filters = null, bool? sortAscending = false);
+
+ [JsonRpcMethod(
+ Description = "Tries to find a transitive transfer path between two addresses in the Circles V2 graph",
+ IsImplemented = true)]
+ Task> circlesV2_findPath(FlowRequest flowRequest);
}
\ No newline at end of file
diff --git a/Circles.Index/Circles.Index.csproj b/Circles.Index/Circles.Index.csproj
index d80d958..99e285b 100644
--- a/Circles.Index/Circles.Index.csproj
+++ b/Circles.Index/Circles.Index.csproj
@@ -8,8 +8,8 @@
Daniel Janz (Gnosis Service GmbH)
Gnosis Service GmbH
Circles
- 1.9.1
- 1.9.1
+ 1.10.0
+ 1.10.0
diff --git a/Circles.Pathfinder/Circles.Pathfinder.csproj b/Circles.Pathfinder/Circles.Pathfinder.csproj
new file mode 100644
index 0000000..2a9213c
--- /dev/null
+++ b/Circles.Pathfinder/Circles.Pathfinder.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Circles.Pathfinder/DTOs/FlowRequest.cs b/Circles.Pathfinder/DTOs/FlowRequest.cs
new file mode 100644
index 0000000..e1d90da
--- /dev/null
+++ b/Circles.Pathfinder/DTOs/FlowRequest.cs
@@ -0,0 +1,8 @@
+namespace Circles.Pathfinder.DTOs;
+
+public class FlowRequest
+{
+ public string? Source { get; set; }
+ public string? Sink { get; set; }
+ public string? TargetFlow { get; set; }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/DTOs/MaxFlowResponse.cs b/Circles.Pathfinder/DTOs/MaxFlowResponse.cs
new file mode 100644
index 0000000..10d080f
--- /dev/null
+++ b/Circles.Pathfinder/DTOs/MaxFlowResponse.cs
@@ -0,0 +1,13 @@
+namespace Circles.Pathfinder.DTOs;
+
+public class MaxFlowResponse
+{
+ public string MaxFlow { get; set; }
+ public List Transfers { get; set; }
+
+ public MaxFlowResponse(string maxFlow, List transfers)
+ {
+ MaxFlow = maxFlow;
+ Transfers = transfers;
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/DTOs/TransferPathStep.cs b/Circles.Pathfinder/DTOs/TransferPathStep.cs
new file mode 100644
index 0000000..3665f2a
--- /dev/null
+++ b/Circles.Pathfinder/DTOs/TransferPathStep.cs
@@ -0,0 +1,9 @@
+namespace Circles.Pathfinder.DTOs;
+
+public class TransferPathStep
+{
+ public string From { get; set; } = string.Empty;
+ public string To { get; set; } = string.Empty;
+ public string TokenOwner { get; set; } = string.Empty;
+ public string Value { get; set; } = string.Empty;
+}
diff --git a/Circles.Pathfinder/Data/LoadGraph.cs b/Circles.Pathfinder/Data/LoadGraph.cs
new file mode 100644
index 0000000..9b5afbe
--- /dev/null
+++ b/Circles.Pathfinder/Data/LoadGraph.cs
@@ -0,0 +1,54 @@
+using Npgsql;
+
+namespace Circles.Pathfinder.Data
+{
+ // TODO: Use CirclesQuery and remove the Npgsql dependency
+ public class LoadGraph(string connectionString)
+ {
+ public IEnumerable<(string Balance, string Account, string TokenAddress)> LoadV2Balances()
+ {
+ var balanceQuery = @"
+ select ""demurragedTotalBalance""::text, ""account"", ""tokenAddress""
+ from ""V_CrcV2_BalancesByAccountAndToken""
+ where ""demurragedTotalBalance"" > 0;
+ ";
+
+ using var connection = new NpgsqlConnection(connectionString);
+ connection.Open();
+
+ using var command = new NpgsqlCommand(balanceQuery, connection);
+ using var reader = command.ExecuteReader();
+
+ while (reader.Read())
+ {
+ var balance = reader.GetString(0);
+ var account = reader.GetString(1);
+ var tokenAddress = reader.GetString(2);
+
+ yield return (balance, account, tokenAddress);
+ }
+ }
+
+ public IEnumerable<(string Truster, string Trustee, int Limit)> LoadV2Trust()
+ {
+ var trustQuery = @"
+ select truster, trustee
+ from ""V_CrcV2_TrustRelations"";
+ ";
+
+ using var connection = new NpgsqlConnection(connectionString);
+ connection.Open();
+
+ using var command = new NpgsqlCommand(trustQuery, connection);
+ using var reader = command.ExecuteReader();
+
+ while (reader.Read())
+ {
+ var truster = reader.GetString(0);
+ var trustee = reader.GetString(1);
+
+ yield return (truster, trustee, 100); // Assuming a default trust limit of 100 in V2
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Edges/CapacityEdge.cs b/Circles.Pathfinder/Edges/CapacityEdge.cs
new file mode 100644
index 0000000..e3e423d
--- /dev/null
+++ b/Circles.Pathfinder/Edges/CapacityEdge.cs
@@ -0,0 +1,18 @@
+using System.Numerics;
+
+namespace Circles.Pathfinder.Edges;
+
+///
+/// Represents a capacity edge for potential token transfers between nodes.
+///
+public class CapacityEdge : Edge
+{
+ public string Token { get; }
+ public BigInteger InitialCapacity { get; }
+
+ public CapacityEdge(string from, string to, string token, BigInteger initialCapacity) : base(from, to)
+ {
+ Token = token;
+ InitialCapacity = initialCapacity;
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Edges/Edge.cs b/Circles.Pathfinder/Edges/Edge.cs
new file mode 100644
index 0000000..4145c46
--- /dev/null
+++ b/Circles.Pathfinder/Edges/Edge.cs
@@ -0,0 +1,13 @@
+namespace Circles.Pathfinder.Edges;
+
+public abstract class Edge
+{
+ public string From { get; }
+ public string To { get; }
+
+ public Edge(string from, string to)
+ {
+ From = from;
+ To = to;
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Edges/FlowEdge.cs b/Circles.Pathfinder/Edges/FlowEdge.cs
new file mode 100644
index 0000000..5a40bd6
--- /dev/null
+++ b/Circles.Pathfinder/Edges/FlowEdge.cs
@@ -0,0 +1,20 @@
+using System.Numerics;
+
+namespace Circles.Pathfinder.Edges;
+
+///
+/// Represents a flow edge for actual token transfers between nodes.
+///
+public class FlowEdge : CapacityEdge
+{
+ public BigInteger CurrentCapacity { get; set; }
+ public BigInteger Flow { get; set; }
+ public FlowEdge? ReverseEdge { get; set; }
+
+ public FlowEdge(string from, string to, string token, BigInteger initialCapacity)
+ : base(from, to, token, initialCapacity)
+ {
+ CurrentCapacity = initialCapacity;
+ Flow = 0;
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Edges/TrustEdge.cs b/Circles.Pathfinder/Edges/TrustEdge.cs
new file mode 100644
index 0000000..0a1899c
--- /dev/null
+++ b/Circles.Pathfinder/Edges/TrustEdge.cs
@@ -0,0 +1,11 @@
+namespace Circles.Pathfinder.Edges;
+
+///
+/// Represents a trust relationship between two nodes.
+///
+public class TrustEdge : Edge
+{
+ public TrustEdge(string from, string to) : base(from, to)
+ {
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Graphs/BalanceGraph.cs b/Circles.Pathfinder/Graphs/BalanceGraph.cs
new file mode 100644
index 0000000..9d89de2
--- /dev/null
+++ b/Circles.Pathfinder/Graphs/BalanceGraph.cs
@@ -0,0 +1,38 @@
+using System.Numerics;
+using Circles.Pathfinder.Edges;
+using Circles.Pathfinder.Nodes;
+
+namespace Circles.Pathfinder.Graphs;
+
+public class BalanceGraph : IGraph
+{
+ public IDictionary Nodes { get; } = new Dictionary();
+ public HashSet Edges { get; } = new();
+ public IDictionary BalanceNodes { get; } = new Dictionary();
+ public IDictionary AvatarNodes { get; } = new Dictionary();
+
+ public void AddAvatar(string avatarAddress)
+ {
+ Nodes.Add(avatarAddress, new AvatarNode(avatarAddress));
+ }
+
+ public void AddBalance(string address, string token, BigInteger balance)
+ {
+ address = address.ToLower();
+ token = token.ToLower();
+ if (!AvatarNodes.ContainsKey(address))
+ {
+ AvatarNodes.Add(address, new AvatarNode(address));
+ }
+
+ var balanceNode = new BalanceNode(address, token, balance);
+ Nodes.Add(balanceNode.Address, balanceNode);
+ BalanceNodes.Add(balanceNode.Address, balanceNode);
+
+ var capacityEdge = new CapacityEdge(address, balanceNode.Address, token, balance);
+ Edges.Add(capacityEdge);
+
+ AvatarNodes[address].OutEdges.Add(capacityEdge);
+ BalanceNodes[balanceNode.Address].InEdges.Add(capacityEdge);
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Graphs/CapacityGraph.cs b/Circles.Pathfinder/Graphs/CapacityGraph.cs
new file mode 100644
index 0000000..eb4e6cc
--- /dev/null
+++ b/Circles.Pathfinder/Graphs/CapacityGraph.cs
@@ -0,0 +1,63 @@
+using System.Numerics;
+using Circles.Pathfinder.Edges;
+using Circles.Pathfinder.Nodes;
+
+namespace Circles.Pathfinder.Graphs;
+
+public class CapacityGraph : IGraph
+{
+ public IDictionary Nodes { get; } = new Dictionary();
+ public IDictionary AvatarNodes { get; } = new Dictionary();
+ public IDictionary BalanceNodes { get; } = new Dictionary();
+ public HashSet Edges { get; } = new();
+
+ public void AddAvatar(string avatarAddress)
+ {
+ avatarAddress = avatarAddress.ToLower();
+ if (!AvatarNodes.ContainsKey(avatarAddress))
+ {
+ AvatarNodes.Add(avatarAddress, new AvatarNode(avatarAddress));
+ Nodes.Add(avatarAddress, AvatarNodes[avatarAddress]);
+ }
+ }
+
+ public void AddBalanceNode(string address, string token, BigInteger amount)
+ {
+ address = address.ToLower();
+ token = token.ToLower();
+
+ var balanceNode = new BalanceNode(address, token, amount);
+ balanceNode.Address = address;
+ BalanceNodes.TryAdd(balanceNode.Address, balanceNode);
+ Nodes.TryAdd(balanceNode.Address, balanceNode);
+ }
+
+ public void AddCapacityEdge(string from, string to, string token, BigInteger capacity)
+ {
+ from = from.ToLower();
+ to = to.ToLower();
+ token = token.ToLower();
+
+ var edge = new CapacityEdge(from, to, token, capacity);
+ Edges.Add(edge);
+
+ // Optionally, you can manage adjacency lists if needed
+ if (AvatarNodes.TryGetValue(from, out var node))
+ {
+ node.OutEdges.Add(edge);
+ }
+ else if (BalanceNodes.TryGetValue(from, out var balanceNode))
+ {
+ balanceNode.OutEdges.Add(edge);
+ }
+
+ if (AvatarNodes.TryGetValue(to, out var avatarNode))
+ {
+ avatarNode.InEdges.Add(edge);
+ }
+ else if (BalanceNodes.TryGetValue(to, out var balanceNode))
+ {
+ balanceNode.InEdges.Add(edge);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Graphs/FlowGraph.cs b/Circles.Pathfinder/Graphs/FlowGraph.cs
new file mode 100644
index 0000000..5a44936
--- /dev/null
+++ b/Circles.Pathfinder/Graphs/FlowGraph.cs
@@ -0,0 +1,225 @@
+using System.Numerics;
+using Circles.Pathfinder.Edges;
+using Circles.Pathfinder.Nodes;
+
+namespace Circles.Pathfinder.Graphs;
+
+public class FlowGraph : IGraph
+{
+ public IDictionary Nodes { get; } = new Dictionary();
+ public IDictionary AvatarNodes { get; } = new Dictionary();
+ public IDictionary BalanceNodes { get; } = new Dictionary();
+ public HashSet Edges { get; } = new();
+
+ public void AddAvatar(string avatarAddress)
+ {
+ if (!AvatarNodes.ContainsKey(avatarAddress))
+ {
+ AvatarNodes.Add(avatarAddress, new AvatarNode(avatarAddress));
+ Nodes.Add(avatarAddress, AvatarNodes[avatarAddress]);
+ }
+ }
+
+ public void AddBalanceNode(string address, string token, BigInteger amount)
+ {
+ var balanceNode = new BalanceNode(address, token, amount);
+ balanceNode.Address = address;
+ BalanceNodes.TryAdd(balanceNode.Address, balanceNode);
+ Nodes.TryAdd(balanceNode.Address, balanceNode);
+ }
+
+ public void AddCapacity(CapacityGraph capacityGraph)
+ {
+ foreach (var capacityEdge in capacityGraph.Edges)
+ {
+ AddCapacityEdge(capacityGraph, capacityEdge);
+ }
+ }
+
+ public void AddFlowEdge(FlowGraph flowGraph, FlowEdge flowEdge)
+ {
+ var fromNode = flowGraph.Nodes[flowEdge.From];
+ if (!Nodes.TryGetValue(fromNode.Address, out var from))
+ {
+ if (fromNode is AvatarNode)
+ {
+ AddAvatar(fromNode.Address);
+ }
+ else if (fromNode is BalanceNode fromBalance)
+ {
+ AddBalanceNode(fromBalance.Address, fromBalance.Token, fromBalance.Amount);
+ }
+
+ from = Nodes[fromNode.Address];
+ }
+
+ var toNode = flowGraph.Nodes[flowEdge.To];
+ if (!Nodes.TryGetValue(toNode.Address, out var to))
+ {
+ if (toNode is AvatarNode)
+ {
+ AddAvatar(toNode.Address);
+ }
+ else if (toNode is BalanceNode toBalance)
+ {
+ AddBalanceNode(toBalance.Address, toBalance.Token, toBalance.Amount);
+ }
+
+ to = Nodes[toNode.Address];
+ }
+
+ if (from.OutEdges.Any(o => o.To == to.Address && (o as FlowEdge)?.Flow == flowEdge.Flow))
+ {
+ return;
+ }
+
+ if (to.InEdges.Any(o => o.From == from.Address && (o as FlowEdge)?.Flow == flowEdge.Flow))
+ {
+ return;
+ }
+
+ var newFlowEdge = new FlowEdge(from.Address, to.Address, flowEdge.Token, flowEdge.CurrentCapacity);
+ newFlowEdge.Flow = flowEdge.Flow;
+
+ var newReverseEdge = new FlowEdge(to.Address, from.Address, flowEdge.Token,
+ flowEdge.ReverseEdge?.CurrentCapacity ?? BigInteger.Zero);
+ newReverseEdge.Flow = flowEdge.ReverseEdge?.Flow ?? BigInteger.Zero;
+
+ newFlowEdge.ReverseEdge = newReverseEdge;
+ newReverseEdge.ReverseEdge = newFlowEdge;
+
+ Edges.Add(newFlowEdge);
+ Edges.Add(newReverseEdge);
+
+ if (AvatarNodes.TryGetValue(from.Address, out var fromAvatarNode))
+ {
+ fromAvatarNode.OutEdges.Add(newFlowEdge);
+ }
+ else if (BalanceNodes.TryGetValue(from.Address, out var fromBalanceNode))
+ {
+ fromBalanceNode.OutEdges.Add(newFlowEdge);
+ }
+
+ if (AvatarNodes.TryGetValue(to.Address, out var toAvatarNode))
+ {
+ toAvatarNode.InEdges.Add(newFlowEdge);
+ }
+ else if (BalanceNodes.TryGetValue(to.Address, out var toBalanceNode))
+ {
+ toBalanceNode.InEdges.Add(newFlowEdge);
+ }
+ }
+
+ public void AddCapacityEdge(CapacityGraph capacityGraph, CapacityEdge capacityEdge)
+ {
+ var from = capacityEdge.From;
+ var to = capacityEdge.To;
+ var token = capacityEdge.Token;
+ var capacity = capacityEdge.InitialCapacity;
+
+ // Create edge and reverse edge
+ var edge = new FlowEdge(from, to, token, capacity);
+ Edges.Add(edge);
+
+ var reverseEdge = new FlowEdge(to, from, token, 0);
+ Edges.Add(reverseEdge);
+
+ edge.ReverseEdge = reverseEdge;
+ reverseEdge.ReverseEdge = edge;
+
+ // Create nodes if they don't exist
+ var fromNode = capacityGraph.Nodes[from];
+ if (fromNode is AvatarNode)
+ {
+ AddAvatar(fromNode.Address);
+ }
+ else if (fromNode is BalanceNode fromBalance)
+ {
+ AddBalanceNode(fromBalance.Address, fromBalance.Token, fromBalance.Amount);
+ }
+
+ var toNode = capacityGraph.Nodes[to];
+ if (toNode is AvatarNode)
+ {
+ AddAvatar(toNode.Address);
+ }
+ else if (toNode is BalanceNode toBalance)
+ {
+ AddBalanceNode(toBalance.Address, toBalance.Token, toBalance.Amount);
+ }
+
+ // manage adjacency lists
+ if (AvatarNodes.TryGetValue(from, out var node))
+ {
+ node.OutEdges.Add(edge);
+ }
+ else if (BalanceNodes.TryGetValue(from, out var balanceNode))
+ {
+ balanceNode.OutEdges.Add(edge);
+ }
+
+ if (AvatarNodes.TryGetValue(to, out var avatarNode))
+ {
+ avatarNode.InEdges.Add(edge);
+ }
+ else if (BalanceNodes.TryGetValue(to, out var balanceNode))
+ {
+ balanceNode.InEdges.Add(edge);
+ }
+
+ if (AvatarNodes.TryGetValue(to, out var avatarNode2))
+ {
+ avatarNode2.OutEdges.Add(reverseEdge);
+ }
+ else if (BalanceNodes.TryGetValue(to, out var balanceNode2))
+ {
+ balanceNode2.OutEdges.Add(reverseEdge);
+ }
+
+ if (AvatarNodes.TryGetValue(from, out var node2))
+ {
+ node2.InEdges.Add(reverseEdge);
+ }
+ else if (BalanceNodes.TryGetValue(from, out var balanceNode2))
+ {
+ balanceNode2.InEdges.Add(reverseEdge);
+ }
+ }
+
+ public List> ExtractPathsWithFlow(string sourceNode, string sinkNode)
+ {
+ var resultPaths = new List>();
+ var visited = new HashSet();
+
+ // A helper method to perform DFS and collect paths with positive flow
+ void Dfs(string currentNode, List currentPath)
+ {
+ if (currentNode == sinkNode)
+ {
+ resultPaths.Add(new List(currentPath)); // Store a copy of the path
+ return;
+ }
+
+ if (!Nodes.TryGetValue(currentNode, out var node)) return;
+
+ visited.Add(currentNode);
+
+ foreach (var edge in node.OutEdges.OfType())
+ {
+ if (edge.Flow > 0 && !visited.Contains(edge.To))
+ {
+ currentPath.Add(edge); // Add edge to the current path
+ Dfs(edge.To, currentPath); // Recursively go deeper
+ currentPath.Remove(edge); // Backtrack
+ }
+ }
+
+ visited.Remove(currentNode);
+ }
+
+ // Start DFS from the source node
+ Dfs(sourceNode, new List());
+
+ return resultPaths;
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Graphs/GraphExtensions.cs b/Circles.Pathfinder/Graphs/GraphExtensions.cs
new file mode 100644
index 0000000..a4bc9a5
--- /dev/null
+++ b/Circles.Pathfinder/Graphs/GraphExtensions.cs
@@ -0,0 +1,119 @@
+using System.Numerics;
+using Circles.Pathfinder.Edges;
+
+namespace Circles.Pathfinder.Graphs;
+
+public static class GraphExtensions
+{
+ ///
+ /// Computes the maximum flow from source to sink in the FlowGraph and collects all augmenting paths.
+ /// Stops when the target flow is met and prunes the flow to exactly match the target.
+ ///
+ /// The flow graph instance.
+ /// The source node identifier.
+ /// The sink node identifier.
+ /// The desired flow value to reach.
+ /// The total flow value up to the target flow.
+ public static BigInteger ComputeMaxFlowWithPaths(
+ this FlowGraph graph,
+ string source,
+ string sink,
+ BigInteger targetFlow)
+ {
+ BigInteger maxFlow = 0;
+
+ while (true)
+ {
+ // Use BFS to find an augmenting path
+ var (path, pathFlow) = graph.Bfs(source, sink);
+
+ if (pathFlow == 0)
+ {
+ break; // No more augmenting paths
+ }
+
+ // Calculate how much more flow we need
+ BigInteger remainingFlow = targetFlow - maxFlow;
+
+ // If the pathFlow exceeds the remaining targetFlow, prune it to the remaining amount
+ if (pathFlow > remainingFlow)
+ {
+ pathFlow = remainingFlow;
+ }
+
+ // Update the flow and capacities along the path
+ foreach (var edge in path)
+ {
+ edge.Flow += pathFlow;
+ edge.CurrentCapacity -= pathFlow;
+
+ if (edge.ReverseEdge != null)
+ {
+ edge.ReverseEdge.CurrentCapacity += pathFlow;
+ }
+ }
+
+ // Update the accumulated maxFlow
+ maxFlow += pathFlow;
+
+ // Stop if we've reached the target flow exactly
+ if (maxFlow >= targetFlow)
+ {
+ break;
+ }
+ }
+
+ // Return the accumulated flow, limited to the targetFlow
+ return maxFlow;
+ }
+
+ ///
+ /// Finds the shortest augmenting path in the FlowGraph using BFS and calculates the flow that can be pushed through it.
+ ///
+ /// The flow graph instance.
+ /// The source node identifier.
+ /// The sink node identifier.
+ /// A tuple containing the list of edges constituting the path and the flow that can be pushed through this path.
+ private static (List path, BigInteger flow) Bfs(this FlowGraph graph, string source, string sink)
+ {
+ var visited = new HashSet();
+ var queue = new Queue<(string node, List path)>();
+ visited.Add(source);
+ queue.Enqueue((source, new List()));
+
+ while (queue.Count > 0)
+ {
+ var (currentNode, currentPath) = queue.Dequeue();
+
+ // Check if the current node has outgoing edges
+ if (!graph.Nodes.TryGetValue(currentNode, out var node))
+ {
+ continue;
+ }
+
+ // Iterate through all outgoing edges of the current node
+ foreach (var edge in node.OutEdges.OfType())
+ {
+ if (edge.CurrentCapacity > 0 && !visited.Contains(edge.To))
+ {
+ // Append the current edge to the path
+ var newPath = new List(currentPath) { edge };
+
+ // If the sink is reached, return the path immediately
+ if (edge.To == sink)
+ {
+ BigInteger pathFlow = newPath.Min(e => e.CurrentCapacity);
+ return (newPath, pathFlow);
+ }
+
+ // Otherwise, continue traversing
+ visited.Add(edge.To);
+ queue.Enqueue((edge.To, newPath));
+ }
+ }
+ }
+
+ // No valid path found
+ return (new List(), 0);
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Graphs/GraphFactory.cs b/Circles.Pathfinder/Graphs/GraphFactory.cs
new file mode 100644
index 0000000..1b9fafa
--- /dev/null
+++ b/Circles.Pathfinder/Graphs/GraphFactory.cs
@@ -0,0 +1,148 @@
+using System.Numerics;
+using Circles.Pathfinder.Data;
+
+namespace Circles.Pathfinder.Graphs
+{
+ public class GraphFactory
+ {
+ ///
+ /// Loads all v2 trust edges from the database and creates a trust graph from them.
+ ///
+ /// A trust graph containing all v2 trust edges.
+ public TrustGraph V2TrustGraph(LoadGraph loadGraph)
+ {
+ var trustEdges = loadGraph.LoadV2Trust().ToArray();
+ var graph = new TrustGraph();
+ foreach (var trustEdge in trustEdges)
+ {
+ if (!graph.AvatarNodes.ContainsKey(trustEdge.Truster))
+ {
+ graph.AddAvatar(trustEdge.Truster);
+ }
+
+ if (!graph.AvatarNodes.ContainsKey(trustEdge.Trustee))
+ {
+ graph.AddAvatar(trustEdge.Trustee);
+ }
+
+ graph.AddTrustEdge(trustEdge.Truster, trustEdge.Trustee);
+ }
+
+ return graph;
+ }
+
+ ///
+ /// Loads all v2 balances from the database and creates a balance graph from them.
+ ///
+ /// A balance graph containing all v2 balances and holders.
+ public BalanceGraph V2BalanceGraph(LoadGraph loadGraph)
+ {
+ var balances = loadGraph.LoadV2Balances().ToArray();
+ var graph = new BalanceGraph();
+ foreach (var balance in balances)
+ {
+ if (!graph.AvatarNodes.ContainsKey(balance.Account))
+ {
+ graph.AddAvatar(balance.Account);
+ }
+
+ graph.AddBalance(balance.Account, balance.TokenAddress, BigInteger.Parse(balance.Balance));
+ }
+
+ return graph;
+ }
+
+ ///
+ /// Takes a balance graph and a trust graph and creates a capacity graph from them.
+ ///
+ /// The balance graph to use.
+ /// The trust graph to use.
+ /// A capacity graph created from the balance and trust graphs.
+ public CapacityGraph CreateCapacityGraph(BalanceGraph balanceGraph, TrustGraph trustGraph)
+ {
+ // Take the balance and trust graphs and create a capacity graph.
+ // 1. Create a unified list of nodes from both graphs
+ // 2. Leave the capacity edges from the balance graph in place
+ // 3. Create more capacity edges based on the trust graph:
+ // - For each balance, check if there is a node that is willing to accept the balance (is trusting the token issuer)
+ // - If there is, create a capacity edge from the balance node to the accepting node
+
+ var capacityGraph = new CapacityGraph();
+
+ // Step 1: Create a unified list of nodes from both graphs
+ foreach (var avatar in balanceGraph.AvatarNodes.Values)
+ {
+ capacityGraph.AddAvatar(avatar.Address);
+ }
+
+ foreach (var avatar in trustGraph.AvatarNodes.Values)
+ {
+ capacityGraph.AddAvatar(avatar.Address);
+ }
+
+ // Add BalanceNodes
+ foreach (var balanceNode in balanceGraph.BalanceNodes.Values)
+ {
+ capacityGraph.AddBalanceNode(balanceNode.Address, balanceNode.Token, balanceNode.Amount);
+ }
+
+ // Step 2: Leave the capacity edges from the balance graph in place
+ foreach (var capacityEdge in balanceGraph.Edges)
+ {
+ capacityGraph.AddCapacityEdge(
+ capacityEdge.From,
+ capacityEdge.To,
+ capacityEdge.Token,
+ capacityEdge.InitialCapacity
+ );
+ }
+
+ // Step 3: Create more capacity edges based on the trust graph
+ // Optimization: Precompute a trustee-to-trusters lookup dictionary
+ var trusteeToTrusters = new Dictionary>();
+
+ foreach (var edge in trustGraph.Edges)
+ {
+ if (!trusteeToTrusters.TryGetValue(edge.To, out var trusters))
+ {
+ trusters = new List();
+ trusteeToTrusters[edge.To] = trusters;
+ }
+
+ trusters.Add(edge.From);
+ }
+
+ foreach (var balanceNode in balanceGraph.BalanceNodes.Values)
+ {
+ string tokenIssuer = balanceNode.Token;
+
+ if (trusteeToTrusters.TryGetValue(tokenIssuer, out var acceptingNodes))
+ {
+ foreach (var acceptingNode in acceptingNodes)
+ {
+ // Avoid creating edges to self or invalid nodes
+ if (acceptingNode == balanceNode.HolderAddress)
+ continue;
+
+ capacityGraph.AddCapacityEdge(
+ balanceNode.Address,
+ acceptingNode,
+ balanceNode.Token,
+ balanceNode.Amount
+ );
+ }
+ }
+ }
+
+ return capacityGraph;
+ }
+
+ public FlowGraph CreateFlowGraph(CapacityGraph capacityGraph)
+ {
+ var flowGraph = new FlowGraph();
+ flowGraph.AddCapacity(capacityGraph);
+
+ return flowGraph;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Graphs/GraphTraversal.cs b/Circles.Pathfinder/Graphs/GraphTraversal.cs
new file mode 100644
index 0000000..a7bab9f
--- /dev/null
+++ b/Circles.Pathfinder/Graphs/GraphTraversal.cs
@@ -0,0 +1,138 @@
+using Circles.Pathfinder.Edges;
+
+namespace Circles.Pathfinder.Graphs;
+
+///
+/// Enumeration for specifying graph traversal algorithms.
+///
+public enum TraversalType
+{
+ DFS,
+ BFS
+}
+
+///
+/// Provides graph traversal methods using DFS and BFS.
+///
+public static class GraphTraversal
+{
+ ///
+ /// Traverses the graph from the start node to the end node using the specified traversal algorithm.
+ /// Optionally filters edges based on a predicate and allows early termination after the first path.
+ ///
+ /// The type of edge in the graph, must inherit from Edge.
+ /// The graph instance.
+ /// The starting node identifier.
+ /// The ending node identifier.
+ /// The traversal algorithm to use (DFS or BFS).
+ /// A predicate to filter edges. If null, all edges are considered.
+ /// If true, the traversal stops after finding the first path.
+ /// An enumerable of paths, where each path is a list of edges.
+ public static IEnumerable> Traverse(
+ this IGraph graph,
+ string start,
+ string end,
+ TraversalType traversalType,
+ Func? edgePredicate = null,
+ bool returnAfterFirst = false)
+ where TEdge : Edge
+ {
+ edgePredicate ??= _ => true; // If no predicate is provided, consider all edges.
+
+ switch (traversalType)
+ {
+ case TraversalType.DFS:
+ return DfsTraverse(graph, start, end, edgePredicate, returnAfterFirst);
+ case TraversalType.BFS:
+ return BfsTraverse(graph, start, end, edgePredicate, returnAfterFirst);
+ default:
+ throw new ArgumentException("Invalid traversal type.");
+ }
+ }
+
+ ///
+ /// Performs an iterative DFS traversal.
+ ///
+ private static IEnumerable> DfsTraverse(
+ IGraph graph,
+ string start,
+ string end,
+ Func? edgePredicate,
+ bool returnAfterFirst)
+ where TEdge : Edge
+ {
+ var stack = new Stack<(string currentNode, List path, HashSet visited)>();
+ stack.Push((start, new List(), new HashSet { start }));
+
+ while (stack.Count > 0)
+ {
+ var (current, path, visited) = stack.Pop();
+
+ if (current == end)
+ {
+ yield return path;
+
+ if (returnAfterFirst)
+ yield break;
+
+ continue;
+ }
+
+ if (graph.Nodes.TryGetValue(current, out var node))
+ {
+ foreach (var edge in node.OutEdges.OfType().Where(edgePredicate).Reverse())
+ {
+ if (!visited.Contains(edge.To))
+ {
+ var newPath = new List(path) { edge };
+ var newVisited = new HashSet(visited) { edge.To };
+ stack.Push((edge.To, newPath, newVisited));
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Performs an iterative BFS traversal.
+ ///
+ private static IEnumerable> BfsTraverse(
+ IGraph graph,
+ string start,
+ string end,
+ Func? edgePredicate,
+ bool returnAfterFirst)
+ where TEdge : Edge
+ {
+ var queue = new Queue<(string currentNode, List path, HashSet visited)>();
+ queue.Enqueue((start, new List(), new HashSet { start }));
+
+ while (queue.Count > 0)
+ {
+ var (current, path, visited) = queue.Dequeue();
+
+ if (current == end)
+ {
+ yield return path;
+
+ if (returnAfterFirst)
+ yield break;
+
+ continue;
+ }
+
+ if (graph.Nodes.TryGetValue(current, out var node))
+ {
+ foreach (var edge in node.OutEdges.OfType().Where(edgePredicate))
+ {
+ if (!visited.Contains(edge.To))
+ {
+ var newPath = new List(path) { edge };
+ var newVisited = new HashSet(visited) { edge.To };
+ queue.Enqueue((edge.To, newPath, newVisited));
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Graphs/IGraph.cs b/Circles.Pathfinder/Graphs/IGraph.cs
new file mode 100644
index 0000000..2d97a29
--- /dev/null
+++ b/Circles.Pathfinder/Graphs/IGraph.cs
@@ -0,0 +1,11 @@
+using Circles.Pathfinder.Edges;
+using Circles.Pathfinder.Nodes;
+
+namespace Circles.Pathfinder.Graphs;
+
+public interface IGraph
+ where TEdge : Edge
+{
+ IDictionary Nodes { get; }
+ HashSet Edges { get; }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Graphs/TrustGraph.cs b/Circles.Pathfinder/Graphs/TrustGraph.cs
new file mode 100644
index 0000000..1ef8c82
--- /dev/null
+++ b/Circles.Pathfinder/Graphs/TrustGraph.cs
@@ -0,0 +1,41 @@
+using Circles.Pathfinder.Edges;
+using Circles.Pathfinder.Nodes;
+
+namespace Circles.Pathfinder.Graphs;
+
+public class TrustGraph : IGraph
+{
+ public IDictionary Nodes { get; } = new Dictionary();
+ public IDictionary AvatarNodes { get; } = new Dictionary();
+ public HashSet Edges { get; } = new();
+
+ public void AddAvatar(string avatarAddress)
+ {
+ var avatar = new AvatarNode(avatarAddress);
+ AvatarNodes.Add(avatarAddress, avatar);
+ Nodes.Add(avatarAddress, avatar);
+ }
+
+ public void AddTrustEdge(string truster, string trustee)
+ {
+ truster = truster.ToLower();
+ trustee = trustee.ToLower();
+
+ if (!AvatarNodes.TryGetValue(truster, out var trusterNode))
+ {
+ trusterNode = new AvatarNode(truster);
+ AvatarNodes[truster] = trusterNode;
+ }
+
+ if (!AvatarNodes.TryGetValue(trustee, out var trusteeNode))
+ {
+ trusteeNode = new AvatarNode(trustee);
+ AvatarNodes[trustee] = trusteeNode;
+ }
+
+ trusterNode.OutEdges.Add(new TrustEdge(truster, trustee));
+ AvatarNodes[trustee].InEdges.Add(new TrustEdge(truster, trustee));
+
+ Edges.Add(new TrustEdge(truster, trustee));
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/IPathfinder.cs b/Circles.Pathfinder/IPathfinder.cs
new file mode 100644
index 0000000..f9358a5
--- /dev/null
+++ b/Circles.Pathfinder/IPathfinder.cs
@@ -0,0 +1,8 @@
+using Circles.Pathfinder.DTOs;
+
+namespace Circles.Pathfinder;
+
+public interface IPathfinder
+{
+ public Task ComputeMaxFlow(FlowRequest request);
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Nodes/AvatarNode.cs b/Circles.Pathfinder/Nodes/AvatarNode.cs
new file mode 100644
index 0000000..5abb068
--- /dev/null
+++ b/Circles.Pathfinder/Nodes/AvatarNode.cs
@@ -0,0 +1,8 @@
+namespace Circles.Pathfinder.Nodes;
+
+public class AvatarNode : Node
+{
+ public AvatarNode(string address) : base(address)
+ {
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Nodes/BalanceNode.cs b/Circles.Pathfinder/Nodes/BalanceNode.cs
new file mode 100644
index 0000000..1adcad5
--- /dev/null
+++ b/Circles.Pathfinder/Nodes/BalanceNode.cs
@@ -0,0 +1,17 @@
+using System.Numerics;
+
+namespace Circles.Pathfinder.Nodes;
+
+public class BalanceNode : Node
+{
+ public string Token { get; }
+ public BigInteger Amount { get; }
+
+ public string HolderAddress => Address.Split("-")[0];
+
+ public BalanceNode(string address, string token, BigInteger amount) : base(address + "-" + token)
+ {
+ Token = token;
+ Amount = amount;
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/Nodes/Node.cs b/Circles.Pathfinder/Nodes/Node.cs
new file mode 100644
index 0000000..7708737
--- /dev/null
+++ b/Circles.Pathfinder/Nodes/Node.cs
@@ -0,0 +1,15 @@
+using Circles.Pathfinder.Edges;
+
+namespace Circles.Pathfinder.Nodes;
+
+public abstract class Node
+{
+ public string Address { get; set; }
+ public List OutEdges { get; } = new();
+ public List InEdges { get; } = new();
+
+ protected Node(string address)
+ {
+ Address = address;
+ }
+}
\ No newline at end of file
diff --git a/Circles.Pathfinder/V2Pathfinder.cs b/Circles.Pathfinder/V2Pathfinder.cs
new file mode 100644
index 0000000..5e241a6
--- /dev/null
+++ b/Circles.Pathfinder/V2Pathfinder.cs
@@ -0,0 +1,209 @@
+using System.Numerics;
+using Circles.Pathfinder.Data;
+using Circles.Pathfinder.DTOs;
+using Circles.Pathfinder.Edges;
+using Circles.Pathfinder.Graphs;
+
+namespace Circles.Pathfinder;
+
+public class V2Pathfinder : IPathfinder
+{
+ private readonly LoadGraph _loadGraph;
+ private readonly GraphFactory _graphFactory;
+
+ public V2Pathfinder(LoadGraph loadGraph, GraphFactory graphFactory)
+ {
+ _loadGraph = loadGraph;
+ _graphFactory = graphFactory;
+ }
+
+ public async Task ComputeMaxFlow(FlowRequest request)
+ {
+ if (string.IsNullOrEmpty(request.Source) || string.IsNullOrEmpty(request.Sink))
+ {
+ throw new ArgumentException("Source and Sink must be provided.");
+ }
+
+ if (!BigInteger.TryParse(request.TargetFlow, out var targetFlow))
+ {
+ throw new ArgumentException("TargetFlow must be a valid integer.");
+ }
+
+ // Load Trust and Balance Graphs
+ var trustGraph = _graphFactory.V2TrustGraph(_loadGraph);
+ var balanceGraph = _graphFactory.V2BalanceGraph(_loadGraph);
+
+ // Create Capacity Graph
+ var capacityGraph = _graphFactory.CreateCapacityGraph(balanceGraph, trustGraph);
+
+ // Create Flow Graph
+ var flowGraph = _graphFactory.CreateFlowGraph(capacityGraph);
+
+ // Validate Source and Sink
+ if (!flowGraph.Nodes.ContainsKey(request.Source))
+ {
+ throw new ArgumentException($"Source node '{request.Source}' does not exist in the graph.");
+ }
+
+ if (!flowGraph.Nodes.ContainsKey(request.Sink))
+ {
+ throw new ArgumentException($"Sink node '{request.Sink}' does not exist in the graph.");
+ }
+
+ if (IsBalanceNode(request.Source))
+ {
+ throw new ArgumentException("Source node cannot be a balance node.");
+ }
+
+ if (IsBalanceNode(request.Sink))
+ {
+ throw new ArgumentException("Sink node cannot be a balance node.");
+ }
+
+ // Compute Max Flow
+ var maxFlow = flowGraph.ComputeMaxFlowWithPaths(request.Source, request.Sink, targetFlow);
+
+ // Extract Paths with Flow
+ var pathsWithFlow = flowGraph.ExtractPathsWithFlow(request.Source, request.Sink);
+
+ // Collapse balance nodes to get a collapsed graph
+ var collapsedGraph = CollapseBalanceNodes(pathsWithFlow);
+
+ // Create transfer steps from the collapsed graph
+ var transferSteps = new List();
+
+ foreach (var edge in collapsedGraph.Edges)
+ {
+ // For each edge, create a transfer step
+ if (edge.Flow == BigInteger.Zero)
+ {
+ // Filter reverse edges
+ continue;
+ }
+
+ transferSteps.Add(new TransferPathStep
+ {
+ From = edge.From,
+ To = edge.To,
+ TokenOwner = edge.Token,
+ Value = edge.Flow.ToString()
+ });
+ }
+
+ // Prepare the response
+ var response = new MaxFlowResponse(maxFlow.ToString(), transferSteps);
+
+ return response;
+ }
+
+ ///
+ /// Collapses balance nodes in the paths and returns a collapsed flow graph.
+ ///
+ /// The list of paths with flow.
+ /// A FlowGraph with balance nodes collapsed.
+ private FlowGraph CollapseBalanceNodes(List> pathsWithFlow)
+ {
+ var collapsedGraph = new FlowGraph();
+
+ // 1. Collect all avatar nodes
+ var avatars = new HashSet();
+ pathsWithFlow.ForEach(o => o.ForEach(p =>
+ {
+ if (!IsBalanceNode(p.From))
+ {
+ avatars.Add(p.From);
+ }
+
+ if (!IsBalanceNode(p.To))
+ {
+ avatars.Add(p.To);
+ }
+ }));
+ foreach (var avatar in avatars)
+ {
+ collapsedGraph.AddAvatar(avatar);
+ }
+
+ // 2. Remove all balance nodes, fuse the ends together, and add that edge to the new flow graph
+ pathsWithFlow.ForEach(o =>
+ {
+ for (int i = 0; i < o.Count; i++)
+ {
+ var currentEdge = o[i];
+ var nextEdge = i < o.Count - 1 ? o[i + 1] : null;
+
+ if (IsBalanceNode(currentEdge.To) && nextEdge != null && nextEdge.From == currentEdge.To)
+ {
+ // We are at a balance node, so we need to collapse it by merging currentEdge and nextEdge
+
+ // The flow through the balance node is limited by both the incoming and outgoing flows
+ var mergedFlow = BigInteger.Min(currentEdge.Flow, nextEdge.Flow);
+
+ var mergedEdge = new FlowEdge(
+ currentEdge.From,
+ nextEdge.To,
+ nextEdge.Token,
+ currentEdge.CurrentCapacity // Adjust as needed
+ )
+ {
+ Flow = mergedFlow
+ };
+ try
+ {
+ collapsedGraph.AddFlowEdge(collapsedGraph, mergedEdge);
+ i++; // Skip the nextEdge since we have merged it
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message);
+
+ // Log the stack trace
+ Console.WriteLine(e.StackTrace);
+
+ // Unpack the inner exception(s) recursively
+ while (e.InnerException != null)
+ {
+ e = e.InnerException;
+ Console.WriteLine(e.Message);
+ Console.WriteLine(e.StackTrace);
+ }
+ }
+ }
+ else
+ {
+ try
+ {
+ // If not a balance node, add the current edge to the collapsed graph
+ collapsedGraph.AddFlowEdge(collapsedGraph, currentEdge);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message);
+
+ // Log the stack trace
+ Console.WriteLine(e.StackTrace);
+
+ // Unpack the inner exception(s) recursively
+ while (e.InnerException != null)
+ {
+ e = e.InnerException;
+ Console.WriteLine(e.Message);
+ Console.WriteLine(e.StackTrace);
+ }
+ }
+ }
+ }
+ });
+ return collapsedGraph;
+ }
+
+ ///
+ /// Determines if a given node address is a balance node.
+ ///
+ /// The node address to check.
+ /// True if it's a balance node; otherwise, false.
+ private bool IsBalanceNode(string nodeAddress)
+ {
+ return nodeAddress.Contains("-");
+ }
+}
\ No newline at end of file
diff --git a/Circles.sln b/Circles.sln
index ce6f8e1..6b8397a 100644
--- a/Circles.sln
+++ b/Circles.sln
@@ -69,6 +69,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "protobuf-types", "protobuf-
v2.standardTreasury.proto = v2.standardTreasury.proto
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Circles.Pathfinder", "Circles.Pathfinder\Circles.Pathfinder.csproj", "{756580B1-2D2A-4FFE-9711-046A10853003}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -123,6 +125,10 @@ Global
{C68CD7EC-9F4D-481E-AC4E-E063794E9488}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C68CD7EC-9F4D-481E-AC4E-E063794E9488}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C68CD7EC-9F4D-481E-AC4E-E063794E9488}.Release|Any CPU.Build.0 = Release|Any CPU
+ {756580B1-2D2A-4FFE-9711-046A10853003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {756580B1-2D2A-4FFE-9711-046A10853003}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {756580B1-2D2A-4FFE-9711-046A10853003}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {756580B1-2D2A-4FFE-9711-046A10853003}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2B7D2126-6D6F-4A5B-81A1-3E5F1A9645F6} = {64189094-62E5-48CA-BD66-1A7B82263BA8}
diff --git a/arm64.Dockerfile b/arm64.Dockerfile
index 9bee603..115e014 100644
--- a/arm64.Dockerfile
+++ b/arm64.Dockerfile
@@ -20,5 +20,6 @@ COPY --from=build /circles-nethermind-plugin/Circles.Index.CirclesViews.dll /net
COPY --from=build /circles-nethermind-plugin/Circles.Index.Rpc.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Circles.Index.Query.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Circles.Index.Utils.dll /nethermind/plugins
+COPY --from=build /circles-nethermind-plugin/Circles.Pathfinder.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Nethermind.Int256.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Npgsql.dll /nethermind/plugins
diff --git a/x64.Dockerfile b/x64.Dockerfile
index 8c6b56b..11c24db 100644
--- a/x64.Dockerfile
+++ b/x64.Dockerfile
@@ -21,5 +21,6 @@ COPY --from=build /circles-nethermind-plugin/Circles.Index.Postgres.dll /netherm
COPY --from=build /circles-nethermind-plugin/Circles.Index.Rpc.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Circles.Index.Query.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Circles.Index.Utils.dll /nethermind/plugins
+COPY --from=build /circles-nethermind-plugin/Circles.Pathfinder.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Nethermind.Int256.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Npgsql.dll /nethermind/plugins
diff --git a/x64.debug.Dockerfile b/x64.debug.Dockerfile
index 90f744c..c6ee674 100644
--- a/x64.debug.Dockerfile
+++ b/x64.debug.Dockerfile
@@ -32,5 +32,7 @@ COPY --from=build /circles-nethermind-plugin/Circles.Index.Query.dll /nethermind
COPY --from=build /circles-nethermind-plugin/Circles.Index.Query.pdb /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Circles.Index.Utils.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Circles.Index.Utils.pdb /nethermind/plugins
+COPY --from=build /circles-nethermind-plugin/Circles.Pathfinder.dll /nethermind/plugins
+COPY --from=build /circles-nethermind-plugin/Circles.Pathfinder.pdb /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Nethermind.Int256.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Npgsql.dll /nethermind/plugins
\ No newline at end of file
diff --git a/x64.debug.spaceneth.Dockerfile b/x64.debug.spaceneth.Dockerfile
index 752a0f7..9c18c0f 100644
--- a/x64.debug.spaceneth.Dockerfile
+++ b/x64.debug.spaceneth.Dockerfile
@@ -32,6 +32,8 @@ COPY --from=build /circles-nethermind-plugin/Circles.Index.Query.dll /nethermind
COPY --from=build /circles-nethermind-plugin/Circles.Index.Query.pdb /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Circles.Index.Utils.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Circles.Index.Utils.pdb /nethermind/plugins
+COPY --from=build /circles-nethermind-plugin/Circles.Pathfinder.dll /nethermind/plugins
+COPY --from=build /circles-nethermind-plugin/Circles.Pathfinder.pdb /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Nethermind.Int256.dll /nethermind/plugins
COPY --from=build /circles-nethermind-plugin/Npgsql.dll /nethermind/plugins
From 855aedcef2622b1804bf645996a544353417dda7 Mon Sep 17 00:00:00 2001
From: daniel <4954577+jaensen@users.noreply.github.com>
Date: Thu, 24 Oct 2024 03:14:16 +0200
Subject: [PATCH 2/5] remove the spaceneth section from the readme
---
Readme.md | 135 +-----------------------------------------------------
1 file changed, 2 insertions(+), 133 deletions(-)
diff --git a/Readme.md b/Readme.md
index beb782b..0643b52 100644
--- a/Readme.md
+++ b/Readme.md
@@ -12,12 +12,6 @@ query [Circles](https://www.aboutcircles.com/) protocol events.
* [Run node](#4-run-node)
* [Ports](#ports)
* [Volumes](#volumes)
- * [Run a spaceneth node](#run-a-spaceneth-node)
- * [Deploying the Circles contracts](#deploying-the-circles-contracts)
- * [Blockscout](#blockscout)
- * [Get a funded account](#get-a-funded-account)
- * [Manipulate time](#manipulate-time)
- * [Reset the spaceneth node](#reset-the-spaceneth-node)
* [Circles RPC methods](#circles-rpc-methods)
* [circles_getTotalBalance / circlesV2_getTotalBalance](#circles_gettotalbalance--circlesv2_gettotalbalance)
* [circles_getTokenBalances](#circles_gettokenbalances)
@@ -51,10 +45,10 @@ For a detailed description of the available RPC methods, see the [Circles RPC me
### Run a node
The repository contains a docker-compose file to start a Nethermind node with the Circles plugin installed. There are
-configurations for Gnosis Chain, Chiado and Spaceneth (a local testnet).
+configurations for Gnosis Chain and Chiado.
The quickstart configurations use [lighthouse](https://github.com/sigp/lighthouse) as consensus engine and spin up a
-postgres database to store the indexed data. The spaceneth configuration comes with a local blockscout instance.
+postgres database to store the indexed data.
#### 1. Clone the repository
@@ -124,131 +118,6 @@ at the same RPC endpoint.
* `./.state/postgres-chiado|postgres-gnosis` - Postgres data
* `./.state/jwtsecret-chiado|jwtsecret-gnosis` - Shared secret between execution and consensus engine
-### Run a spaceneth node
-
-The process of setting up a local only node is a bit more involved. However, by using this approach, you gain
-the possibility to manipulate the node's time and don't need any xDai, which is useful for testing purposes.
-
-```bash
-docker compose -f docker-compose.spaceneth.yml up -d
-```
-
-#### Deploying the Circles contracts
-
-Since a new spaceneth node is empty except for the genesis block, you need to deploy the Circles contracts yourself.
-
-```bash
-# Clone the Circles contracts submodules
-git submodule update --init --recursive
-```
-
-To deploy the contracts, we add a script to the Circles contracts repository that deploys the contracts to the spaceneth
-node.
-
-```bash
-# Add the deploy script to the Circles contracts repository
-./add-deploy-script-tp-v2-repo.sh
-```
-
-As a last step, we need to replace the `tload` and `tstore` based reentrancy guards with a more classic approach.
-Spaceneth does not support these instructions.
-
-1. Open `circles-contracts-v2/src/hub/Hub.sol`
-2. Replace this modifier:
- ```solidity
- modifier nonReentrant(uint8 _code) {
- assembly {
- if tload(0) { revert(0, 0) }
- tstore(0, 1)
- }
- _;
- assembly {
- tstore(0, 0)
- }
- }
- ```
-
- with this modifier:
-
- ```solidity
- bool private _reentrancyGuard;
- modifier nonReentrant(uint8 _code) {
- if (_reentrancyGuard) {
- revert CirclesReentrancyGuard(_code);
- }
- _reentrancyGuard = true;
- _;
- _reentrancyGuard = false;
- }
- ```
-3. Open `circles-contracts-v2/foundry.toml`
-4. Remove this line:
- ```toml
- evm_version = 'cancun'
- ```
-
-Now you can deploy the contracts to the spaceneth node.
-
-```bash
-# Deploy the contracts
-npm install && ./deploy.sh
-```
-
-#### Blockscout
-
-You can access the blockscout instance at `http://localhost:4000`.
-
-#### Get a funded account
-
-You can get a funded account private key by running:
-
-```bash
-npm install
-node createFundedAccount.js
-```
-
-#### Manipulate time
-
-You can fast-forward the time by running:
-
-```bash
-curl -X POST -H "Content-Type: application/json" -d '{"fake_time": "+1d x1"}' http://localhost:5000/set_time
-```
-
-**Explanation:**
-
-```json
-{
- "fake_time": "+1d x1"
-}
-```
-
-`+1d` means to offset the current time by 1 day. `x1` means that the time will pass as real time. If you want to
-fast-forward the time, you can increase the number of `x` (e.g. `x10`).
-
-_NOTE: This will restart the nethermind node._
-
-#### Reset the spaceneth node
-
-If you want to start over, you can reset the spaceneth node by running:
-
-```bash
-# Stop the stack
-docker compose -f docker-compose.spaceneth.yml down
-```
-
-```bash
-# Delete all persisted data
-sudo rm -rf .state/nethermind-spaceneth
-sudo rm -rf .state/postgres-spaceneth
-sudo rm -rf .state/postgres2-spaceneth
-sudo rm -rf .state/redis-spaceneth
-```
-
-```bash
-# Start the stack again
-docker compose -f docker-compose.spaceneth.yml up
-```
## Circles RPC methods
From 73fb36e92f73386cb55b16ec4c45ef39b2e2ba0e Mon Sep 17 00:00:00 2001
From: daniel <4954577+jaensen@users.noreply.github.com>
Date: Thu, 24 Oct 2024 03:17:53 +0200
Subject: [PATCH 3/5] removed unused class
---
Circles.Pathfinder/Graphs/GraphTraversal.cs | 138 --------------------
Circles.sln | 10 --
2 files changed, 148 deletions(-)
delete mode 100644 Circles.Pathfinder/Graphs/GraphTraversal.cs
diff --git a/Circles.Pathfinder/Graphs/GraphTraversal.cs b/Circles.Pathfinder/Graphs/GraphTraversal.cs
deleted file mode 100644
index a7bab9f..0000000
--- a/Circles.Pathfinder/Graphs/GraphTraversal.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-using Circles.Pathfinder.Edges;
-
-namespace Circles.Pathfinder.Graphs;
-
-///
-/// Enumeration for specifying graph traversal algorithms.
-///
-public enum TraversalType
-{
- DFS,
- BFS
-}
-
-///
-/// Provides graph traversal methods using DFS and BFS.
-///
-public static class GraphTraversal
-{
- ///
- /// Traverses the graph from the start node to the end node using the specified traversal algorithm.
- /// Optionally filters edges based on a predicate and allows early termination after the first path.
- ///
- /// The type of edge in the graph, must inherit from Edge.
- /// The graph instance.
- /// The starting node identifier.
- /// The ending node identifier.
- /// The traversal algorithm to use (DFS or BFS).
- /// A predicate to filter edges. If null, all edges are considered.
- /// If true, the traversal stops after finding the first path.
- /// An enumerable of paths, where each path is a list of edges.
- public static IEnumerable> Traverse(
- this IGraph graph,
- string start,
- string end,
- TraversalType traversalType,
- Func? edgePredicate = null,
- bool returnAfterFirst = false)
- where TEdge : Edge
- {
- edgePredicate ??= _ => true; // If no predicate is provided, consider all edges.
-
- switch (traversalType)
- {
- case TraversalType.DFS:
- return DfsTraverse(graph, start, end, edgePredicate, returnAfterFirst);
- case TraversalType.BFS:
- return BfsTraverse(graph, start, end, edgePredicate, returnAfterFirst);
- default:
- throw new ArgumentException("Invalid traversal type.");
- }
- }
-
- ///
- /// Performs an iterative DFS traversal.
- ///
- private static IEnumerable> DfsTraverse(
- IGraph graph,
- string start,
- string end,
- Func? edgePredicate,
- bool returnAfterFirst)
- where TEdge : Edge
- {
- var stack = new Stack<(string currentNode, List path, HashSet visited)>();
- stack.Push((start, new List(), new HashSet { start }));
-
- while (stack.Count > 0)
- {
- var (current, path, visited) = stack.Pop();
-
- if (current == end)
- {
- yield return path;
-
- if (returnAfterFirst)
- yield break;
-
- continue;
- }
-
- if (graph.Nodes.TryGetValue(current, out var node))
- {
- foreach (var edge in node.OutEdges.OfType().Where(edgePredicate).Reverse())
- {
- if (!visited.Contains(edge.To))
- {
- var newPath = new List(path) { edge };
- var newVisited = new HashSet(visited) { edge.To };
- stack.Push((edge.To, newPath, newVisited));
- }
- }
- }
- }
- }
-
- ///
- /// Performs an iterative BFS traversal.
- ///
- private static IEnumerable> BfsTraverse(
- IGraph graph,
- string start,
- string end,
- Func? edgePredicate,
- bool returnAfterFirst)
- where TEdge : Edge
- {
- var queue = new Queue<(string currentNode, List path, HashSet visited)>();
- queue.Enqueue((start, new List(), new HashSet { start }));
-
- while (queue.Count > 0)
- {
- var (current, path, visited) = queue.Dequeue();
-
- if (current == end)
- {
- yield return path;
-
- if (returnAfterFirst)
- yield break;
-
- continue;
- }
-
- if (graph.Nodes.TryGetValue(current, out var node))
- {
- foreach (var edge in node.OutEdges.OfType().Where(edgePredicate))
- {
- if (!visited.Contains(edge.To))
- {
- var newPath = new List(path) { edge };
- var newVisited = new HashSet(visited) { edge.To };
- queue.Enqueue((edge.To, newPath, newVisited));
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Circles.sln b/Circles.sln
index 6b8397a..4fe7154 100644
--- a/Circles.sln
+++ b/Circles.sln
@@ -20,23 +20,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{B30CB7
x64.Dockerfile = x64.Dockerfile
arm64.Dockerfile = arm64.Dockerfile
docker-compose.chiado.yml = docker-compose.chiado.yml
- docker-compose.spaceneth.yml = docker-compose.spaceneth.yml
.dockerignore = .dockerignore
- circles-chainspec.json = circles-chainspec.json
- circles-config.cfg = circles-config.cfg
- time_controller.py = time_controller.py
x64.debug.Dockerfile = x64.debug.Dockerfile
.env.example = .env.example
docker-compose.gnosis.yml = docker-compose.gnosis.yml
Readme.md = Readme.md
- envs\common-smart-contract-verifier.env = envs\common-smart-contract-verifier.env
- envs\common-blockscout.env = envs\common-blockscout.env
.gitmodules = .gitmodules
- createFundedAccount.js = createFundedAccount.js
- package.json = package.json
- deploy.sh = deploy.sh
.gitignore = .gitignore
- add-deploy-script-tp-v2-repo.sh = add-deploy-script-tp-v2-repo.sh
v2-example-requests.md = v2-example-requests.md
v1-example-requests.md = v1-example-requests.md
general-example-requests.md = general-example-requests.md
From d8614bc7bd68b8b7239468f8dff053e0d7302a67 Mon Sep 17 00:00:00 2001
From: daniel <4954577+jaensen@users.noreply.github.com>
Date: Thu, 24 Oct 2024 03:29:21 +0200
Subject: [PATCH 4/5] Filter paths with less than 0.1 Circles
---
Circles.Pathfinder/Graphs/FlowGraph.cs | 15 ++++++++++++++-
Circles.Pathfinder/V2Pathfinder.cs | 4 +++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/Circles.Pathfinder/Graphs/FlowGraph.cs b/Circles.Pathfinder/Graphs/FlowGraph.cs
index 5a44936..730daa7 100644
--- a/Circles.Pathfinder/Graphs/FlowGraph.cs
+++ b/Circles.Pathfinder/Graphs/FlowGraph.cs
@@ -186,7 +186,14 @@ public void AddCapacityEdge(CapacityGraph capacityGraph, CapacityEdge capacityEd
}
}
- public List> ExtractPathsWithFlow(string sourceNode, string sinkNode)
+ ///
+ /// Searches the graph for liquid paths from the source node to the sink node.
+ ///
+ /// The source
+ /// The sink
+ /// Only consider edges with more or equal flow
+ /// A list of paths with flow
+ public List> ExtractPathsWithFlow(string sourceNode, string sinkNode, BigInteger threshold)
{
var resultPaths = new List>();
var visited = new HashSet();
@@ -209,6 +216,12 @@ void Dfs(string currentNode, List currentPath)
if (edge.Flow > 0 && !visited.Contains(edge.To))
{
currentPath.Add(edge); // Add edge to the current path
+ if (edge.Flow < threshold)
+ {
+ // Filter edges with less flow than the threshold
+ continue;
+ }
+
Dfs(edge.To, currentPath); // Recursively go deeper
currentPath.Remove(edge); // Backtrack
}
diff --git a/Circles.Pathfinder/V2Pathfinder.cs b/Circles.Pathfinder/V2Pathfinder.cs
index 5e241a6..32dd05a 100644
--- a/Circles.Pathfinder/V2Pathfinder.cs
+++ b/Circles.Pathfinder/V2Pathfinder.cs
@@ -64,7 +64,9 @@ public async Task ComputeMaxFlow(FlowRequest request)
var maxFlow = flowGraph.ComputeMaxFlowWithPaths(request.Source, request.Sink, targetFlow);
// Extract Paths with Flow
- var pathsWithFlow = flowGraph.ExtractPathsWithFlow(request.Source, request.Sink);
+ // (Don't consider paths smaller than 0.1 Circles)
+ var pathsWithFlow =
+ flowGraph.ExtractPathsWithFlow(request.Source, request.Sink, BigInteger.Parse("100000000000000000"));
// Collapse balance nodes to get a collapsed graph
var collapsedGraph = CollapseBalanceNodes(pathsWithFlow);
From 3f11066955484befa9faafb3330cd80d17786e66 Mon Sep 17 00:00:00 2001
From: daniel <4954577+jaensen@users.noreply.github.com>
Date: Thu, 24 Oct 2024 04:13:22 +0200
Subject: [PATCH 5/5] keep try{}catch
---
Circles.Pathfinder/V2Pathfinder.cs | 3 ++-
docker-compose.gnosis.yml | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/Circles.Pathfinder/V2Pathfinder.cs b/Circles.Pathfinder/V2Pathfinder.cs
index 32dd05a..f6bf159 100644
--- a/Circles.Pathfinder/V2Pathfinder.cs
+++ b/Circles.Pathfinder/V2Pathfinder.cs
@@ -148,7 +148,8 @@ private FlowGraph CollapseBalanceNodes(List> pathsWithFlow)
currentEdge.CurrentCapacity // Adjust as needed
)
{
- Flow = mergedFlow
+ Flow = mergedFlow,
+ ReverseEdge = nextEdge.ReverseEdge
};
try
{
diff --git a/docker-compose.gnosis.yml b/docker-compose.gnosis.yml
index a5ed39d..40de40f 100644
--- a/docker-compose.gnosis.yml
+++ b/docker-compose.gnosis.yml
@@ -2,7 +2,7 @@ services:
nethermind-gnosis:
build:
context: .
- dockerfile: x64.Dockerfile
+ dockerfile: x64.debug.Dockerfile
restart: unless-stopped
depends_on:
- postgres-gnosis