diff --git a/CodexDistTestCore/CodexDistTestCore.csproj b/CodexDistTestCore/CodexDistTestCore.csproj index 142eefb..cb1fb2e 100644 --- a/CodexDistTestCore/CodexDistTestCore.csproj +++ b/CodexDistTestCore/CodexDistTestCore.csproj @@ -8,8 +8,6 @@ - - diff --git a/DistTestCore/Codex/CodexContainerRecipe.cs b/DistTestCore/Codex/CodexContainerRecipe.cs index 31a9a7f..50d6441 100644 --- a/DistTestCore/Codex/CodexContainerRecipe.cs +++ b/DistTestCore/Codex/CodexContainerRecipe.cs @@ -5,9 +5,10 @@ namespace DistTestCore.Codex { public class CodexContainerRecipe : ContainerRecipeFactory { + public const string DockerImage = "thatbenbierens/nim-codex:sha-b204837"; public const string MetricsPortTag = "metrics_port"; - protected override string Image => "thatbenbierens/nim-codex:sha-b204837"; + protected override string Image => DockerImage; protected override void Initialize(StartupConfig startupConfig) { @@ -38,6 +39,7 @@ namespace DistTestCore.Codex { var gethConfig = startupConfig.Get(); var companionNode = gethConfig.CompanionNodes[Index]; + Additional(companionNode); // Bootstrap node access from within the cluster: //var ip = gethConfig.BootstrapNode.RunningContainers.RunningPod.Ip; diff --git a/DistTestCore/CodexNodeFactory.cs b/DistTestCore/CodexNodeFactory.cs index 8320111..9b67158 100644 --- a/DistTestCore/CodexNodeFactory.cs +++ b/DistTestCore/CodexNodeFactory.cs @@ -25,7 +25,7 @@ namespace DistTestCore public OnlineCodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group) { var metricsAccess = metricsAccessFactory.CreateMetricsAccess(access.Container); - var marketplaceAccess = marketplaceAccessFactory.CreateMarketplaceAccess(); + var marketplaceAccess = marketplaceAccessFactory.CreateMarketplaceAccess(access); return new OnlineCodexNode(lifecycle, access, group, metricsAccess, marketplaceAccess); } } diff --git a/DistTestCore/CodexStarter.cs b/DistTestCore/CodexStarter.cs index e6d4484..e90c0f0 100644 --- a/DistTestCore/CodexStarter.cs +++ b/DistTestCore/CodexStarter.cs @@ -69,7 +69,6 @@ namespace DistTestCore { var group = new CodexNodeGroup(lifecycle, codexSetup, runningContainers, codexNodeFactory); RunningGroups.Add(group); - return group; } diff --git a/DistTestCore/DistTest.cs b/DistTestCore/DistTest.cs index e3631f4..e6c9ac0 100644 --- a/DistTestCore/DistTest.cs +++ b/DistTestCore/DistTest.cs @@ -1,5 +1,8 @@ -using DistTestCore.Logs; +using DistTestCore.Codex; +using DistTestCore.Logs; +using DistTestCore.Marketplace; using DistTestCore.Metrics; +using Logging; using NUnit.Framework; namespace DistTestCore @@ -8,6 +11,7 @@ namespace DistTestCore public abstract class DistTest { private TestLifecycle lifecycle = null!; + private TestLog log = null!; [OneTimeSetUp] public void GlobalSetup() @@ -26,7 +30,10 @@ namespace DistTestCore Error($"Global setup cleanup failed with: {ex}"); throw; } - Log("Global setup cleanup successful"); + log.Log("Global setup cleanup successful"); + log.Log($"Codex image: {CodexContainerRecipe.DockerImage}"); + log.Log($"Prometheus image: {PrometheusContainerRecipe.DockerImage}"); + log.Log($"Geth image: {GethContainerRecipe.DockerImage}"); } [SetUp] @@ -38,6 +45,7 @@ namespace DistTestCore } else { + log.Log($"Run: {TestContext.CurrentContext.Test.Name}"); CreateNewTestLifecycle(); } } @@ -47,6 +55,7 @@ namespace DistTestCore { try { + log.Log($"{TestContext.CurrentContext.Test.Name} = {TestContext.CurrentContext.Result.Outcome.Status}"); lifecycle.Log.EndTest(); IncludeLogsAndMetricsOnTestFailure(); lifecycle.DeleteAllResources(); diff --git a/DistTestCore/DistTestCore.csproj b/DistTestCore/DistTestCore.csproj index 944e748..ccd5f66 100644 --- a/DistTestCore/DistTestCore.csproj +++ b/DistTestCore/DistTestCore.csproj @@ -16,6 +16,6 @@ - + diff --git a/DistTestCore/GethStarter.cs b/DistTestCore/GethStarter.cs index c0db927..3e01a5e 100644 --- a/DistTestCore/GethStarter.cs +++ b/DistTestCore/GethStarter.cs @@ -5,34 +5,41 @@ namespace DistTestCore { public class GethStarter { - private readonly TestLifecycle lifecycle; - private readonly GethBootstrapNodeStarter bootstrapNodeStarter; + private readonly GethBootstrapNodeCache bootstrapNodeCache; private readonly GethCompanionNodeStarter companionNodeStarter; - private GethBootstrapNodeInfo? bootstrapNode; + private readonly TestLifecycle lifecycle; public GethStarter(TestLifecycle lifecycle, WorkflowCreator workflowCreator) { - this.lifecycle = lifecycle; - - bootstrapNodeStarter = new GethBootstrapNodeStarter(lifecycle, workflowCreator); + bootstrapNodeCache = new GethBootstrapNodeCache(new GethBootstrapNodeStarter(lifecycle, workflowCreator)); companionNodeStarter = new GethCompanionNodeStarter(lifecycle, workflowCreator); + this.lifecycle = lifecycle; } public GethStartResult BringOnlineMarketplaceFor(CodexSetup codexSetup) { if (codexSetup.MarketplaceConfig == null) return CreateMarketplaceUnavailableResult(); - EnsureBootstrapNode(); - var companionNodes = StartCompanionNodes(codexSetup); + var bootstrapNode = bootstrapNodeCache.Get(); + var companionNodes = StartCompanionNodes(codexSetup, bootstrapNode); - TransferInitialBalance(codexSetup.MarketplaceConfig.InitialBalance, bootstrapNode, companionNodes); + TransferInitialBalance(bootstrapNode, codexSetup.MarketplaceConfig.InitialBalance, companionNodes); - return new GethStartResult(CreateMarketplaceAccessFactory(), bootstrapNode!, companionNodes); + return CreateGethStartResult(bootstrapNode, companionNodes); } - private void TransferInitialBalance(int initialBalance, GethBootstrapNodeInfo? bootstrapNode, GethCompanionNodeInfo[] companionNodes) + private void TransferInitialBalance(GethBootstrapNodeInfo bootstrapNode, int initialBalance, GethCompanionNodeInfo[] companionNodes) { - aaaa + var interaction = bootstrapNode.StartInteraction(lifecycle.Log); + foreach (var node in companionNodes) + { + interaction.TransferTo(node.Account, initialBalance); + } + } + + private GethStartResult CreateGethStartResult(GethBootstrapNodeInfo bootstrapNode, GethCompanionNodeInfo[] companionNodes) + { + return new GethStartResult(CreateMarketplaceAccessFactory(bootstrapNode), bootstrapNode, companionNodes); } private GethStartResult CreateMarketplaceUnavailableResult() @@ -40,20 +47,34 @@ namespace DistTestCore return new GethStartResult(new MarketplaceUnavailableAccessFactory(), null!, Array.Empty()); } - private IMarketplaceAccessFactory CreateMarketplaceAccessFactory() + private IMarketplaceAccessFactory CreateMarketplaceAccessFactory(GethBootstrapNodeInfo bootstrapNode) { - throw new NotImplementedException(); + return new GethMarketplaceAccessFactory(lifecycle.Log, bootstrapNode!); } - private void EnsureBootstrapNode() + private GethCompanionNodeInfo[] StartCompanionNodes(CodexSetup codexSetup, GethBootstrapNodeInfo bootstrapNode) { - if (bootstrapNode != null) return; - bootstrapNode = bootstrapNodeStarter.StartGethBootstrapNode(); + return companionNodeStarter.StartCompanionNodesFor(codexSetup, bootstrapNode); + } + } + + public class GethBootstrapNodeCache + { + private readonly GethBootstrapNodeStarter bootstrapNodeStarter; + private GethBootstrapNodeInfo? bootstrapNode; + + public GethBootstrapNodeCache(GethBootstrapNodeStarter bootstrapNodeStarter) + { + this.bootstrapNodeStarter = bootstrapNodeStarter; } - private GethCompanionNodeInfo[] StartCompanionNodes(CodexSetup codexSetup) + public GethBootstrapNodeInfo Get() { - return companionNodeStarter.StartCompanionNodesFor(codexSetup, bootstrapNode!); + if (bootstrapNode == null) + { + bootstrapNode = bootstrapNodeStarter.StartGethBootstrapNode(); + } + return bootstrapNode; } } } diff --git a/DistTestCore/Marketplace/GethBootstrapNodeInfo.cs b/DistTestCore/Marketplace/GethBootstrapNodeInfo.cs index 8f897ef..39b6715 100644 --- a/DistTestCore/Marketplace/GethBootstrapNodeInfo.cs +++ b/DistTestCore/Marketplace/GethBootstrapNodeInfo.cs @@ -1,4 +1,6 @@ using KubernetesWorkflow; +using Logging; +using NethereumWorkflow; namespace DistTestCore.Marketplace { @@ -14,5 +16,14 @@ namespace DistTestCore.Marketplace public RunningContainers RunningContainers { get; } public string Account { get; } public string GenesisJsonBase64 { get; } + + public NethereumInteraction StartInteraction(TestLog log) + { + var ip = RunningContainers.RunningPod.Cluster.IP; + var port = RunningContainers.Containers[0].ServicePorts[0].Number; + + var creator = new NethereumInteractionCreator(log, ip, port, Account); + return creator.CreateWorkflow(); + } } } diff --git a/DistTestCore/Marketplace/GethContainerRecipe.cs b/DistTestCore/Marketplace/GethContainerRecipe.cs index 2db8529..2dbfbde 100644 --- a/DistTestCore/Marketplace/GethContainerRecipe.cs +++ b/DistTestCore/Marketplace/GethContainerRecipe.cs @@ -4,10 +4,12 @@ namespace DistTestCore.Marketplace { public class GethContainerRecipe : ContainerRecipeFactory { - protected override string Image => "thatbenbierens/geth-confenv:latest"; + public const string DockerImage = "thatbenbierens/geth-confenv:latest"; public const string HttpPortTag = "http_port"; public const string AccountFilename = "account_string.txt"; public const string GenesisFilename = "genesis.json"; + + protected override string Image => DockerImage; protected override void Initialize(StartupConfig startupConfig) { diff --git a/DistTestCore/Marketplace/MarketplaceAccess.cs b/DistTestCore/Marketplace/MarketplaceAccess.cs index aa55cbf..8de63dd 100644 --- a/DistTestCore/Marketplace/MarketplaceAccess.cs +++ b/DistTestCore/Marketplace/MarketplaceAccess.cs @@ -15,21 +15,14 @@ namespace DistTestCore.Marketplace public class MarketplaceAccess : IMarketplaceAccess { private readonly TestLog log; - private readonly CodexNodeGroup group; + private readonly GethBootstrapNodeInfo bootstrapNode; + private readonly GethCompanionNodeInfo companionNode; - public MarketplaceAccess(TestLog log, CodexNodeGroup group) + public MarketplaceAccess(TestLog log, GethBootstrapNodeInfo bootstrapNode, GethCompanionNodeInfo companionNode) { this.log = log; - this.group = group; - } - - public void Initialize() - { - EnsureAccount(); - - marketplaceController.AddToBalance(container.Account, group.Origin.MarketplaceConfig!.InitialBalance); - - log.Log($"Initialized Geth companion node with account '{container.Account}' and initial balance {group.Origin.MarketplaceConfig!.InitialBalance}"); + this.bootstrapNode = bootstrapNode; + this.companionNode = companionNode; } public void RequestStorage(ContentId contentId, int pricePerBytePerSecond, float requiredCollateral, float minRequiredNumberOfNodes) @@ -44,12 +37,13 @@ namespace DistTestCore.Marketplace public void AssertThatBalance(IResolveConstraint constraint, string message = "") { - throw new NotImplementedException(); + Assert.That(GetBalance(), constraint, message); } public decimal GetBalance() { - return marketplaceController.GetBalance(container.Account); + var interaction = bootstrapNode.StartInteraction(log); + return interaction.GetBalance(companionNode.Account); } } diff --git a/DistTestCore/Marketplace/MarketplaceAccessFactory.cs b/DistTestCore/Marketplace/MarketplaceAccessFactory.cs index e3de573..fba1155 100644 --- a/DistTestCore/Marketplace/MarketplaceAccessFactory.cs +++ b/DistTestCore/Marketplace/MarketplaceAccessFactory.cs @@ -1,13 +1,16 @@ -namespace DistTestCore.Marketplace +using DistTestCore.Codex; +using Logging; + +namespace DistTestCore.Marketplace { public interface IMarketplaceAccessFactory { - IMarketplaceAccess CreateMarketplaceAccess(); + IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access); } public class MarketplaceUnavailableAccessFactory : IMarketplaceAccessFactory { - public IMarketplaceAccess CreateMarketplaceAccess() + public IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access) { return new MarketplaceUnavailable(); } @@ -15,10 +18,25 @@ public class GethMarketplaceAccessFactory : IMarketplaceAccessFactory { - public IMarketplaceAccess CreateMarketplaceAccess() + private readonly TestLog log; + private readonly GethBootstrapNodeInfo bootstrapNode; + + public GethMarketplaceAccessFactory(TestLog log, GethBootstrapNodeInfo bootstrapNode) { - - return new MarketplaceAccess(query, codexContainer); + this.log = log; + this.bootstrapNode = bootstrapNode; + } + + public IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access) + { + var companionNode = GetGethCompanionNode(access); + return new MarketplaceAccess(log, bootstrapNode, companionNode); + } + + private GethCompanionNodeInfo GetGethCompanionNode(CodexAccess access) + { + var node = access.Container.Recipe.Additionals.Single(a => a is GethCompanionNodeInfo); + return (GethCompanionNodeInfo)node; } } } diff --git a/DistTestCore/Metrics/PrometheusContainerRecipe.cs b/DistTestCore/Metrics/PrometheusContainerRecipe.cs index 5152ff7..46587cf 100644 --- a/DistTestCore/Metrics/PrometheusContainerRecipe.cs +++ b/DistTestCore/Metrics/PrometheusContainerRecipe.cs @@ -4,7 +4,9 @@ namespace DistTestCore.Metrics { public class PrometheusContainerRecipe : ContainerRecipeFactory { - protected override string Image => "thatbenbierens/prometheus-envconf:latest"; + public const string DockerImage = "thatbenbierens/prometheus-envconf:latest"; + + protected override string Image => DockerImage; protected override void Initialize(StartupConfig startupConfig) { diff --git a/KubernetesWorkflow/ContainerRecipe.cs b/KubernetesWorkflow/ContainerRecipe.cs index f676c7f..9fbbb9f 100644 --- a/KubernetesWorkflow/ContainerRecipe.cs +++ b/KubernetesWorkflow/ContainerRecipe.cs @@ -2,13 +2,14 @@ { public class ContainerRecipe { - public ContainerRecipe(int number, string image, Port[] exposedPorts, Port[] internalPorts, EnvVar[] envVars) + public ContainerRecipe(int number, string image, Port[] exposedPorts, Port[] internalPorts, EnvVar[] envVars, object[] additionals) { Number = number; Image = image; ExposedPorts = exposedPorts; InternalPorts = internalPorts; EnvVars = envVars; + Additionals = additionals; } public string Name { get { return $"ctnr{Number}"; } } @@ -17,6 +18,7 @@ public Port[] ExposedPorts { get; } public Port[] InternalPorts { get; } public EnvVar[] EnvVars { get; } + public object[] Additionals { get; } public Port GetPortByTag(string tag) { diff --git a/KubernetesWorkflow/ContainerRecipeFactory.cs b/KubernetesWorkflow/ContainerRecipeFactory.cs index 136b209..07dc58e 100644 --- a/KubernetesWorkflow/ContainerRecipeFactory.cs +++ b/KubernetesWorkflow/ContainerRecipeFactory.cs @@ -5,6 +5,7 @@ private readonly List exposedPorts = new List(); private readonly List internalPorts = new List(); private readonly List envVars = new List(); + private readonly List additionals = new List(); private RecipeComponentFactory factory = null!; public ContainerRecipe CreateRecipe(int index, int containerNumber, RecipeComponentFactory factory, StartupConfig config) @@ -15,11 +16,12 @@ Initialize(config); - var recipe = new ContainerRecipe(containerNumber, Image, exposedPorts.ToArray(), internalPorts.ToArray(), envVars.ToArray()); + var recipe = new ContainerRecipe(containerNumber, Image, exposedPorts.ToArray(), internalPorts.ToArray(), envVars.ToArray(), additionals.ToArray()); exposedPorts.Clear(); internalPorts.Clear(); envVars.Clear(); + additionals.Clear(); this.factory = null!; return recipe; @@ -63,5 +65,10 @@ { envVars.Add(factory.CreateEnvVar(name, value.Number)); } + + protected void Additional(object userData) + { + additionals.Add(userData); + } } } diff --git a/Nethereum/NethereumWorkflow.cs b/Nethereum/NethereumInteraction.cs similarity index 60% rename from Nethereum/NethereumWorkflow.cs rename to Nethereum/NethereumInteraction.cs index 795ef27..832f32f 100644 --- a/Nethereum/NethereumWorkflow.cs +++ b/Nethereum/NethereumInteraction.cs @@ -1,34 +1,41 @@ -using Nethereum.Hex.HexTypes; +using Logging; +using Nethereum.Hex.HexTypes; using Nethereum.Web3; using System.Numerics; using Utils; namespace NethereumWorkflow { - public class NethereumWorkflow + public class NethereumInteraction { + private readonly TestLog log; private readonly Web3 web3; private readonly string rootAccount; - internal NethereumWorkflow(Web3 web3, string rootAccount) + internal NethereumInteraction(TestLog log, Web3 web3, string rootAccount) { + this.log = log; this.web3 = web3; this.rootAccount = rootAccount; } - public void AddToBalance(string account, decimal amount) + public void TransferTo(string account, decimal amount) { if (amount < 1 || string.IsNullOrEmpty(account)) throw new ArgumentException("Invalid arguments for AddToBalance"); var value = ToHexBig(amount); var transactionId = Time.Wait(web3.Eth.TransactionManager.SendTransactionAsync(rootAccount, account, value)); Time.Wait(web3.Eth.TransactionManager.TransactionReceiptService.PollForReceiptAsync(transactionId)); + + Log($"Transferred {amount} to {account}"); } public decimal GetBalance(string account) { var bigInt = Time.Wait(web3.Eth.GetBalance.SendRequestAsync(account)); - return (decimal)bigInt.Value; + var result = ToDecimal(bigInt); + Log($"Balance of {account} is {result}"); + return result; } private HexBigInteger ToHexBig(decimal amount) @@ -37,5 +44,15 @@ namespace NethereumWorkflow var str = bigint.ToString("X"); return new HexBigInteger(str); } + + private decimal ToDecimal(HexBigInteger hexBigInteger) + { + return (decimal)hexBigInteger.Value; + } + + private void Log(string msg) + { + log.Log(msg); + } } } diff --git a/Nethereum/NethereumWorkflowCreator.cs b/Nethereum/NethereumInteractionCreator.cs similarity index 57% rename from Nethereum/NethereumWorkflowCreator.cs rename to Nethereum/NethereumInteractionCreator.cs index 33ebf92..b0b41f7 100644 --- a/Nethereum/NethereumWorkflowCreator.cs +++ b/Nethereum/NethereumInteractionCreator.cs @@ -1,23 +1,26 @@ -using Nethereum.Web3; +using Logging; +using Nethereum.Web3; namespace NethereumWorkflow { - public class NethereumWorkflowCreator + public class NethereumInteractionCreator { + private readonly TestLog log; private readonly string ip; private readonly int port; private readonly string rootAccount; - public NethereumWorkflowCreator(string ip, int port, string rootAccount) + public NethereumInteractionCreator(TestLog log, string ip, int port, string rootAccount) { + this.log = log; this.ip = ip; this.port = port; this.rootAccount = rootAccount; } - public NethereumWorkflow CreateWorkflow() + public NethereumInteraction CreateWorkflow() { - return new NethereumWorkflow(CreateWeb3(), rootAccount); + return new NethereumInteraction(log, CreateWeb3(), rootAccount); } private Web3 CreateWeb3() diff --git a/Tests/BasicTests/SimpleTests.cs b/Tests/BasicTests/SimpleTests.cs index bf0c331..943b598 100644 --- a/Tests/BasicTests/SimpleTests.cs +++ b/Tests/BasicTests/SimpleTests.cs @@ -103,42 +103,42 @@ namespace Tests.BasicTests primary2.Metrics.AssertThat("libp2p_peers", Is.EqualTo(1)); } - //[Test] - //public void MarketplaceExample() - //{ - // var group = SetupCodexNodes(4) - // .WithStorageQuota(10.GB()) - // .EnableMarketplace(initialBalance: 20) - // .BringOnline(); + [Test] + public void MarketplaceExample() + { + var group = SetupCodexNodes(4) + .WithStorageQuota(10.GB()) + .EnableMarketplace(initialBalance: 20) + .BringOnline(); - // foreach (var node in group) - // { - // Assert.That(node.Marketplace.GetBalance(), Is.EqualTo(20)); - // } + foreach (var node in group) + { + Assert.That(node.Marketplace.GetBalance(), Is.EqualTo(20)); + } - // // WIP: Balance is now only ETH. - // // todo: All nodes should have plenty of ETH to pay for transactions. - // // todo: Upload our own token, use this exclusively. ETH should be invisibile to the tests. + // WIP: Balance is now only ETH. + // todo: All nodes should have plenty of ETH to pay for transactions. + // todo: Upload our own token, use this exclusively. ETH should be invisibile to the tests. - // //var secondary = SetupCodexNodes(1) - // // .EnableMarketplace(initialBalance: 1000) - // // .BringOnline()[0]; + //var secondary = SetupCodexNodes(1) + // .EnableMarketplace(initialBalance: 1000) + // .BringOnline()[0]; - // //primary.ConnectToPeer(secondary); - // //primary.Marketplace.MakeStorageAvailable(10.GB(), minPricePerBytePerSecond: 1, maxCollateral: 20); + //primary.ConnectToPeer(secondary); + //primary.Marketplace.MakeStorageAvailable(10.GB(), minPricePerBytePerSecond: 1, maxCollateral: 20); - // //var testFile = GenerateTestFile(10.MB()); - // //var contentId = secondary.UploadFile(testFile); - // //secondary.Marketplace.RequestStorage(contentId, pricePerBytePerSecond: 2, - // // requiredCollateral: 10, minRequiredNumberOfNodes: 1); + //var testFile = GenerateTestFile(10.MB()); + //var contentId = secondary.UploadFile(testFile); + //secondary.Marketplace.RequestStorage(contentId, pricePerBytePerSecond: 2, + // requiredCollateral: 10, minRequiredNumberOfNodes: 1); - // //primary.Marketplace.AssertThatBalance(Is.LessThan(20), "Collateral was not placed."); - // //var primaryBalance = primary.Marketplace.GetBalance(); + //primary.Marketplace.AssertThatBalance(Is.LessThan(20), "Collateral was not placed."); + //var primaryBalance = primary.Marketplace.GetBalance(); - // //secondary.Marketplace.AssertThatBalance(Is.LessThan(1000), "Contractor was not charged for storage."); - // //primary.Marketplace.AssertThatBalance(Is.GreaterThan(primaryBalance), "Storer was not paid for storage."); - //} + //secondary.Marketplace.AssertThatBalance(Is.LessThan(1000), "Contractor was not charged for storage."); + //primary.Marketplace.AssertThatBalance(Is.GreaterThan(primaryBalance), "Storer was not paid for storage."); + } private void PerformOneClientTest(IOnlineCodexNode primary) { diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln index b47da0c..1f5c04f 100644 --- a/cs-codex-dist-testing.sln +++ b/cs-codex-dist-testing.sln @@ -17,7 +17,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "Utils\Utils.csproj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Logging", "Logging\Logging.csproj", "{8481A4A6-4BDD-41B0-A3EB-EF53F7BD40D1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nethereum", "Nethereum\Nethereum.csproj", "{D6C3555E-D52D-4993-A87B-71AB650398FD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NethereumWorkflow", "Nethereum\NethereumWorkflow.csproj", "{D6C3555E-D52D-4993-A87B-71AB650398FD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution