兄弟连Go语言+区块链技术培训以太坊源码分析35ethfetcher源码分析.docx
《兄弟连Go语言+区块链技术培训以太坊源码分析35ethfetcher源码分析.docx》由会员分享,可在线阅读,更多相关《兄弟连Go语言+区块链技术培训以太坊源码分析35ethfetcher源码分析.docx(21页珍藏版)》请在冰豆网上搜索。
兄弟连Go语言+区块链技术培训以太坊源码分析35ethfetcher源码分析
兄弟连Go语言+区块链技术培训以太坊源码分析(35)eth-fetcher源码分析
fetcher包含基于块通知的同步。
当我们接收到NewBlockHashesMsg消息得时候,我们只收到了很多Block的hash值。
需要通过hash值来同步区块,然后更新本地区块链。
fetcher就提供了这样的功能。
数据结构
//announceisthehashnotificationoftheavailabilityofanewblockinthe
//network.
//announce是一个hash通知,表示网络上有合适的新区块出现。
typeannouncestruct{
hashcommon.Hash//Hashoftheblockbeingannounced//新区块的hash值
numberuint64//Numberoftheblockbeingannounced(0=unknown|oldprotocol)区块的高度值,
header*types.Header//Headeroftheblockpartiallyreassembled(newprotocol) 重新组装的区块头
timetime.Time//Timestampoftheannouncement
originstring//Identifierofthepeeroriginatingthenotification
fetchHeaderheaderRequesterFn//Fetcherfunctiontoretrievetheheaderofanannouncedblock获取区块头的函数指针,里面包含了peer的信息。
就是说找谁要这个区块头
fetchBodiesbodyRequesterFn//Fetcherfunctiontoretrievethebodyofanannouncedblock获取区块体的函数指针
}
//headerFilterTaskrepresentsabatchofheadersneedingfetcherfiltering.
typeheaderFilterTaskstruct{
peerstring//Thesourcepeerofblockheaders
headers[]*types.Header//Collectionofheaderstofilter
timetime.Time//Arrivaltimeoftheheaders
}
//headerFilterTaskrepresentsabatchofblockbodies(transactionsanduncles)
//needingfetcherfiltering.
typebodyFilterTaskstruct{
peerstring//Thesourcepeerofblockbodies
transactions[][]*types.Transaction//Collectionoftransactionsperblockbodies
uncles[][]*types.Header//Collectionofunclesperblockbodies
timetime.Time//Arrivaltimeoftheblocks'contents
}
//injectrepresentsaschedulesimportoperation.
//当节点收到NewBlockMsg的消息时候,会插入一个区块
typeinjectstruct{
originstring
block*types.Block
}
//Fetcherisresponsibleforaccumulatingblockannouncementsfromvariouspeers
//andschedulingthemforretrieval.
typeFetcherstruct{
//Variouseventchannels
notifychan*announce //announce的通道,
injectchan*inject //inject的通道
blockFilterchanchan[]*types.Block //通道的通道?
headerFilterchanchan*headerFilterTask
bodyFilterchanchan*bodyFilterTask
donechancommon.Hash
quitchanstruct{}
//Announcestates
announcesmap[string]int//Perpeerannouncecountstopreventmemoryexhaustionkey是peer的名字,value是announce的count,为了避免内存占用太大。
announcedmap[common.Hash][]*announce//Announcedblocks,scheduledforfetching等待调度fetching的announce
fetchingmap[common.Hash]*announce//Announcedblocks,currentlyfetching正在fetching的announce
fetchedmap[common.Hash][]*announce//Blockswithheadersfetched,scheduledforbodyretrieval//已经获取区块头的,等待获取区块body
completingmap[common.Hash]*announce//Blockswithheaders,currentlybody-completing//头和体都已经获取完成的announce
//Blockcache
queue*prque.Prque//Queuecontainingtheimportoperations(blocknumbersorted)//包含了import操作的队列(按照区块号排列)
queuesmap[string]int//Perpeerblockcountstopreventmemoryexhaustionkey是peer,value是block数量。
避免内存消耗太多。
queuedmap[common.Hash]*inject//Setofalreadyqueuedblocks(todedupimports)已经放入队列的区块。
为了去重。
//Callbacks依赖了一些回调函数。
getBlockblockRetrievalFn//Retrievesablockfromthelocalchain
verifyHeaderheaderVerifierFn//Checksifablock'sheadershaveavalidproofofwork
broadcastBlockblockBroadcasterFn//Broadcastsablocktoconnectedpeers
chainHeightchainHeightFn//Retrievesthecurrentchain'sheight
insertChainchainInsertFn//Injectsabatchofblocksintothechain
dropPeerpeerDropFn//Dropsapeerformisbehaving
//Testinghooks仅供测试使用。
announceChangeHookfunc(common.Hash,bool)//Methodtocalluponaddingordeletingahashfromtheannouncelist
queueChangeHookfunc(common.Hash,bool)//Methodtocalluponaddingordeletingablockfromtheimportqueue
fetchingHookfunc([]common.Hash)//Methodtocalluponstartingablock(eth/61)orheader(eth/62)fetch
completingHookfunc([]common.Hash)//Methodtocalluponstartingablockbodyfetch(eth/62)
importedHookfunc(*types.Block)//Methodtocalluponsuccessfulblockimport(botheth/61andeth/62)
}
启动fetcher,直接启动了一个goroutine来处理。
这个函数有点长。
后续再分析。
//Startbootsuptheannouncementbasedsynchroniser,acceptingandprocessing
//hashnotificationsandblockfetchesuntilterminationrequested.
func(f*Fetcher)Start(){
gof.loop()
}
loop函数函数太长。
我先帖一个省略版本的出来。
fetcher通过四个map(announced,fetching,fetched,completing)记录了announce的状态(等待fetch,正在fetch,fetch完头等待fetchbody,fetch完成)。
loop其实通过定时器和各种消息来对各种map里面的announce进行状态转换。
//Loopisthemainfetcherloop,checkingandprocessingvariousnotification
//events.
func(f*Fetcher)loop(){
//Iteratetheblockfetchinguntilaquitisrequested
fetchTimer:
=time.NewTimer(0)//fetch的定时器。
completeTimer:
=time.NewTimer(0)//compelte的定时器。
for{
//Cleanupanyexpiredblockfetches
//如果fetching的时间超过5秒,那么放弃掉这个fetching
forhash,announce:
=rangef.fetching{
iftime.Since(announce.time)>fetchTimeout{
f.forgetHash(hash)
}
}
//Importanyqueuedblocksthatcouldpotentiallyfit
//这个fetcher.queue里面缓存了已经完成fetch的block等待按照顺序插入到本地的区块链中
//fetcher.queue是一个优先级队列。
优先级别就是他们的区块号的负数,这样区块数小的排在最前面。
height:
=f.chainHeight()
for!
f.queue.Empty(){//
op:
=f.queue.PopItem().(*inject)
iff.queueChangeHook!
=nil{
f.queueChangeHook(op.block.Hash(),false)
}
//Iftoohighupthechainorphase,continuelater
number:
=op.block.NumberU64()
ifnumber>height+1{//当前的区块的高度太高,还不能import
f.queue.Push(op,-float32(op.block.NumberU64()))
iff.queueChangeHook!
=nil{
f.queueChangeHook(op.block.Hash(),true)
}
break
}
//Otherwiseiffreshandstillunknown,tryandimport
hash:
=op.block.Hash()
ifnumber+maxUncleDist=nil{
//区块的高度太低低于当前的height-maxUncleDist
//或者区块已经被import了
f.forgetBlock(hash)
continue
}
//插入区块
f.insert(op.origin,op.block)
}
//Waitforanoutsideeventtooccur
select{
case<-f.quit:
//Fetcherterminating,abortalloperations
return
casenotification:
=<-f.notify:
//在接收到NewBlockHashesMsg的时候,对于本地区块链还没有的区块的hash值会调用fetcher的Notify方法发送到notify通道。
...
caseop:
=<-f.inject:
//在接收到NewBlockMsg的时候会调用fetcher的Enqueue方法,这个方法会把当前接收到的区块发送到inject通道。
...
f.enqueue(op.origin,op.block)
casehash:
=<-f.done:
//当完成一个区块的import的时候会发送该区块的hash值到done通道。
...
case<-fetchTimer.C:
//fetchTimer定时器,定期对需要fetch的区块头进行fetch
...
case<-completeTimer.C:
//completeTimer定时器定期对需要fetch的区块体进行fetch
...
casefilter:
=<-f.headerFilter:
//当接收到BlockHeadersMsg的消息的时候(接收到一些区块头),会把这些消息投递到headerFilter队列。
这边会把属于fetcher请求的数据留下,其他的会返回出来,给其他系统使用。
...
casefilter:
=<-f.bodyFilter:
//当接收到BlockBodiesMsg消息的时候,会把这些消息投递给bodyFilter队列。
这边会把属于fetcher请求的数据留下,其他的会返回出来,给其他系统使用。
...
}
}
}
###区块头的过滤流程
####FilterHeaders请求
FilterHeaders方法在接收到BlockHeadersMsg的时候被调用。
这个方法首先投递了一个channelfilter到headerFilter。
然后往filter投递了一个headerFilterTask的任务。
然后阻塞等待filter队列返回消息。
//FilterHeadersextractsalltheheadersthatwereexplicitlyrequestedbythefetcher,
//returningthosethatshouldbehandleddifferently.
func(f*Fetcher)FilterHeaders(peerstring,headers[]*types.Header,timetime.Time)[]*types.Header{
log.Trace("Filteringheaders","peer",peer,"headers",len(headers))
//Sendthefilterchanneltothefetcher
filter:
=make(chan*headerFilterTask)
select{
casef.headerFilter<-filter:
case<-f.quit:
returnnil
}
//Requestthefilteringoftheheaderlist
select{
casefilter<-&headerFilterTask{peer:
peer,headers:
headers,time:
time}:
case<-f.quit:
returnnil
}
//Retrievetheheadersremainingafterfiltering
select{
casetask:
=<-filter:
returntask.headers
case<-f.quit:
returnnil
}
}
####headerFilter的处理
这个处理在loop()的goroutine中。
casefilter:
=<-f.headerFilter:
//Headersarrivedfromaremotepeer.Extractthosethatwereexplicitly
//requestedbythefetcher,andreturneverythingelsesoit'sdelivered
//tootherpartsofthesystem.
vartask*headerFilterTask
select{
casetask=<-filter:
case<-f.quit:
return
}
headerFilter