死磕以太坊源码分析之Fetcher同步

死磕以太坊源码分析之Fetcher同步

Fetcher 功能概述

区块数据同步分为被动同步和主动同步:

  • 被动同步是指本地节点收到其他节点的一些广播的消息,然后请求区块信息。

  • 主动同步是指节点主动向其他节点请求区块数据,比如geth刚启动时的syning,以及运行时定时和相邻节点同步

Fetcher负责被动同步,主要做以下事情:

  • 收到完整的block广播消息(NewBlockMsg)
  • 收到blockhash广播消息(NewBlockHashesMsg)

这两个消息又是分别由 peer.AsyncSendNewBlockHashpeer.AsyncSendNewBlock 两个方法发出的,这两个方法只有在矿工挖到新的区块时才会被调用:

// 订阅本地挖到新的区块的消息
func (pm *ProtocolManager) minedBroadcastLoop() {
    for obj := range pm.minedBlockSub.Chan() {
        if ev,ok := obj.Data.(core.NewMinedBlockEvent); ok {
            pm.BroadcastBlock(ev.Block,true)  // First propagate block to peers
            pm.BroadcastBlock(ev.Block,false) // Only then announce to the rest
        }
    }
}
func (pm *ProtocolManager) BroadcastBlock(block *types.Block,propagate bool) {
    ......
    if propagate {
        ......
        for _,peer := range transfer {
            peer.AsyncSendNewBlock(block,td) //发送区块数据
        }
    }
    if pm.blockchain.HasBlock(hash,block.NumberU64()) {
        for _,peer := range peers {
            peer.AsyncSendNewBlockHash(block) //发送区块哈希
        }
    }
}

所以,当某个矿工产生了新的区块、并将这个新区块广播给其它节点,而其它远程节点收到广播的消息时,才会用到 fetcher 模块去同步这些区块。

fetcher的状态字段

Fetcher 内部对区块进行同步时,会被分成如下几个阶段,并且每个阶段都有一个状态字段与之对应,用来记录这个阶段的数据:

  • Fetcher.announced:此阶段代表节点宣称产生了新的区块(这个新产生的区块不一定是自己产生的,也可能是同步了其它节点新产生的区块),Fetcher 对象将相关信息放到 Fetcher.announced 中,等待下载。
  • Fetcher.fetching:此阶段代表之前「announced」的区块正在被下载。
  • Fetcher.fetched:代表区块的 header 已下载成功,现在等待下载 body
  • Fetcher.completing:代表 body 已经发起了下载,正在等待 body 下载成功。
  • Fetcher.queued:代表 body 已经下载成功。因此一个区块的数据:header 和 body 都已下载完成,此区块正在等待写入本地数据库。

Fetcher 同步区块哈希

而新产生区块时,会使用消息 NewBlockHashesMsgNewBlockMsg 对其进行传播。因此 Fetcher 对象也是从这两个消息处发现新的区块信息的。先来看同步区块哈希的过程。

case msg.Code == NewBlockHashesMsg:
		var announces newBlockHashesData
		if err := msg.Decode(&announces); err != nil {
			return errResp(ErrDecode,"%v: %v",msg,err)
		}
		// Mark the hashes as present at the remote node
		// 将hash 标记存在于远程节点上
		for _,block := range announces {
			p.MarkBlock(block.Hash)
		}
		// Schedule all the unknown hashes for retrieval 检索所有未知哈希
		unknown := make(newBlockHashesData,len(announces))
		for _,block := range announces {
			if !pm.blockchain.HasBlock(block.Hash,block.Number) {
				unknown = append(unknown,block) // 本地不存在的话就扔到unkonwn里面
			}
		}
		for _,block := range unknown {
			pm.fetcher.Notify(p.id,block.Hash,block.Number,time.Now(),p.RequestOneHeader,p.RequestBodies)
		}

先将接收的哈希标记在远程节点上,然后去本地检索是否有这个哈希,如果本地数据库不存在的话,就放到unknown里面,然后通知本地的fetcher模块再去远程节点上请求此区块的headerbody。 接下来进入到fetcher.Notify方法中。

