前些天朋友遇到一個關於以太坊使用的leveldb導致的數組越界問題,一起討論了很久。如果大家持續使用以太坊節點,遲早也會遇到此問題,在本篇文章中給大家分析一下,做好提前準備。
前些天朋友遇到一個關於以太坊使用的leveldb導致的數組越界問題,一起討論了很久。如果大家持續使用以太坊節點,遲早也會遇到此問題,在本篇文章中給大家分析一下,做好提前準備。
異常信息
前些天朋友遇到一個關於以太坊使用的leveldb導致的數組越界問題,一起討論了很久。如果大家持續使用以太坊節點,遲早也會遇到此問題,在本篇文章中給大家分析一下,做好提前準備。
我們先看一下具體的異常信息,對於普通的異常重啟geth節點即可解決,但如果遇到下面這個異常信息,重啟或升級版本都是無法解決的。
INFO [04-28|10:03:35] Starting peer-to-peer node instance=Geth/v1.7.3-stable/linux-amd64/go1.9INFO [04-28|10:03:35] Allocated cache and file handles database=/mnt/data/eth/geth/chaindata cache=128 handles=1024panic: runtime error: index out of rangegoroutine 1 [running]:github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb.shortenb(0x10040ff1126, 0x4, 0xc4204bf9f8) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/util.go:30 +0x14dgithub.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb.(*version).computeCompaction(0xc4416120f0) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/version.go:395 +0x4b3github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb.(*versionStaging).finish(0xc4204bfd18, 0xc4201e8000) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/version.go:510 +0x935github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb.(*version).spawn(0xc420182230, 0xc4201e8000, 0xc420182230) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/version.go:279 +0x7agithub.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb.(*session).commit(0xc4201d0240, 0xc4201e8000, 0x0, 0x0) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/session.go:195 +0x88github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb.(*DB).recoverJournal(0xc4200ee600, 0xc4200ee600, 0xc420068660) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/db.go:538 +0xdb8github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb.openDB(0xc4201d0240, 0x0, 0x0, 0xc4201d0240) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/db.go:122 +0x6bagithub.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb.Open(0x185e540, 0xc4202047e0, 0xc4204c02f0, 0x0, 0x0, 0x0) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/db.go:194 +0x100github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb.OpenFile(0xc4201f7ba0, 0x1c, 0xc4204c02f0, 0xc4201c5bb0, 0x4, 0x4) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/db.go:216 +0x97github.com/ethereum/go-ethereum/ethdb.NewLDBDatabase(0xc4201f7ba0, 0x1c, 0x80, 0x400, 0x1c, 0x0, 0x0) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/ethdb/database.go:72 +0x363github.com/ethereum/go-ethereum/node.(*ServiceContext).OpenDatabase(0xc4202a2da0, 0xf4ad6c, 0x9, 0x80, 0x400, 0x0, 0x0, 0x0, 0x0) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/node/service.go:46 +0x133github.com/ethereum/go-ethereum/eth.CreateDB(0xc4202a2da0, 0xc4203f4800, 0xf4ad6c, 0x9, 0x0, 0x0, 0x0, 0x0) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/eth/backend.go:201 +0x5dgithub.com/ethereum/go-ethereum/eth.New(0xc4202a2da0, 0xc4203f4800, 0x181d560, 0xc4204c5808, 0x417268) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/eth/backend.go:111 +0x93github.com/ethereum/go-ethereum/cmd/utils.RegisterEthService.func2(0xc4202a2da0, 0xc4204b4420, 0xc4204c5b18, 0x0, 0xc4204b4450) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/cmd/utils/flags.go:1065 +0x3dgithub.com/ethereum/go-ethereum/node.(*Node).Start(0xc4201f4480, 0x0, 0x0) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/node/node.go:175 +0x433github.com/ethereum/go-ethereum/cmd/utils.StartNode(0xc4201f4480) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/cmd/utils/cmd.go:62 +0x2fmain.startNode(0xc420230840, 0xc4201f4480) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/cmd/geth/main.go:225 +0x43main.geth(0xc420230840, 0xffbbe0, 0xb2d05e00) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/cmd/geth/main.go:215 +0x43github.com/ethereum/go-ethereum/vendor/gopkg.in/urfave/cli%2ev1.HandleAction(0xdd0280, 0xffd068, 0xc420230840, 0xc420230840, 0xc4204c5f40) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/gopkg.in/urfave/cli.v1/app.go:490 +0xd2github.com/ethereum/go-ethereum/vendor/gopkg.in/urfave/cli%2ev1.(*App).Run(0xc420250000, 0xc4200100e0, 0xe, 0xe, 0x0, 0x0) /mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/gopkg.in/urfave/cli.v1/app.go:264
異常分析
先查看一下異常信息的第一段代碼位置:
/mnt/go/src/github.com/ethereum/go-ethereum-1.7.3/build/_workspace/src/github.com/ethereum/go-ethereum/vendor/github.com/syndtr/goleveldb/leveldb/util.go:30
進入此源代碼之後,能夠看到如下代碼:
var bunits = [...]string{"", "Ki", "Mi", "Gi"}func shortenb(bytes int) string { i := 0 for ; bytes > 1024 && i < 4; i++ { bytes /= 1024 } return fmt.Sprintf("%d%sB", bytes, bunits[i])}
其中異常就發生在return代碼部分,也就是通過bunits[i]獲取數據時,i的值超出了bunits數組的範圍。
看這段代碼,當shortenb傳入的bytes<1024 * 1024 * 1024是沒問題的,i <= 3。但是,當bytes>1024 * 1024 * 1024 * 1024時,也就是單位到TB的時候,i的值將等於4,此時將發生數組越界異常。
為什麼剛才說大家遲早會遇到這個問題呢,就是當我們同步區塊鏈數據一開始就使用full或者很早就採用full模式的話,數據量很快會到達TB級別,而leveldb的這段代碼,當到達TB級別之後就會出現數組越界異常。
問題解決方案
上面已經分析了問題的原因,那麼怎麼解決這個問題呢?將數組bunits再擴展一個“Ti”項?這樣修改不敢打包票會修復問題,因為只是在數組裡面添加一個類型,不確定其他地方是否能夠使用此類型。如果要這樣修改,可能需要通讀相關的代碼,然後測試驗證才可以。
另外一種比較輕量級的改動是將for循環中i<4的判斷修改為i<3,修改後的代碼為:
var bunits = [...]string{"", "Ki", "Mi", "Gi"}func shortenb(bytes int) string { i := 0 for ; bytes > 1024 && i < 3; i++ { bytes /= 1024 } return fmt.Sprintf("%d%sB", bytes, bunits[i])}
這樣再拿上面bytes>1024 * 1024 * 1024 * 1024計算一下,當單位編程TB的時候,會使用1024GB,符合原來數組的最大單位。
PS:當然,修改之後大家是需要進行相應級別數據量的測試驗證的。