From 4085e57e0bf6b25e4959fadf904ee486d254f238 Mon Sep 17 00:00:00 2001 From: Lingfu Che Date: Thu, 27 Sep 2018 14:15:33 +0800 Subject: [PATCH 1/6] Add a sample about how to let Netereum to connect to the transaction node directly without the connector --- samples/Nethereum_sample/.gitignore | 32 ++++++ samples/Nethereum_sample/NethereumSample.sln | 25 +++++ .../NethereumSample/NethereumSample.csproj | 14 +++ .../NethereumSample/Program.cs | 100 ++++++++++++++++++ samples/Nethereum_sample/README.md | 68 ++++++++++++ 5 files changed, 239 insertions(+) create mode 100644 samples/Nethereum_sample/.gitignore create mode 100644 samples/Nethereum_sample/NethereumSample.sln create mode 100644 samples/Nethereum_sample/NethereumSample/NethereumSample.csproj create mode 100644 samples/Nethereum_sample/NethereumSample/Program.cs create mode 100644 samples/Nethereum_sample/README.md diff --git a/samples/Nethereum_sample/.gitignore b/samples/Nethereum_sample/.gitignore new file mode 100644 index 0000000..b5e7d97 --- /dev/null +++ b/samples/Nethereum_sample/.gitignore @@ -0,0 +1,32 @@ +*.swp +*.*~ +project.lock.json +.DS_Store +*.pyc + +# Visual Studio Code +.vscode + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +msbuild.log +msbuild.err +msbuild.wrn + +# Visual Studio 2015 +.vs/ diff --git a/samples/Nethereum_sample/NethereumSample.sln b/samples/Nethereum_sample/NethereumSample.sln new file mode 100644 index 0000000..f818c07 --- /dev/null +++ b/samples/Nethereum_sample/NethereumSample.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2035 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NethereumSample", "NethereumSample\NethereumSample.csproj", "{2FB6E152-E08B-47CA-84F7-1FDD5D0B5B79}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2FB6E152-E08B-47CA-84F7-1FDD5D0B5B79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FB6E152-E08B-47CA-84F7-1FDD5D0B5B79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FB6E152-E08B-47CA-84F7-1FDD5D0B5B79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FB6E152-E08B-47CA-84F7-1FDD5D0B5B79}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B51E8970-A8BF-4FA9-BBA7-9D4085FFCEBC} + EndGlobalSection +EndGlobal diff --git a/samples/Nethereum_sample/NethereumSample/NethereumSample.csproj b/samples/Nethereum_sample/NethereumSample/NethereumSample.csproj new file mode 100644 index 0000000..d30597f --- /dev/null +++ b/samples/Nethereum_sample/NethereumSample/NethereumSample.csproj @@ -0,0 +1,14 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + diff --git a/samples/Nethereum_sample/NethereumSample/Program.cs b/samples/Nethereum_sample/NethereumSample/Program.cs new file mode 100644 index 0000000..8c0076f --- /dev/null +++ b/samples/Nethereum_sample/NethereumSample/Program.cs @@ -0,0 +1,100 @@ +using System; +using System.Text; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Diagnostics; + +using Nethereum.Web3; +using Nethereum.JsonRpc.Client; +using Nethereum.Web3.Accounts.Managed; +using Nethereum.Hex.HexTypes; + +namespace NethereumSample +{ + class Program + { + //Please set your own parameters here!!! + //If you haven't got an account, you can uncomment the code in the region "new account", + //set the passphase of your future account in the variable accountPassphase, and skip the setting of the variable "account". + #region parameters + static string username = "testmember"; + static string password = "123456"; + static string nodeUri = "https://testmember.blockchain.ppe.azure-int.net:3200"; + //static string nodeUri = "http://127.0.0.1:3101"; + static string account = "0xafc7400cdec0b3ed6267d73c327e5c6689e2171b"; + static string accountPassphase = "123456"; + /* + static string username = ""; + static string password = ""; + static string nodeUri = ""; + static string account = ""; + static string accountPassphase = ""; + */ + #endregion + + static Web3 web3 = null; + static void Main(string[] args) + { + //This region is the key part to let Nethereum to connect to the transaction nodes directly without the blockchain connector! + //Following the code in this region, you can use the instance web3 to connect to the transaction nodes. + #region web3 initialization + var authValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", username, password))); + var authHeader = new AuthenticationHeaderValue("Basic", authValue); + web3 = new Web3(new RpcClient(new Uri(nodeUri), authHeader, null, null)); + + //if you want to construct a Web3 instance with a managed account, you can use the code below: + //web3 = new Web3(new ManagedAccount(account, accountPassphase), new RpcClient(new Uri(nodeUri), authHeader, null, null)); + #endregion + + #region new account + newAccount(); + #endregion + + #region account preparation + unlockAccount(); + #endregion + + #region test Transaction + testTransaction(); + #endregion + + Console.ReadKey(); + } + static void unlockAccount() + { + //As Parity and Quorum have different interface about + + #region Parity unlock + var success = web3.Personal.UnlockAccount.SendRequestAsync(account, accountPassphase, new HexBigInteger(60)).Result; + #endregion + + #region Quorum unlock + //var success = web3.Personal.UnlockAccount.SendRequestAsync(account, accountPassphase, 60).Result; + #endregion + + Trace.Assert(success, "Unlock account failed! Please check your account address and the passphase!"); + Console.WriteLine("Account unlock successed!\n"); + } + static void testTransaction() + { + const string contractByteCode = "0x608060405234801561001057600080fd5b50602a60008190555060df806100276000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636d4ce63c14604e578063e5c19b2d146076575b600080fd5b348015605957600080fd5b50606060a0565b6040518082815260200191505060405180910390f35b348015608157600080fd5b50609e6004803603810190808035906020019092919050505060a9565b005b60008054905090565b80600081905550505600a165627a7a723058200e577c111b0ee4c2177cd4431abe395d21431e594a9441e820442f4ddbbe484f0029"; + const string abi = @"[{""constant"":true, ""inputs"":[],""name"":""get"",""outputs"":[{""name"":"""",""type"":""int256""}],""payable"":false,""stateMutability"":""view"",""type"":""function""},{""constant"":false,""inputs"":[{""name"":""_a"",""type"":""int256""}],""name"":""set"",""outputs"":[],""payable"":false,""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[],""payable"":false,""stateMutability"":""nonpayable"",""type"":""constructor""}]"; + var receiptDeploy = web3.Eth.DeployContract.SendRequestAndWaitForReceiptAsync(abi, contractByteCode, account, new HexBigInteger(4700000), new HexBigInteger(0), new HexBigInteger(0), null).Result; + Trace.Assert(receiptDeploy.Status.Value.IsOne, "Contract Deployment failed. Check if it ran out of gas."); + Console.WriteLine("Contract deploying finished:"); + Console.WriteLine("ContractAddress: " + receiptDeploy.ContractAddress); + Console.WriteLine("BlockHash: " + receiptDeploy.BlockHash); + Console.WriteLine("TransactionHash: " + receiptDeploy.TransactionHash); + Console.WriteLine("Blocknumber: " + receiptDeploy.BlockNumber.Value); + Console.WriteLine(); + + } + static void newAccount() + { + account = web3.Personal.NewAccount.SendRequestAsync(accountPassphase).Result; + Console.WriteLine("New account successed! The account address is:"); + Console.WriteLine(account); + Console.WriteLine(); + } + } +} diff --git a/samples/Nethereum_sample/README.md b/samples/Nethereum_sample/README.md new file mode 100644 index 0000000..ae14183 --- /dev/null +++ b/samples/Nethereum_sample/README.md @@ -0,0 +1,68 @@ +#Nethereum Connection Sample + +This is the sample code to show how we can let Nethereum to connect to our transaction nodes directly, without the blockchain connector. + +To start quickly, you can just have a look on **Nethereum/Program.cs**. Note that the region "web3 initialization" is the most important part! + +If you want to have a deep look on this sample code, please see the sections below for more infomation. + +#Dependency + +This sample is a .Net Core 2.0 console app. It has 3 dependencies from Nuget: + + - package "Nethereum.Portable" Version="2.5.1" + + - package "Common.Logging.Core" Version="3.4.1" + + - package "Newtonsoft.Json" Version="11.0.2" + +Note: + - Here the last two packages are dependencies of "Nethereum.Portable" and haven't beed directly used in the sample code. + +#How to Run the Sample Code + +1. Set the parameters: + - Go to the region "parameters", and set the parameters following the comments. + - If you don't have an account, see the section "New Account" for help. + +2. Unlock the account: + - As Parity and Quorum have different interface of jsonrpc method "personal_unlockAccount", we need to switch the unlock code manually. + - For Quorum, please comment the code in the region "Parity unlock", and uncomment the code in the region "Quorum unlock". + - For Parity, please do the opposite. + +3. Build and run the code! If every thing goes right, you'll see the logs like below: +```bash +Account unlock successed! + +Contract deploying finished: + ContractAddress: 0xf8b90b91ea42954f620d159cdf157847037b72e5 + BlockHash: 0xc5dc4a030a3d4e982c92218d13e11a36f1107dd4838b6a507234d6546e62af93 + TransactionHash: 0x9d2ceeea587e99685bb0edb7a43a507fee67da3c365bc89493aa739f30b24633 + Blocknumber: 14307 + +Sample code finished successfully! +``` + +#Explanation of the Code + +1. The most important part of the code is the region "web3 initialization". After init the Web3 instance in this way, you can connect to the transaction node with the Web3 instance. + +2. Difference about construct Web3 with or without a managed account: + - If a Web3 instance is constructed without a managed account, the jsonrpc method "eth_sendtransaction" is called when using this Web3 instance to send transaction. + - If a Web3 instance is constructed with a managed account, the jsonrpc method "personal_sendtransaction" is called when using this Web3 instance to send transaction. + +# New Account + +Enabling Nethereum to connect to the transaction node doesn't need a blockchain account, but our sample code does (because we deploy a contract in the code). If you didn't have an account before, you can apply for an account in our sample code by doing the following steps: + +1. Uncomment the code in the region "new account". + +2. In the region "parameters", set the variable accountPassphase. It will become the passphase of the new account. + +3. In the region "parameters", skip the setting of the variable account, but do not comment it. (For Quorum and Parity, when you are applying for an account, you only need to give it the passphase, and then the account address will be given by Quorum and Parity). + +Note: + - The new account also need to be unlocked, so do not forget to do the 2nd step in section "How to Run the Sample Code". + - It's not recommend to apply for a new account everytime you run the sample code (or other code that need an account). + + From a037716f3d33c79a93b0d1f472810444fac03b3b Mon Sep 17 00:00:00 2001 From: Lingfu Che Date: Thu, 27 Sep 2018 14:20:21 +0800 Subject: [PATCH 2/6] Update README.md --- samples/Nethereum_sample/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/Nethereum_sample/README.md b/samples/Nethereum_sample/README.md index ae14183..f6933c8 100644 --- a/samples/Nethereum_sample/README.md +++ b/samples/Nethereum_sample/README.md @@ -1,4 +1,4 @@ -#Nethereum Connection Sample +# Nethereum Connection Sample This is the sample code to show how we can let Nethereum to connect to our transaction nodes directly, without the blockchain connector. @@ -6,7 +6,7 @@ To start quickly, you can just have a look on **Nethereum/Program.cs**. Note tha If you want to have a deep look on this sample code, please see the sections below for more infomation. -#Dependency +# Dependency This sample is a .Net Core 2.0 console app. It has 3 dependencies from Nuget: @@ -19,7 +19,7 @@ This sample is a .Net Core 2.0 console app. It has 3 dependencies from Nuget: Note: - Here the last two packages are dependencies of "Nethereum.Portable" and haven't beed directly used in the sample code. -#How to Run the Sample Code +# How to Run the Sample Code 1. Set the parameters: - Go to the region "parameters", and set the parameters following the comments. @@ -43,7 +43,7 @@ Contract deploying finished: Sample code finished successfully! ``` -#Explanation of the Code +# Explanation of the Code 1. The most important part of the code is the region "web3 initialization". After init the Web3 instance in this way, you can connect to the transaction node with the Web3 instance. From c3e17a26cd68ba4ea4beb3c3cd7b0cbd77b930cd Mon Sep 17 00:00:00 2001 From: Lingfu Che Date: Thu, 27 Sep 2018 14:32:15 +0800 Subject: [PATCH 3/6] Change some output format and update the code format for samples/Nethereum_sample/Program.cs --- .../NethereumSample/Program.cs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/samples/Nethereum_sample/NethereumSample/Program.cs b/samples/Nethereum_sample/NethereumSample/Program.cs index 8c0076f..5b07a95 100644 --- a/samples/Nethereum_sample/NethereumSample/Program.cs +++ b/samples/Nethereum_sample/NethereumSample/Program.cs @@ -16,23 +16,16 @@ class Program //Please set your own parameters here!!! //If you haven't got an account, you can uncomment the code in the region "new account", //set the passphase of your future account in the variable accountPassphase, and skip the setting of the variable "account". - #region parameters - static string username = "testmember"; - static string password = "123456"; - static string nodeUri = "https://testmember.blockchain.ppe.azure-int.net:3200"; - //static string nodeUri = "http://127.0.0.1:3101"; - static string account = "0xafc7400cdec0b3ed6267d73c327e5c6689e2171b"; - static string accountPassphase = "123456"; - /* + #region parameters static string username = ""; static string password = ""; static string nodeUri = ""; static string account = ""; - static string accountPassphase = ""; - */ + static string accountPassphase = ""; #endregion static Web3 web3 = null; + static void Main(string[] args) { //This region is the key part to let Nethereum to connect to the transaction nodes directly without the blockchain connector! @@ -47,7 +40,7 @@ static void Main(string[] args) #endregion #region new account - newAccount(); + //newAccount(); #endregion #region account preparation @@ -80,13 +73,15 @@ static void testTransaction() const string contractByteCode = "0x608060405234801561001057600080fd5b50602a60008190555060df806100276000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636d4ce63c14604e578063e5c19b2d146076575b600080fd5b348015605957600080fd5b50606060a0565b6040518082815260200191505060405180910390f35b348015608157600080fd5b50609e6004803603810190808035906020019092919050505060a9565b005b60008054905090565b80600081905550505600a165627a7a723058200e577c111b0ee4c2177cd4431abe395d21431e594a9441e820442f4ddbbe484f0029"; const string abi = @"[{""constant"":true, ""inputs"":[],""name"":""get"",""outputs"":[{""name"":"""",""type"":""int256""}],""payable"":false,""stateMutability"":""view"",""type"":""function""},{""constant"":false,""inputs"":[{""name"":""_a"",""type"":""int256""}],""name"":""set"",""outputs"":[],""payable"":false,""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[],""payable"":false,""stateMutability"":""nonpayable"",""type"":""constructor""}]"; var receiptDeploy = web3.Eth.DeployContract.SendRequestAndWaitForReceiptAsync(abi, contractByteCode, account, new HexBigInteger(4700000), new HexBigInteger(0), new HexBigInteger(0), null).Result; + Trace.Assert(receiptDeploy.Status.Value.IsOne, "Contract Deployment failed. Check if it ran out of gas."); + Console.WriteLine("Contract deploying finished:"); - Console.WriteLine("ContractAddress: " + receiptDeploy.ContractAddress); - Console.WriteLine("BlockHash: " + receiptDeploy.BlockHash); - Console.WriteLine("TransactionHash: " + receiptDeploy.TransactionHash); - Console.WriteLine("Blocknumber: " + receiptDeploy.BlockNumber.Value); - Console.WriteLine(); + Console.WriteLine(" ContractAddress: " + receiptDeploy.ContractAddress); + Console.WriteLine(" BlockHash: " + receiptDeploy.BlockHash); + Console.WriteLine(" TransactionHash: " + receiptDeploy.TransactionHash); + Console.WriteLine(" Blocknumber: " + receiptDeploy.BlockNumber.Value); + Console.WriteLine("\nSample code finished successfully!"); } static void newAccount() From d5f834c98bfc0a1788efe73a9b26bc5176e3580f Mon Sep 17 00:00:00 2001 From: Lingfu Che Date: Thu, 27 Sep 2018 14:37:15 +0800 Subject: [PATCH 4/6] Update some comments for samples/Nethereum_sample/Program.cs --- samples/Nethereum_sample/NethereumSample/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/Nethereum_sample/NethereumSample/Program.cs b/samples/Nethereum_sample/NethereumSample/Program.cs index 5b07a95..dcad09f 100644 --- a/samples/Nethereum_sample/NethereumSample/Program.cs +++ b/samples/Nethereum_sample/NethereumSample/Program.cs @@ -55,7 +55,8 @@ static void Main(string[] args) } static void unlockAccount() { - //As Parity and Quorum have different interface about + //As Parity and Quorum have different interface on jsonrpc method "personal_unlockAccount", we need to switch the unlock code manually. + //Please make sure that, in the two regions below, only one region is uncommented. #region Parity unlock var success = web3.Personal.UnlockAccount.SendRequestAsync(account, accountPassphase, new HexBigInteger(60)).Result; From 781dcfd9868ef51f3faf73cebe8799705d00c69a Mon Sep 17 00:00:00 2001 From: Lingfu Che Date: Sat, 29 Sep 2018 13:02:58 +0800 Subject: [PATCH 5/6] deal with the possible case that the cert of the transaction node cannot be verified --- .../NethereumSample/Program.cs | 45 ++++++++++++++++--- samples/Nethereum_sample/README.md | 10 ++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/samples/Nethereum_sample/NethereumSample/Program.cs b/samples/Nethereum_sample/NethereumSample/Program.cs index dcad09f..b10e9ee 100644 --- a/samples/Nethereum_sample/NethereumSample/Program.cs +++ b/samples/Nethereum_sample/NethereumSample/Program.cs @@ -3,12 +3,13 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Diagnostics; +using System.Security.Cryptography.X509Certificates; +using System.Net.Security; using Nethereum.Web3; using Nethereum.JsonRpc.Client; using Nethereum.Web3.Accounts.Managed; using Nethereum.Hex.HexTypes; - namespace NethereumSample { class Program @@ -25,18 +26,27 @@ class Program #endregion static Web3 web3 = null; + static HttpClientHandler clientHandler = null; static void Main(string[] args) { + //uncomment the code in this region if you met the issue that the cert of the transaction node cannot be verified. + #region clietHandler init + //InitHandler(); + #endregion + //This region is the key part to let Nethereum to connect to the transaction nodes directly without the blockchain connector! - //Following the code in this region, you can use the instance web3 to connect to the transaction nodes. - #region web3 initialization + //Following the code in this region, you can use the instance web3 to connect to the transaction nodes. + #region web3 init + var authValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", username, password))); var authHeader = new AuthenticationHeaderValue("Basic", authValue); - web3 = new Web3(new RpcClient(new Uri(nodeUri), authHeader, null, null)); + web3 = new Web3(new RpcClient(new Uri(nodeUri), authHeader, null, clientHandler)); + //Notice: clientHandler is null if the code in region "clietHandler init" remains commented. //if you want to construct a Web3 instance with a managed account, you can use the code below: - //web3 = new Web3(new ManagedAccount(account, accountPassphase), new RpcClient(new Uri(nodeUri), authHeader, null, null)); + //web3 = new Web3(new ManagedAccount(account, accountPassphase), new RpcClient(new Uri(nodeUri), authHeader, null, clientHandler)); + #endregion #region new account @@ -92,5 +102,30 @@ static void newAccount() Console.WriteLine(account); Console.WriteLine(); } + static void InitHandler() + { + //We just hard coded the issuer and the thumbPrint of the Root CA in the code, + //and we just want to verify if the Root CA of the transaction node is the hard-encoded ca. + const string issuer = "CN=Baltimore CyberTrust Root, OU=CyberTrust, O=Baltimore, C=IE"; + const string thumbPrint = "D4DE20D05E66FC53FE1A50882C78DB2852CAE474"; + clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, errors) => + { + if (chain == null) + return false; + if (errors != SslPolicyErrors.None) + return false; + X509Certificate2 cert2 = null; + + //the last element of chain.ChainElements should be the root ca + foreach (var x in chain.ChainElements) + { + cert2 = x.Certificate; + } + if (issuer == cert2.Issuer && thumbPrint == cert2.Thumbprint) + return true; + return false; + }; + } } } diff --git a/samples/Nethereum_sample/README.md b/samples/Nethereum_sample/README.md index f6933c8..366e6d0 100644 --- a/samples/Nethereum_sample/README.md +++ b/samples/Nethereum_sample/README.md @@ -43,9 +43,11 @@ Contract deploying finished: Sample code finished successfully! ``` +Notice: If you meet the problem that the cert of the transaction node cannot be verified, see section "Cert Verification" for help. + # Explanation of the Code -1. The most important part of the code is the region "web3 initialization". After init the Web3 instance in this way, you can connect to the transaction node with the Web3 instance. +1. The most important part of the code is the region "web3 init". After init the Web3 instance in this way, you can connect to the transaction node with the Web3 instance. 2. Difference about construct Web3 with or without a managed account: - If a Web3 instance is constructed without a managed account, the jsonrpc method "eth_sendtransaction" is called when using this Web3 instance to send transaction. @@ -65,4 +67,10 @@ Note: - The new account also need to be unlocked, so do not forget to do the 2nd step in section "How to Run the Sample Code". - It's not recommend to apply for a new account everytime you run the sample code (or other code that need an account). +# Cert Verification + +If the cert of the transaction node cannot be verified, you can simply uncomment the code in the region "clietHandler init" to tackle the issue. Here is some notice about it: + +1. From some experiments, we found the reason why the cert cannot be verified is that the client doesn't know the root ca (while the cert chain is a right cert chain). +2. We just hard coded the right root ca's issuer and thumbPrint in clientHandler.ServerCertificateCustomValidationCallback(), and verify the node's root ca. \ No newline at end of file From 3d8b8c8de25a631c3b605a9c20b1937d3bd0d518 Mon Sep 17 00:00:00 2001 From: Lingfu Che Date: Sat, 29 Sep 2018 13:09:28 +0800 Subject: [PATCH 6/6] update the output --- samples/Nethereum_sample/NethereumSample/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/Nethereum_sample/NethereumSample/Program.cs b/samples/Nethereum_sample/NethereumSample/Program.cs index b10e9ee..f48242e 100644 --- a/samples/Nethereum_sample/NethereumSample/Program.cs +++ b/samples/Nethereum_sample/NethereumSample/Program.cs @@ -61,6 +61,7 @@ static void Main(string[] args) testTransaction(); #endregion + Console.WriteLine("Sample code finished successfully!"); Console.ReadKey(); } static void unlockAccount()