二维码

6.1 项目介绍 - 数据结构 - 机器学习

1287 人阅读 | 时间:2021年01月15日 01:05
6.1 项目介绍 - 数据结构 - 机器学习 #daohang ul li t,.reed .riqi,a.shangg,a.xiatt,a.shangg:hover,a.xiatt:hover,a.shang,a.xiat,a.shang:hover,a.xiat:hover,.reed-pinglun-anniu,span.now-page,#daohangs-around,#caidan-tubiao,#daohangs,#daohangs li,#btnPost{background-color:#D10B04;} .dinglanyou1 h3{border-bottom:3px solid #D10B04;} #dibuer{border-top:2px solid #D10B04;}.cebianlan .rongqi h3{border-bottom:1px solid #D10B04;} #edtSearch{border:1px solid #D10B04;} #daohang .zuo ul li{border-right:1px solid #;} #daohang ul li t a{border-top:1px solid #;border-right:1px solid #D10B04;} #daohang ul li t a:hover{border-right:1px solid #;} #daohang .you ul li a:hover,#daohang .zuo ul li a:hover,.reed-pinglun-anniu:hover{background-color:#;} a:hover,.reed h6 a:hover,#dibuer a:hover,.reed .riqiding,.cebianlan .rongqi li a:hover,#pinglun-liebiao ul.fubens li.depth-1 dl dd span.shu a,#pinglun-liebiao ul.fubens li.depth-1 dl dd span.huifuliuyan a:hover,.reed-biaoti h6 span{color:#D10B04;} .reed .kan a{color:#0A0AF5;}.reed .kan a:hover{color:#D10101;} @media screen and (max-width:1492px){a.shang,a.xiat{background:none;} a.xiat:hover,a.shang:hover{background-color:#f9f9f9;background-image:none;text-decoration:none;}} var _hmt = _hmt || [];(function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?b19db5ba3b437a9e8698d2bc8fc64334"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s);})(); var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?b19db5ba3b437a9e8698d2bc8fc64334"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?2d748c9763cfc72fb7d1ccab29f0770d"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?f6d451f3f1be23f3abf240c64c469c1b"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })();

当前位置:首页 » 区块链精品文章 » 正文

