core, eth/filters, miner, xeth: Optimised log filtering

Log filtering is now using a MIPmap like approach where addresses of
logs are added to a mapped bloom bin. The current levels for the MIP are
in ranges of 1.000.000, 500.000, 100.000, 50.000, 1.000. Logs are
therefor filtered in batches of 1.000.
This commit is contained in:
Jeffrey Wilcke
2015-10-12 17:58:51 +02:00
parent 30f057aaf9
commit 6dc14788a2
14 changed files with 647 additions and 134 deletions

View File

@ -654,10 +654,17 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
events = append(events, ChainEvent{block, block.Hash(), logs}) events = append(events, ChainEvent{block, block.Hash(), logs})
// This puts transactions in a extra db for rpc // This puts transactions in a extra db for rpc
PutTransactions(self.chainDb, block, block.Transactions()) if err := PutTransactions(self.chainDb, block, block.Transactions()); err != nil {
return i, err
}
// store the receipts // store the receipts
PutReceipts(self.chainDb, receipts) if err := PutReceipts(self.chainDb, receipts); err != nil {
return i, err
}
// Write map map bloom filters
if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil {
return i, err
}
case SideStatTy: case SideStatTy:
if glog.V(logger.Detail) { if glog.V(logger.Detail) {
glog.Infof("inserted forked block #%d (TD=%v) (%d TXs %d UNCs) (%x...). Took %v\n", block.Number(), block.Difficulty(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart)) glog.Infof("inserted forked block #%d (TD=%v) (%d TXs %d UNCs) (%x...). Took %v\n", block.Number(), block.Difficulty(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart))
@ -743,8 +750,18 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
// insert the block in the canonical way, re-writing history // insert the block in the canonical way, re-writing history
self.insert(block) self.insert(block)
// write canonical receipts and transactions // write canonical receipts and transactions
PutTransactions(self.chainDb, block, block.Transactions()) if err := PutTransactions(self.chainDb, block, block.Transactions()); err != nil {
PutReceipts(self.chainDb, GetBlockReceipts(self.chainDb, block.Hash())) return err
}
receipts := GetBlockReceipts(self.chainDb, block.Hash())
// write receipts
if err := PutReceipts(self.chainDb, receipts); err != nil {
return err
}
// Write map map bloom filters
if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil {
return err
}
addedTxs = append(addedTxs, block.Transactions()...) addedTxs = append(addedTxs, block.Transactions()...)
} }

View File

@ -105,7 +105,12 @@ func (b *BlockGen) AddTx(tx *types.Transaction) {
b.receipts = append(b.receipts, receipt) b.receipts = append(b.receipts, receipt)
} }
func (b *BlockGen) AddReceipt(receipt *types.Receipt) { // AddUncheckedReceipts forcefully adds a receipts to the block without a
// backing transaction.
//
// AddUncheckedReceipts will cause consensus failures when used during real
// chain processing. This is best used in conjuction with raw block insertion.
func (b *BlockGen) AddUncheckedReceipt(receipt *types.Receipt) {
b.receipts = append(b.receipts, receipt) b.receipts = append(b.receipts, receipt)
} }

View File

@ -18,6 +18,8 @@ package core
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -42,6 +44,9 @@ var (
ExpDiffPeriod = big.NewInt(100000) ExpDiffPeriod = big.NewInt(100000)
blockHashPre = []byte("block-hash-") // [deprecated by eth/63] blockHashPre = []byte("block-hash-") // [deprecated by eth/63]
mipmapPre = []byte("mipmap-log-bloom-")
MIPMapLevels = []uint64{1000000, 500000, 100000, 50000, 1000}
) )
// CalcDifficulty is the difficulty adjustment algorithm. It returns // CalcDifficulty is the difficulty adjustment algorithm. It returns
@ -346,3 +351,42 @@ func GetBlockByHashOld(db ethdb.Database, hash common.Hash) *types.Block {
} }
return (*types.Block)(&block) return (*types.Block)(&block)
} }
// returns a formatted MIP mapped key by adding prefix, canonical number and level
//
// ex. fn(98, 1000) = (prefix || 1000 || 0)
func mipmapKey(num, level uint64) []byte {
lkey := make([]byte, 8)
binary.BigEndian.PutUint64(lkey, level)
key := new(big.Int).SetUint64(num / level * level)
return append(mipmapPre, append(lkey, key.Bytes()...)...)
}
// WriteMapmapBloom writes each address included in the receipts' logs to the
// MIP bloom bin.
func WriteMipmapBloom(db ethdb.Database, number uint64, receipts types.Receipts) error {
batch := db.NewBatch()
for _, level := range MIPMapLevels {
key := mipmapKey(number, level)
bloomDat, _ := db.Get(key)
bloom := types.BytesToBloom(bloomDat)
for _, receipt := range receipts {
for _, log := range receipt.Logs() {
bloom.Add(log.Address.Big())
}
}
batch.Put(key, bloom.Bytes())
}
if err := batch.Write(); err != nil {
return fmt.Errorf("mipmap write fail for: %d: %v", number, err)
}
return nil
}
// GetMipmapBloom returns a bloom filter using the number and level as input
// parameters. For available levels see MIPMapLevels.
func GetMipmapBloom(db ethdb.Database, number, level uint64) types.Bloom {
bloomDat, _ := db.Get(mipmapKey(number, level))
return types.BytesToBloom(bloomDat)
}

