我们基于区块链在企业中的应用最广泛的就是“存证”功能需求,这是利用了区块链不可篡改和数据共享的特点,存证的业务数据一方面可以保证留痕和追溯,另一方面也实现了多个节点(如果部署在不同企业和部门)之间的数据共享。如果要实现存证,我们最关心并不是图灵完备,也不是去中心化,而是 存证的性能(也就是TPS)和数据膨胀率(也就是存1M的业务数据,单个节点要消耗多少M的磁盘空间)。
在开源的区块链系统中,最常使用的就是长安链、Fabric和以太坊。
长安链的优势自不必说,国产自主可控(支持国密、支持国产操作系统、国产数据库、国产芯片),性能高(信通院测试存证性能可达到10W TPS),膨胀率低(基于泓存储引擎,对冷数据可启用压缩,可以将膨胀率做到1以下)。
下面我们主要来看看如果用以太坊做存证,那么性能和膨胀率怎么样。
一、搭建以太坊私有链
因为只是测试,所以我搭建的是POA共识的单节点私有链。具体操作过程如下:
1. 环境准备
在Linux服务器上下载并安装Go环境,安装git,clone go-ethereum代码到本地。这里注意我们采用的是v1.10.20版,并不是最新版本,因为以太坊代码升级改动很大,新版本可能一些命令已经不支持了。git checkout v1.10.20 make all会准备好我们需要建测试链用的工具到build/bin文件夹中。
2.创建以太坊账号
执行以下命令,geth会创建一个新的以太坊私钥和地址: ./geth account new --datadir=./test1 这里注意我们的测试链用的是test1文件夹,所以在命令中要指定文件夹路径。以下是我执行结果:./geth account new --datadir=./ INFO [09-13|14:25:52.668] Maximum peer count ETH=50 LES=0 total=50 INFO [09-13|14:25:52.670] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory" Your new account is locked with a password. Please give a password. Do not forget this password. Password: Repeat password: Your new key was generated Public address of the key: 0x70dA66C22f52f1869B028Ae2D2A86ffF8558cA38 Path of the secret key file: keystore/UTC--2023-09-13T06-25-57.998394704Z--70da66c22f52f1869b028ae2d2a86fff8558ca38 - You can share your public address with anyone. Others need it to interact with you. - You must NEVER share the secret key with anyone! The key controls access to your funds! - You must BACKUP your key file! Without the key, it's impossible to access account funds! - You must REMEMBER your password! Without the password, it's impossible to decrypt the key!
3. 创建创世块配置json
推荐使用puppeth来生成创世区块配置文件,(这个工具在新版本的go-ethereum中已经被删除了。)./puppeth +-----------------------------------------------------------+ | Welcome to puppeth, your Ethereum private network manager | | | | This tool lets you create a new Ethereum network down to | | the genesis block, bootnodes, miners and ethstats servers | | without the hassle that it would normally entail. | | | | Puppeth uses SSH to dial in to remote servers, and builds | | its network components out of Docker containers using the | | docker-compose toolset. | +-----------------------------------------------------------+ Please specify a network name to administer (no spaces, hyphens or capital letters please) > test1 Sweet, you can set this via --network=test1 next time! INFO [09-13|14:11:02.594] Administering Ethereum network name=test1 WARN [09-13|14:11:02.594] No previous configurations found path=/data/home/devinyzeng/.puppeth/test1 What would you like to do? (default = stats) 1. Show network stats 2. Configure new genesis 3. Track new remote server 4. Deploy network components > 2 What would you like to do? (default = create) 1. Create new genesis from scratch 2. Import already existing genesis > 1 Which consensus engine to use? (default = clique) 1. Ethash - proof-of-work 2. Clique - proof-of-authority > 2 How many seconds should blocks take? (default = 15) > 1 Which accounts are allowed to seal? (mandatory at least one) > 0x70dA66C22f52f1869B028Ae2D2A86ffF8558cA38 > 0x Which accounts should be pre-funded? (advisable at least one) > 0x Should the precompile-addresses (0x1 .. 0xff) be pre-funded with 1 wei? (advisable yes) > no Specify your chain/network ID if you want an explicit one (default = random) > 42 INFO [09-13|14:12:36.658] Configured new genesis block What would you like to do? (default = stats) 1. Show network stats 2. Manage existing genesis 3. Track new remote server 4. Deploy network components > 2 1. Modify existing configurations 2. Export genesis configurations 3. Remove genesis configuration > 2 Which folder to save the genesis specs into? (default = current) Will create test1.json, test1-aleth.json, test1-harmony.json, test1-parity.json > INFO [09-13|14:13:53.792] Saved native genesis chain spec path=test1.json ERROR[09-13|14:13:53.792] Failed to create Aleth chain spec err="unsupported consensus engine" ERROR[09-13|14:13:53.792] Failed to create Parity chain spec err="unsupported consensus engine" INFO [09-13|14:13:53.792] Saved genesis chain spec client=harmony path=test1-harmony.json这里我们需要注意的是,共识算法要选Clique,这个算法在测试链上性能比传统的Pow高很多,而且不耗资源。出块时间可以写的比较小,我这里选的是1s出一个块。 至此我们要用的创世配置文件已经导出好了。按Control+D退出当前命令行界面。下面是我刚才生成的创世区块配置文件:
{ "config": { "chainId": 42, "homesteadBlock": 0, "eip150Block": 0, "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "clique": { "period": 1, "epoch": 30000 } }, "nonce": "0x0", "timestamp": "0x6501568c", "extraData": "0x000000000000000000000000000000000000000000000000000000000000000070da66c22f52f1869b028ae2d2a86fff8558ca380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "gasLimit": "0x47b760000", "difficulty": "0x1", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "alloc": { "70da66c22f52f1869b028ae2d2a86fff8558ca38": { "balance": "50000000000000000000000" } }, "number": "0x0", "gasUsed": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "baseFeePerGas": null }
这里我们需要去修改一下test1.json文件,可以把gasLimit改大一些,这样我们一个区块中才能放下更多的交易,另外alloc要设置一个初始的ETH在账户1手中,因为是Wei做单位,所以我这里设置的balance是50000000000000000000000看上去很大,只有账户1有ETH,后续才能发起存证交易。
4. 初始化链并启动链
./geth --datadir=./test1 init test1.json
打印日志:Successfully wrote genesis state database=lightchaindata hash=122ef9..a11196
geth会根据刚才的配置文件,在./test1文件夹下创建geth文件夹,里面包含了创世区块数据库。初始化成功后,我们就可以启动链了,启动命令:
./geth --datadir=./test1 --networkid 42 --nodiscover --maxpeers 0 --allow-insecure-unlock console
链启动后,我们这个终端会打印链成功启动的Log,而且进入了与链进行交互的模式。
Welcome to the Geth JavaScript console! instance: Geth/v1.10.20-stable-8f2416a8/linux-amd64/go1.20.7 coinbase: 0x70da66c22f52f1869b028ae2d2a86fff8558ca38 at block: 0 (Wed Sep 13 2023 14:28:28 GMT+0800 (CST)) datadir: /data/go/src/github.com/ethereum/go-ethereum/build/bin/test1 modules: admin:1.0 clique:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 To exit, press ctrl-d or type exit >
5.解锁账号并开始产块
personal.unlockAccount(eth.accounts[0], "123", 0)可以解锁刚才创建的以太坊账号,因为我密码设的是123,所以第二个参数是123,第三个参数0是表示这个解锁不会过期,一直处于解锁状态。 解锁账号后,我们就可以用这个账号进行产块了。命令行输入:
miner.start(1)开始产块。
二、压测存证交易
6.尝试发送存证交易
因为产块后终端会不断的打印日志,所以我们要发送交易最好是开启一个新的终端,然后在新终端输入命令:
./geth attach ./test1/geth.ipc进入了与链交互的模式。
personal.newAccount("123")可以创建一个新的账号2,我们使用以下命令查询账号1的余额:
web3.eth.getBalance(eth.accounts[0])如果返回0,那么账号1是不能发起任何交易的。所以这里确保返回值不是0。如果是0,那么回到步骤3,重新修改json中的“alloc”字段,然后重新初始化,重新启动链哈。 使用以下命令,我们尝试从账号1给账号2转账1Wei,转账的同时附加上字符串“HelloWorld”:
eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(0.001, "ether"), data: web3.toHex("HelloWorld")})
7.发起大量存证交易进行压测
我们这里先准备一个产生随机字符串的函数randomString,将以下内容粘贴到交互终端中:
function randomString(length) { var result = ''; var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var charactersLength = characters.length; for (var i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; }
然后,我们写一个for循环的逻辑,不断的往链上发送转账交易,交易内容是账号1向账号2转账1Wei,转账的同时附加上1024字节的随机字符串。为了统计一下TPS,我们再在循环开始之前和循环结束之后各记录一下时间。下面是完整的代码:
var startTime = new Date().getTime(); for (var i = 0; i < 10000; i++) { var randomData = 'xx'+randomString(1022); eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: 1, data: web3.toHex(randomData)}); } var endTime = new Date().getTime(); var elapsedTime = endTime - startTime; console.log("For loop execution time: " + elapsedTime + " milliseconds");
8.观察产块和交易池状态
我们回到终端1,可看到大量的交易被接收和被打包的日志,我们再新建一个终端3,同样是附加到交互界面中:
./geth attach ./test1/geth.ipc
然后执行:
txpool.status可以查看交易池的状态,如果发送交易的速度远快于交易被打包到区块中的速度,那么交易池中就会堆积大量交易。所以我们在终端2中如果交易发送完了,还需要再在终端3中查看一下交易池状态,确保所有交易被打包完毕,也就是交易池为0,则说明我们的压测才算完成了。 9.结束链并统计 我们回到终端1,因为一直在产块,所以终端1一直在打印日志,不过没关系,我们任然可以输入命令:
miner.stop()即可结束产块,然后输入exit即可退出终端1的交互,与此同时整个链进程也结束了。 我们来到./test1/geth文件夹,运行如下命令:
du -h -d 1会列出这个文件夹下每个子文件夹的磁盘占用:
三、总结
性能
因为只是我们低配的Linux服务器,所以在TPS上数字并不大,也就是196 TPS,不具有生产环境的参考意义,在POA共识下,高性能服务器的以太坊链TPS肯定是可以轻松上千的。而且我用的是1个账号循环,所以在产生交易上就是串行的,如果真要测性能,可能需要准备几十甚至几百个有账户余额的账号,然后每个账号独立发送交易。
膨胀率
而膨胀率是和机器的配置无关的,只和要存证的数据大小以及每个区块能打包多少笔交易有关。我们简单计算一下:
- 10W笔存证交易,1K/Tx,业务数据大小:100000*1024/1024/1024=97.6M
- 磁盘占用113M
- 所以膨胀率是113/97.6=1.16