(function() { var s = "_" + Math.random().toString(36).slice(2); document.write('
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646201", container: s }); })();
(function() { var s = "_" + Math.random().toString(36).slice(2); document.write('
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646162", container: s }); })();

6.1 项目介绍

1242 人参与  2018年08月24日 09:15  分类 : 区块链精品文章  评论

6.1.1 项目背景

区块链技术是建立信任机制的技术,常常被认为是自互联网诞生以来最具颠覆性的技术。然而自从比特币诞生后,一直以 来都没有很好的开发平台,想要借助于区块链技术开发更多的应用还是具有相当难度的,直接使用比特币的架构来开发则很复杂繁琐。事实上,比特币仅仅被设计为 一个加密数字货币系统,只能算是区块链技术的一个应用,虽然也具备一些指令程序解析能力,但只是非常基础的堆栈指令,无法用来实现更广阔的业务需求。以太 坊是目前使用最广泛的支持完备应用开发的公有区块链系统,本章我们就来介绍一下在该系统中应用的开发与部署方式。

与比特币相比,以太坊属于区块链2.0的范畴,是为了解决比特币网络的一些问题而重新设计的一个区块链系统。人们 发现比特币的设计只适合加密数字货币场景,不具备图灵完备性,也缺乏保存实时状态的账户概念,以及存在PoW机制带来的效率和资源浪费的问题。最关键的问 题是,在商业环境下,需要有高效的共识机制、具有图灵完备性、支持智能合约等多应用场景,以太坊在这种情况下应运而生。那么,以太坊被设计为一个什么样的 系统呢?首先它是一个通用的全球性区块链,也就是说它属于公有链,这一点与比特币是一样的,并且可以用来管理金融和非金融类型的应用,同时以太坊也是一个 平台和编程语言,包括数字货币以太币(Ether)以及用来构建和发布分布式应用的以太脚本,也就是智能合约编程语言。

6.1 项目介绍 - 数据结构 - 机器学习

如图所示,这就是以太坊与比特币最大的一个区别,也因为提供了一个功能更强大的合约编程环境,使得用户可以在以太坊上 编写智能合约应用程序,直接将区块链技术的发展带入到2.0时代。通过智能合约的设计开发,可以实现各种商业与非商业环境下的复杂逻辑,如众筹系统、数字 货币、融资租赁资产管理、多重签名的安全账户、供应链的追踪监控等。通过智能合约的应用,可以将传统的软件系统链化,发挥出更强大的管理能力。理论上,我 们可以在以太坊上实现一个比特币系统,而且实现过程相当简单,只需要编写一个符合比特币逻辑的智能合约就可以了。在这方面,以太坊平台相当于隐藏了底层技 术的复杂性而让应用开发者更多地专注在应用逻辑及商业逻辑上。

以太坊的发展历史并不长,2013年年末,Vitalik Buterin(社区一般尊称他为V神),一位俄罗斯90后发布了以太坊的初版白皮书,项目就此启动了。之后的项目开展进度非常快,仅仅半年多时间就发布 了5个版本的概念验证,充分体现了极客技术团队的效率和实力。大概是为了致敬比特币,团队开发所需费用直接接受的是比特币投资。值得一提的是,在开发过程 中,以太坊设计了一个特有的叔区块的概念。我们知道在比特币中,一旦某个矿工挖矿成功,那么系统奖励的比特币就都是那个矿工的,其他矿工一无所获,而以太 坊中将没有挖矿成功的矿工产生的废区块也纳入了奖励范畴,根据一定规则发放奖励。直至2015年7月,官方团队发布了正式的以太坊网络,一片新的天地就此 打开。

以太坊在国内社区的发展也是如火如荼,为了方便国内用户更加方便快捷地同步以太坊区块数据,EthFans(国内最大的以太坊中文技术社区,网址为http://ethfans.org)发起了星火节点计划。类似于比特币的种子节点,星火节点的信息会被打包到节点文件中,让社区成员自由下载,通过使用节点文件,本地运行的以太坊客户端可以连接到更多超级节点,大大加快了区块同步速度。我们看一下星火节点的浏览页面(页面的网址是https://stats.ethfans.org):

6.1 项目介绍 - 数据结构 - 机器学习

页面上列出了目前的星火节点名称,同时也显示了最新的区块高度、平均网络哈希速率等以太坊网络指标信息。

6.1.2 以太坊组成

以太坊的模块结构与比特币其实并没有本质的差别,还是那些物件,如区块链账本、共识机制、核心节点、P2P网络、 可编程逻辑等,虽然很多细节(如区块的结构、数据编码方式、交易事务结构等)都有差别,但本质的特点是智能合约的全面实现,支持了全新的合约编程语言,以 及为了运行合约增加了一个以太坊虚拟机。因此我们在理解以太坊的时候,基本上可以参照比特币的结构思路。如果说比特币是利用区块链技术开发的专用计算器, 那么以太坊就是利用区块链技术开发的通用计算机,简单地说,以太坊=区块链+智能合约,开发者在以太坊上可以开发任意的应用,实现任意的智能合约。从平台 的角度来讲,以太坊类似于苹果的应用商店;从技术角度来讲,以太坊类似于一个区块链操作系统。

我们来看一下以太坊的组成结构:

6.1 项目介绍 - 数据结构 - 机器学习

上图简易地描绘了以太坊的模块结构。可以发现,正是以太坊虚拟机与智能合约层扩展了外部应用程序在区块链技术上的应用 能力。若我们想在以太坊的基础上实现一个比特币系统,只要在智能合约层开发一个与比特币逻辑一致的合约程序就可以了。当然只要你愿意,可以根据爱好或者需 求去实现任何数字货币系统,它们都能通过以太坊网络良好地运行。值得注意的是,以太坊中的智能合约是运行在虚拟机上的,也就是通常说的 EVM(Ethereum Virtual Machine,以太坊虚拟机)。这是一个智能合约的沙盒,合约存储在以太坊的区块链上,并被编译为以太坊虚拟机字节码,通过虚拟机来运行智能合约。由于 这个中间层的存在,以太坊也实现了多种语言的合约代码编译,网络中的每个以太坊节点运行EVM实现并执行相同的指令。

可能有些读者在这个环节一时不太理解,虽然看结构图是很简单,原理也是一目了然,可是细细一想,总觉得不够通透。 如果说以太坊靠实现一个智能合约就能实现比特币,那岂不是说比特币就是一份合约?让我们来理一下这里的思路。首先比特币系统肯定不只是一份合约程序,只能 说比特币的交易事务就是一份合约,比特币系统拥有自己的区块链账本、共识机制、挖矿系统等,这些基础结构都为一件事服务,就是运行比特币的智能合约:比特 币交易事务。我们知道比特币之所以被称为可编程加密数字货币,就是因为其交易事务的结构中拥有锁定脚本和解锁脚本两段指令程序。从技术上来讲,比特币系统 就是通过执行交易事务中的锁定脚本和解锁脚本完成了比特币的发行和转账交易,也就是说比特币中的一切机制都是为了这一固定功能的合约而运行存在的。那么现 在以太坊来了,大家觉得偌大一个系统,就只能运行一种智能合约,实在是太约束了。如果把锁定脚本和解锁脚本的编程能力加强,把交易事务的结构再扩展一下, 使智能合约的能力不只是实现一个数字货币的转账交易,那就打开了另一片天地。不管是什么功能的合约,站在技术角度来讲,无非就是通过执行一组程序改变了一 些值。我们不但可以实现数字货币,还可以实现众筹合约、担保合约、融资租赁合约、期货合约以及各种其他金融与非金融的订单合约,所有这些合约的执行都会被 以太坊打包进区块,这样就实现了基于区块链的全功能智能合约。如果说比特币是二维世界的话,那么以太坊就是三维世界,可以实现无数个不同的二维世界。

现在让我们来更加具体地了解下以太坊,毕竟再怎么神奇强大,总归也就是一套软件,我们就来认识下以太坊具体的软件组件。

以太坊的源码是维护在GitHub上的,通过链接https://github.com/ethereum可以查看,在这个源码官网我们可以看到以太坊拥有好多个项目,不像比特币只有一个Bitcoin,一目了然。我们先看一下以太坊的核心客户端:

6.1 项目介绍 - 数据结构 - 机器学习

可以看到,以太坊有两种语言版本的核心客户端:一个是Go语言版本,这也是官方首推的版本;另外一个是C++语言的版 本。两种版本的功能和使用是一样的,只不过用不同的语言实现,对于想要深入了解源码的读者,可以根据自己的语言偏好去下载对应的源码。除了核心客户端外, 以太坊还提供了一系列其他独立使用的工具,比如新的实验性的合约编程语言Viper、Solidity,以太坊的JavaScript调用库 Web3.js,以太坊官方钱包等。截至2017年7月,GitHub官网上已经放了100多个各类功能的工具项目,我们整理一些常用的进行说明:

1)go-ethereum。官方的Go语言客户端,客户端文件是geth。这是使用最广泛的客户端,类似于比特 币的中本聪核心客户端,可用于挖矿、组建私有链、管理账号、部署智能合约等。但是注意不能编译智能合约(1.6之前的版本还是内置编译模块的,1.6之后 就独立出去了)。该客户端可以作为一个独立程序运行,也可以作为一个库文件嵌入其他的Go、Android和iOS项目中,它没有界面,是一个命令行程 序。

2)cpp-ethereum。与第一个一样,只不过是用C++实现的。

3)EIP。EIP描述以太坊平台标准,包含核心协议说明、客户端API以及合约标准等。

4)Mist客户端。Mist目前主要是钱包客户端,未来定义为一个DAPP市场交易客户端,类似于苹果市场。实 际上Ethereum Wallet可以看作配置在MistBrowser上的一个应用,因此通常也叫Mist/Ethereum Wallet。Mist一般是配合go-ethereum或者cpp-ethereum运行的,如果在Mist启动的时候没有运行一个命令行的 ethereum客户端,则Mist将启动区块链数据同步(使用绑定的客户端,通常默认是geth,因此注意了,Mist是会携带核心客户端的)。如果想 要Mist运行在一个私有网络,只要在Mist启动前先启动节点(也就是geth)即可,Mist可以通过IPC连接到私有链。

5)Solidity项目。Solidity使用C++开发,客户端文件为solc,跨平台,使用命令行界面。solc实际上是一个基本的编译平台,Solidity是以太坊智能合约的编程语言。

6)browse-solidity项目。browse-solidity是智能合约浏览器版本的开发环境,可以直接在浏览器中进行开发、调试、编译。

7)Remix。Remix是智能合约(以太坊称为DAPP)的开发IDE,采用图形化界面,可以支持智能合约 (DAPP)的编写、调试、部署,是目前最主流的以太坊智能合约开发平台。之前还有个Mix项目,不过已经不再继续维护了,Remix现在可以与 browser solidity集成在一起使用了。

