当前位置:首页 » 币种行情 » ethpending

ethpending

发布时间: 2023-07-12 06:27:19

A. 以太坊源码分析--p2p节点发现

节点发现功能主要涉及 Server Table udp 这几个数据结构,它们有独自的事件响应循环,节点发现功能便是它们互相协作完成的。其中,每个以太坊客户端启动后都会在本地运行一个 Server ,并将网络拓扑中相邻的节点视为 Node ,而 Table Node 的容器, udp 则是负责维持底层的连接。下面重点描述它们中重要的字段和事件循环处理的关键部分。

PrivateKey - 本节点的私钥,用于与其他节点建立时的握手协商
Protocols - 支持的所有上层协议
StaticNodes - 预设的静态 Peer ,节点启动时会首先去向它们发起连接,建立邻居关系
newTransport - 下层传输层实现,定义握手过程中的数据加密解密方式,默认的传输层实现是用 newRLPX() 创建的 rlpx ,这不是本文的重点
ntab - 典型实现是 Table ,所有 peer Node 的形式存放在 Table
ourHandshake - 与其他节点建立连接时的握手信息,包含本地节点的版本号以及支持的上层协议
addpeer - 连接握手完成后,连接过程通过这个通道通知 Server

Server 的监听循环,启动底层监听socket,当收到连接请求时,Accept后调用 setupConn() 开始连接建立过程

Server的主要事件处理和功能实现循环

Node 唯一表示网络上的一个节点

IP - IP地址
UDP/TCP - 连接使用的UDP/TCP端口号
ID - 以太坊网络中唯一标识一个节点,本质上是一个椭圆曲线公钥(PublicKey),与 Server 的 PrivateKey 对应。一个节点的IP地址不一定是固定的,但ID是唯一的。
sha - 用于节点间的距离计算

Table 主要用来管理与本节点与其他节点的连接的建立更新删除

bucket - 所有 peer 按与本节点的距离远近放在不同的桶(bucket)中,详见之后的 节点维护
refreshReq - 更新 Table 请求通道

Table 的主要事件循环,主要负责控制 refresh revalidate 过程。
refresh.C - 定时(30s)启动Peer刷新过程的定时器
refreshReq - 接收其他线程投递到 Table 的 刷新Peer连接 的通知,当收到该通知时启动更新,详见之后的 更新邻居关系
revalidate.C - 定时重新检查以连接节点的有效性的定时器,详见之后的 探活检测

udp 负责节点间通信的底层消息控制,是 Table 运行的 Kademlia 协议的底层组件

conn - 底层监听端口的连接
addpending - udp 用来接收 pending 的channel。使用场景为:当我们向其他节点发送数据包后(packet)后可能会期待收到它的回复,pending用来记录一次这种还没有到来的回复。举个例子,当我们发送ping包时,总是期待对方回复pong包。这时就可以将构造一个pending结构,其中包含期待接收的pong包的信息以及对应的callback函数,将这个pengding投递到udp的这个channel。 udp 在收到匹配的pong后,执行预设的callback。
gotreply - udp 用来接收其他节点回复的通道,配合上面的addpending,收到回复后,遍历已有的pending链表,看是否有匹配的pending。
Table - 和 Server 中的ntab是同一个 Table

udp 的处理循环,负责控制消息的向上递交和收发控制

udp 的底层接受数据包循环,负责接收其他节点的 packet

以太坊使用 Kademlia 分布式路由存储协议来进行网络拓扑维护,了解该协议建议先阅读 易懂分布式 。更权威的资料可以查看 wiki 。总的来说该协议:

源码中由 Table 结构保存所有 bucket bucket 结构如下

节点可以在 entries replacements 互相转化,一个 entries 节点如果 Validate 失败,那么它会被原本将一个原本在 replacements 数组的节点替换。

有效性检测就是利用 ping 消息进行探活操作。 Table.loop() 启动了一个定时器(0~10s),定期随机选择一个bucket,向其 entries 中末尾的节点发送 ping 消息,如果对方回应了 pong ,则探活成功。

Table.loop() 会定期(定时器超时)或不定期(收到refreshReq)地进行更新邻居关系(发现新邻居),两者都调用 doRefresh() 方法,该方法对在网络上查找离自身和三个随机节点最近的若干个节点。

Table 的 lookup() 方法用来实现节点查找目标节点,它的实现就是 Kademlia 协议,通过节点间的接力,一步一步接近目标。

当一个节点启动后,它会首先向配置的静态节点发起连接,发起连接的过程称为 Dial ,源码中通过创建 dialTask 跟踪这个过程

