天天看點

hyperledger fabric 源碼調試(peer)環境搭建教程

前面寫了一篇 [hyperledger fabric 源碼調試(orderer)環境搭建教程 ],按照相同的思路,這次來搭建peer的調試環境。部分相同的步驟和講解請看上一篇,這裡不再重新解釋。

peer

first-network示例中包含4個peer,我們隻把peer0.org1配置成源碼調試。

一、Run/Debug Configuration 配置。新增一個Go build 配置具體如下:

name: peer0.org1
Run kind: Package
Package path: <Project Dir>/work_dir/bin              #最好都使用絕對路徑
Output directory: <Project Dir>/work_dir/bin  
Working directory: <Project Dir>/work_dir/peer           

二、環境變量(還是Run/Debug Configuration)

還是一樣的思路,從base/peer-base.yaml, base/docker-compose-base.yaml, docker-compose-cli.yaml中查找peer0.org1.example.com相關的環境變量以及volumes配置,對照修改成本地路徑映射,結果如下:

# 注意,這個雖然找不到替換,但能找到/var/run:/host/var/run的volume項,可推斷出來
CORE_VM_ENDPOINT=unix:///var/run/docker.sock     
# 這一項能找到first-network下.env中有COMPOSE_PROJECT_NAME=net,故寫死
CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=net_byfn
FABRIC_LOGGING_SPEC=INFO
CORE_PEER_TLS_ENABLED=true
CORE_PEER_GOSSIP_USELEADERELECTION=true
CORE_PEER_GOSSIP_ORGLEADER=false
CORE_PEER_PROFILE_ENABLED=true
CORE_PEER_TLS_CERT_FILE=../first-network/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
CORE_PEER_TLS_KEY_FILE=../first-network/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key
CORE_PEER_TLS_ROOTCERT_FILE=../first-network/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
      
CORE_PEER_ID=peer0.org1.example.com
CORE_PEER_ADDRESS=peer0.org1.example.com:7051
CORE_PEER_LISTENADDRESS=0.0.0.0:7051
CORE_PEER_CHAINCODEADDRESS=peer0.org1.example.com:7052
CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:7052
CORE_PEER_GOSSIP_BOOTSTRAP=peer1.org1.example.com:8051
CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.example.com:7051
CORE_PEER_LOCALMSPID=Org1MSP

# 添加一項配置,理由同orderer
FABRIC_CFG_PATH=../config           

跟orderer的配置一樣,最後添加了一行FABRIC_CFG_PATH,不加的話運作時會遇到提示的,可以試試。

注意:你可能發現有一行volumes映射沒有找到要用的地方,先記着,後邊會解釋:

volumes:
  ../first-network/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp           

嘗試運作一下做個粗略檢查。确定Run/Debug Configuration是選中的peer0.org1,點選run按鈕,報錯:

2019-12-19 20:28:32.170 CST [main] InitCmd -> ERRO 001 Cannot run peer because cannot init crypto, folder "< go path >/src/github.com/hyperledger/fabric/work_dir/config/msp" does not exist

從錯誤提示來看程式是嘗試從work_dir/config/下去找msp然後沒找到。記得嗎?config目錄是之前從fabric-samples下直接拷貝過來的,裡頭原本就隻有三個yaml檔案,并沒有msp檔案夾:

cd work_dir
tree config

config
├── configtx.yaml
├── core.yaml
└── orderer.yaml           

找不到方法,索性直接在源碼中搜錯誤資訊"Cannot run peer because",找到fabric/peer/common/common.go中有這麼一段:

image-20191219204706810.png

hyperledger fabric 源碼調試(peer)環境搭建教程

其實作在已經可以debug了,隻是環境還沒有完全配置好而已,是以可以通過單步調試來分析錯誤原因。經調試分析後就會發現一個大緻思路:

peer 程式啟動時去FABRIC_CFG_PATH下讀取了core.yaml,然後找到該檔案中的 mspConfigPath: msp 拼接上FABRIC_CFG_PATH得到了.../work_dir/config/msp這麼個路徑。

現在回想前面說到的有一行volumes沒有用上就能明白了:這個路徑沒有直接使用,而是兩處路徑拼接出來的。合在一起來看:

volumes:

../first-network/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp

[ ../work_dir/config/msp ] 等于 [ work_dir/config 拼接 core.yaml中的mspConfigPath項 ]

是以我們隻需要修改core.yaml中的mspConfigPath,使得上述拼接值與volume配置中的路徑相同即可,修改core.yaml如下:

...
mspConfigPath: ../first-network/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp           