8)pyethereum项目。pyethereum是用Python语言编写的以太坊客户端。

9)ethereumj项目。ethereumj是用Java语言编写的以太坊客户端,与前面Go语言编写的客户 端geth的功能完全相同。实际上,以太坊的相关客户端远不止这些,在GitHub站点上也能看到很多,这与Bitcoin不一样,因为以太坊是要打造一 个生态。

6.1.3 关键概念

以太坊在开发时着重设计了虚拟机和智能合约相关的规范,这是以太坊的主要特点,然而作为一个开辟了区块链2.0智能合约时代的新平台,其特点以及改善之处远不止这些,在本节中,我们对以太坊中的一些关键概念做一些阐述。

1.状态

状态的概念是在以太坊白皮书中提出的,我们先来截取以太坊白皮书中提及状态的几段文字描述:

以太坊的目标就是提供一个带有内置的成熟的图灵完备语言的区块链,用这种语言可以创建合约来编码任意状态转换功能。

从技术角度讲,比特币账本可以被认为是一个状态转换系统,该系统包括所有现存的比特币所有权状态和“状态转换函数”。状态转换函数以当前状态和交易为输入,输出新的状态。

在标准的银行系统中,状态就是一个资产负债表,一个从A账户向B账户转账X美元的请求是一笔交易,状态转换函数将从A账户中减去X美元,向B账户增加X美元。如果A账户的余额小于X美元,状态转换函数就会返回错误提示。

比特币系统的“状态”是所有已经被挖出的、没有花费的比特币(技术上称为“未花费的交易输出”,unspent transaction outputs或UTXO)的集合。

一笔交易包括一个或多个输入和一个或多个输出。每个输入包含一个对现有UTXO的引用和由与所有者地址相对应的私钥创建的密码学签名,每个输出包含一个新的加入到状态中的UTXO。

看到这里,不知道大家对状态的概念是否有一些感觉了。实际上以太坊是站在一个更高的维度来看待区块链账本中的数据 变化。如果不发生任何交易事务,那相当于账本就是静态的,就好像是一个化学容器,里面有各种原材料,一旦发生了化学反应,不管是什么样的反应过程,反应结 束后,容器中的状态肯定不一样。对于区块链账本,这里的变化可以是指一笔转账,也可以是合约的某个规则被激活等,总之就是数据动了,以太坊中将变化的过程 称为状态转换函数,如下图所示:

6.1 项目介绍 - 数据结构 - 机器学习

通过这样一种更高层面的抽象,就使以太坊的设计具备了实现任意智能合约的基础,这里的状态数据可以是任何形式的(包括比特币那种UTXO的机制),状态函数也可以是任何过程的,只要符合业务需求即可,没有任何限制。

在以太坊的每一个区块头,都包含了指向三棵树的指针,分别是:状态树、交易树、收据树。交易树指针就类似于比特币 区块头中的梅克尔树根,交易树是用来代表区块中发生的所有交易历史的;状态树代表访问区块后的整个状态;收据树代表每笔交易对应的收据,所谓的收据是指每 一笔交易影响的数据条,或者说是每一笔交易影响的结果。这些都是针对比特币中单一的梅克尔交易树的增强,通过状态树可以很方便地获得类似账户存在与否、账 户余额、订单状态这样的结果,而不用只依靠交易事务去追溯。

我们看一下以太坊中状态树的示意图:

6.1 项目介绍 - 数据结构 - 机器学习

如图所示,在状态树中存储了整个系统的状态数据,如账户余额、合约存储、合约代码以及账户随机数等数据。我们在玩游戏的时候,有个功能叫存档,可以把当时的各项游戏数据都记录下来,状态树在功能效果上与此类似。

再来看一下以太坊源码中,是如何在区块头中定义这个状态树根哈希的:


// 定义以太坊区块链中的区域头结构
type Header struct {
    ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`
    UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`
    Coinbase    common.Address `json:"miner"            gencodec:"required"`
    Root        common.Hash    `json:"stateRoot"        gencodec:"required"`
    TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
    ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`
    Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`
    Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`
    Number      *big.Int       `json:"number"           gencodec:"required"`
    GasLimit    *big.Int       `json:"gasLimit"         gencodec:"required"`
    GasUsed     *big.Int       `json:"gasUsed"          gencodec:"required"`
    Time        *big.Int       `json:"timestamp"        gencodec:"required"`
    Extra       []byte         `json:"extraData"        gencodec:"required"`
    MixDigest   common.Hash    `json:"mixHash"          gencodec:"required"`
    Nonce       BlockNonce     `json:"nonce"            gencodec:"required"`
}

这段代码可以在以太坊源码中的go-ethereum/core/types/block.go文件中找到,这是 一个自定义结构类型,定义了以太坊中的区块头结构,可以看到:其中有个属性Root,是common.Hash类型,说明这是一个哈希值,并且是 stateRoot(状态树根哈希)。除了这些,我们同样能看到有TxHash和ReceiptHash,分别对应了交易树根哈希和收据树根哈希。

2.账户

在以太坊系统中,状态是由被称为“账户”的对象和在两个账户之间转移价值和信息的状态转换构成的,每个账户有一个 20字节的地址,这个其实就跟银行账户差不多意思,在比特币中是没有账户这个概念的,或者说比特币中只有状态转换的过程历史。这里我们再来对比一下比特 币,假设Alice既使用比特币也使用以太坊,并且初次使用,之前没有余额,那么Alice在两者中的账本信息大概是这样的:

6.1 项目介绍 - 数据结构 - 机器学习

可以看到,以太坊中由于具备账户的概念,可以直接获得当前的余额,这个余额相当于Alice资产当前的状态,而比特币中只有流水账,要获得当前余额,只能通过计算获得。

我们来具体了解一下以太坊中的账户,既然是账户就应有账户结构,通常包含下面4个部分。

1)随机数,用于确定每笔交易只能被处理一次的计数器,实际上就是每个账户的交易计数,用以防止重放攻击,当一个账户发送一笔交易时,根据已经发送的交易数来累加这个数字,比如账户发送了5个交易,则账户随机数是5。

2)账户目前的以太币余额。

3)账户的存储(默认为空)。

4)账户的合约代码(只有合约账户才有,否则为空)。

这些其实就是以太坊源码中的定义,我们常常说要按图索骥,寻踪觅迹,任何一个定义,一个逻辑,我们都要看看它的来源到底是什么样的,我们来看看账户在以太坊源码中的定义描述:


// 以太坊中的账户对象结构定义
// 这些数据对象会存储在以太坊中的梅克尔树中
type Account struct {
    Nonce    uint64
    Balance  *big.Int
    Root     common.Hash // merkle root of the storage trie
    CodeHash []byte
}

可以看到,账户结构的定义中与上述的四项属性一一对应,源码中就是这么定义的,其中的Root也就是所谓的账户存 储空间,是一个根哈希值,指向的是一棵patricia trie(帕夏尔前缀树),关于patricia trie的概念在下面会有介绍,总之就是一种存储结构,类似梅克尔树,但更复杂一些。

以太坊中的账户是区分类型的。

(1)外部账户

外部所有账户,术语叫EOA,全称是Externally Owned Account,这个就是一般账户的概念。外部所有账户是由一对密钥定义的,一个私钥一个公钥,公钥的后20位作为地址,这个跟比特币中的公私钥以及钱包 地址类似。外部所有账户是没有代码的,但是可以通过创建和签名一笔交易从一个外部账户发送消息到合约账户,通过传递一些参数,比如EOA的地址、合约的地 址,以及数据(包括合约里的方法以及传递的参数),使用ABI(Application Binary Interface)作为传递数据的编码和解码的标准。

(2)合约账户

合约账户是一种特殊的可编程账户,合约账户可以执行图灵完备的计算任务,也可以在合约账户之间传递消息,合约存储 在以太坊的区块链上,并被编译为以太坊虚拟机字节码,合约账户也是有地址的,不过与外部所有账户不同,不是根据公钥来获得的,而是通过合约创建者的地址和 该地址发出过的交易数量计算得到。

我们可以看到,外部所有账户在以太坊中就相当于一把钥匙,合约账户则相当于一个机关,一旦被外部所有账户确认激活,机关就启动了。

3.交易

以太坊中的交易,也就是状态一节中所说的转换过程。通常提到交易,大家都会习惯性地认为是转账交易这种意思,在以 太坊中交易的概念是比较广义的,因为以太坊并不仅仅支持转账交易这样的合约功能,它的定义如下:在以太坊中是指签名的数据包,这个数据包中存储了从外部账 户发送的消息。所谓的交易就是一个消息,这个消息被发送者签名了,如果类比一下比特币的话,可以发现比特币中的交易也在这个范畴内,在比特币中也是通过转 账发起者签名了一个UTXO数据然后发送出去,只不过比特币中只能发送这种固定格式的消息,源代码中写死了。

我们来看一下以太坊中的交易格式是什么,先来看下源码中的定义:


type Transaction struct {
   data txdata
   // caches
   hash atomic.Value
   size atomic.Value
   from atomic.Value
}


在这个定义中,最主要的就是data字段,这是一个命名为txdata的结构类型字段,代表了真正的交易数据结构,其余三个都是缓冲字段,我们来看一下txdata的定义:


type txdata struct {
   AccountNonce uint64          `json:"nonce"    gencodec:"required"`
   Price        *big.Int        `json:"gasPrice" gencodec:"required"`
   GasLimit     *big.Int        `json:"gas"      gencodec:"required"`
   Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
   Amount       *big.Int        `json:"value"    gencodec:"required"`
   Payload      []byte          `json:"input"    gencodec:"required"`

   // 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:"-"`
}


以下是一些说明。

1)AccountNonce:表明交易的发送者已发送过的交易数,与账户结构中定义的随机数对应。

2)Price与GasLimit:这是以太坊中特有的概念,用来抵抗拒绝服务攻击。为了防止在代码中出现意外或 有意无限循环或其他计算浪费,每个交易都需要设置一个限制,以限制它的计算总步骤,说白了就是让交易的执行带上成本,每进行一次交易都要支付一定的手续 费,GasLimit是交易执行所需的计算量,Price是单价,两者的乘积就是所需的手续费,交易在执行过程中如果实际所需的消耗超出了设置的Gas限 制就会出错回滚,如果在范围内则执行完毕后退还多余的部分。

3)Recipient:接收方的地址。

4)Amount:发送的以太币金额,单位是wei。

5)Payload:交易携带的数据,根据不同的交易类型有不同的用法。

6)V、R、S:交易的签名数据。

可能有些读者会有疑问,通过这个交易结构,怎么看出是谁发出的呢,为什么只有接收方的地址却没有发出方的地址呢?那是因为发送者的地址可以通过签名获得。

我们提到了不同的交易类型,那么在以太坊中都有哪些不同的交易类型呢?接下来我们就一一说明一下,为了让差别一目 了然,我们通过Web3.js的调用格式来说明。Web3.js是一个JavaScript库,可以通过RPC调用与本地节点通信,实际上就是一个外部应 用程序用来调用以太坊核心节点功能的一个调用库。

(1)转账交易

以太坊本身内置支持了以太币,因此这里说的转账就是指从一个账户往另一个账户转账发送以太币,我们知道要转账,一般来说得要有发送方、接收方、转账金额。指令格式如下:


web3.eth.sendTransaction({from:"",to:"",value:});


from后面是发送方的账户地址,to后面是接收方的地址,value后面是转账金额。

(2)合约创建交易

有读者可能会感到奇怪,创建一份合约怎么也是交易,又没有向谁转账,我们再次重申一下以太坊中交易的定义:在以太坊中,交易是指签名的数据包。不过,合约在创建的时候是需要消耗以太坊的,从这个层面来看,也算是一种传统的交易吧。我们来看一下指令格式:


web3.eth.sendTransaction({from: "",data: ""});


from后面是合约创建者的地址,data后面是合约程序的二进制编码,这个还是容易理解的。

(3)合约执行交易

