最近研究以太坊的收據(jù)(Receipt),發(fā)現(xiàn)區(qū)塊頭中有一個(gè)Bloom字段,這個(gè)字段是個(gè)2048的字節(jié)數(shù)組:
const (
// BloomByteLength represents the number of bytes used in a header log bloom.
BloomByteLength = 256
// BloomBitLength represents the number of bits used in a header log bloom.
BloomBitLength = 8 * BloomByteLength
)
// Bloom represents a 2048 bit bloom filter.
type Bloom [BloomByteLength]byte
通過(guò)注釋知道這是一個(gè)Bloom過(guò)濾器,但是這個(gè)過(guò)濾器是如何工作的。引起了我的興趣,這一看相關(guān)代碼,讓我預(yù)悶了一周多的時(shí)間。各種通道和文件讓我沒(méi)有任何頭緒來(lái)了解這個(gè)Bloom是如何起到過(guò)濾作用的?,F(xiàn)在有了點(diǎn)頭緒,想通過(guò)幾篇文章做個(gè)分享。
一。Bloom是如何生成的
~~~
b.header.Bloom = CreateBloom(receipts)
~~~
func CreateBloom(receipts Receipts) Bloom {
bin := new(big.Int)
for _, receipt := range receipts {
bin.Or(bin, LogsBloom(receipt.Logs))
}
return BytesToBloom(bin.Bytes())
}
func LogsBloom(logs []*Log) *big.Int {
bin := new(big.Int)
for _, log := range logs {
bin.Or(bin, bloom9(log.Address.Bytes()))
for _, b := range log.Topics {
bin.Or(bin, bloom9(b[:]))
}
}
return bin
}
func bloom9(b []byte) *big.Int {
b = crypto.Keccak256(b)
r := new(big.Int)
for i := 0; i < 6; i += 2 {
t := big.NewInt(1)
b := (uint(b[i+1]) + (uint(b[i]) << 8)) & 2047
r.Or(r, t.Lsh(t, b))
}
return r
}
通過(guò)源碼我們可以看到Bloom數(shù)組就是從收據(jù)(Receipt)中的Logs字段生成的。具體的內(nèi)容是Logs中的Address和Topics字段,為了大家好理解,我簡(jiǎn)單介紹下Log中的Address和Topics字段所代表的內(nèi)容:
type Log struct {
//以太坊智能合約的地址
Address common.Addressjson:"address" gencodec:"required"
// 以太坊智能合約轉(zhuǎn)賬過(guò)程中相關(guān)的轉(zhuǎn)入轉(zhuǎn)出方的地址hash
Topics []common.Hashjson:"topics" gencodec:"required"
//以太坊智能合約轉(zhuǎn)賬的金額
Data []bytejson:"data" gencodec:"required"
Address:代表合約,Topics代表地址,合起來(lái)就是某個(gè)合約Log的相關(guān)地址
為了大家了解就先這樣了解上面的字段。如果想更深入,請(qǐng)查看其他文檔。
所以看到這里我們就明白了區(qū)塊頭的Bloom字段其實(shí)就是根據(jù)一套算法(bloom9)將合約Address和Topics生成了一個(gè)2048字節(jié)的數(shù)組。
二。Bloom9算法
我們已經(jīng)知道布隆過(guò)濾器里存儲(chǔ)的是Log的合約地址(Address)和相關(guān)賬戶哈希(Topics)。布隆過(guò)濾器的一個(gè)特性就是不保證一個(gè)內(nèi)容100%存在,但可以100%保證一個(gè)內(nèi)容不存在過(guò)濾器中。了解了這個(gè)特性我們就介紹下布隆過(guò)濾器的生成算法Bloom9:
1.首先將傳入的任何數(shù)據(jù),進(jìn)行Keccak256的運(yùn)算,得到一個(gè)32字節(jié)的hash 。
2.循環(huán)取此hash值的前6字節(jié),注意循環(huán)步進(jìn)是i+=2,意思是每?jī)蓚€(gè)字節(jié)合為一個(gè)uint,并和2047進(jìn)行位與運(yùn)算(其實(shí)就是和2048求余),得到一個(gè)小于2048的值b,這個(gè)值b就是一個(gè)索引(index),表示在Bloom過(guò)濾器中的位置下標(biāo),也就是Bloom[b]的值為1。所以前6個(gè)字節(jié)生成了三個(gè)下標(biāo),共同表示此值是否在Bloom過(guò)濾器中。所以如果它生成的三個(gè)下標(biāo)都不都為1。就能100%保證此值不在過(guò)濾器中,如果都為1,也不一定保證在這個(gè)過(guò)濾器中,但我們就認(rèn)為在了。這就是過(guò)濾器的特性.
3.將生成的三個(gè)下標(biāo)值進(jìn)行左位移和位或運(yùn)算,將相應(yīng)的下標(biāo)值值為1,,得到的這個(gè)bigInt就是傳入?yún)?shù)的bloom過(guò)濾器的索引(index)值.
三。LogsBloom算法
這個(gè)方法就很簡(jiǎn)單了,該方法就是把Log的合約地址(Address)和Topics分別轉(zhuǎn)為相應(yīng)的下標(biāo)值(bloom9值),然后分別將相應(yīng)的下標(biāo)值進(jìn)行位或操作。這樣就將他們的值都存在了一個(gè)數(shù)據(jù)里面。最后在函數(shù)CreateBloom()中將這個(gè)數(shù)據(jù)轉(zhuǎn)為了2048位的Bloom類(lèi)型的值。
所以此Bloom過(guò)濾器只是一個(gè)相應(yīng)數(shù)據(jù)的標(biāo)志器,用于判斷某個(gè)數(shù)據(jù)是否存在里面的工具,本身不包括任何數(shù)據(jù)
四。如何判斷一個(gè)數(shù)據(jù)(BloomLookup)
我們知道了Bloom中的內(nèi)容并不包含相應(yīng)的數(shù)據(jù),所以判斷一個(gè)數(shù)據(jù)的存在不存在,并不是查找相應(yīng)的數(shù)據(jù),而且將相應(yīng)的數(shù)據(jù)索引化(bloom9)后的下標(biāo)值在Bloom中的位置的值是否全為1。但以太坊的BloomLookup提供了另一個(gè)思路:
func BloomLookup(bin Bloom, topic bytesBacked) bool {
bloom := bin.Big()
cmp := bloom9(topic.Bytes())
return bloom.And(bloom, cmp).Cmp(cmp) == 0
}
通過(guò)源碼可以看到其判斷流程為:
1.先將傳入的數(shù)據(jù)轉(zhuǎn)成bloom9值cmp
2.將Bloom過(guò)濾器轉(zhuǎn)為Big.Int然后和cmp進(jìn)行位與運(yùn)算
3.將位與后的值和cmp比較是否相等,如果相等表示存在
func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
if len(addresses) > 0 {
var included bool
for _, addr := range addresses {
if types.BloomLookup(bloom, addr) {
included = true
break
}
}
if !included {
return false
}
}
for _, sub := range topics {
included := len(sub) == 0 // empty rule set == wildcard
for _, topic := range sub {
if types.BloomLookup(bloom, topic) {
included = true
break
}
}
if !included {
return false
}
}
return true
}
這個(gè)bloomFilter方法就是一個(gè)使用示例,這個(gè)函數(shù)的功能就是:
1.判斷是否存在某些合約(addresses),如果不存在,直接返回false,就不用判斷topics了。因?yàn)閠opics就是某個(gè)合約的轉(zhuǎn)賬過(guò)程的賬戶地址,沒(méi)有合約,當(dāng)然也就沒(méi)有topics了
2.如果存在某些合約,就查找是否存在某些賬戶(topic)。
這就是布隆過(guò)濾器的使用方式和原理了 。
OK,這篇我們就先介紹到這里,讓大家了解以太坊中區(qū)塊頭的Bloom字段的作用就是根據(jù)合約地址和賬戶地址過(guò)濾以太坊日志的功能。方便用戶可以查看自己感興趣的日志。下面我們會(huì)逐步將這些內(nèi)容鋪開(kāi)來(lái)介紹。