儲存再運作,已經不再報msp找不到的問題了,當然還會有其他錯:“mkdir /var/hyperledger: permission denied”,這個在orderer的配置中有提過的,直接改core.yaml就好:

...
fileSystemPath: ../peer #怕出錯可用絕對路徑           

再次嘗試運作,已經不再報錯,可以準備網絡測試了。

3.配合網絡設定

道理同orderer,需要在docker-compose-cli.yaml中禁用掉peer0.org1的啟動配置,已經為其他節點配上指向peer0.org1.example.com的extra_hosts映射。為了互相不影響我們直接複制一份原始docker-compose-cli.yaml改名為docker-compose-cli-no-peer.yaml,修改如下:

...
volumes:
#  peer0.org1.example.com:
...


#  peer0.org1.example.com:
#    container_name: peer0.org1.example.com
#    extends:
#      file:  base/docker-compose-base.yaml
#      service: peer0.org1.example.com
#    networks:
#      - byfn
#    extra_hosts:
#      - "orderer.example.com:< host IP >"
...

peer1.org1.example.com:
...
    extra_hosts:
      - "peer0.org1.example.com:< host IP >"
peer0.org2.example.com:
...
    extra_hosts:
      - "peer0.org1.example.com:< host IP >"
peer1.org2.example.com:
...
    extra_hosts:
     - "peer0.org1.example.com:< host IP >"

cli:
...
    depends_on:
      - orderer.example.com
#      - peer0.org1.example.com
      - peer1.org1.example.com
      - peer0.org2.example.com
      - peer1.org2.example.com
...
    extra_hosts:
      - "peer0.org1.example.com:< host IP >"
             

這就是沒有peer0的first-network了。

4.啟動docker網絡,注意用新配置的yaml檔案