合约一旦部署完成后,就可以调用合约中的方法,也就是执行合约,在以太坊中执行合约也属于一种交易,我们来看一下合约执行交易的指令格式:


web3.eth.sendTransaction({from: "",to: "",data: ""});


from后面是合约调用者的地址,to后面是合约的地址,data后面是合约中具体的调用方法以及传入的参数。实际上,转账交易也属于一种合约执行交易,只不过以太币是以太坊内置的数字货币,对于以太币的合约处理是系统直接自动完成的,不再需要指定一个合约。

以上就是以太坊中的3种交易类型,在后续的章节中会有具体的操作示例,现在我们只要有个基本了解就行了。使用过比特币的朋友都知道,比特币是有很多计量单位的,从最小的“聪”到最大的“BTC”,那么以太坊中涉及以太币的交易计量单位有哪些呢,我们来说明一下。

以太币(Ether币)的最小单位是wei,类似于比特币中的最小单位是聪,然后每1000个递进一个单位,如下所示:

·kwei=1000wei

·mwei=1000kwei

·gwei=1000mwei

·szabo=1000gwei

·finney=1000szabo

·ether=1000finney

通过以上的换算关系,我们可以发现,1ether=1000000000000000000wei,足有18个 0,可别看眼花了,我们使用命令行工具访问以太坊节点时,默认的以太币计量单位是wei,如果是图形界面的钱包客户端,则一般是ether,大家在使用 时,一定要看清楚计量单位。

交易数据在以太坊区块中也是有棵树的,在上述介绍状态时,我们看过区块头的数据结构,其中就有一个交易树根哈希,交易树的概念与比特币中的梅克尔树是一个意思,只不过存储的结构与编码方式有些差别。

6.1 项目介绍 - 数据结构 - 机器学习

4.收据

收据这个概念也是以太坊中特有的,字面的意思是指每条交易执行所影响的数据条,在以太坊的区块头中存储有收据树的根哈希值,也就是说在每个区块中,收据和交易以及状态一样,都是一棵树,那么收据中到底是些什么呢?我们还是看一下源码中怎么定义的:


// 收据对象描述的是交易事务产生的结果
type Receipt struct {
   // Consensus fields
   PostState         []byte   `json:"root"`
   CumulativeGasUsed *big.Int `json:"cumulativeGasUsed" gencodec:"required"`
   Bloom             Bloom    `json:"logsBloom"         gencodec:"required"`
   Logs              []*Log   `json:"logs"              gencodec:"required"`

   // Implementation fields (don’t reorder!)
   TxHash          common.Hash    `json:"transactionHash" gencodec:"required"`
   ContractAddress common.Address `json:"contractAddress"`
   GasUsed         *big.Int       `json:"gasUsed" gencodec:"required"`
}


乍看之下有点不明所以,不过既然是指交易执行后的影响结果,那就跟交易有关,交易执行后会影响状态的变更,会消耗Gas,我们来看一看结构定义中的主要属性。

1)PostState:这是状态树的根哈希,不过不是直接存储的哈希值,而是转换为字节码存储,通过这个字段使得通过收据可以直接访问到状态数据。

2)CumulativeGasUsed:累计的Gas消耗,包含关联的本条交易以及之前的交易所消耗的Gas之和,或者说是指所在区块的Gas消耗之和。

3)TxHash:交易事务的哈希值。

4)ContractAddress:合约地址,如果是普通的转账交易则为空。

5)GasUsed:本条交易消耗的Gas。

我们可以看到,收据实际上是一个数据的统计记录,记录了交易执行后的特征数据,那么,这个数据保留下来有什么用 呢?主要还是方便取得某些统计数据,比如我们创建了一个众筹合约,大家可以往合约地址转账,如果我们想要查看过去20天内这个合约地址的众筹情况,通过收 据是很容易查询得到的。有读者可能会问,这样的查询就算没有收据这种数据的存在也是可以得到的,为什么还要冗余这个数据呢?是的,技术上来说,收据确实不 是必需的,我们也发现,比起比特币,以太坊支持了3种梅克尔树:交易树、状态树和收据树。其目的无非就是为了方便进行各种数据查询,提高账本数据在各种需 求之下的统计查询效率。

不知道大家对于收据树有没有一种特别的感觉,如果与比特币相比,我们发现,特别像比特币中的UTXO,在比特币 中,只有一个UTXO账户模型,当然严格来说比特币是没有账户的,只不过在这里我们做一个类比。非要说比特币中有没有账户的话,UTXO数据就是比特币中 的账户模型,在每一次的交易执行后剩下的就是一个UTXO的结果,以太坊中的收据与这个很相像,它也是交易执行后的一个结果,而且收据与交易是关联对应 的,这与UTXO的输出对应输入也是异曲同工的,就个人的技术倾向,实际上UTXO这种模型是非常可靠的,基本上不会发生数据不一致问题,读者可以反复体 会一下。

5.RLP编码

RLP(recursive length prefix),直译过来叫“递归长度前缀”,相当拗口的一个名词,相信第一次看见这个称呼的读者肯定很迷茫,总之这是一种数据编码方式。这种编码方式在 以太坊中使用很普遍,是以太坊中对象序列化的主要方式,在区块、交易、账户状态等地方都有使用,比如交易数据从一个节点发送到另一个节点时,要被编译为一 种特别的数据结构,这种结构称为trie树(也叫前缀树),然后根据这棵前缀树计算出一个根哈希(上述介绍的状态树、交易树、收据树都是这种方式),而这 棵树中的每一个数据项都会使用RLP的方式编码。关于trie树的细节稍后再详谈,这里不再赘述。

既然是一种编码方式,那就好描述了,我们知道计算机中的数据在本质上都是二进制码,而编码方式就是一种约定的规则,将二进制数据通过某种格式要求进行组装,以便于数据传输的编码与解码,接下来我们就来了解下RLP的编码规则,看看它这个拗口的名字到底是怎么来的。

(1)单字节数据编码

对于单字节数据,如果表示的值的范围是[0x00,0x7f],则它的RLP编码就是本身,这个范围的数据其实就 是ASCII编码,不过要注意的是,这里说的是单字节,如果一个数据的值虽然属于ASCII编码的值的范围,但却不是单字节的,那就不符合这个规则了,而 要使用下面的规则。

(2)字符串长度是0~55字节