dialTask表示一次向其他节点主动发起连接的任务

在 Server 启动时,会调用 newDialState() 根据预配置的 StaticNodes 初始化一批 dialTask , 并在 Server.run() 方法中,启动这些这些任务。

Dial 过程需要知道目标节点( dest )的IP地址,如果不知道的话,就要先使用 recolve() 解析出目标的IP地址,怎么解析?就是先要用借助 Kademlia 协议在网络中查找目标节点。

当得到目标节点的IP后,下一步便是建立连接,这是通过 dialTask.dial() 建立连接

连接建立的握手过程分为两个阶段,在在 SetupConn() 中实现
第一阶段为 ECDH密钥建立 :

第二阶段为协议握手,互相交换支持的上层协议

如果两次握手都通过,dialTask将向 Server 的 addpeer 通道发送 peer 的信息

B. 以太坊多节点私有链部署

假设两台电脑A和B
要求:
1、两台电脑要在一个网络中,能ping通
2、两个节点使用相同的创世区块文件
3、禁用ipc;同时使用参数--nodiscover
4、networkid要相同,端口号可以不同

1.4 搭建私有链
1.4.1 创建目录和genesis.json文件
创建私有链根目录./testnet
创建数据存储目录./testnet/data0
创建创世区块配置文件./testnet/genesis.json

1.4.2 初始化操作
cd ./eth_test
geth --datadir data0 init genesis.json

1.4.3 启动私有节点

1.4.4 创建账号
personal.newAccount()
1.4.5 查看账号
eth.accounts
1.4.6 查看账号余额
eth.getBalance(eth.accounts[0])
1.4.7 启动&停止挖矿
启动挖矿:
miner.start(1)
其中 start 的参数表示挖矿使用的线程数。第一次启动挖矿会先生成挖矿所需的 DAG 文件,这个过程有点慢,等进度达到 100% 后,就会开始挖矿,此时屏幕会被挖矿信息刷屏。
停止挖矿,在 console 中输入:
miner.stop()
挖到一个区块会奖励5个以太币,挖矿所得的奖励会进入矿工的账户,这个账户叫做 coinbase,默认情况下 coinbase 是本地账户中的第一个账户,可以通过 miner.setEtherbase() 将其他账户设置成 coinbase。

1.4.8 转账
目前,账户 0 已经挖到了 3 个块的奖励,账户 1 的余额还是0:

我们要从账户 0 向账户 1 转账,所以要先解锁账户 0,才能发起交易:

发送交易,账户 0 -> 账户 1:

需要输入密码 123456

此时如果没有挖矿,用 txpool.status 命令可以看到本地交易池中有一个待确认的交易,可以使用 eth.getBlock("pending", true).transactions 查看当前待确认交易。

使用 miner.start() 命令开始挖矿:
miner.start(1);admin.sleepBlocks(1);miner.stop();

新区块挖出后,挖矿结束,查看账户 1 的余额,已经收到了账户 0 的以太币:
web3.fromWei(eth.getBalance(eth.accounts[1]),'ether')

用同样的genesis.json初始化操作
cd ./eth_test
geth --datadir data1 init genesis.json

启动私有节点一,修改 rpcport 和port

可以通过 admin.addPeer() 方法连接到其他节点,两个节点要要指定相同的 chainID。

假设有两个节点:节点一和节点二,chainID 都是 1024,通过下面的步骤就可以从节点二连接到节点一。

首先要知道节点一的 enode 信息,在节点一的 JavaScript console 中执行下面的命令查看 enode 信息:

admin.nodeInfo.enode
" enode://@[::]:30303 "

然后在节点二的 JavaScript console 中执行 admin.addPeer(),就可以连接到节点一:

addPeer() 的参数就是节点一的 enode 信息,注意要把 enode 中的 [::] 替换成节点一的 IP 地址。连接成功后,节点一就会开始同步节点二的区块,同步完成后,任意一个节点开始挖矿,另一个节点会自动同步区块,向任意一个节点发送交易,另一个节点也会收到该笔交易。

通过 admin.peers 可以查看连接到的其他节点信息,通过 net.peerCount 可以查看已连接到的节点数量。

除了上面的方法,也可以在启动节点的时候指定 --bootnodes 选项连接到其他节点。 bootnode 是一个轻量级的引导节点,方便联盟链的搭建 下一节讲 通过 bootnode 自动找到节点

参考: https://cloud.tencent.com/developer/article/1332424

C. Miner 流程

以太坊的矿工出块的流程,不同版本有过变更,下面基于1.7.3版本和1.8.4版本来分享

channel: 用于1发1收

发送 :sampleChan<-

