Ethereum中的Bloom Filter

以太坊中的布隆过滤器

Posted by ZBX on February 24, 2020

概念

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询效率高,不会漏判,缺点是有一定的误判率。

日志中的Bloom Filter

以太坊提供Bloom Filter的功能是为了快速查找合约的交易或者发起交易的账户。某个账户根据合约地址查找相应的合约发生过的交易,就需要去每个区块头一个一个找到交易收据,Bloom Filter就可以快速的判断出来该块是否包含相关的记录。

下面是以太坊源码中区块头的结构的定义,可以看到中间有个Bloom的字段。

type Header struct {
   ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`
   UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`
   Coinbase    common.Address `json:"miner"            gencodec:"required"`
   Root        common.Hash    `json:"stateRoot"        gencodec:"required"`
   TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
   ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`
   Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`
   Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`
   Number      *big.Int       `json:"number"           gencodec:"required"`
   GasLimit    uint64         `json:"gasLimit"         gencodec:"required"`
   GasUsed     uint64         `json:"gasUsed"          gencodec:"required"`
   Time        uint64         `json:"timestamp"        gencodec:"required"`
   Extra       []byte         `json:"extraData"        gencodec:"required"`
   MixDigest   common.Hash    `json:"mixHash"`
   Nonce       BlockNonce     `json:"nonce"`
}

Bloom的结构如下:

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

区块头中的Bloom字段是个2048的字节数组。

生成Bloom

下面是生成Bloom的CreateBloom方法的源码:

func CreateBloom(receipts Receipts) Bloom {
    bin := new(big.Int)
    for _, receipt := range receipts {
        bin.Or(bin, LogsBloom(receipt.Logs))  //Logs中包含Address和Topics字段, Address是合约地址,Topics是合约的转账过程的账户地址
    }

    return BytesToBloom(bin.Bytes())
}

其中调用的LogsBloom方法如下:

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
}

该方法就是把Log中的Address和Topics放入bloom9中,bloom9方法如下:

func bloom9(b []byte) *big.Int {
    b = crypto.Keccak256(b)  // Keccak256是哈希算法,运算得160比特hash值
    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
}

BloomLookup查找

接下来就是检索元素是否在日志中

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) { //首先查找Address
                included = true
                break
            }
        }
        if !included {
            return false
        }
    }

    for _, sub := range topics {      //如果包含相应的Address,则查找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
}

其中BloomLookup方法如下:

func BloomLookup(bin Bloom, topic bytesBacked) bool {
    bloom := bin.Big()
    cmp := bloom9(topic.Bytes())

    return bloom.And(bloom, cmp).Cmp(cmp) == 0
}