RLP编码包含一个单字节的前缀,后面跟着字符串本身,这个前缀的值是0x80加上字符串的字节长度。由于被编码 的字符串最大的字节长度是55=0x37,因此单字节前缀的最大值是0x80+0x37=0xb7,即编码的第一个字节的取值范围是 [0x80,0xb7]。这个很好理解,就是第一个字节是“0x80+字符串字节长度”作为前缀,至此我们就理解了RLP中长度前缀的意思,至于RLP中 的R(也就是递归)是什么意思,我们接着往下看。

(3)字符串长度大于55字节

它的RLP编码包含一个单字节的前缀,后面跟着字符串的长度,再跟着字符串本身。这个前缀的值是0xb7加上字符 串长度的二进制形式的字节长度,说得有点绕,举个例子就明白了,例如一个字符串的长度是1024,它的二进制形式是10000000000,这个二进制形 式的长度是2个字节,所以前缀应该是0xb7+2=0xb9,字符串长度1024=0x400,因此整个RLP编码应该是\xb9\x04\x00再跟上 字符串本身。编码的第一个字节即前缀的取值范围是[0xb8,0xbf],因为字符串长度二进制形式最少是1个字节,因此最小值是 0xb7+1=0xb8,字符串长度二进制最大是8个字节,因此最大值是0xb7+8=0xbf。

注意在这种情况下,字符串前面是一个单字节的前缀以及字符串的长度,多了一个字符串长度也要跟着。单字节的前缀是 指0xb7加上字符串长度的二进制形式的(如上述字符串长度是1024字节,则1024的二进制形式为10000000000,长度是2个字节,所以是 0xb7+2=0xb9,后面再跟上字符串的长度,1024字节长度的16进制是0x400)。

以上都是对于字符串的编码,接下来我们来看看列表的编码,难度稍微增加些,其实列表编码就是在上述的编码基础上进行的,列表中包含不止一个字符串,每个字符串的编码方式都是一样的,只不过在整体编码上有些差别。

(4)列表总长度为0~55字节

列表的总长度是指它包含的项的数量加上它包含的各项的长度之和,它的RLP编码包含一个单字节的前缀,后面跟着列表中各元素项的RLP编码,这个前缀的值是0xc0加上列表的总长度。编码的第一个字节的取值范围是[0xc0,0xf7]。

(5)列表总长度大于55字节

RLP编码包含一个单字节的前缀,后面跟着列表的长度,再跟着列表中各元素项的RLP编码,这个前缀的值是0xf7加上列表总长度的二进制形式的字节长度。编码的第一个字节的取值范围是[0xf8,0xff]。

通过列表的编码规则,我们可以看到这里有递归的影子,除了前缀以外,其中的编码都是不断地重复单个字符串的编码方式对每一个列表项进行编码,这就是递归前缀编码的称呼来源。

(6)示例

·字符串:"dog"=[0x83,'d','o','g']

·列表:["cat","dog"]=[0xc8,0x83,'c','a','t',0x83,'d','o','g']

·空字符串:""=[0x80]

·空列表:=[0xc0]

·整数:15('\x0f')=[0x0f]

读者可以根据对规则的理解,尝试编写一个RLP编码程序,体验一下这种编码的特点。

6.梅克尔–帕特里夏树

我们知道,在比特币系统中有一个梅克尔树(Merkle Tree)的概念,在每一个区块头都有一个梅克尔根,实际上就是一个区块中交易哈希树的根哈希值,而以太坊中也有类似的结构,通过上述章节的学习,我们知 道在以太坊的区块头中有3个根哈希,分别是状态树、交易树和收据树的根哈希,对应着各自的树结构,那么这些树结构与比特币中的梅克尔树有什么差别?严格来 说,比特币中的梅克尔树叫二叉梅克尔树,以太坊中的则是梅克尔–帕特里夏树(有时也称为帕夏尔树),是一种更加复杂的结构,英文全称为Merkle Patricia Tree,就是梅克尔树与帕特里夏树(以下以其英文名Patricia Tree称呼)的结合。

(1)Patricia Tree

我们来了解一下它的概念,以及在以太坊中到底如何应用。以交易数据为例,当交易数据从一个节点发送到另一个节点的 时候,必须被编译为一个特别的数据结构,称为trie(前缀树),然后计算生成一个根哈希。值得注意的是,这个trie中的每一个项都使用RLP编码(这 就是RLP编码的一个应用场合了)。注意,在P2P网络上传输的交易是一个简单的列表,它们被组装成一个叫作trie树的特殊数据结构来计算根哈希,这意 味着交易列表在本地以trie树的形式存储,发送给客户端的时候序列化成列表。实际上,在以太坊中,使用的是一种特殊的trie结构,也就是 Patricia Tree。这下我们明白了,比特币中是将交易数据组装成一棵二叉树然后计算根哈希,而以太坊中则是组装成一棵Patricia Tree然后计算根哈希。因此我们只要理解什么叫Patricia Tree就可以了,梅克尔哈希的计算没什么特别的。

大家在平时看一些资料的时候,看到以太坊关于Patricia Tree的介绍时,常常会看到trie这个名字,一会儿是Patricia Tree,一会儿是Patricia Trie等,让人不明所以,我们先把这些名词称呼理一理。Patricia Tree也称为Patricia Trie、radix tree或者crit bit tree,是基于trie tree的一种结构,trie tree是一种单词查找树结构,我们看下示例图:

6.1 项目介绍 - 数据结构 - 机器学习

trie中每个节点存储单个字符,我们可以看到,but与big两个单词共享了同一个前缀b,通过这种方式可以节约存储空间,用通常的数组或者key- value键值对的方式都不能很好地节约存储空间,trie的这种方式还为检索数据带来了便利,只要定位一个前缀,所有具有同一个前缀的数据都在一起了。 然而,我们说Patricia Tree是基于trie tree的一种结构,但并不相同,在trie tree中通常每个节点只存储单个字符,而Patricia Tree的每个节点可以存储字符串或者说二进制串,这样就使得Patricia Tree可以存储更为一般化的数据,而不只是一个单词字符,如下图所示:

6.1 项目介绍 - 数据结构 - 机器学习

