近年来大热的美剧《创业公司(StartUp)》虚构了一种被称为 GenCoin 的数字货币,可用于各种创新式的金融交易场景中。而在我看来,它可以被理解为一种具有 Web3 核心属性的区块链分布式设计产品。如果您对 Web3 还不甚了解的话,让我们先回顾一下 Web 的三个主要时代:
- Web1 – 静态网页(1991 年–2004 年),请参见 –https://en.wikipedia.org/wiki/Web_2.0#Web_1.0
- Web2 – Web 作为一个平台(自 2004 年起),请参见 –https://en.wikipedia.org/wiki/Web_2.0#Web_2.0
- Web3- 去中心化的设计,包含了区块链技术(自 2009 年起,且近年来发展势头迅猛、前景广阔),请参见 –https://en.wikipedia.org/wiki/Web3
在 Web2 时代,Web 服务主要集中和被控制在诸如:谷歌、苹果和亚马逊等少数技术提供商的手里。而作为 Web2 的替代方案(https://consensys.net/blog/blockchain-explained/what-is-web3-here-are-some-ways-to-explain-it-to-a-friend/),Web3 创建了一个无需准入的数据存储方式。其中不存在任何个人或公司控制或拥有着数据,而且数据的真实性也得到了充分保证。这些数据会被存储在区块链网络中的公共分类账本(public ledger)上。因此,不再是由一个实体拥有数据,而是由多个节点(即:运行着区块链的计算机)存储着数据,并就数据是否有效达成了共识。
从比特币(https://bitcoin.org/en/)到以太坊等协议的应用,Web3 以此类数据存储协议为基础,开启了各种全新的用例。例如:
- 由用户而非公司控制着个人身份
- 未经许可的金融系统(如:比特币)可开展贷款、货币、投资等的数字化货币业务
- 由 NFT(https://en.wikipedia.org/wiki/Non-fungible_token)去证明诸如音乐(https://royal.io/)、艺术(https://www.artblocks.io/)等数字项目的数字所有权
- 通过去中心化的自治组织(decentralized autonomous organizations,DAO)临时组建具有相同目的的团体,例如:Constitution DAO(https://www.constitutiondao.com/)和 social DAO(https://www.fwb.help/)
- 通过边玩边赚(Play-to-earn,p2e)的游戏,用户可以在玩游戏的同时,用来谋生(例如 Axie Infinity,https://axieinfinity.com/)
当然,上述应用的关键在于,数字货币的所有权(如:DAO 会员资格、或音乐版权等)都被掌控在用户的手中。在世界上任何地方,只要有互联网连接,任何人都可以自由地交易、销售和构建这些物品,而完全脱离了某个公司或政府的规则控制。对于这样的 Web3 理想主义,我在此不做评判,只是单纯从开发者的角度和您探讨,一个全栈开发者将如何具备 Web3 的技术能力。
从全栈说起
源于 2015 年的“全栈开发者”一词是指:一个软件工程师可以为任何级别的软件技术栈做出贡献。例如,面对某个与服务层相关的功能性缺陷,刚刚完成了客户端相关任务的同一开发者,可以无缝“接单”,去高效地抓 bug。您可以通过链接 –https://dzone.com/articles/do-not-publishfull-stack-development-truly-possibl,了解更多有关全栈开发的概念。
Web3 基础
为了深入研究 Web3,我依次创建了一个智能合约,以及一个 Dapp 与之进行交互。其中,
- 智能合约(https://www.coinbase.com/learn/crypto-basics/what-is-a-smart-contract)是部署在区块链上的一段代码(我下面会以以太坊(https://ethereum.org/en/what-is-ethereum/)为例)。该合约一旦被部署到区块链上,就不可改变、也无需许可(permissionless),但是任何人都可以检索到它。
- Dapp(decentralized application,去中心化应用)是我们通过 UI(通常来自网页或应用)与智能合约交互的方式。Dapp 会在后端利用智能合约的开放性,采用诸如 IPFS(InterPlanetary File Storage,星际文件存储)的方式,实现文件的分散存储,且不会出现停机。毕竟 DDoS 攻击无法攻击负责存储的每个节点。当然,在考虑部署之前,我们需要针对其安全性,开展全面测试,并处置好代码中的潜在缺陷与漏洞。
Web3 技术栈
目前,针对 Web3 的成熟技术栈组合,通常包括以下组件:
- NPM – 备受 Web2 开发人员欢迎的节点包管理器,请参见 –https://nodejs.org/en/
- Truffle 框架 – 专注于 Web3 的开发工具,请参见 –https://www.trufflesuite.com/
- Ganache – 可以在本地主机上启动私有区块链,请参见 –https://www.trufflesuite.com/ganache
- MetaMask – 以太坊的区块链用户界面与网关,属于开源且去中心化的区块链类型,请参见 –https://metamask.io/
- Solidity – 先进的智能合约编程语言,请参见 –https://solidity.readthedocs.io/en/v0.7.1/
- HTML/CSS/JavaScript – 客户端的层面,请参见 –https://www.w3.org/standards/webdesign/htmlcss
- Web3.js – 通过以太坊网络交互的以太坊 API 库,请参见 –https://web3js.readthedocs.io/en/v1.3.0/
- Infura – 授予以太坊网络访问权限的以太坊 API 服务,请参见 –https://infura.io/
以太坊 Dapp 的需求
假设有一个居委会即将举办定期选举,附近的居民将对一系列的决议进行投票。那么,我们就可以将该选举构建成为一个以太坊 Dapp。由于数据被存储在公开的区块链上,而不是单个公司的私有服务器上,因此任何人都可以通过与智能合约的交互,以无需许可的方式,检索投票结果。据此,投票结果就不存在被篡改或伪造的情况,进而避免了争议的发生。
创建智能合约
首先,我们需要利用前文提到的:Infura、NPM、Truffle 框架、Ganache、以及 Solidity 等 Web3 技术栈组件,来创建一个能与应用协同的智能合约。其创建的流程如下图所示:
我们可以根据该流程,去招募以太坊的开发者,具体内容请参见链接 –https://consensys.net/developers/onboarding-step-2/。
使用 React 创建 Dapp
有了智能合约,Web3 工程师便可以使用 NPM、MetaMask、HTML/CSS/JavaScript/React、以及 Web3.js 等 Web3 技术栈组件,构建居委会选举的应用。在本例中,我们将采用 React(https://reactjs.org/) 框架和如下流程:
首个以太坊 Dapp
我会通过 Infura 的注册页面(https://infura.io/register)创建一个免费帐户,并创建一个名为 jvc-homeowners-ballot 的项目:
下图中有关该项目的细节,我会在下文中详细讨论:
Truffle 入门在本地主机上,我创建了一个名为 jvc-homeowners-ballot 的文件夹,并使用 CLI 命令 –truffle init,来初始化 Truffle。初始化完成后的目录结构为:
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
└── truffle-config.js
接着,我用如下命令为基于 Truffle 的钱包 provider,添加了对应的依赖项:
npm install --save @truffle/hdwallet-provider为了创建本地开发网络,我通过命令 ganache 启动 Ganache CLI。
根据 CLI 的如下响应信息,我们可以看到 Ganache 已在本地主机的 8545 端口上运行:
ganache v7.0.1(@ganache/cli:0.1.2, @ganache/core:0.1.2)
Starting RPC server
Available Accounts
==================
(0)0x2B475e4fd7F600fF1eBC7B9457a5b58469b9EDDb(1000 ETH)
(1)0x5D4BB40f6fAc40371eF1C9B90E78F82F6df33977(1000 ETH)
(2)0xFaab2689Dbf8b7354DaA7A4239bF7dE2D97e3A22(1000 ETH)
(3)0x8940fcaa55D5580Ac82b790F08500741326836e0(1000 ETH)
(4)0x4c7a1b7EB717F98Fb0c430eB763c3BB9212F49ad(1000 ETH)
(5)0x22dFCd5df8d4B19a42cB14E87219fea7bcA7C92D(1000 ETH)
(6)0x56882f79ecBc2D68947C6936D4571f547890D07c(1000 ETH)
(7)0xD257AFd8958c6616bf1e61f99B2c65dfd9fEE95A(1000 ETH)
(8)0x4Bb2EE0866578465E3a2d3eCCC41Ea2313372B20(1000 ETH)
(9)0xdf267AeFeAfE4b7053ca10c3d661a8CB24E98236(1000 ETH)
Private Keys
==================
(0)0x5d58d27b0f294e3222bbd99a3a1f07a441ea4873de6c3a2b7c40b73186eb616d
(1)0xb9e52d6cfb2c074fa6a6578b946e3d00ea2a332bb356d0b3198ccf909a97fdc8
(2)0xc52292ce17633fe2724771e81b3b4015374d2a2ea478891dab74f2028184edeb
(3)0xbc7b0b4581592e48ffb4f6420228fd6b3f954ac8cfef778c2a81188415274275
(4)0xc63310ccdd9b8c2da6d80c886bef4077359bb97e435fb4fe83fcbec529a536fc
(5)0x90bc16b1520b66a02835530020e43048198195239ac9880b940d7b2a48b0b32c
(6)0x4fb227297dafb879e148d44cf4872611819412cdd1620ad028ec7c189a53e973
(7)0xf0d4dbe2f9970991ccc94a137cfa7cf284c09d0838db0ce25e76c9ab9f4316d9
(8)0x495fbc6a16ade5647d82c6ad12821667f95d8b3c376dc290ef86c0d926f50fea
(9)0x434f5618a3343c5e3b0b4dbeaf3f41c62777d91c3314b83f74e194be6c09416b
HD Wallet
==================
Mnemonic: immense salmon nominee toy jungle main lion universe seminar output oppose hungry
Base HD Path: m/44'/60'/0'/0/{account_index}
Default Gas Price
==================
2000000000
BlockGas Limit
==================
30000000
Call Gas Limit
==================
50000000
Chain Id
==================
1337
RPC Listening on 127.0.0.1:8545
项目文件夹中的 truffle-config.js 文件,会被激活并更新如下代码行:
JSON
development:{
host:"127.0.0.1",// Localhost (default: none)
port:8545,// Standard Ethereum port (default: none)
network_id:"*",// Any network (default: none)
},
现在,我们可以在新的终端中通过命令 –truffle console,来启动 Truffle 控制台,能显示如下提示:
truffle(development)>
我们可以在控制台中,通过命令 –const HDWalletProvider = require(‘@truffle/hdwallet-provider’); 来创建钱包。当然,它可能会导致未定义的响应。
接下来,我需要通过 Mnemonic Code Converter(https://iancoleman.io/bip39/)网站,生成一个 12 字的助记词(12-word mnemonic phrase,类似私钥),并将其通过如下命令,更新到 Truffle 控制台处:
const mnemonic ='12 words here';
const wallet = new HDWalletProvider(mnemonic,"http://localhost:8545");
上述两条命令虽然也会导致未定义的响应,但是钱包的控制台最终会显示如下运行结果:
truffle(development)> wallet
HDWalletProvider {
walletHdpath:"m/44'/60'/0'/0/",
wallets:{
...
},
addresses:[
'0xa54b012b406c01dd99a6b18ef8b55a15681449af',
'0x6d507a70924ea3393ae1667fa88801650b9964ad',
'0x1237e0a8522a17e29044cde69b7b10b112544b0b',
'0x80b4adb18698cd47257be881684fff1e14836b4b',
'0x09867536371e43317081bed18203df4ca5f0490d',
'0x89f1eeb95b7a659d4748621c8bdbabc33ac47bbb',
'0x54ceb6f0d722dcb33152c953d5758a08045f254d',
'0x25d2a8716792b98bf9cce5781b712f00cf33227e',
'0x37b6364fb97028830bfeb0cb8d2b14e95e2efa05',
'0xe9f56031cb6208ddefcd3cdd5a1a41f7f3400af5'
],
...
添加以太坊资金进行测试
现在我们需要为 Dapp 获取一些测试资金,并使用 Ropsten Ethereum Faucet(https://faucet.ropsten.be/)将资金添加到现有的、由ConsenSys(https://consensys.net/)创建的MetaMask(https://metamask.io/index.html)钱包中。当然,为了降低意外情况所导致的真实资金损失的风险,您可以在 MetaMask 中创建多个帐户,其中至少有一个帐户可专用于开发和测试。请记住:永远不要与任何人分享您的助记词,也不要在任何地方上传您的私钥!
如下图所示,为了添加测试资金,我需要输入自己的帐户地址:
如下图所示,通过 Ropsten Etherscan 站点,我们可以验证交易是否能够成功完成:
最终准备步骤
请使用如下命令将 dotenv 依赖项添加到该项目中:
npm install --save dotenv接着,请在项目的根目录下创建一个.env 的新文件,并在其中包含如下两行:
INFURA_API_KEY=INSERT YOUR API KEY HERE (no quotations)
MNEMONIC="12 words here"
其中,INFURA_API_KEY 是在创建 jvc-homeowners-ballot 项目时给定的项目 ID。注意:请确保.env 文件被包含在.gitignore 文件中,以避免其他有权访问该存储库的人,擅自使用此机密信息。
最后一项准备步骤是更新 truffle-config.js 文件。我们首先需要在文件的顶部添加如下三行:
JavaScript
require("dotenv").config();
const HDWalletProvider = require("@truffle/hdwallet-provider");
接着,我们利用 dotenv 将如下网络信息,添加至上述依赖项:
JavaScript
ropsten:{
provider:()=>
new HDWalletProvider(
process.env.MNEMONIC,
`https://ropsten.infura.io/v3/${process.env.INFURA_API_KEY}`
),
network_id:3,// Ropsten's id
gas: 5500000, // Ropsten has a lower block limit than mainnet
confirmations: 2, // # of confs to wait between deployments. (default: 0)
timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
},
设置智能合约
准备好了 Infura、Truffle、以及测试资金后,让我们开始设置智能合约。针对前面的居委会选举示例,我们将使用位于本项目 contracts 文件夹中的 JvcHomeownerBallot.sol 合约:
JavaScript
// SPDX-License-Identifier: UNLICENSED (it is common practice to include an open source license or declare it unlicensed)
pragma solidity ^0.8.7;// tells the compiler which version to use
contract Homeowners {
// store the addresses of voters on the blockchain in these 2 arrays
address[] votedYes;
address[] votedNo;
function voteYes() public {
votedYes.push(msg.sender);
}
function voteNo() public {
votedNo.push(msg.sender);
}
function getYesVotes() public view returns (uint){
return votedYes.length;
}
function getNoVotes() public view returns (uint){
return votedNo.length;
}
}
正如上面的代码所示,该合同将非常简单,参选居民只需选择是或否即可。其对应的 contracts 文件夹结构如下图所示:
.
├── JvcHomeownersBallot.sol
└── Migrations.sol
有了合约,我们就需要建立部署合约的方法。下面让我们转移到 migrations 文件夹,将如下内容添加到该文件夹下的 2_deploy_contracts.js 文件中:
JavaScript
const JvcHomeownersBallot = artifacts.require("JvcHomeownersBallot.sol");
module.exports= function(deployer){
deployer.deploy(JvcHomeownersBallot);
};
然后,我们可以使用如下命令执行合约的迁移:
truffle migrate --network ropsten 迁移的响应结果为:
Compiling your contracts...
===========================
> Compiling ./contracts/JvcHomeownersBallot.sol
> Artifacts written to /Users/john.vester/projects/jvc/consensys/jvc-homeowners-ballot/build/contracts
> Compiled successfully using:
- solc:0.8.11+commit.d7f03943.Emscripten.clang
Network up to date.
truffle(development)> truffle migrate --network ropsten
Compiling your contracts...
===========================
> Compiling ./contracts/JvcHomeownersBallot.sol
> Compiling ./contracts/Migrations.sol
> Artifacts written to /Users/john.vester/projects/jvc/consensys/jvc-homeowners-ballot/build/contracts
> Compiled successfully using:
- solc:0.8.11+commit.d7f03943.Emscripten.clang
Starting migrations...
======================
> Network name:'ropsten'
> Network id:3
> Block gas limit:8000000(0x7a1200)
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash:0x5f227f26a31a3667a689be2d7fa6121a21153eb219873f6fc9aecede221b3b82
> Blocks:5 Seconds:168
> contract address:0x9e6008B354ba4b9f91ce7b8D95DBC6130324024f
> block number:11879583
> block timestamp:1643257600
> account:0xa54b012B406C01dd99A6B18eF8b55A15681449Af
> balance:1.573649230299520359
> gas used:250142(0x3d11e)
> gas price:2.506517682 gwei
> value sent:0 ETH
> total cost:0.000626985346010844 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number:1(block:11879584)
> confirmation number:2(block:11879585)
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost:0.000626985346010844 ETH
2_deploy_contracts.js
=====================
Deploying 'JvcHomeownersBallot'
-------------------------------
> transaction hash:0x1bf86b0eddf625366f65a996e633db589cfcef1a4d6a4d6c92a5c1f4e63c767f
> Blocks:0 Seconds:16
> contract address:0xdeCef6474c95E5ef3EFD313f617Ccb126236910e
> block number:11879590
> block timestamp:1643257803
> account:0xa54b012B406C01dd99A6B18eF8b55A15681449Af
> balance:1.573133154908720216
> gas used:159895(0x27097)
> gas price:2.507502486 gwei
> value sent:0 ETH
> total cost:0.00040093710999897 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number:1(block:11879591)
> confirmation number:2(block:11879592)
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost:0.00040093710999897 ETH
Summary
=======
> Total deployments:2
> Final cost:0.001027922456009814 ETH
- Blocks:0 Seconds:0
- Saving migration to chain.
- Blocks:0 Seconds:0
- Saving migration to chain.
至此,我们已将 JvcHomeownersBallot 智能合约部署到了 Ropsten 网络中。我们可以进一步使用如下 URL,来验证智能合约,并在“Deploying JvcHomeownersBallot”日志中提供合约的地址:
https://ropsten.etherscan.io/
或是:
https://ropsten.etherscan.io/address/0xdeCef6474c95E5ef3EFD313f617Ccb126236910e
使用 React 创建 Dapp
在上述提到的 jvc-homeowners-ballot 文件夹的同级目录,我将创建一个名为 jvc-homeowners-ballot-client 的目录,通过调用 React CLI 和如下命令,来创建同名的 React 应用:
npx create-react-app jvc-homeowners-ballot-client
接着,我通过如下命令,将 Web3 的依赖项安装到 React 应用中:
cd jvc-homeowners-ballot-client
npm installWeb3
核心的 React 应用一旦就绪,我们就需要建立合约应用的二进制接口(application binary interface,ABI),以便 Dapp 与以太坊生态系统上的各种合约进行通信。
根据 JvcHomeownerBallot.sol 智能合约文件的内容,我们在 build/contracts 文件夹下打开 JvcHomeownersBallet.json 文件,并使用 abi.js 文件的 jvcHomeOwnersBallot 常量的“abi”属性值。具体内容如下:
JavaScript
export const jvcHomeownersBallot =[
{
"inputs":[],
"name":"voteYes",
"outputs":[],
"stateMutability":"nonpayable",
"type":"function"
},
{
"inputs":[],
"name":"voteNo",
"outputs":[],
"stateMutability":"nonpayable",
"type":"function"
},
{
"inputs":[],
"name":"getYesVotes",
"outputs":[
{
"internalType":"uint256",
"name":"",
"type":"uint256"
}
],
"stateMutability":"view",
"type":"function",
"constant":true
},
{
"inputs":[],
"name":"getNoVotes",
"outputs":[
{
"internalType":"uint256",
"name":"",
"type":"uint256"
}
],
"stateMutability":"view",
"type":"function",
"constant":true
}
];
该文件应当被放置在 React 应用目录 src 的新建子文件夹 abi 内。
下面,我们根据如下配置,从头开始更新 Apps.js:
JavaScript
import React,{ useState }from"react";
import { jvcHomeownersBallot }from"./abi/abi";
importWeb3from "web3";
import "./App.css";
constWeb3= newWeb3(Web3.givenProvider);
const contractAddress ="0xdeCef6474c95E5ef3EFD313f617Ccb126236910e";
const storageContract = newWeb3.eth.Contract(jvcHomeownersBallot, contractAddress);
我们可以通过多种方式找到上面提到的 contactAddress。除了我在此使用的 truffle 的 migrate CLI 命令之外,您还可以使用 Etherscan 站点(https://ropsten.etherscan.io/)。
标准的 React 开发
在开始标准的 React 开发之前,让我们先来看看完整的 App.js 文件 (如下所示):
JavaScript
import React,{ useState }from"react";
import { jvcHomeownersBallot }from"./abi/abi";
importWeb3from "web3";
import Nav from"./components/Nav.js";
import "./App.css";
import { makeStyles }from"@material-ui/core/styles";
import Button from"@material-ui/core/Button";
import {CircularProgress, Grid, Typography}from"@material-ui/core";
const useStyles = makeStyles((theme)=>({
root:{
"& > *":{
margin: theme.spacing(1),
},
},
}));
constWeb3= newWeb3(Web3.givenProvider);
const contractAddress ="0xdeCef6474c95E5ef3EFD313f617Ccb126236910e";
const storageContract = newWeb3.eth.Contract(jvcHomeownersBallot, contractAddress);
function App(){
const classes = useStyles();
const [voteSubmitted, setVoteSubmitted]= useState("");
const [yesVotes, setYesVotes]= useState(0);
const [noVotes, setNoVotes]= useState(0);
const [waiting, setWaiting]= useState(false);
const getVotes = async ()=>{
const postYes = await storageContract.methods.getYesVotes().call();
setYesVotes(postYes);
const postNo = await storageContract.methods.getNoVotes().call();
setNoVotes(postNo);
};
const voteYes = async ()=>{
setWaiting(true);
const accounts = await window.ethereum.enable();
const account = accounts[0];
const gas =(await storageContract.methods.voteYes().estimateGas())*1.5;
const post = await storageContract.methods.voteYes().send({
from: account,
gas,
});
setVoteSubmitted(post.from);
setWaiting(false);
};
const voteNo = async ()=>{
setWaiting(true);
const accounts = await window.ethereum.enable();
const account = accounts[0];
const gas =(await storageContract.methods.voteNo().estimateGas()*1.5);
const post = await storageContract.methods.voteNo().send({
from: account,
gas,
});
setVoteSubmitted(post.from);
setWaiting(false);
};
return (
<div className={classes.root}>
<Nav />
<div className="main">
<div className="card">
<Typography variant="h3" gutterBottom>
JVC Homeowners Ballot
</Typography>
<Typography gutterBottom>
How do you wish to vote?
</Typography>
<span className="buttonSpan">
<Button
id="yesButton"
className="button"
variant="contained"
color="primary"
type="button"
onClick={voteYes}>Vote Yes</Button>
<div className="divider"/>
<Button
id="noButton"
className="button"
color="secondary"
variant="contained"
type="button"
onClick={voteNo}>Vote No</Button>
<div className="divider"/>
</span>
{waiting &&(
<div>
<CircularProgress />
<Typography gutterBottom>
Submitting Vote ... please wait
</Typography>
</div>
)}
{!waiting && voteSubmitted &&(
<Typography gutterBottom>
Vote Submitted:{voteSubmitted}
</Typography>
)}
<span className="buttonSpan">
<Button
id="getVotesButton"
className="button"
color="default"
variant="contained"
type="button"
onClick={getVotes}>Get Votes</Button>
</span>
{(yesVotes >0|| noVotes >0)&&(
<div>
<Typography variant="h5" gutterBottom>
Current Results
</Typography>
<Grid container spacing={1}>
<Grid item xs={6}>
<div className="resultsAnswer resultsHeader">Vote</div>
</Grid>
<Grid item xs={6}>
<div className="resultsValue resultsHeader"># of Votes</div>
</Grid>
<Grid item xs={6}>
<div className="resultsAnswer">Yes</div>
</Grid>
<Grid item xs={6}>
<div className="resultsValue">{yesVotes}</div>
</Grid>
<Grid item xs={6}>
<div className="resultsAnswer">No</div>
</Grid>
<Grid item xs={6}>
<div className="resultsValue">{noVotes}</div>
</Grid>
</Grid>
</div>
)}
</div>
</div>
</div>
);
}
export default App;
运行 Dapp
我们可以使用 Yarn CLI 的如下命令,来启动基于 React 的 Dapp:
yarn start
在完成编译和验证之后,您会看到如下应用界面:
它拥有三个选项:
- VOTE YES – 提交赞成票
- VOTE NO – 提交反对票
- GET VOTES – 在 Dapp 的下部显示投赞成与反对票的总数
小结
综上所述,一旦建立了智能合约,从客户端的角度来看,我们将能够沿用 Web2 的如下方面到 Web3 上:
- 目前在 Web2 项目中常用的 JavaScript 客户端框架,可以被继续使用。
- NPM 可以被用来包含依赖项,以促进 Web3 的开发。
- 类似于 Web2 应用程序与传统数据存储的交互方式,Web3 的 Truffle 和 MetaMask 库也允许应用程序与数据进行交互。
- 现有的业务规则和 UI/UX 设计,将继续满足产品所有者对于 Web3 特性和功能上的要求。
而 Web3 的独特之处主要体现在:
- 在区块链上构建的 Dapps,都采用同一个事实源向各个信息消费者的请求,提供可靠的数据。
- 我们不再需要知道“谁”参与了交易、或查询存储在区块链智能合约中的信息(哪怕是别的 Dapp 去访问存储的数据)。由于结果总是固定不变的,因此 Dapp 只需关注应用程序的业务规则。正如上述居委会选举的简单示例那样,无论选票被查询多少次,即便是有另一个 Dapp,其结果总是完全相同的。
- 由于其分布式的特性,因此控制权被返回给了消费者,而非停留在少数人的手中。
可见,从全栈开发者迈向 Web3 的学习曲线并不陡峭,而且我们可以寻求各种工具、框架和库的帮助。如果您对上述项目所涉及到的源代码感兴趣的话,可以通过如下链接,访问到它在 GitLab 上两个存储库:
- https://gitlab.com/johnjvester/jvc-homeowners-ballot
- https://gitlab.com/johnjvester/jvc-homeowners-ballot-client
译者介绍
陈峻 (Julian Chen),IDC.NET 社区编辑,具有十多年的 IT 项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验;持续以博文、专题和译文等形式,分享前沿技术与新知;经常以线上、线下等方式,开展信息安全类培训与授课。
文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/227713.html<

