以太坊源碼研讀0x04 RLP源碼解析

RLP(Recursive Length Prefix)话浇,遞歸長度前綴編碼增蹭,它是以太坊序 化所采取的編碼方式奔脐。RLP主要用于以太坊中數(shù)據(jù)的網(wǎng)絡(luò)傳輸和持久化存儲。

RLP理解

RLP編碼

RLP編碼針對的數(shù)據(jù)類型主要有兩種:

  • byte數(shù)組
  • byte數(shù)組的數(shù)組星澳,即列表

設(shè)定Rc(x)為RLP編碼函數(shù)疚顷。
首先來看針對byte數(shù)組的幾個編碼規(guī)則:

1.針對單字節(jié)b,b ∈ [0,127], Rc(b) = b

eg:Rc(a) = 97, Rc(w) = 119

2.針對字節(jié)數(shù)組bytes禁偎,length(bytes) <= 55, Rc(bytes) = 128+length(bytes) ++ bytes(++符號表示拼接)

eg:Rc(abc) = [131 97 98 99], 其中131 = 128+length(abc), [97 98 99]為[abc]本身編碼

3.針對字節(jié)數(shù)組bytes腿堤,length(bytes) > 55,
Rc(bytes) = 183+sizeof(sizeof(bytes)) ++ Rc(sizeof(bytes)) + bytes

eg: str = "The length of this sentence is more than 55 bytes, I know it because I pre-designed it"
Rc(str) = [184 86 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116]
經(jīng)計算該字符串占用字節(jié)數(shù)為86,顯然184 = 183 + sizeof(86)如暖,86 = sizeof(str), 84為“T”的編碼笆檀,從84開始后面便是str本身的編碼。

以上是針對字節(jié)數(shù)組盒至,接下來看以字節(jié)數(shù)組為元素的數(shù)組酗洒,這里稱之為列表的編碼方式:

首先要明確幾個概念,針對一個列表list枷遂,lenL是指list內(nèi)每個bytes字節(jié)數(shù)的總和樱衷,lenDl是指list每個bytes經(jīng)過Rc(x)編碼后的總長度。

lenL(list) = \sum_{i=0}^Nsizeof(bytes_i)
lenDl(list) = \sum_{i=0}^Nsizeof(Rc(bytes_i))

4.針對list[bytes0, bytes1...bytesN]酒唉,lenL(list) <= 55,
Rc(list) = 192+lenDl(list) ++ Rc(bytes_i)

eg: list = ["abc", "def"],
Rc(list) = [200 131 97 98 99 131 100 101 102]
首先矩桂,lenL(list) = 3+3 <= 55,
然后,根據(jù)規(guī)則3可以得出:
Rc[abc] = [131 97 98 99]痪伦,
Rc[def] = [131 100 101 102](128+3 100 101 102)
現(xiàn)在就知道侄榴,lenDl(list) = 4 + 4 = 8,所以開始的200就是192+8的結(jié)構(gòu)网沾,后面跟的是list里每個bytes的RLP編碼癞蚕。

5.針對list[bytes0, bytes1...bytesN],lenL(list) > 55,
Rc(list) = 247+sizeof(lenDl(list)) ++ lenDl(list) ++ Rc(bytes_i)

eg: list = ["The length of this sentence is more than 55 bytes,",
" I know it because I pre-designed it"]
Rc(list) = [248 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116]
首先辉哥,lenL(list) = 51+35 = 86 > 55,
然后桦山,根據(jù)規(guī)則2可以得出:
Rc[The length of this sentence is more than 55 bytes,] = [179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32](179 = 128 + 51),
Rc[ I know it because I pre-designed it]
= [163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116](163 = 128 + 35)
現(xiàn)在就知道,lenDl(list) = 52 + 36 = 88度苔,88只需占1個字節(jié)即可匆篓,所以開始的248就是247+1的結(jié)構(gòu)浑度,后面的88是lenDl(list)本身的編碼寇窑,再后面跟的是list里每個bytes的RLP編碼。

RLP解碼

我們知道箩张,有編碼就有解碼甩骏。編碼是解碼的一個逆過程,我們發(fā)現(xiàn)在編碼的時候不同的情況都有一個不同的字節(jié)前綴先慷,所以解碼的時候也是從這個字節(jié)前綴入手饮笛。