View File

@ -18,12 +18,15 @@ package core
import ( import (
"encoding/json" "encoding/json"
"io/ioutil"
"math/big" "math/big"
"os" "os"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -318,3 +321,112 @@ func TestHeadStorage(t *testing.T) {
t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFull.Hash()) t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFull.Hash())
} }
} }
func TestMipmapBloom(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
receipt1 := new(types.Receipt)
receipt1.SetLogs(vm.Logs{
&vm.Log{Address: common.BytesToAddress([]byte("test"))},
&vm.Log{Address: common.BytesToAddress([]byte("address"))},
})
receipt2 := new(types.Receipt)
receipt2.SetLogs(vm.Logs{
&vm.Log{Address: common.BytesToAddress([]byte("test"))},
&vm.Log{Address: common.BytesToAddress([]byte("address1"))},
})
WriteMipmapBloom(db, 1, types.Receipts{receipt1})
WriteMipmapBloom(db, 2, types.Receipts{receipt2})
for _, level := range MIPMapLevels {
bloom := GetMipmapBloom(db, 2, level)
if !bloom.Test(new(big.Int).SetBytes([]byte("address1"))) {
t.Error("expected test to be included on level:", level)
}
}
// reset
db, _ = ethdb.NewMemDatabase()
receipt := new(types.Receipt)
receipt.SetLogs(vm.Logs{
&vm.Log{Address: common.BytesToAddress([]byte("test"))},
})
WriteMipmapBloom(db, 999, types.Receipts{receipt1})
receipt = new(types.Receipt)
receipt.SetLogs(vm.Logs{
&vm.Log{Address: common.BytesToAddress([]byte("test 1"))},
})
WriteMipmapBloom(db, 1000, types.Receipts{receipt})
bloom := GetMipmapBloom(db, 1000, 1000)
if bloom.TestBytes([]byte("test")) {
t.Error("test should not have been included")
}
}
func TestMipmapChain(t *testing.T) {
dir, err := ioutil.TempDir("", "mipmap")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
var (
db, _ = ethdb.NewLDBDatabase(dir, 16)
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key1.PublicKey)
addr2 = common.BytesToAddress([]byte("jeff"))
hash1 = common.BytesToHash([]byte("topic1"))
)
defer db.Close()
genesis := WriteGenesisBlockForTesting(db, GenesisAccount{addr, big.NewInt(1000000)})
chain := GenerateChain(genesis, db, 1010, func(i int, gen *BlockGen) {
var receipts types.Receipts
switch i {
case 1:
receipt := types.NewReceipt(nil, new(big.Int))
receipt.SetLogs(vm.Logs{
&vm.Log{
Address: addr,
Topics: []common.Hash{hash1},
},
})
gen.AddUncheckedReceipt(receipt)
receipts = types.Receipts{receipt}
case 1000:
receipt := types.NewReceipt(nil, new(big.Int))
receipt.SetLogs(vm.Logs{&vm.Log{Address: addr2}})
gen.AddUncheckedReceipt(receipt)
receipts = types.Receipts{receipt}
}
// store the receipts
err := PutReceipts(db, receipts)
if err != nil {
t.Fatal(err)
}
WriteMipmapBloom(db, uint64(i+1), receipts)
})
for _, block := range chain {
WriteBlock(db, block)
if err := WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil {
t.Fatalf("failed to insert block number: %v", err)
}
if err := WriteHeadBlockHash(db, block.Hash()); err != nil {
t.Fatalf("failed to insert block number: %v", err)
}
if err := PutBlockReceipts(db, block, block.Receipts()); err != nil {
t.Fatal("error writing block receipts:", err)
}
}
bloom := GetMipmapBloom(db, 0, 1000)
if bloom.TestBytes(addr2[:]) {
t.Error("address was included in bloom and should not have")
}
}