接收 : <-sampleChan 

Feed:用于1发多收,参考chainHeadCh

接收者注册 :Subscribe(sampleChan)

发送 :send, 发送的地方不太好找,需要通过send和event/channel类型查找,例如miner中主要涉及到的就是 PostChainEvents

接收 :<-sampleChan

数据结构:

可以理解为操作间(eth)中有了矿(tx),那么矿主(miner)安排工人(worker)挖矿(seal)。结构体定义如下:

Type Miner struct {  -- - 理解为矿主

    mux        *event.TypeMux

    worker     *worker    ---- 理解为干活的工人

    coinbase    common.Address

    eth            Backend    - --- 理解为操作间

    engine      consensus.Engine    ---- 理解为挖矿的工具

    exitCh        chan struct {}

    canStart        int32 //canstart indicates whether we can start the mining operation

    shouldStart  int32 //shouldstart indicates whether we should start after sync

}

 流程图如下:

1.  节点启动: backend.new->miner.new->worker.new: 调用commitNewWork,里面使用push把work传递给cpuAgent, 之后在geth命令行敲miner.start()后->miner.start->worker.start->cpuAgent.start,调用Seal,计算nonce值,再发送 recv 消息,通知 worker . wait ,在收到之后将块打包插入到区块链,之后调用PostChainEvents,发送消息chainHeadCh, Worker.update 在收到消息后,重新调用 commitNewWor k,形成一个循环。

 2.  创世块: 调用geth的init命令触发调用initGenesis->SetupGenesisBlock, 里面具体强调一下time是使用的genesisBlock.json中的值,一般都是0.

  3.  正常情况: worker . wait ,在收到之后将块打包插入到区块链,之后调用PostChainEvents,发送消息chainHeadCh, Worker.update 在收到消息后,重新调用 commitNewWor k,形成一个循环。

Miner .new: 在backend new的时候调用,即在节点启动的时候调用。

Miner . update :在节点启动的时候调用,用于监控是否有块同步,如果有则停止挖矿,如果没有启动挖矿,这个在POW这种竞争性出块的环境中需要。

Worker .new: 在miner.new的时候调用,记载节点启动的时候调用 

Worker.update: 节点启动的时候调用,如果是非全节点的话用于监控接受交易transaction,关键函数 commitTransactions ,还用于调度在收到 chainHeadCh 的消息后,触发 commitNewWork

其中 commitNewWork :  用于将pending的tx输入到系统,计算trie等等操作,生成block,并将work push到cpuAgent处理,注意没有盖章

Worker. wait (对应于 1.8.4 的 resultLoop ) :节点启动的时候调用,循环监听 recv 消息,将携带的block插入区块链中、发送广播消息( NewMinedBlockEvent )、发送消息 PostChainEvents (发送 ChainHeadEvent ,即 chainHeadCh ),其中的关键函数是 WriteBlockAndState 。

cpuAgent .update() :  在cpuAgent.start()->worker.start->miner.start->geth的命令行调用之后启动循环,用于接收 commitNewWork 分配下来的work,关键函数 mine ,里面调用 Seal ,主要是完成POW寻找nonce值的操作,发送 recv 消息通知worker,也可以叫做盖章。

类图如下:

具体结构不再赘述

流程:

Miner.update:用于监控是否有块同步,如果有则停止挖矿,这个在POW这种竞争性出块的环境中需要

mainLoop:收到newWorkCh消息后处理,调用commitNewWork中的commit发送taskCh消息

newWorkLoop:收到startCh消息和chainHeadCh消息后发送newWorkCh消息

resultLoop:循环监听resultCh(seal发送)消息,将携带的block插入区块链中,并发送广播消息,关键函数WriteBlockAndState,并发送chainHeadCh消息

taskLoop:以前agent做的事情,收到taskCh消息后,调用seal,里面发送resultCh消息

D. 以太坊ETH覆盖或删除处于pending状态交易

有人肯定遇到跟我一样的问题,账号里还有一些eth,但是有一笔交易一直处于pending状态,导致后续的交易全部卡死。除非这一笔pending状态的交易被矿工打包。请注意nonce,由于每一个账号的每一个交易nonce都是递增的,因此如果用已经成功的交易的nonce重新交易,一定会报错nonce too low。

1、发现有一笔订单一直处于pending状态,后续的所有交易都不能正常进行

2、解决方案,通过设置较高的gasprice来覆盖或替换该交易

3、接下来,该账号就可以正常转账啦。

目前市场上尚未找到能满足该功能的工具/钱包,如需提供技术服务,请联系作者,微信号:hqfeijian ,备注:以太坊替换交易