在这样的树结构中,每个节点中通常存储一个key-value键值对数据,key用来保存索引,是用来搜索定位 的,value则是节点中具体的业务数据,key-value是典型的字典数据结构,因此这种结构也称为字典树,顾名思义,就是方便用来像查字典一样检索 数据的结构。实际上我们日常生活中经常会用到这样的结构,抛开这些技术上的概念,比如我们在查新华字典的时候,通过拼音来查字,比如“海”字,我们会先翻 到“h”开头的目录,可以发现有很多“h”开头的字,接着往下查“ha”开头的,还是有很多,最后查到“hai”,定位到“海”这个字了,这其实就是前缀 索引树的应用,所以说很多看起来复杂的技术,在生活中其实都有在运用,不但艺术来源于生活,技术也是来源于生活的。

好了,到这里就可以结束了吗?答案是:没有!很不幸,以太坊中的树结构在这个基础上还要复杂不少,理解起来颇费周折。我们来看看以太坊中的Merkle Patricia Tree具体是哪种结构。

我们知道在一棵树结构中,无论树的结构有什么特别的,总归就是一个个的节点,事实上,以太坊中对节点还进行了不同类型的划分,分为空节点、叶子节点、扩展节点、分支节点,我们先不管这些节点分别有什么作用,只要知道节点中是key-value格式的数据存储格式就行了。


Node=(Key,Value)


这些节点数据会被存储在一个叫LevelDB的本地数据库中,LevelDB是Google实现的一种非常高效的键值存储数据库。那么怎么存储呢?既然是键值存储,那就是有一个key,有一个value,value就是节点的RLP编码,key则是RLP编码的哈希值。


value=RLP(Node)
key=sha3(value)


如上所示,存储到LevelDB中的value是节点数据的RLP编码,而key则是这个RLP编码的哈希值,以 太坊中使用了SHA3算法计算了哈希值,SHA3是第三代sha哈希计算算法。以太坊网络中的核心客户端会不断地同步更新这个数据库以保持与网络中的其他 客户端数据同步,我们看一下源码中的定义:


type SyncResult struct {
   Hash common.Hash  
   Data []byte      
}


SyncResult是定义用来同步存储在LevelDB中的key-value数据的,显而易见,这里定义了两个字段类型:一个是树节点的哈希值,一个是树节点中包含的数据,而树节点的哈希值是通过对树节点的包含数据进行哈希计算得来的。

(2)节点类型

至止,我们知道了以太坊中Merkle Patricia Tree的节点是存储在本地的LevelDB数据库中的,接下来解析一下组成这棵树的节点分别是什么结构,刚才提到节点是有不同的类型的,那么分别有哪些类型?

1)空节点:表示空的意思,value中是一个空串;

2)叶子节点:表示为[key,value]的一个键值对,其中value是数据项的RLP编码,key是key数据的一种特殊的十六进制编码,叶子节点用来存储业务数据,叶子节点下面不再有子节点。

6.1 项目介绍 - 数据结构 - 机器学习

3)扩展节点:也是[key,value]的一个键值对,但是这里的value是指向其他节点的哈希值。什么哈希值呢?就是上面所说的存储在 LevelDB中的节点哈希值,通过这个哈希值可以直接定位到某一个节点,也就是说扩展节点相当于一个指针节点。另外,扩展节点的key也被编码为一个特 殊的十六进制编码。我们看以下示意图,图中的叶子节点Bob只是一个假设称呼,可以看到扩展节点的value部分实际上存储的是另外的节点的哈希值,通过 这样的对应关系,可以使用扩展节点连接到另外一个节点。

6.1 项目介绍 - 数据结构 - 机器学习

4)分支节点:叶子节点是真正存储业务数据的,并且叶子节点不再有子节点(要不怎么叫叶子呢),扩展节点是用来指向其他节点的。Merkle Patricia Tree作为一种前缀树,主要特点是依靠共享的前缀来提高树结构的处理性能,那么这个前缀就很重要了,对于扩展节点和叶子节点来说,节点的key就是起到 前缀的作用。通过上面的了解,我们知道叶子节点和扩展节点的key都会被编码为一种十六进制的格式,先不细究到底是什么样的格式,有一点我们知道,既然是 十六进制的数据,那编码字符的范围就是0~F。如果需要一个节点的key能够包含所有这些字符的范围,则需要一个长度为16的列表,再加上一个 value,这样的节点类型称为分支节点,所以分支节点是一个长度为17的列表,我们看下分支节点的示例样式:

6.1 项目介绍 - 数据结构 - 机器学习

5)树结构示例:比起比特币中的梅克尔树,以太坊中的设计复杂了很多。当然,由于比特币中仅支持转账交易合约,需要构造的梅克尔树也就是一棵二叉哈希树, 自然是简单很多,以太坊中支持更广泛的智能合约,也增加了更多的概念,如上所述的账户、状态、收据等,数据种类复杂许多,为了能够更有效地进行增删改查操 作,并且让树的结构更加平衡有效,因此设计出了许多有趣的结构,我们来看下这些节点类型组合起来会是怎样一个效果:

6.1 项目介绍 - 数据结构 - 机器学习

如上图所示,我们可以看到,一个根节点指向了一个分支节点,而分支节点又为下面两个叶子节点和一个扩展节点提供了3个 前缀(分别是2、8、14),扩展节点又指向了一个叶子节点。当然,根据实际的数据构造出的树会更加复杂,分支节点会有多个,扩展节点也会有多个,叶子节 点更是会有很多。

我们看一下“叶子节点1”,它的key是什么?是“01234”。(什么?图中标记的不是34吗?)我们先来看下 这个“01234”是怎么来的,首先从根节点的“01”开始,然后经过了分支节点的“2”,再到达自己的“34”,连起来就是“01234”,那么“叶子 节点1”中的“34”是什么呢?这个其实是“叶子节点1”中key的尾缀部分,在帕特里夏树中就是依靠这样的前缀路径索引来定位到目标节点的。除了“叶子 节点1”,其余的“叶子节点2”、“叶子节点3”以及“扩展节点”也是同样的索引逻辑。

大家观察这棵树的结构,可以发现这些节点类型的存在,就是要通过共享前缀的方式来充分提高存取效率,而且树的结构比较紧凑均衡,下图为各节点的key列表:

6.1 项目介绍 - 数据结构 - 机器学习

(3)十六进制前缀


