本文描述了在dotNet核心中使用像以太坊这样的区块链平台的过程。目标受众是其他想要从以太坊开始的dotNet开发者。需要了解区块链。在本文中,我们构建了一个完整的示例,允许你与自定义编写的智能合约进行交互。
第一代区块链的可以被视为仅比特币而没有智能合约。尽管如此,第二代区块链的表现明显给人更有希望。随着比特币以外的更多区块链平台,变得更加成熟,区块链有了更多可能性。以太坊区块链更像是一个使用加密货币的智能合约的分布式分类账。以太坊的重点更多地放在智能合约部分,然后是加密货币。以太币(以太坊的加密货币)的目的是为执行采矿合约或执行合约的交易提供报酬。
智能合约是为以太坊虚拟机编写的一段代码。这可以用Solidity编写并编译为字节代码。此字节代码放在分类帐中并变为不可变但仍可以与之交互,并且可以更改状态。正如以太坊文档所说:“从实用的角度来看,EVM可以被认为是一个包含数百万个对象的大型分散计算机,称为”帐户“,它们能够维护内部数据库,执行代码并相互通信。“从开发人员的角度来看,你可以将Solidity视为类似Javascript的语言,这有点受限。由于Solidity代码在区块链中运行,因此有充分的理由限制它。像随机数这样简单的东西也是一个挑战。也无法通过Http调用获取数据,因为所有事实需要在系统中。你仍然可以调用合约并输入数据来改变状态,因此外部影响是可行的。
首先安装Mist浏览器和Geth。Mist浏览器是一个GUI,可用作Ether的钱包。Geth是代码连接到的程序接口,Geth连接到以太坊的区块链。对于本文,我们将使用testnet。这样我们就可以免费开采一些以太币。启动Mist后,从菜单中选择使用测试网。创建一个帐户并挖掘一些以太币(菜单项目开发并开始挖掘)。
过了一段时间,你会有一些以太币。这在交易时很方便。即使发布合约或执行合约也要花费成本。现在让我们关闭钱包,否则你无法打开一个新的geth过程。所以在控制台中启动已安装的Geth:
“\Program Files\Geth\geth” --testnet --rpcapi eth,web3,personal --rpc
上图是我们命令的结果。我们看到它正在接收当前的区块链缓存,并且它的http端点正在localhost:8545
上进行侦听。这很重要,因为我们需要Mist浏览器和其他应用程序使用IPC或RPC访问它。由于在Windows上只支持IPC实现,我们不能在dotNetCore中使用它。我们在解决方案中使用web3 RPC
。
现在你可以再次打开钱包。只是不能开始挖掘,因为有独立的Geth正在运行。
现在是时候开始开发,打开Visual Studio并创建一个新项目了。请注意,我们的Github提供了该代码。创建“ASP.NET核心Web应用程序”,然后选择“Web.API模板”。我们将创建一个服务,其中包含一些与区块链交互的方法,并向区块链发布合约。这个存钱合约将存储我们的代币余额。合约开采后我们可以调用合约方法。没什么高大上的,也不是一个完整的应用程序,但很高兴看到我们能做什么。我们选择使用Azure Table存储来保持系统的持久性,它快速且便宜。
首先将这些依赖项添加到Project.json中:
"Nethereum.Web3": "2.0.0-rc1",
"Portable.BouncyCastle": "1.8.1.1",
"WindowsAzure.Storage": "8.1.1"
保存并查看正在恢复的软件包。前两个是以太坊相关,最后一个用于表存储。Nethereum.Web3
是通过RPC json
访问本地Geth进程的完整类库。BouncyCastle
是Nethereum所需的加密库。
首先,我们需要一个模型来捕获我们的以太坊合约状态。以太坊没有任何选择让合约退出区块链,主要是出于安全/不可变的原因。一旦合约被放入区块链,就无法更改,也无法检索到Solidity代码。这就是我们需要将这些信息存储在我们的系统中的原因。在模型文件夹中创建一个名为EthereumContractInfo
的文件,该文件派生自Azure Storage类TableEntity
:
using Microsoft.WindowsAzure.Storage.Table;
namespace EthereumStart.Models
{
public class EthereumContractInfo : TableEntity
{
public string Abi { get; set; }
public string Bytecode { get; set; }
public string TransactionHash { get; set; }
public string ContractAddress { get; set; }
public EthereumContractInfo()
{
}
public EthereumContractInfo(string name, string abi, string bytecode, string transactionHash)
{
PartitionKey = "contract";
RowKey = name;
Abi = abi;
Bytecode = bytecode;
TransactionHash = transactionHash;
}
}
}
现在创建一个名为Services的文件夹并创建文件IEthereumService
接口,这样我们就可以将它用于依赖注入:
using System.Threading.Tasks;
using EthereumStart.Models;
using Nethereum.Contracts;
namespace EthereumStart.Services
{
public interface IEthereumService
{
string AccountAddress { get; set; }
Task<bool> SaveContractToTableStorage(EthereumContractInfo contract);
Task<EthereumContractInfo> GetContractFromTableStorage(string name);
Task<decimal> GetBalance(string address);
Task<bool> ReleaseContract(string name, string abi, string byteCode, int gas);
Task<string> TryGetContractAddress(string name);
Task<Contract> GetContract(string name);
}
}
所有方法都应该返回一个任务,因为我们希望使实现使用异步。我们的想法是,我们将发布合约,尝试获取它的地址,然后在该地址上调用它的方法。现在我们创建文件BasicEthereumService
来实现接口。
using Microsoft.Extensions.Options;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.Windows