我们基于区块链在企业中的应用最广泛的就是“存证”功能需求,这是利用了区块链不可篡改和数据共享的特点,存证的业务数据一方面可以保证留痕和追溯,另一方面也实现了多个节点(如果部署在不同企业和部门)之间的数据共享。如果要实现存证,我们最关心并不是图灵完备,也不是去中心化,而是 存证的性能(也就是TPS)和数据膨胀率(也就是存1M的业务数据,单个节点要消耗多少M的磁盘空间)。
在开源的区块链系统中,最常使用的就是长安链、Fabric和以太坊。
长安链的优势自不必说,国产自主可控(支持国密、支持国产操作系统、国产数据库、国产芯片),性能高(信通院测试存证性能可达到10W TPS),膨胀率低(基于泓存储引擎,对冷数据可启用压缩,可以将膨胀率做到1以下)。
下面我们主要来看看如果用以太坊做存证,那么性能和膨胀率怎么样。
一、搭建以太坊私有链
因为只是测试,所以我搭建的是POA共识的单节点私有链。具体操作过程如下:
1. 环境准备
在Linux服务器上下载并安装Go环境,安装git,clone go-ethereum代码到本地。这里注意我们采用的是v1.10.20版,并不是最新版本,因为以太坊代码升级改动很大,新版本可能一些命令已经不支持了。
代码语言:javascript复制git checkout v1.10.20
make all
会准备好我们需要建测试链用的工具到build/bin文件夹中。
2.创建以太坊账号
执行以下命令,geth会创建一个新的以太坊私钥和地址:
./geth account new --datadir=./test1
这里注意我们的测试链用的是test1文件夹,所以在命令中要指定文件夹路径。以下是我执行结果:
代码语言:javascript复制./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中已经被删除了。)
代码语言:javascript复制./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退出当前命令行界面。下面是我刚才生成的创世区块配置文件:
代码语言:javascript复制{
"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. 初始化链并启动链
代码语言:javascript复制./geth --datadir=./test1 init test1.json
打印日志:Successfully wrote genesis state database=lightchaindata hash=122ef9..a11196
geth会根据刚才的配置文件,在./test1文件夹下创建geth文件夹,里面包含了创世区块数据库。初始化成功后,我们就可以启动链了,启动命令:
代码语言:javascript复制./geth --datadir=./test1 --networkid 42 --nodiscover --maxpeers 0 --allow-insecure-unlock console
链启动后,我们这个终端会打印链成功启动的Log,而且进入了与链进行交互的模式。
代码语言:javascript复制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.解锁账号并开始产块
代码语言:javascript复制personal.unlockAccount(eth.accounts[0], "123", 0)
可以解锁刚才创建的以太坊账号,因为我密码设的是123,所以第二个参数是123,第三个参数0是表示这个解锁不会过期,一直处于解锁状态。
解锁账号后,我们就可以用这个账号进行产块了。命令行输入:
代码语言:javascript复制miner.start(1)
开始产块。
二、压测存证交易
6.尝试发送存证交易
因为产块后终端会不断的打印日志,所以我们要发送交易最好是开启一个新的终端,然后在新终端输入命令:
代码语言:javascript复制./geth attach ./test1/geth.ipc
进入了与链交互的模式。
代码语言:javascript复制personal.newAccount("123")
可以创建一个新的账号2,我们使用以下命令查询账号1的余额:
代码语言:javascript复制web3.eth.getBalance(eth.accounts[0])
如果返回0,那么账号1是不能发起任何交易的。所以这里确保返回值不是0。如果是0,那么回到步骤3,重新修改json中的“alloc”字段,然后重新初始化,重新启动链哈。
使用以下命令,我们尝试从账号1给账号2转账1Wei,转账的同时附加上字符串“HelloWorld”:
代码语言:javascript复制eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(0.001, "ether"), data: web3.toHex("HelloWorld")})
7.发起大量存证交易进行压测
我们这里先准备一个产生随机字符串的函数randomString,将以下内容粘贴到交互终端中:
代码语言:javascript复制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,我们再在循环开始之前和循环结束之后各记录一下时间。下面是完整的代码:
代码语言:javascript复制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,同样是附加到交互界面中:
代码语言:javascript复制./geth attach ./test1/geth.ipc
然后执行:
代码语言:javascript复制txpool.status
可以查看交易池的状态,如果发送交易的速度远快于交易被打包到区块中的速度,那么交易池中就会堆积大量交易。所以我们在终端2中如果交易发送完了,还需要再在终端3中查看一下交易池状态,确保所有交易被打包完毕,也就是交易池为0,则说明我们的压测才算完成了。
9.结束链并统计
我们回到终端1,因为一直在产块,所以终端1一直在打印日志,不过没关系,我们任然可以输入命令:
代码语言:javascript复制miner.stop()
即可结束产块,然后输入exit即可退出终端1的交互,与此同时整个链进程也结束了。
我们来到./test1/geth文件夹,运行如下命令:
代码语言:javascript复制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
这个膨胀率还算是很优秀的,只比长安链泓存储引擎开启冷热分离后的0.9高出一点点。需要注意这是在存证1K业务数据的情况下,如果存证32字节,那么膨胀率就会高很多,10W笔32字节是3.05M业务数据,上链后磁盘占用18M,膨胀率达到6.0。
其他业务数据大小的时候膨胀率大家可以根据我上面给出的脚本实际测试。