cd work_dir/first-network
./byfn.sh down
# 清理peer0的資料
rm -rf work_dir/peer/*

./byfn.sh generate
docker-compose -f docker-compose-cli-no_peer.yaml up -d           

5.特别注意。雖說本文大緻參照orderer的配置思路完成,但是難度并未減小,因為有3個新的地方需要注意:

1) peer在執行chaincode的時候會啟動一個chaincode容器并與其保持通信,peer0是在本機,chaincode是在容器裡,它找不到peer0.org1.example.com的ip,現象是鍊碼初始化不成功,chaincode容器起來幾秒鐘就會退出:container exited with 0. 排查起來很費勁(參考

踩坑之chaincode無法執行個體化-"container exited with 0"

);

2) peer還需要與orderer,peer1.org1通信,peer1.org1相對好排查一點,因為一啟動peer0就會有報錯出現;但orderer需要在操作網絡的時候才會報錯來,而那時候可能注意力不在這上邊很容易錯過;

3) 最後是關于docker網絡的知識,先見下方代碼中的注釋,以後再詳細補一篇這方面的博文。

是以需要再為/etc/hosts添加兩行:

參考連結: https://pythonspeed.com/articles/docker-connection-refused/ https://stackoverflow.com/questions/31249112/allow-docker-container-to-connect-to-a-local-host-postgres-database
sudo vi /etc/hosts

# 為chaincode容器添加peer0映射,注意不能是127.0.0.1,
# 得是路由器配置設定的ip位址,換一個網路就得更新(可能有别的法子)
< host IP > peer0.org1.example.com

# 為peer0添加peer1.org1.example.com位址映射,這是從local連結到container的,可以是127.0.0.1
# 不加的話會報類似錯誤
# 2019-12-20 16:19:25.110 CST [gossip.discovery] func1 -> WARN 024 Could not connect to Endpoint: peer1.org1.example.com:8051, InternalEndpoint: peer1.org1.example.com:8051, PKI-ID: <nil>, Metadata:  : context deadline exceeded

127.0.0.1 peer1.org1.example.com

# 為peer0添加orderer的位址映射,同上,錯誤資訊會更隐秘一點
127.0.0.1 orderer.example.com           

測試

确認Run/Debug Configuration是剛設定好的peer0.org1,然後點選debug按鈕。

1.打斷點。在fabric/core/endorser/endorser.go下的ProcessProposal處打個斷點;

2.跟orderer源碼調試的測試類似,參照build your first network的手動執行部分(但隻選擇peer0相關的即可),在控制台建立channel後做鍊碼安裝和調用操作:

docker exec -it cli bash

# 發送channel建立請求
export CHANNEL_NAME=mychannel

peer channel create -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/channel.tx --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

# peer0 加入到mychannel
peer channel join -b mychannel.block

# 安裝鍊碼到peer0
peer chaincode install -n mycc -v 1.0 -p github.com/chaincode/chaincode_example02/go/

# 初始化鍊碼 會觸發斷點
peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "AND ('Org1MSP.peer','Org2MSP.peer')"

# 鍊碼查詢調用 會觸發斷點
peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
           

可以看到斷點觸發成功:

image-20191222194757894.png

hyperledger fabric 源碼調試(peer)環境搭建教程

附言

以下是我在調試排查“context deadline exceeded”錯誤時的部分筆記,很亂,而且還有些是錯誤的。貼在這裡是想分享我個人的一個觀點:源碼分析就是一個“猜想 -> 調試驗證 -> 猜想”不斷重複的過程,通常并不像部落格裡那麼清晰自然方向明确,别人也是先看些說明資料,然後按照猜想一步步反複調試分析,最後把錯誤的或者無關緊要的分支去掉整理好寫成文分享出來。是以有源碼在手不要慌,一定要多調試。

cc, err := grpc.DialContext(ctx, remotePeer.Endpoint, dialOpts...)
... ->
if !cc.WaitForStateChange(ctx, s) {

這是用cc.msMgr下的notifyChan來接收通知,但并未找到該chan的寫入操作,隻有在updateState下有close操作,經驗證,這也會導緻chan的讀操作傳回,是以追蹤到
func (csm *connectivityStateManager) updateState(state connectivity.State)
經排查,其調用來自于func (ccb *ccBalancerWrapper) UpdateBalancerState() 來自
func (b *pickfirstBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) 來自
func (ccb *ccBalancerWrapper) watcher() 這已經是線程入口了,直接分析HandleResolvedAddrs

HandleResolvedAddrs 先将狀态置為Idle(此次不觸發notifyChan),然後調用b.sc.Connect()
Connect() 内部跟進去先檢查目前狀态是否為Idle,如果是的話就修改狀态Connecting,将狀态通過
func (ccb *ccBalancerWrapper) handleSubConnStateChange 放進了queue:
ccb.stateChangeQueue.put
這個queue的值會由watcher輪詢讀取,按理應該能及時到達WaitForStateChange
及時到達,并調用func (b *pickfirstBalancer) HandleSubConnStateChange
最後到func (csm *connectivityStateManager) updateState(state connectivity.State)
問題是此時csm.state已經是Shutdown了,是以設定不成功。(應該與單步調試有關)
該csm.state就是要updateState的對象,也就是說,可能在本次updateState之前有其他地方調了updateState并将其設為Shutdown了,經條件斷點得到驗證
shutdown的狀态來自于
func (cc *ClientConn) Close() error 來自于
func DialContext(ctx context.Context, target string, opts ...DialOption) 下的
defer func
是以重點調試defer func後續的代碼,看是否正常執行完畢,看起來似乎沒毛病,最後到WaitForStateChange,
看起來似乎不是從defer func 調用的Close,還有别處?
經斷點,沒有其他地方調用Close,且該Close是WaitForStateChange之後才調的,也就是說不是由Close引起。
那還有别處調用updateState更新shutdown?
經在func (ccb *ccBalancerWrapper) watcher()中添加日志列印state發現CONNECTING狀态正常,然後有個transientFailure導緻了Close:
state is:CONNECTING
state is:TRANSIENT_FAILURE
2019-12-20 14:52:40.588 CST [gossip.discovery] func1 -> WARN 024 Could not connect to Endpoint: peer1.org1.example.com:8051, InternalEndpoint: peer1.org1.example.com:8051, PKI-ID: <nil>, Metadata:  : context deadline exceeded

查這個狀态,追到如下關系(順序) connect->resetTransport->createTransport:ac.state = connectivity.TransientFailure
經調試是在createTransport中的transport.NewClientTransport()失敗了
追查後是如下調用關系(順序)失敗:
func NewClientTransport() ->
func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts ConnectOptions, onSuccess func())->
func dial(ctx context.Context, fn func(context.Context, string) -> mapAddress -> httpProxyFromEnvironment

其中httpProxyFromEnvironment是一個func,取值 envProxyFuncValue func(*url.URL) 這是找代理的,如果有代理就走代理,如果沒有設定代理就繼續往下,是以失敗了沒關系

往下的代碼還是會連接配接,不詳述了,結論是grpc底層發起解析peer1.org1.example.com的請求,因為網絡上并沒有這麼個ip,解析自然逾時,最後失敗:
解決方案:編輯/etc/hosts 添加127.0.0.1 peer1.org1.example.com,重新啟動網絡和debug,成功!           

(完)