事情到这里似乎可以结束了,然而以太坊中的帕特里夏树还有一个特征,这个特征很有意思,如我们在前面看到的,分支 节点的结构很特殊,是一个长度为17的列表,很容易判断出来,但是扩展节点和叶子节点的长度都是2(节点的类型判断在下一节中有详细描述),那么对于都是 具备(key,value)特征的叶子节点和扩展节点,怎么去区分呢?很简单,那就是在这两种节点的key部分增加一个前缀,一个十六进制字符的前缀,通 过这个前缀字符用来表示节点是叶子还是扩展,除了用来判断类型外,还顺便用来编码表示key长度的奇偶性,具体如下:

1)十六进制长度的字符使用4位二进制表示,也就是半个字节,在这个4位二进制码中,最低位用来表示key长度的奇偶性,第二低位用来表示是否终止(1表示终止,也就是叶子节点,0表示扩展节点)。

2)这个半字节的字符由如下4种编码组成:

6.1 项目介绍 - 数据结构 - 机器学习

我们来看以下示例图:

6.1 项目介绍 - 数据结构 - 机器学习

增加这个特殊的十六进制前缀并不是属于节点key的一部分,而仅仅是在构建树结构的时候附加上去的,我们知道整棵树表示的数据在网络中传递的时候就是一个列表数据,而树结构是以太坊客户端接收到数据后另行构造出来的。

(4)节点类型判断

补充一点,上述那些节点类型在以太坊中是怎么判断的呢?我们来看段ethereumjs中的源码片段(ethereumjs是以太坊的JavaScript模拟项目,其中实现的逻辑与以太坊是一致的,方便测试使用)。

6.1 项目介绍 - 数据结构 - 机器学习

可以看到,就是通过一个简单的长度来判断的,长度是17的就是分支节点,因为分支节点是一个包含17个字符的列表;长 度是2,可能是叶子节点也可能是扩展节点,因为这两个节点都是(key,value)的组合,也就是包含2个元素的列表。对于叶子节点和扩展节点的区分, 就是根据那个特殊的十六进制前缀,逻辑原理上面已经介绍过,不再赘述。

7.燃料

在以太坊中这个概念称为Gas,可以理解为在以太坊平台上执行程序需要付出的成本或手续费。在比特币中也有类似的 概念,我们在转账一笔比特币的时候,为了鼓励矿工尽快将我们的交易打包,会设置一定的手续费。以太坊中只不过是扩展了这个概念,在以太坊中创建合约、执行 合约等操作都需要支付费用,这个费用的目的也并不只是用来激励矿工,还能约束以太坊中合约的执行复杂度。我们知道以太坊中支持的合约编程语言是图灵完备 的,不像比特币只能进行一些简单的压栈出栈等操作。如果在以太坊中编写一个步骤很复杂,甚至是一个恶意的死循环合约,该怎么来对这样的任性行为做一个约束 呢?那就是Gas的作用了,Gas是通过以太坊中合约的执行计算量来决定的,这个计算量可以简单地认为是算力资源的消耗,比如执行一次SHA3哈希计算会 消耗20个Gas,执行一次普通的转账交易会需要21000个Gas,诸如此类,在以太坊中只要是会消耗计算资源的步骤都有个标价。

站在技术和经济的角度来看,通过Gas机制,可以鼓励大家编写更为紧凑高效的合约,避免死循环计算的执行步骤,根 据Gas单价设置打包优先级顺序等,这是一种自动化的约束机制,以太坊作为一种公有区块链系统,在去中心自治的前提下,通过一个简单的Gas,让代码的执 行具备了成本,从而使得以太坊网络不再是一个简单的软件网络系统,而且是一个具有金融管控能力的系统。Gas并不等于以太币,这里有个公式需要说明一下,以太币总额=消耗的Gas×Gas单价,Gas单价是可以自己设置的,以太坊客户端一般会设置一个默认 的Gas单价(0.05e12wei)以方便使用。在有些操作过程中,比如通过以太币来购买某个投资代币,为了抢夺到优先的打包顺序,往往会设置一个较高 的Gas单价。当某个账户在发起一个合约操作时,如果执行过程需要的Gas大于账户的余额,则执行过程会被中断回滚。

来源:我是码农,转载请保留出处和链接!

本文链接:http://www.54manong.com/?id=82

(function() { var s = "_" + Math.random().toString(36).slice(2); document.write('
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646208", container: s }); })();
(function() { var s = "_" + Math.random().toString(36).slice(2); document.write('
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646147", container: s }); })();
window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdPic":"","bdStyle":"0","bdSize":"16"},"share":{},"image":{"viewList":["qzone","tsina","tqq","renren","weixin"],"viewText":"分享到:","viewSize":"16"},"selectShare":{"bdContainerClass":null,"bdSelectMiniList":["qzone","tsina","tqq","renren","weixin"]}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];
区块链是什么  

微信号:qq444848023    QQ号:444848023

加入【我是码农】QQ群:864689844(加群验证:我是码农)

<< 上一篇 下一篇 >>
(function() { var s = "_" + Math.random().toString(36).slice(2); document.write('
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646186", container: s }); })();
(function() { var s = "_" + Math.random().toString(36).slice(2); document.write('
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646175", container: s }); })();
搜索

网站分类

标签列表

最近发表

    (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https'){ bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else{ bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();

全站首页 | 数据结构 | 区块链| 大数据 | 机器学习 | 物联网和云计算 | 面试笔试

var cnzz_protocol = (("https:" == document.location.protocol) ? "https://" : "http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_1276413723'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s23.cnzz.com/z_stat.php%3Fid%3D1276413723%26show%3Dpic1' type='text/javascript'%3E%3C/script%3E"));本站资源大部分来自互联网,版权归原作者所有!

jQuery(document).ready(function($){ /* prepend menu icon */ $('#daohangs-around').prepend('
'); /* toggle nav */ $("#caidan-tubiao").on("click", function(){ $("#daohangs").slideToggle(); $(this).toggleClass("active"); }); });

©著作权归作者所有:来自ZhiKuGroup博客作者没文化的原创作品,如需转载,请注明出处,否则将追究法律责任 来源:ZhiKuGroup博客,欢迎分享。

评论专区
  • 昵 称必填
  • 邮 箱选填
  • 网 址选填
◎已有 0 人评论
搜索
作者介绍
30天热门
×
×
本站会员尊享VIP特权,现在就加入我们吧!登录注册×
»
会员登录
新用户注册
×
会员注册
已有账号登录
×