func (f *Fetcher) Notify(peer string,hash common.Hash,number uint64,time time.Time,headerFetcher headerRequesterFn,bodyFetcher bodyRequesterFn) error {
	block := &announce{
		hash:        hash,number:      number,time:        time,origin:      peer,fetchHeader: headerFetcher,fetchBodies: bodyFetcher,}
	select {
	case f.notify <- block:
		return nil
	case <-f.quit:
		return errTerminated
	}

它构造了一个 announce 结构,并将其发送给了 Fetcher.notify 这个 channel。注意 announce 这个结构里带着下载 header 和 body 的方法: fetchHeaderfetchBodies 。这两个方法在下面的过程中会讲到。 接下来我们进入到fetcher.go的loop函数中,找到notify,分以下几个内容:

①:校验防止Dos攻击(限制为256个)

count := f.announces[notification.origin] + 1
			if count > hashLimit {
				log.Debug("Peer exceeded outstanding announces","peer",notification.origin,"limit",hashLimit)
				propAnnounceDOSMeter.Mark(1)
				break
			}

②:新来的块号必须满足 $chainHeight - blockno < 7$ 或者 $blockno - chainHeight < 32$

if notification.number > 0 {
				if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
			...			}
			}

③:准备下载headerfetching中存在此哈希则跳过

if _,ok := f.fetching[notification.hash]; ok { 
  break
			}

④:准备下载bodycompleting中存在此哈希也跳过

if _,ok := f.completing[notification.hash]; ok {
				break
			}

⑤:当确定fetchingcompleting不存在此区块哈希时,则把此区块哈希放入到announced中,准备拉取headerbody

f.announced[notification.hash] = append(f.announced[notification.hash],notification)

⑥:如果 Fetcher.announced 中只有刚才新加入的这一个区块哈希,那么调用 Fetcher.rescheduleFetch 重新设置变量 fetchTimer 的周期

if len(f.announced) == 1 {
				f.rescheduleFetch(fetchTimer)
			}

拉取header

接下来就是到fetchTimer.C函数中:进行拉取header的操作了,具体步骤如下:

①:选择要下载的区块,从 announced 转移到 fetching

for hash,announces := range f.announced {
				if time.Since(announces[0].time) > arriveTimeout-gatherSlack {
				// 随机挑一个进行fetching
					announce := announces[rand.Intn(len(announces))]
					f.forgetHash(hash)

					// If the block still didn't arrive,queue for fetching
					if f.getBlock(hash) == nil {
						request[announce.origin] = append(request[announce.origin],hash)
						f.fetching[hash] = announce //
					}
				}
			}

②:发送下载 header 的请求

//发送所有的header请求
			for peer,hashes := range request {
				log.Trace("Fetching scheduled headers",peer,"list",hashes)
				fetchHeader,hashes := f.fetching[hashes[0]].fetchHeader,hashes
				go func() {
					if f.fetchingHook != nil {
						f.fetchingHook(hashes)
					}
					for _,hash := range hashes {
						headerFetchMeter.Mark(1)
						fetchHeader(hash) 
					}
				}()
			}

现在我们再回到f.notify函数中,找到p.RequestOneHeader,发送GetBlockHeadersMsg给远程节点,然后远程节点再通过case msg.Code == GetBlockHeadersMsg进行处理,本地区块链会返回headers,然后再发送回去。

origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash)
...
p.SendBlockHeaders(headers)

这时候我们请求的headers被远程节点给发送回来了,又是通过新的消息BlockHeadersMsg来传递的,当请求的 header 到来时,会通过两种方式来过滤header :

  1. Fetcher.FilterHeaders 通知 Fetcher 对象
case msg.Code == BlockHeadersMsg:
....
filter := len(headers) == 1
if filter {
  headers = pm.fetcher.FilterHeaders(p.id,headers,time.Now())
}

2.downloader.DeliverHeaders 通知downloader对象

if len(headers) > 0 || !filter {
			err := pm.downloader.DeliverHeaders(p.id,headers)
		...
		}

downloader相关的放在接下的文章探讨。继续看FilterHeaders:

filter := make(chan *headerFilterTask)
	select {
	case f.headerFilter <- filter: ①
....
	select {
	case filter <- &headerFilterTask{peer: peer,headers: headers,time: time}: ②
...
	select {
	case task := <-filter: ③
		return task.headers
...
	}

主要分为3个步骤:

  1. 先发一个通信用的 channelheaderFilter
  2. 将要过滤的 headerFilterTask 发送给 filter
  3. 检索过滤后剩余的标题

主要的处理步骤还是在loop函数中的filter := <-f.headerFilter,在探讨处理前,先了解三个参数的含义:

  • unknown:未知的header
  • incomplete:header拉取完成,但是body还没有拉取
  • complete:headerbody都拉取完成,一个完整的块,可导入到数据库

接下来正式进入到for _,header := range task.headers {}循环中: 这是第一段重要的循环

①:判断是否是在fetching中的header,并且不是其他同步算法的header

if announce := f.fetching[hash]; announce != nil && announce.origin == task.peer && f.fetched[hash] == nil && f.completing[hash] == nil && f.queued[hash] == nil {
  .....
}

②:如果传递的header与承诺的number不匹配,删除peer

if header.Number.Uint64() != announce.number {
  f.dropPeer(announce.origin)
		f.forgetHash(hash)
}

③:判断此区块在本地是否已存在,如果不存在且只有header(空块),直接放入complete以及f.completing中,否则就放入到incomplete中等待同步body

if f.getBlock(hash) == nil {
						announce.header = header
						announce.time = task.time

						if header.TxHash == types.DeriveSha(types.Transactions{}) && header.UncleHash == types.CalcUncleHash([]*types.Header{}) {
				...
							block := types.NewBlockWithHeader(header)
							block.ReceivedAt = task.time

							complete = append(complete,block)
							f.completing[hash] = announce
							continue
            }
						incomplete = append(incomplete,announce) // 否则添加到需要完成拉取body的列表中

④:如果f.fetching中不存在此哈希,就放入到unkown

else {
					// Fetcher doesn't know about it,add to the return list |fetcher 不认识的放到unkown中
					unknown = append(unknown,header)
				}

⑤:之后再把Unknownheader再通知fetcher继续过滤

select {
			case filter <- &headerFilterTask{headers: unknown,time: task.time}:
			case <-f.quit:
				return
		}

接着就是进入到第二个循环,要准备拿出incomplete里的哈希,进行同步body的同步

for _,announce := range incomplete {
				hash := announce.header.Hash()
				if _,ok := f.completing[hash]; ok {
					continue
				}
				f.fetched[hash] = append(f.fetched[hash],announce)
				if len(f.fetched) == 1 {
					f.rescheduleComplete(completeTimer)
				}
			}

如果f.completing中存在,就表明已经在开始同步body了,直接跳过,否则把这个哈希放入到f.fetched,表示header同步完毕,准备body同步,由f.rescheduleComplete(completeTimer)完成。最后是安排只有header的区块进行导入操作.

for _,block := range complete {
				if announce := f.completing[block.Hash()]; announce != nil {
					f.enqueue(announce.origin,block)
				}
			}

重点分析completeTimer.C,同步body的操作,这步完成就是要准备区块导入到数据库流程了。

拉取body

进入completeTimer.C,从f.fetched获取哈希,如果本地区块链查不到的话就把这个哈希放入到f.completing中,再循环进行fetchBodies,整个流程就结束了,代码大致如下:

case <-completeTimer.C:
...
			for hash,announces := range f.fetched {
		....
				if f.getBlock(hash) == nil {
					request[announce.origin] = append(request[announce.origin],hash)
					f.completing[hash] = announce
				}
			}
			for peer,hashes := range request {
        ...
				go f.completing[hashes[0]].fetchBodies(hashes)
			}
...

关键的拉取body函数: p.RequestBodies,发送GetBlockBodiesMsg消息同步body。回到handler里面去查看对应的消息:

case msg.Code == GetBlockBodiesMsg:
		// Decode the retrieval message
		msgStream := rlp.NewStream(msg.Payload,uint64(msg.Size))
		if _,err := msgStream.List(); err != nil {
			return err
		}
		var (
			hash   common.Hash
			bytes  int
			bodies []rlp.RawValue
		)
		for bytes < softResponseLimit && len(bodies) < downloader.MaxBlockFetch {
			...
			if data := pm.blockchain.GetBodyRLP(hash); len(data) != 0 {
				bodies = append(bodies,data)
				bytes += len(data)
			}
		}
		return p.SendBlockBodiesRLP(bodies)

softResponseLimit返回的body大小最大为$2 * 1024 * 1024$,MaxBlockFetch表示每个请求最多128个body

之后直接通过GetBodyRLP返回数据通过SendBlockBodiesRLP发回给节点。

节点将会接收到新消息:BlockBodiesMsg,进入查看:

// 过滤掉filter请求的body 同步,其他的都交给downloader
		filter := len(transactions) > 0 || len(uncles) > 0
		if filter {
			transactions,uncles = pm.fetcher.FilterBodies(p.id,transactions,uncles,time.Now())
		}

		if len(transactions) > 0 || len(uncles) > 0 || !filter {
			err := pm.downloader.DeliverBodies(p.id,uncles)
...
		}

过滤掉filter请求的body 同步,其他的都交给downloaderdownloader部分之后的篇章讲。进入到FilterBodies

	filter := make(chan *bodyFilterTask)
select {
	case f.bodyFilter <- filter:  ①
	case <-f.quit:
		return nil,nil
	}
	// Request the filtering of the body list
	// 请求过滤body 列表
	select { ②
	case filter <- &bodyFilterTask{peer: peer,transactions: transactions,uncles: uncles,time: time}:
	case <-f.quit:
		return nil,nil
	}
	// Retrieve the bodies remaining after filtering
	select { ③:
	case task := <-filter:
		return task.transactions,task.uncles

主要分为3个步骤:

  1. 先发一个通信用的 channelbodyFilter
  2. 将要过滤的 bodyFilterTask 发送给 filter
  3. 检索过滤后剩余的body

现在进入到case filter := <-f.bodyFilter里面,大致做了以下几件事:

①:首先从f.completing中获取要同步body的哈希

for i := 0; i < len(task.transactions) && i < len(task.uncles); i++ {
  for hash,announce := range f.completing {
    ...
  }
}

②:然后从f.queued去查这个哈希是不是已经获取了body,如果没有并满足条件就创建一个完整block

if f.queued[hash] == nil {
						txnHash := types.DeriveSha(types.Transactions(task.transactions[i]))
						uncleHash := types.CalcUncleHash(task.uncles[i])
  if txnHash == announce.header.TxHash && uncleHash == announce.header.UncleHash && announce.origin == task.peer {
							matched = true

							if f.getBlock(hash) == nil {
								block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i],task.uncles[i])
								block.ReceivedAt = task.time

                blocks = append(blocks,block)
              }
  }

③:最后对完整的块进行导入

for _,block := range blocks {
				if announce := f.completing[block.Hash()]; announce != nil {
					f.enqueue(announce.origin,block)
				}
			}

最后用一张粗略的图来大概的描述一下整个同步区块哈希的流程:

image-20201203090304059

同步区块哈希的最终会走到f.enqueue里面,这个也是同步区块最重要的要做的一件事,下文就会讲到。

Fetcher 同步区块

分析完上面比较复杂的同步区块哈希过程,接下来就要分析比较简单的同步区块过程。从NewBlockMsg开始:

主要做两件事:

①:fetcher模块导入远程节点发过来的区块

pm.fetcher.Enqueue(p.id,request.Block)

②:主动同步远程节点

if _,td := p.Head(); trueTD.Cmp(td) > 0 {
			p.SetHead(trueHead,trueTD)
			currentBlock := pm.blockchain.CurrentBlock()
			if trueTD.Cmp(pm.blockchain.GetTd(currentBlock.Hash(),currentBlock.NumberU64())) > 0 {
				go pm.synchronise(p)
			}
		}

主动同步由Downloader去处理,我们这篇只讨论fetcher相关。

区块入队列

pm.fetcher.Enqueue(p.id,request.Block)
case op := <-f.inject:
			propBroadcastInMeter.Mark(1)
			f.enqueue(op.origin,op.block)

正式进入将区块送进queue中,主要做了以下几件事:

①: 确保新加peer没有导致DOS攻击

count := f.queues[peer] + 1
	if count > blockLimit {
		log.Debug("Discarded propagated block,exceeded allowance","number",block.Number(),"hash",hash,blockLimit)
		propBroadcastDOSMeter.Mark(1)
		f.forgetHash(hash)
		return
	}

②:丢弃掉过去的和比较老的区块

if dist := int64(block.NumberU64()) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
  f.forgetHash(hash)
}

③:安排区块导入

	if _,ok := f.queued[hash]; !ok {
		op := &inject{
			origin: peer,block:  block,}
		f.queues[peer] = count
		f.queued[hash] = op
		f.queue.Push(op,-int64(block.NumberU64()))
		if f.queueChangeHook != nil {
			f.queueChangeHook(op.block.Hash(),true)
		}
		log.Debug("Queued propagated block","queued",f.queue.Size())
	}

到此为止,已经将区块送入到queue中,接下来就是要回到loop函数中去处理queue中的区块。

区块入库

loop函数在处理队列中的区块主要做了以下事情:

  1. 判断队列是否为空
  2. 取出区块哈希,并且和本地链进行比较,如果太高的话,就暂时不导入
  3. 最后通过f.insert将区块插入到数据库。

代码如下:

height := f.chainHeight()
		for !f.queue.Empty() {
			op := f.queue.PopItem().(*inject)
			hash := op.block.Hash()
		...
			number := op.block.NumberU64()
			if number > height+1 {
				f.queue.Push(op,-int64(number))
	...
				break
			}
			if number+maxUncleDist < height || f.getBlock(hash) != nil {
				f.forgetBlock(hash)
				continue
			}
			f.insert(op.origin,op.block) //导入块
		}

进入到f.insert中,主要做了以下几件事:

①:判断区块的父块是否存在,不存在则中断插入

		parent := f.getBlock(block.ParentHash())
		if parent == nil {
			log.Debug("Unknown parent of propagated block","parent",block.ParentHash())
			return
		}

②: 快速验证header,并在传递时广播该块

switch err := f.verifyHeader(block.Header()); err {
		case nil:
			propBroadcastOutTimer.UpdateSince(block.ReceivedAt)
			go f.broadcastBlock(block,true)

③:运行真正的插入逻辑

if _,err := f.insertChain(types.Blocks{block}); err != nil {
			log.Debug("Propagated block import failed","err",err)
			return
		}

④:导入成功广播此块

go f.broadcastBlock(block,false)

真正做区块入库的是f.insertChain,这里会调用blockchain模块去操作,具体细节会后续文章讲述,到此为止Fether模块的同步就到此结束了,下面是同步区块的流程图:

image-20201203090327173

参考

https://mindcarver.cn

https://github.com/blockchainGuide

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读903次。文章主要介绍了收益聚合器Beefy协议在币安智能链测试网网上的编译测试部署流程,以Pancake上的USDC-BUSD最新Curve版流动池的农场质押为例,详细介绍了完整的操作流程。_怎么在bsc网络上部署应用
文章浏览阅读952次。比特币的主要思路是,构建一个无中心、去信任的分布式记账系统。交易签名只能保证交易不是他人伪造的,却不能阻止交易的发起者自己进行多重交易,即交易的发起者将一个比特币同时转账给两个人,也就是所谓的双花。比特币应用的区块链场景也叫做公链,因为这个区块链对所有人都是公开的。除此之外,还有一种区块链应用场景,被称作联盟链。区块链的出现,使得低成本,去信任的跨组织合作成为可能,将重构组织间的关系,这个关系既包括企业间的关系,也包括政府和企业间的关系,还有政府部门间的关系。
文章浏览阅读2.5k次。虚拟人从最初的不温不火,到现在步入“出生高峰期”,元宇宙可以说是功不可没。此前,量子位发布了《虚拟数字人深度产业报告》,报告显示,到2030年我国虚拟数字人整体市场规模将达到2700亿元。其中,“身份型虚拟人”市场规模预计达到1750亿元,占主导地位,而“服务型虚拟人”总规模也将超过950亿元。得益于AI、VR/AR 等技术的发展,虚拟人的应用场景正在从传统的虚拟偶像等娱乐行业迈向更多元化的领域。_最喜欢的虚拟角色
文章浏览阅读1.3k次,点赞25次,收藏13次。通过调查和分析用户需求、兴趣和行为,你可以更好地定位你的目标受众,并在市场中找到你的定位。在设计你的Web3.0项目时,注重用户界面的友好性、交互流畅性和功能的创新性,以提供独特的用户体验。通过与有影响力的人或组织进行合作,推广你的Web3.0项目。通过与他们分享你的项目并抓住他们的推荐,可以迅速获得更多的关注度。通过优化你的网站和内容,将有助于提高你的排名,并增加有机流量。通过提供奖励激励计划,如空投、奖励机制等,激励用户参与你的Web3.0项目。的人或组织合作,可以增加你的项目的曝光度。
文章浏览阅读1.7k次。这个智能合约安全系列提供了一个广泛的列表,列出了在 Solidity 智能合约中容易反复出现的问题和漏洞。Solidity 中的安全问题可以归结为智能合约的行为方式不符合它们的意图。我们不可能对所有可能出错的事情做一个全面的列表。然而,正如传统的软件工程有常见的漏洞主题,如 SQL 注入、缓冲区超限和跨网站脚本,智能合约中也有反复出现的。_solidity安全漏洞
文章浏览阅读1.3k次。本文描述了比特币核心的编译与交互方法_编译比特币
文章浏览阅读884次。四水归堂,是中国建筑艺术中的一种独特形式。这种形式下,由四面房屋围出一个天井,房屋内侧坡向天井内倾斜,下雨时雨水会从东西南北四方流入天井,从而起到收集水源,防涝护屋的作用,寓意水聚天心,天人合一。在科技产业当中,很多时候我们需要学习古人的智慧与意蕴,尝试打通各个生态,聚四方之力为我所用,这样才能为最终用户带来最大化价值。随着数字化、智能化的发展,算力成为生产力的根基。在这一大背景下,算力需要贯通软..._超聚变csdn
文章浏览阅读1k次,点赞24次,收藏19次。云计算和区块链是当代科技领域两个备受关注的核心技术。本文将深入探讨云计算和区块链的发展历程,详细剖析其起初阶段的奠基、面临的问题、业务内容、当前研究方向、用到的技术、实际应用场景、未来发展趋势,并提供相关链接供读者深入了解。
文章浏览阅读1.5k次。融入对等网络的奥妙,了解集中式、全分布式和混合式对等网络的差异,以及区块链网络的结构与协议,让你跃入区块链的连结网络。揭开密码学的神秘面纱,探寻对称密码学、非对称密码学、哈希函数、数字签名等关键技术,让你了解信息安全的核心。解码共识算法的精髓,从理论到实践,从PoW、PoS到PBFT,让你深入了解区块链如何达成共识。探索智能合约的世界,从定义到生命周期,从执行引擎到开发与部署,带你进入无限可能的合约领域。了解令人惊叹的区块链世界,从概念到价值,从发展历程到政策法规,一篇章串联出区块链的精髓。
文章浏览阅读777次。8 月份,加密货币市场经历了明显的波动,比特币价格波动幅度较大。与此同时,NFT 市场出现大幅下跌,引发了人们对这一新兴行业未来发展趋势的担忧
文章浏览阅读8.8k次,点赞53次,收藏37次。近二十年来,我国信息科技发展日益成熟,出现的网络完全问题也是“百花齐放”。而元宇宙作为5G技术、AR/VR技术、云计算以及区块链等技术的组合体,其安全性指定会被人们所广泛关注。根据前面所讲,元宇宙融合了虚拟世界和现实世界,通过数据将现实世界的各种元素映射到数字化的虚拟世界中。所以没有数据,就等于没有元宇宙的一切;没有信息安全,元宇宙的社会生产、生活就不能正常有序地进行。所以足以可见数据安全、信息安全对元宇宙发展起到的重要作用!!_元宇宙 安全计算
文章浏览阅读1.4k次。最早使用历史 1991年采用 时间戳 追溯 数字文档,之后 2009年后创始人**中本聪** (satoshi nakamoto )日裔美国人,在设计比特币数字货币中将此理念写入应用程序中_web3.0学习
文章浏览阅读1.7k次。DeFi收益来源全面概述_drfi收益
文章浏览阅读941次,点赞17次,收藏21次。号外:教链内参1.28《从BTC现货ETF的近期数据看到的》隔夜BTC经历现货ETF通过后的情绪冷静,一度破位40k后又逐渐修复至42k上方。请珍惜42k的BTC吧。也许到下个周期,我们将不再有机会见到这个高度的BTC了。下面,让我们重温,42k的BTC,在过去四年穿越牛熊的过程中,带给我们的启迪吧。需要提醒的是,历史文字,自有历史局限性,回顾,也须带着批判性的目光阅读和审视。2021年2月8日,...
文章浏览阅读1.2k次,点赞23次,收藏21次。其实一开始我也是这么想的,但根据PoW算法机制,如果你的计算量不够大,是无法控制区块链的走向的,也就是说,即使你投入了大量的成本用于完成任务,也不能保证自己成功。例如,你持有100个币,总共持有了30天,那么,此时你的币龄就为3000,这个时候,如果你发现了一个PoS区块,那么你的币龄就会被减去一定的值,每减少365个币龄,将会从区块中获得0.05个币的利息(可理解为年利率5%),那么在这个案例中,利息=3000×5%/365=0.41个币。前面说过,谁的算力强,谁最先解决问题的概率就越大。
文章浏览阅读1.9k次。这里主要实现的部分继续下去,对 Blockchain 这个对象有一些修改,如果使用 TS 的话可能要修改对应的 interface,但是如果是 JS 的话就无所谓了。需要安装的依赖有:express现在的 express 已经不内置 body-parser,需要作为单独的依赖下载request不下载会报错,是使用 request-promise 所需要的依赖和已经 deprecated 了,具体 reference 可以参考。_js区块链
文章浏览阅读1k次,点赞19次,收藏19次。作者:Zach Pandl Grayscale编译:象牙山首席村民 碳链价值以太坊在2023年取得了丰厚的回报。但表现不如比特币以及其他一些智能合约公链代币。我们认为,这反映了今年比特币特有的积极因素以及以太坊链上活动的缓慢复苏。尽管以太坊的涨幅低于比特币,但从绝对值和风险调整值来看,今年以太坊的表现优于传统资产类别。以太坊不断增长的L2生态系统的发展可能会吸引新用户,并在2024年支撑以太币的...
文章浏览阅读908次,点赞20次,收藏20次。通证是以数字形式存在,代表的是一种权利、一种固有和内在的价值。徐教授告诉我:多年的职业经历,多年的为易货贸易的思考,认识到在处理贸易和经济领域的关系时,应以提高人民生活水平、保证社会成员充分就业、保证就业成员实际收入和有效需求的大幅稳定增长、实现世界资源的充分利用以及扩大货物的生产和交换为目的,期望通过达成互惠互利安排,实行公开、公平、公正的“三公原则”,开展国家与国家、企业与企业之间的易货贸易,规避因信用问题引起的各类风险,消除国际贸易中的歧视待遇,促进全球国家的经济发展,从而为实现上述目标做出贡献。
文章浏览阅读2.5k次。由于webase文档原因,查找起来比较局限,有时候想找一个api却又忘了在哪个模块的目录下,需要一步一步单独点,而利用文档自带的检索功能又因为查找文档全部信息,显得十分缓慢,所以整理了有关WeBASE的api列表但不可否认,现在只有列表,没有对应的页面跳转,文章目的也只是为了多了解webase的接口_webase私钥管理里获取
文章浏览阅读1.4k次,点赞28次,收藏21次。基于​openzeppelin来构建我们的NFT,并用一个例子来手把手的说明如何在opensea快速发布自己的NFT智能合约(ERC721)。