View File

@ -17,6 +17,8 @@
package core package core
import ( import (
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -32,22 +34,16 @@ var (
) )
// PutTransactions stores the transactions in the given database // PutTransactions stores the transactions in the given database
func PutTransactions(db ethdb.Database, block *types.Block, txs types.Transactions) { func PutTransactions(db ethdb.Database, block *types.Block, txs types.Transactions) error {
batch := new(leveldb.Batch) batch := db.NewBatch()
_, batchWrite := db.(*ethdb.LDBDatabase)
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
rlpEnc, err := rlp.EncodeToBytes(tx) rlpEnc, err := rlp.EncodeToBytes(tx)
if err != nil { if err != nil {
glog.V(logger.Debug).Infoln("Failed encoding tx", err) return fmt.Errorf("failed encoding tx: %v", err)
return
} }
if batchWrite {
batch.Put(tx.Hash().Bytes(), rlpEnc) batch.Put(tx.Hash().Bytes(), rlpEnc)
} else {
db.Put(tx.Hash().Bytes(), rlpEnc)
}
var txExtra struct { var txExtra struct {
BlockHash common.Hash BlockHash common.Hash
@ -59,22 +55,16 @@ func PutTransactions(db ethdb.Database, block *types.Block, txs types.Transactio
txExtra.Index = uint64(i) txExtra.Index = uint64(i)
rlpMeta, err := rlp.EncodeToBytes(txExtra) rlpMeta, err := rlp.EncodeToBytes(txExtra)
if err != nil { if err != nil {
glog.V(logger.Debug).Infoln("Failed encoding tx meta data", err) return fmt.Errorf("failed encoding tx meta data: %v", err)
return
} }
if batchWrite {
batch.Put(append(tx.Hash().Bytes(), 0x0001), rlpMeta) batch.Put(append(tx.Hash().Bytes(), 0x0001), rlpMeta)
} else {
db.Put(append(tx.Hash().Bytes(), 0x0001), rlpMeta)
}
} }
if db, ok := db.(*ethdb.LDBDatabase); ok { if err := batch.Write(); err != nil {
if err := db.LDB().Write(batch, nil); err != nil { return fmt.Errorf("failed writing tx to db: %v", err)
glog.V(logger.Error).Infoln("db write err:", err)
}
} }
return nil
} }
func DeleteTransaction(db ethdb.Database, txHash common.Hash) { func DeleteTransaction(db ethdb.Database, txHash common.Hash) {

View File

@ -17,6 +17,7 @@
package types package types
import ( import (
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -28,6 +29,46 @@ type bytesBacked interface {
Bytes() []byte Bytes() []byte
} }
const bloomLength = 256
type Bloom [bloomLength]byte
func BytesToBloom(b []byte) Bloom {
var bloom Bloom
bloom.SetBytes(b)
return bloom
}
func (b *Bloom) SetBytes(d []byte) {
if len(b) < len(d) {
panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d)))
}
copy(b[bloomLength-len(d):], d)
}
func (b *Bloom) Add(d *big.Int) {
bin := new(big.Int).SetBytes(b[:])
bin.Or(bin, bloom9(d.Bytes()))
b.SetBytes(bin.Bytes())
}
func (b Bloom) Big() *big.Int {
return common.Bytes2Big(b[:])
}
func (b Bloom) Bytes() []byte {
return b[:]
}
func (b Bloom) Test(test *big.Int) bool {
return BloomLookup(b, test)
}
func (b Bloom) TestBytes(test []byte) bool {
return b.Test(common.BytesToBig(test))
}
func CreateBloom(receipts Receipts) Bloom { func CreateBloom(receipts Receipts) Bloom {
bin := new(big.Int) bin := new(big.Int)
for _, receipt := range receipts { for _, receipt := range receipts {

View File

@ -16,6 +16,40 @@
package types package types
import (
"math/big"
"testing"
)
func TestBloom(t *testing.T) {
positive := []string{
"testtest",
"test",
"hallo",
"other",
}
negative := []string{
"tes",
"lo",
}
var bloom Bloom
for _, data := range positive {
bloom.Add(new(big.Int).SetBytes([]byte(data)))
}
for _, data := range positive {
if !bloom.Test(new(big.Int).SetBytes([]byte(data))) {
t.Error("expected", data, "to test true")
}
}
for _, data := range negative {
if bloom.Test(new(big.Int).SetBytes([]byte(data))) {
t.Error("did not expect", data, "to test true")
}
}
}
/* /*
import ( import (
"testing" "testing"

View File

@ -16,41 +16,8 @@
package types package types
import ( import "github.com/ethereum/go-ethereum/core/vm"
"math/big"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
)
type BlockProcessor interface { type BlockProcessor interface {
Process(*Block) (vm.Logs, Receipts, error) Process(*Block) (vm.Logs, Receipts, error)
} }
const bloomLength = 256
type Bloom [bloomLength]byte
func BytesToBloom(b []byte) Bloom {
var bloom Bloom
bloom.SetBytes(b)
return bloom
}
func (b *Bloom) SetBytes(d []byte) {
if len(b) < len(d) {
panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d)))
}
copy(b[bloomLength-len(d):], d)
}
func (b Bloom) Big() *big.Int {
return common.Bytes2Big(b[:])
}
func (b Bloom) Bytes() []byte {
return b[:]
}

View File

@ -47,6 +47,7 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/whisper" "github.com/ethereum/go-ethereum/whisper"
) )
@ -288,6 +289,9 @@ func New(config *Config) (*Ethereum, error) {
if err := upgradeChainDatabase(chainDb); err != nil { if err := upgradeChainDatabase(chainDb); err != nil {
return nil, err return nil, err
} }
if err := addMipmapBloomBins(chainDb); err != nil {
return nil, err
}
dappDb, err := newdb(filepath.Join(config.DataDir, "dapp")) dappDb, err := newdb(filepath.Join(config.DataDir, "dapp"))
if err != nil { if err != nil {
@ -769,3 +773,45 @@ func upgradeChainDatabase(db ethdb.Database) error {
} }
return nil return nil
} }
func addMipmapBloomBins(db ethdb.Database) (err error) {
const mipmapVersion uint = 2
// check if the version is set. We ignore data for now since there's
// only one version so we can easily ignore it for now
var data []byte
data, _ = db.Get([]byte("setting-mipmap-version"))
if len(data) > 0 {
var version uint
if err := rlp.DecodeBytes(data, &version); err == nil && version == mipmapVersion {
return nil
}
}
defer func() {
if err == nil {
var val []byte
val, err = rlp.EncodeToBytes(mipmapVersion)
if err == nil {
err = db.Put([]byte("setting-mipmap-version"), val)
}
return
}
}()
latestBlock := core.GetBlock(db, core.GetHeadBlockHash(db))
if latestBlock == nil { // clean database
return
}
tstart := time.Now()
glog.V(logger.Info).Infoln("upgrading db log bloom bins")
for i := uint64(0); i <= latestBlock.NumberU64(); i++ {
hash := core.GetCanonicalHash(db, i)
if (hash == common.Hash{}) {
return fmt.Errorf("chain db corrupted. Could not find block %d.", i)
}
core.WriteMipmapBloom(db, i, core.GetBlockReceipts(db, hash))
}
glog.V(logger.Info).Infoln("upgrade completed in", time.Since(tstart))
return nil
}

67
eth/backend_test.go Normal file
View File

@ -0,0 +1,67 @@
package eth
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
)
func TestMipmapUpgrade(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
addr := common.BytesToAddress([]byte("jeff"))
genesis := core.WriteGenesisBlockForTesting(db)
chain := core.GenerateChain(genesis, db, 10, func(i int, gen *core.BlockGen) {
var receipts types.Receipts
switch i {
case 1:
receipt := types.NewReceipt(nil, new(big.Int))
receipt.SetLogs(vm.Logs{&vm.Log{Address: addr}})
gen.AddUncheckedReceipt(receipt)
receipts = types.Receipts{receipt}
case 2:
receipt := types.NewReceipt(nil, new(big.Int))
receipt.SetLogs(vm.Logs{&vm.Log{Address: addr}})
gen.AddUncheckedReceipt(receipt)
receipts = types.Receipts{receipt}
}
// store the receipts
err := core.PutReceipts(db, receipts)
if err != nil {
t.Fatal(err)
}
})
for _, block := range chain {
core.WriteBlock(db, block)
if err := core.WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil {
t.Fatalf("failed to insert block number: %v", err)
}
if err := core.WriteHeadBlockHash(db, block.Hash()); err != nil {
t.Fatalf("failed to insert block number: %v", err)
}
if err := core.PutBlockReceipts(db, block, block.Receipts()); err != nil {
t.Fatal("error writing block receipts:", err)
}
}
err := addMipmapBloomBins(db)
if err != nil {
t.Fatal(err)
}
bloom := core.GetMipmapBloom(db, 1, core.MIPMapLevels[0])
if (bloom == types.Bloom{}) {
t.Error("got empty bloom filter")
}
data, _ := db.Get([]byte("setting-mipmap-version"))
if len(data) == 0 {
t.Error("setting-mipmap-version not written to database")
}
}

View File

@ -17,6 +17,8 @@
package filters package filters
import ( import (
"math"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -31,11 +33,8 @@ type AccountChange struct {
// Filtering interface // Filtering interface
type Filter struct { type Filter struct {
db ethdb.Database db ethdb.Database
earliest int64 begin, end int64
latest int64 addresses []common.Address
skip int
address []common.Address
max int
topics [][]common.Hash topics [][]common.Hash
BlockCallback func(*types.Block, vm.Logs) BlockCallback func(*types.Block, vm.Logs)
@ -52,59 +51,82 @@ func New(db ethdb.Database) *Filter {
// Set the earliest and latest block for filtering. // Set the earliest and latest block for filtering.
// -1 = latest block (i.e., the current block) // -1 = latest block (i.e., the current block)
// hash = particular hash from-to // hash = particular hash from-to
func (self *Filter) SetEarliestBlock(earliest int64) { func (self *Filter) SetBeginBlock(begin int64) {
self.earliest = earliest self.begin = begin
} }
func (self *Filter) SetLatestBlock(latest int64) { func (self *Filter) SetEndBlock(end int64) {
self.latest = latest self.end = end
} }
func (self *Filter) SetAddress(addr []common.Address) { func (self *Filter) SetAddresses(addr []common.Address) {
self.address = addr self.addresses = addr
} }
func (self *Filter) SetTopics(topics [][]common.Hash) { func (self *Filter) SetTopics(topics [][]common.Hash) {
self.topics = topics self.topics = topics
} }
func (self *Filter) SetMax(max int) {
self.max = max
}
func (self *Filter) SetSkip(skip int) {
self.skip = skip
}
// Run filters logs with the current parameters set // Run filters logs with the current parameters set
func (self *Filter) Find() vm.Logs { func (self *Filter) Find() vm.Logs {
earliestBlock := core.GetBlock(self.db, core.GetHeadBlockHash(self.db)) latestBlock := core.GetBlock(self.db, core.GetHeadBlockHash(self.db))
var earliestBlockNo uint64 = uint64(self.earliest) var beginBlockNo uint64 = uint64(self.begin)
if self.earliest == -1 { if self.begin == -1 {
earliestBlockNo = earliestBlock.NumberU64() beginBlockNo = latestBlock.NumberU64()
} }
var latestBlockNo uint64 = uint64(self.latest) var endBlockNo uint64 = uint64(self.end)
if self.latest == -1 { if self.end == -1 {
latestBlockNo = earliestBlock.NumberU64() endBlockNo = latestBlock.NumberU64()
} }
var ( // if no addresses are present we can't make use of fast search which
logs vm.Logs // uses the mipmap bloom filters to check for fast inclusion and uses
block *types.Block // higher range probability in order to ensure at least a false positive
) if len(self.addresses) == 0 {
hash := core.GetCanonicalHash(self.db, latestBlockNo) return self.getLogs(beginBlockNo, endBlockNo)
}
return self.mipFind(beginBlockNo, endBlockNo, 0)
}
func (self *Filter) mipFind(start, end uint64, depth int) (logs vm.Logs) {
level := core.MIPMapLevels[depth]
// normalise numerator so we can work in level specific batches and
// work with the proper range checks
for num := start / level * level; num <= end; num += level {
// find addresses in bloom filters
bloom := core.GetMipmapBloom(self.db, num, level)
for _, addr := range self.addresses {
if bloom.TestBytes(addr[:]) {
// range check normalised values and make sure that
// we're resolving the correct range instead of the
// normalised values.
start := uint64(math.Max(float64(num), float64(start)))
end := uint64(math.Min(float64(num+level-1), float64(end)))
if depth+1 == len(core.MIPMapLevels) {
logs = append(logs, self.getLogs(start, end)...)
} else {
logs = append(logs, self.mipFind(start, end, depth+1)...)
}
// break so we don't check the same range for each
// possible address. Checks on multiple addresses
// are handled further down the stack.
break
}
}
}
return logs
}
func (self *Filter) getLogs(start, end uint64) (logs vm.Logs) {
var block *types.Block
for i := start; i <= end; i++ {
hash := core.GetCanonicalHash(self.db, i)
if hash != (common.Hash{}) { if hash != (common.Hash{}) {
block = core.GetBlock(self.db, hash) block = core.GetBlock(self.db, hash)
} } else { // block not found
return logs
done:
for i := 0; block != nil; i++ {
// Quit on latest
switch {
case block.NumberU64() == 0:
break done
case block.NumberU64() < earliestBlockNo:
break done
} }
// Use bloom filtering to see if this block is interesting given the // Use bloom filtering to see if this block is interesting given the
@ -120,8 +142,6 @@ done:
} }
logs = append(logs, self.FilterLogs(unfiltered)...) logs = append(logs, self.FilterLogs(unfiltered)...)
} }
block = core.GetBlock(self.db, block.ParentHash())
} }
return logs return logs
@ -143,7 +163,7 @@ func (self *Filter) FilterLogs(logs vm.Logs) vm.Logs {
// Filter the logs for interesting stuff // Filter the logs for interesting stuff
Logs: Logs:
for _, log := range logs { for _, log := range logs {
if len(self.address) > 0 && !includes(self.address, log.Address) { if len(self.addresses) > 0 && !includes(self.addresses, log.Address) {
continue continue
} }
@ -179,9 +199,9 @@ Logs:
} }
func (self *Filter) bloomFilter(block *types.Block) bool { func (self *Filter) bloomFilter(block *types.Block) bool {
if len(self.address) > 0 { if len(self.addresses) > 0 {
var included bool var included bool
for _, addr := range self.address { for _, addr := range self.addresses {
if types.BloomLookup(block.Bloom(), addr) { if types.BloomLookup(block.Bloom(), addr) {
included = true included = true
break break

View File

@ -1,6 +1,7 @@
package filters package filters
import ( import (
"io/ioutil"
"math/big" "math/big"
"os" "os"
"testing" "testing"
@ -23,40 +24,42 @@ func makeReceipt(addr common.Address) *types.Receipt {
} }
func BenchmarkMipmaps(b *testing.B) { func BenchmarkMipmaps(b *testing.B) {
const dbname = "/tmp/mipmap" dir, err := ioutil.TempDir("", "mipmap")
if err != nil {
b.Fatal(err)
}
defer os.RemoveAll(dir)
var ( var (
db, _ = ethdb.NewLDBDatabase(dbname, 16) db, _ = ethdb.NewLDBDatabase(dir, 16)
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = crypto.PubkeyToAddress(key1.PublicKey)
addr2 = common.BytesToAddress([]byte("jeff")) addr2 = common.BytesToAddress([]byte("jeff"))
addr3 = common.BytesToAddress([]byte("ethereum")) addr3 = common.BytesToAddress([]byte("ethereum"))
addr4 = common.BytesToAddress([]byte("random addresses please")) addr4 = common.BytesToAddress([]byte("random addresses please"))
) )
defer func() { defer db.Close()
db.Close()
os.Remove(dbname)
}()
genesis := core.WriteGenesisBlockForTesting(db, core.GenesisAccount{addr1, big.NewInt(1000000)}) genesis := core.WriteGenesisBlockForTesting(db, core.GenesisAccount{addr1, big.NewInt(1000000)})
chain := core.GenerateChain(genesis, db, 100000, func(i int, gen *core.BlockGen) { chain := core.GenerateChain(genesis, db, 100010, func(i int, gen *core.BlockGen) {
var receipts types.Receipts var receipts types.Receipts
switch i { switch i {
case 2403: case 2403:
receipt := makeReceipt(addr1) receipt := makeReceipt(addr1)
receipts = types.Receipts{receipt} receipts = types.Receipts{receipt}
gen.AddReceipt(receipt) gen.AddUncheckedReceipt(receipt)
case 10340: case 1034:
receipt := makeReceipt(addr2) receipt := makeReceipt(addr2)
receipts = types.Receipts{receipt} receipts = types.Receipts{receipt}
gen.AddReceipt(receipt) gen.AddUncheckedReceipt(receipt)
case 34: case 34:
receipt := makeReceipt(addr3) receipt := makeReceipt(addr3)
receipts = types.Receipts{receipt} receipts = types.Receipts{receipt}
gen.AddReceipt(receipt) gen.AddUncheckedReceipt(receipt)
case 99999: case 99999:
receipt := makeReceipt(addr4) receipt := makeReceipt(addr4)
receipts = types.Receipts{receipt} receipts = types.Receipts{receipt}
gen.AddReceipt(receipt) gen.AddUncheckedReceipt(receipt)
} }
@ -65,6 +68,7 @@ func BenchmarkMipmaps(b *testing.B) {
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
core.WriteMipmapBloom(db, uint64(i+1), receipts)
}) })
for _, block := range chain { for _, block := range chain {
core.WriteBlock(db, block) core.WriteBlock(db, block)
@ -82,9 +86,9 @@ func BenchmarkMipmaps(b *testing.B) {
b.ResetTimer() b.ResetTimer()
filter := New(db) filter := New(db)
filter.SetAddress([]common.Address{addr1, addr2, addr3, addr4}) filter.SetAddresses([]common.Address{addr1, addr2, addr3, addr4})
filter.SetEarliestBlock(0) filter.SetBeginBlock(0)
filter.SetLatestBlock(-1) filter.SetEndBlock(-1)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
logs := filter.Find() logs := filter.Find()
@ -93,3 +97,171 @@ func BenchmarkMipmaps(b *testing.B) {
} }
} }
} }
func TestFilters(t *testing.T) {
dir, err := ioutil.TempDir("", "mipmap")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
var (
db, _ = ethdb.NewLDBDatabase(dir, 16)
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key1.PublicKey)
hash1 = common.BytesToHash([]byte("topic1"))
hash2 = common.BytesToHash([]byte("topic2"))
hash3 = common.BytesToHash([]byte("topic3"))
hash4 = common.BytesToHash([]byte("topic4"))
)
defer db.Close()
genesis := core.WriteGenesisBlockForTesting(db, core.GenesisAccount{addr, big.NewInt(1000000)})
chain := core.GenerateChain(genesis, db, 1000, func(i int, gen *core.BlockGen) {
var receipts types.Receipts
switch i {
case 1:
receipt := types.NewReceipt(nil, new(big.Int))
receipt.SetLogs(vm.Logs{
&vm.Log{
Address: addr,
Topics: []common.Hash{hash1},
},
})
gen.AddUncheckedReceipt(receipt)
receipts = types.Receipts{receipt}
case 2:
receipt := types.NewReceipt(nil, new(big.Int))
receipt.SetLogs(vm.Logs{
&vm.Log{
Address: addr,
Topics: []common.Hash{hash2},
},
})
gen.AddUncheckedReceipt(receipt)
receipts = types.Receipts{receipt}
case 998:
receipt := types.NewReceipt(nil, new(big.Int))
receipt.SetLogs(vm.Logs{
&vm.Log{
Address: addr,
Topics: []common.Hash{hash3},
},
})
gen.AddUncheckedReceipt(receipt)
receipts = types.Receipts{receipt}
case 999:
receipt := types.NewReceipt(nil, new(big.Int))
receipt.SetLogs(vm.Logs{
&vm.Log{
Address: addr,
Topics: []common.Hash{hash4},
},
})
gen.AddUncheckedReceipt(receipt)
receipts = types.Receipts{receipt}
}
// store the receipts
err := core.PutReceipts(db, receipts)
if err != nil {
t.Fatal(err)
}
// i is used as block number for the writes but since the i
// starts at 0 and block 0 (genesis) is already present increment
// by one
core.WriteMipmapBloom(db, uint64(i+1), receipts)
})
for _, block := range chain {
core.WriteBlock(db, block)
if err := core.WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil {
t.Fatalf("failed to insert block number: %v", err)
}
if err := core.WriteHeadBlockHash(db, block.Hash()); err != nil {
t.Fatalf("failed to insert block number: %v", err)
}
if err := core.PutBlockReceipts(db, block, block.Receipts()); err != nil {
t.Fatal("error writing block receipts:", err)
}
}
filter := New(db)
filter.SetAddresses([]common.Address{addr})
filter.SetTopics([][]common.Hash{[]common.Hash{hash1, hash2, hash3, hash4}})
filter.SetBeginBlock(0)
filter.SetEndBlock(-1)
logs := filter.Find()
if len(logs) != 4 {
t.Error("expected 4 log, got", len(logs))
}
filter = New(db)
filter.SetAddresses([]common.Address{addr})
filter.SetTopics([][]common.Hash{[]common.Hash{hash3}})
filter.SetBeginBlock(900)
filter.SetEndBlock(999)
logs = filter.Find()
if len(logs) != 1 {
t.Error("expected 1 log, got", len(logs))
}
if len(logs) > 0 && logs[0].Topics[0] != hash3 {
t.Errorf("expected log[0].Topics[0] to be %x, got %x", hash3, logs[0].Topics[0])
}
filter = New(db)
filter.SetAddresses([]common.Address{addr})
filter.SetTopics([][]common.Hash{[]common.Hash{hash3}})
filter.SetBeginBlock(990)
filter.SetEndBlock(-1)
logs = filter.Find()
if len(logs) != 1 {
t.Error("expected 1 log, got", len(logs))
}
if len(logs) > 0 && logs[0].Topics[0] != hash3 {
t.Errorf("expected log[0].Topics[0] to be %x, got %x", hash3, logs[0].Topics[0])
}
filter = New(db)
filter.SetTopics([][]common.Hash{[]common.Hash{hash1, hash2}})
filter.SetBeginBlock(1)
filter.SetEndBlock(10)
logs = filter.Find()
if len(logs) != 2 {
t.Error("expected 2 log, got", len(logs))
}
failHash := common.BytesToHash([]byte("fail"))
filter = New(db)
filter.SetTopics([][]common.Hash{[]common.Hash{failHash}})
filter.SetBeginBlock(0)
filter.SetEndBlock(-1)
logs = filter.Find()
if len(logs) != 0 {
t.Error("expected 0 log, got", len(logs))
}
failAddr := common.BytesToAddress([]byte("failmenow"))
filter = New(db)
filter.SetAddresses([]common.Address{failAddr})
filter.SetBeginBlock(0)
filter.SetEndBlock(-1)
logs = filter.Find()
if len(logs) != 0 {
t.Error("expected 0 log, got", len(logs))
}
filter = New(db)
filter.SetTopics([][]common.Hash{[]common.Hash{failHash}, []common.Hash{hash1}})
filter.SetBeginBlock(0)
filter.SetEndBlock(-1)
logs = filter.Find()
if len(logs) != 0 {
t.Error("expected 0 log, got", len(logs))
}
}

View File

@ -301,6 +301,8 @@ func (self *worker) wait() {
core.PutTransactions(self.chainDb, block, block.Transactions()) core.PutTransactions(self.chainDb, block, block.Transactions())
// store the receipts // store the receipts
core.PutReceipts(self.chainDb, work.receipts) core.PutReceipts(self.chainDb, work.receipts)
// Write map map bloom filters
core.WriteMipmapBloom(self.chainDb, block.NumberU64(), work.receipts)
} }
// broadcast before waiting for validation // broadcast before waiting for validation

View File

@ -543,11 +543,9 @@ func (self *XEth) NewLogFilter(earliest, latest int64, skip, max int, address []
id := self.filterManager.Add(filter) id := self.filterManager.Add(filter)
self.logQueue[id] = &logQueue{timeout: time.Now()} self.logQueue[id] = &logQueue{timeout: time.Now()}
filter.SetEarliestBlock(earliest) filter.SetBeginBlock(earliest)
filter.SetLatestBlock(latest) filter.SetEndBlock(latest)
filter.SetSkip(skip) filter.SetAddresses(cAddress(address))
filter.SetMax(max)
filter.SetAddress(cAddress(address))
filter.SetTopics(cTopics(topics)) filter.SetTopics(cTopics(topics))
filter.LogsCallback = func(logs vm.Logs) { filter.LogsCallback = func(logs vm.Logs) {
self.logMu.Lock() self.logMu.Lock()
@ -652,11 +650,9 @@ func (self *XEth) Logs(id int) vm.Logs {
func (self *XEth) AllLogs(earliest, latest int64, skip, max int, address []string, topics [][]string) vm.Logs { func (self *XEth) AllLogs(earliest, latest int64, skip, max int, address []string, topics [][]string) vm.Logs {
filter := filters.New(self.backend.ChainDb()) filter := filters.New(self.backend.ChainDb())
filter.SetEarliestBlock(earliest) filter.SetBeginBlock(earliest)
filter.SetLatestBlock(latest) filter.SetEndBlock(latest)
filter.SetSkip(skip) filter.SetAddresses(cAddress(address))
filter.SetMax(max)
filter.SetAddress(cAddress(address))
filter.SetTopics(cTopics(topics)) filter.SetTopics(cTopics(topics))
return filter.Find() return filter.Find()