E. 以太坊GasLimit的计算方法

以太坊黄皮书上说的gasLimit的计算方法:

gasLimit = Gtransaction + Gtxdatanonzero × dataByteLength

需要注意的是这只是静态的gas消耗,实际gas消耗还需要加上合约执行的开销。

计算 IntrinsicGas的源码位置 core/state_transition.go

相关源码位置:internal/ethapi/api.go

EstimateGas 采用二分查找法获取要评估交易的gas值。二分查找的下限是 param.TxGas , 如果 args 参数指定 Gas 大于 param.Gas ,那么二分查找的上限就是 args.Gas ,否则以当前pending块的block gas limit(后面简称BGL)作为二分查找的上限。 doCall 函数模拟智能合约的执行,经过多次尝试找到智能合约能够成功运行的最佳gas值。

由于二分查找的上限和BGL有关,而BGL和不是固定不变的,因此每次gas评估的结果不一定都是相同的,可能每个区块周期就会变动一次。

在实际进行gas评估的时候,可能会出现类似下面的错误

该错误出现的最可能是合约执行中出错。

How do you calculate gas limit for transaction with data in Ethereum?

F. ETH开发实践——批量发送交易

在使用同一个地址连续发送交易时,每笔交易往往不可能立即到账, 当前交易还未到账的情况下,下一笔交易无论是通过 eth.getTransactionCount() 获取nonce值来设置,还是由节点自动从区块中查询,都会获得和前一笔交易同样的nonce值,这时节点就会报错 Error: replacement transaction underpriced

在构建一笔新的交易时,在交易数据结构中会产生一个nonce值, nonce是当前区块链下,发送者(from地址)发出的交易(成功记录进区块的)总数, 再加上1。例如新构建一笔从A发往B的交易,A地址之前的交易次数为10,那么这笔交易中的nonce则会设置成11, 节点验证通过后则会放入交易池(txPool),并向其他节点广播,该笔交易等待矿工将其打包进新的区块。

那么,如果在先构建并发送了一笔从地址A发出的,nonce为11的交易,在该交易未打包进区块之前, 再次构建一笔从A发出的交易,并将它发送到节点,不管是先通过web3的eth.getTransactionCount(A)获取到的过往的交易数量,还是由节点自行填写nonce, 后面的这笔交易的nonce同样是11, 此时就出现了问题:

实际场景中,会有批量从一个地址发送交易的需求,首先这些操作可能也应该是并行的,我们不会等待一笔交易成功写入区块后再发起第二笔交易,那么此时有什么好的解决办法呢?先来看看geth节点中交易池对交易的处理流程

如之前所说,构建一笔交易时如果不手动设置nonce值,geth节点会默认计算发起地址此前最大nonce数(写入区块的才算数),然后将其加上1, 然后将这笔交易放入节点交易池中的pending队列,等到节点将其打包进区块。

构建交易时,nonce值是可以手动设置的,如果当前的nonce本应该设置成11, 但是我手动设置成了13, 在节点收到这笔交易时, 发现pending队列中并没有改地址下nonce为11及12的交易, 就会将这笔nonce为13的交易放入交易池的queued队列中。只有当前面的nonce补齐(nonce为11及12的交易被发现并放入pending队列)之后,才会将它放入pending队列中等待打包。

我们把pending队列中的交易视为可执行的,因为它们可能被矿工打包进最新的区块。 而queue队列因为前面的nonce存在缺失,暂时无法被矿工打包,称为不可执行交易。

那么实际开发中,批量从一个地址发送交易时,应该怎么办呢?

方案一:那么在批量从一个地址发送交易时, 可以持久化一个本地的nonce,构建交易时用本地的nonce去累加,逐一填充到后面的交易。(要注意本地的nonce可能会出现偏差,可能需要定期从区块中重新获取nonce,更新至本地)。这个方法也有一定的局限性,适合内部地址(即只有这个服务会使用该地址发送交易)。

说到这里还有个坑,许多人认为通过 eth.getTransactionCount(address, "pending") ,第二个参数为 pending , 就能获得包含本地交易池pending队列的nonce值,但是实际情况并不是这样, 这里的 pending 只包含待放入打包区块的交易, 假设已写入交易区块的数量为20, 又发送了nonce为21,22,23的交易, 通过上面方法取得nonce可能是21(前面的21,22,23均未放入待打包区块), 也可能是22(前面的21放入待打包区块了,但是22,23还未放入)。

方案二是每次构建交易时,从geth节点的pending队列取到最后一笔可执行交易的nonce, 在此基础上加1,再发送给节点。可以通过 txpool.content 或 txpool.inspect 来获得交易池列表,里面可以看到pending及queue的交易列表。