設(shè)定Dr(x)為解碼函數(shù),s為x的第一個字節(jié)论熙。
subX[m,n]表示x的子串福青,從x的第m個開始取n個字節(jié)。
bin2Int(bytes)表示將一個字節(jié)數(shù)組按BigEndian編碼轉(zhuǎn)換為整數(shù)脓诡,目的在于求出被編碼字符串長度无午。

1. s ∈ [0, 127], 對應(yīng)編碼規(guī)則1單字節(jié)解碼,
Dr(x) = x

2. s ∈ [128, 184), 對應(yīng)編碼規(guī)則2祝谚,數(shù)組長度不超過55
Dr(x) = subX[2, s-128]

3. s ∈ [184, 192), 對應(yīng)編碼規(guī)則3宪迟,數(shù)組長度超過55
bin2Int(subX[1, s-183])為被編碼數(shù)組的長度
Dr(x) = subX[bin2Int(subX[1, s-183]+1, bin2Int(subX[1, s-183])]

4. s ∈ [192, 247), 對應(yīng)編碼規(guī)則4,總長不超過55的列表 列表總長lenDl = s-192交惯,然后遞歸調(diào)用解碼規(guī)則1-3即可
Dr(x) = \sum_{i=0}^ NspliceOf(Dr(x_i))

5. s ∈ [247, 256], 對應(yīng)編碼規(guī)則5次泽,總長超過55的列表,列表總長lenDl = bin2Int(subX[1, s-247]),然后遞歸調(diào)用解碼規(guī)則1-3即可
Dr(x) = \sum_{i=0}^ NspliceOf(Dr(x_i))

RLP源碼

上面大概了解了RLP的編解碼方式席爽,下面我們就到eth源代碼中去看看有關(guān)RLP的代碼是怎么寫的意荤。

上篇文章主要介紹了geth源碼結(jié)構(gòu),RLP源碼主要在rlp目錄下只锻。

RLP源碼結(jié)構(gòu)

typeCache.go

該結(jié)構(gòu)主要實(shí)現(xiàn)不同類型和對應(yīng)編解碼器的映射關(guān)系玖像,通過它去獲取對應(yīng)的編解碼器。

核心數(shù)據(jù)結(jié)構(gòu)

// 核心數(shù)據(jù)結(jié)構(gòu)
var (
    typeCacheMutex sync.RWMutex    // 讀寫鎖炬藤,用于在多線程時保護(hù)typeCache
    typeCache      = make(map[typekey]*typeinfo)  // 保存類型 -> 編碼器函數(shù)的數(shù)據(jù)結(jié)構(gòu)
    // Map的key是類型御铃,value是對應(yīng)的編碼和解碼器
)

type typeinfo struct {
    decoder    // 解碼器函數(shù)
    writer     // 編碼器函數(shù)
}

如何獲取編碼器和解碼器的函數(shù)

// 獲取編碼器和解碼器的函數(shù)
func cachedTypeInfo(typ reflect.Type, tags tags) (*typeinfo, error) {
    typeCacheMutex.RLock()    // 加鎖保護(hù)
    info := typeCache[typekey{typ, tags}]    // 將傳入的typ和tags封裝為typekey類型
    typeCacheMutex.RUnlock()  // 解鎖
    if info != nil {    // 成功獲取到typ對應(yīng)的編解碼函數(shù)
        return info, nil
    }
    // not in the cache, need to generate info for this type.
    // 編解碼不在typeCache中,需要創(chuàng)建該typ對應(yīng)的編解碼函數(shù)
    typeCacheMutex.Lock()
    defer typeCacheMutex.Unlock()
    return cachedTypeInfo1(typ, tags)
}

// 新建typ對應(yīng)的編解碼函數(shù)
func cachedTypeInfo1(typ reflect.Type, tags tags) (*typeinfo, error) {
    key := typekey{typ, tags}
    info := typeCache[key]
    if info != nil {
        // another goroutine got the write lock first
        /// 其他線程已經(jīng)成功創(chuàng)建
        return info, nil
    }
    // put a dummmy value into the cache before generating.
    // if the generator tries to lookup itself, it will get
    // the dummy value and won't call itself recursively.
    // 這個地方首先創(chuàng)建了一個值來填充這個類型的位置沈矿,避免遇到一些遞歸定義的數(shù)據(jù)類型形成死循環(huán)
    typeCache[key] = new(typeinfo)
    info, err := genTypeInfo(typ, tags)    // 生成對應(yīng)類型的編解碼器函數(shù)
    if err != nil {
        // remove the dummy value if the generator fails
        // 創(chuàng)建失敗處理
        delete(typeCache, key)
        return nil, err
    }
    *typeCache[key] = *info
    return typeCache[key], err
}

生成對應(yīng)編解碼器的函數(shù)


// 生成對應(yīng)編解碼器的函數(shù)
func genTypeInfo(typ reflect.Type, tags tags) (info *typeinfo, err error) {
    info = new(typeinfo)
    if info.decoder, err = makeDecoder(typ, tags); err != nil {
        return nil, err
    }
    if info.writer, err = makeWriter(typ, tags); err != nil {
        return nil, err
    }
    return info, nil
}

decode.go

typeCache定義了類型與對應(yīng)解編碼器的映射關(guān)系上真,接下來就看看對應(yīng)的編碼和解碼代碼。

// 定義一些解碼錯誤
var (
    // EOL is returned when the end of the current list
    // has been reached during streaming.
    EOL = errors.New("rlp: end of list")

    // Actual Errors
    ErrExpectedString   = errors.New("rlp: expected String or Byte")
    ErrExpectedList     = errors.New("rlp: expected List")
    ErrCanonInt         = errors.New("rlp: non-canonical integer format")
    ErrCanonSize        = errors.New("rlp: non-canonical size information")
    ErrElemTooLarge     = errors.New("rlp: element is larger than containing list")
    ErrValueTooLarge    = errors.New("rlp: value size exceeds available input length")
    ErrMoreThanOneValue = errors.New("rlp: input contains more than one value")

    // internal errors
    errNotInList     = errors.New("rlp: call of ListEnd outside of any list")
    errNotAtEOL      = errors.New("rlp: call of ListEnd not positioned at EOL")
    errUintOverflow  = errors.New("rlp: uint overflow")
    errNoPointer     = errors.New("rlp: interface given to Decode must be a pointer")
    errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil")
)
// 解碼器  根據(jù)不同的類型返回對應(yīng)的解碼器函數(shù)
func makeDecoder(typ reflect.Type, tags tags) (dec decoder, err error) {
    kind := typ.Kind()
    switch {
    case typ == rawValueType:
        return decodeRawValue, nil
    case typ.Implements(decoderInterface):
        return decodeDecoder, nil
    case kind != reflect.Ptr && reflect.PtrTo(typ).Implements(decoderInterface):
        return decodeDecoderNoPtr, nil
    case typ.AssignableTo(reflect.PtrTo(bigInt)):
        return decodeBigInt, nil
    case typ.AssignableTo(bigInt):
        return decodeBigIntNoPtr, nil
    case isUint(kind):
        return decodeUint, nil
    case kind == reflect.Bool:
        return decodeBool, nil
    case kind == reflect.String:
        return decodeString, nil
    case kind == reflect.Slice || kind == reflect.Array:
        return makeListDecoder(typ, tags)
    case kind == reflect.Struct:
        return makeStructDecoder(typ)
    case kind == reflect.Ptr:
        if tags.nilOK {
            return makeOptionalPtrDecoder(typ)
        }
        return makePtrDecoder(typ)
    case kind == reflect.Interface:
        return decodeInterface, nil
    default:
        return nil, fmt.Errorf("rlp: type %v is not RLP-serializable", typ)
    }
}

根據(jù)以上switch方法可以調(diào)到各種解碼函數(shù)羹膳。

encode.go

接下來來看看有關(guān)編碼的代碼解讀睡互。首先定義了兩種特殊情況下的編碼方式。

var (
    // Common encoded values.
    // These are useful when implementing EncodeRLP.
    EmptyString = []byte{0x80}      // 針對空字符串的編碼
    EmptyList   = []byte{0xC0}      // 針對空列表的編碼
)

接著定義了一個接口EncodeRLP供調(diào)用,但是很多情況下EncodeRLP會調(diào)用Encode方法就珠。

type Encoder interface {
    // EncodeRLP should write the RLP encoding of its receiver to w.
    // If the implementation is a pointer method, it may also be
    // called for nil pointers.
    //
    // Implementations should generate valid RLP. The data written is
    // not verified at the moment, but a future version might. It is
    // recommended to write only a single value but writing multiple
    // values or no value at all is also permitted.
    EncodeRLP(io.Writer) error
}
func Encode(w io.Writer, val interface{}) error {
    if outer, ok := w.(*encbuf); ok {
        // Encode was called by some type's EncodeRLP.
        // Avoid copying by writing to the outer encbuf directly.
        return outer.encode(val)
    }
    eb := encbufPool.Get().(*encbuf)    // 獲取一個編碼緩沖區(qū)encbuf
    defer encbufPool.Put(eb)
    eb.reset()
    if err := eb.encode(val); err != nil {
        return err
    }
    return eb.toWriter(w)
}
func (w *encbuf) encode(val interface{}) error {
    rval := reflect.ValueOf(val)
    ti, err := cachedTypeInfo(rval.Type(), tags{})
    if err != nil {
        return err
    }
    return ti.writer(rval, w)
}

makeWriter作為一個switch形式的編碼函數(shù)和上面的makeDecoder類似寇壳。

更多以太坊源碼解析請移駕全球最大同性交友網(wǎng),覺得有用記得給個小star哦??????

.
.
.
.

互聯(lián)網(wǎng)顛覆世界,區(qū)塊鏈顛覆互聯(lián)網(wǎng)!

--------------------------------------------------20180911 00:21
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妻怎,一起剝皮案震驚了整個濱河市壳炎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌逼侦,老刑警劉巖匿辩,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異榛丢,居然都是意外死亡铲球,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門晰赞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稼病,“玉大人,你說我怎么就攤上這事掖鱼∪蛔撸” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵锨用,是天一觀的道長丰刊。 經(jīng)常有香客問我,道長增拥,這世上最難降的妖魔是什么啄巧? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮掌栅,結(jié)果婚禮上咒循,老公的妹妹穿的比我還像新娘舶吗。我一直安慰自己伙窃,他們只是感情好抛丽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晌缘,像睡著了一般齐莲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上磷箕,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天选酗,我揣著相機(jī)與錄音,去河邊找鬼岳枷。 笑死芒填,一個胖子當(dāng)著我的面吹牛呜叫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播殿衰,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼朱庆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了闷祥?” 一聲冷哼從身側(cè)響起娱颊,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜀踏,沒想到半個月后维蒙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掰吕,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡果覆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了殖熟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片局待。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖菱属,靈堂內(nèi)的尸體忽然破棺而出钳榨,到底是詐尸還是另有隱情,我是刑警寧澤纽门,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布薛耻,位于F島的核電站,受9級特大地震影響赏陵,放射性物質(zhì)發(fā)生泄漏饼齿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一蝙搔、第九天 我趴在偏房一處隱蔽的房頂上張望缕溉。 院中可真熱鬧,春花似錦吃型、人聲如沸证鸥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枉层。三九已至,卻和暖如春赐写,著一層夾襖步出監(jiān)牢的瞬間鸟蜡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工血淌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矩欠,地道東北人财剖。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像癌淮,于是被迫代替她去往敵國和親躺坟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 文章分為2部分, 第一部分是綜合整理已有資料而生成的參考文檔, 第二部分是python版以太坊代碼中的源碼實(shí)現(xiàn)分析...
    shi_qinfeng閱讀 3,542評論 0 3
  • 咔嚓乳蓄、咔咪橙、咔、咔嚓嘣!你在干嘛呢虚倒,快來一起嗑呀!不然你就要輸了!咦美侦,這是在干什么呢?哦魂奥,原來這是在玩一一嗑瓜子比賽...
    江南1118閱讀 310評論 2 1
  • 780的經(jīng)典《西游記》里面的歌曲中有一句話叫做菠剩,路在腳下。路在腳下同時露也在眼圈但是當(dāng)我們自己耻煤,只有自己的眼睛的時...
    天之巔海無涯閱讀 351評論 0 0
  • 這是潘藍(lán)一寫的第32篇潘寶貝原創(chuàng)文章 微信:beibaopan 微信公眾號:panl...
    潘藍(lán)一閱讀 1,016評論 0 1