启动节点时,是可以设置交易池中的每个地址的pending队列的容量上限,queue队列的上容量上限, 以及整个交易池的pending队列和queue队列的容量上限。所以高并发的批量交易中,需要增加节点的交易池容量。

当然,除了扩大交易池,控制发送频率,更要设置合理的交易手续费,eth上交易写入区块的速度取决于手续费及eth网络的拥堵状况,发送每笔交易时,设置合理的矿工费用,避免大量的交易积压在交易池。

G. 跨行转账整整8天了仍未到帐,也没退款

我们在转账之后,有时会出现转账迟迟未到账的情况,很多用户十分着急,甚至认为自己的币丢失了。Tokenview收到了一封来自昵称为港湾用户的求助邮件。邮件中说,该用户在进行USDT转账时发生了USDT丢失的情况。用户提供了提币地址,交易ID,接收地址以及转账金额和转账时间,问是否可以找回。

首先,我们需要先确定没到账的原因。一般来说,转账没到账的原因有四个:
1、地址填错
2、网络拥堵,暂未到账
3、确认数未达标,暂未入账
4、手续费不足,交易被退回
我们一个个来分析。如果是第一种情况,地址填错。地址填错大约分二种情况,第一种情况是地址种类填错,或者格式错误。这种情况下,转账可能无法顺利进行,相应的钱包软件会进行提示,如果交易不能发起,也就不存在丢币的情况。但在种类填错的情况下也不是不可能发起交易的。举例来说,如果我们把USDT—OMNI提现到了USDT-ERC20,就会丢币,这样丢失的币是无法找回的。第二种情况就是地址张冠李戴,是对应的链上地址,但是错填成他人地址。这种情况交易将会顺利发起,而此时交易上链后,基于区块链不可逆的特性,任何人都无法对该笔交易进行撤回操作,除非接收方原因将币转回原地址。
如何判断接收地址是否填写错误呢?我们复制交易ID,或者直接复制自己的转出地址,通过Tokenview区块浏览器进行查询。我们通过查询该用户提供的交易ID,可以发现,该用户进行了火币的一笔提现操作,其转入地址与用户提供的转入地址不符,也就是说,出于某种原因,用户将USDT转去了错误的地址。

这种情况下,交易将是无法撤回的,除非改接收地址的持有人愿意将这笔“天降之财”原路退回。但由于区块链的匿名性,除了Tokenview标记出的交易所出入金地址及某些大户地址外,其余BTC、USDT地址我们是无法通过地址哈希定位其所有人的,因此可以说,在这种情况下,找回币的几率微乎其微。
第二种情况是网络拥堵。这种情况我们能做的就是等待交易打包上链。我们可以在tokenview.com的Pending交易池中看看交易是否存在。如:https://btc.tokenview.com/cn/pending。
第三种情况一般存在于交易平台充币。当交易上链时,确认数为1,但由于不同交易所对确认数的要求不同,例如大部分对比特币的确认数要求要达到6才会被确认充值成功,而以太坊则是12个。我们可以通过tokenview.com来查询交易数。如果交易数还没有达到要求,我们还需要再耐心等一下。
最后一种情况是手续费不足,交易被退回。这种情况交易会失败。拿以太坊的转账为例,如果手续费不足,此交易将扣取手续费,并将ETH退回到转出地址,并不存在丢币的情况。
转账未到账的几种情况我们已经介绍完毕了。其中最关键的是大家在转账之前一定要再三确认交易地址是否填写无误。如果是进行USDT的转账,一定要确认其USDT类型。是OMNI,还是ERC20,还是TRC20,避免发生填错类型而丢币的意外,从而造成损失。

热点内容
ethc2c平台 发布:2025-06-23 21:10:40 浏览:538
现在还有币圈吗 发布:2025-06-23 20:57:01 浏览:551
币圈第一新人 发布:2025-06-23 20:38:48 浏览:865
锎数字货币诈骗 发布:2025-06-23 20:38:45 浏览:321
攀爬车trx4功能 发布:2025-06-23 20:27:32 浏览:420
币响比特大亨怎么赚钱 发布:2025-06-23 20:25:00 浏览:350
进军区块链百科 发布:2025-06-23 19:41:42 浏览:829
区块链去中心化举个例子 发布:2025-06-23 19:35:44 浏览:311
币跟比特币有固定的数量吗 发布:2025-06-23 19:32:19 浏览:703
区块链需要哪些构架 发布:2025-06-23 19:13:37 浏览:668