core, eth: clean up bloom filtering, add some tests
This commit is contained in:
		
							
								
								
									
										18
									
								
								core/bloombits/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								core/bloombits/doc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| // Copyright 2017 The go-ethereum Authors | ||||
| // This file is part of the go-ethereum library. | ||||
| // | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Lesser General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // The go-ethereum library is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU Lesser General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| // Package bloombits implements bloom filtering on batches of data. | ||||
| package bloombits | ||||
| @@ -1,101 +0,0 @@ | ||||
| // Copyright 2017 The go-ethereum Authors | ||||
| // This file is part of the go-ethereum library. | ||||
| // | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Lesser General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // The go-ethereum library is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU Lesser General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
| package bloombits | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/binary" | ||||
| 	"math/rand" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const testFetcherReqCount = 5000 | ||||
|  | ||||
| func fetcherTestVector(b uint, s uint64) []byte { | ||||
| 	r := make([]byte, 10) | ||||
| 	binary.BigEndian.PutUint16(r[0:2], uint16(b)) | ||||
| 	binary.BigEndian.PutUint64(r[2:10], s) | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func TestFetcher(t *testing.T) { | ||||
| 	testFetcher(t, 1) | ||||
| } | ||||
|  | ||||
| func TestFetcherMultipleReaders(t *testing.T) { | ||||
| 	testFetcher(t, 10) | ||||
| } | ||||
|  | ||||
| func testFetcher(t *testing.T, cnt int) { | ||||
| 	f := &fetcher{ | ||||
| 		requestMap: make(map[uint64]fetchRequest), | ||||
| 	} | ||||
| 	distCh := make(chan distRequest, channelCap) | ||||
| 	stop := make(chan struct{}) | ||||
| 	var reqCount uint32 | ||||
|  | ||||
| 	for i := 0; i < 10; i++ { | ||||
| 		go func() { | ||||
| 			for { | ||||
| 				req, ok := <-distCh | ||||
| 				if !ok { | ||||
| 					return | ||||
| 				} | ||||
| 				time.Sleep(time.Duration(rand.Intn(100000))) | ||||
| 				atomic.AddUint32(&reqCount, 1) | ||||
| 				f.deliver([]uint64{req.sectionIndex}, [][]byte{fetcherTestVector(req.bloomIndex, req.sectionIndex)}) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	var wg, wg2 sync.WaitGroup | ||||
| 	for cc := 0; cc < cnt; cc++ { | ||||
| 		wg.Add(1) | ||||
| 		in := make(chan uint64, channelCap) | ||||
| 		out := f.fetch(in, distCh, stop, &wg2) | ||||
|  | ||||
| 		time.Sleep(time.Millisecond * 10 * time.Duration(cc)) | ||||
| 		go func() { | ||||
| 			for i := uint64(0); i < testFetcherReqCount; i++ { | ||||
| 				in <- i | ||||
| 			} | ||||
| 		}() | ||||
|  | ||||
| 		go func() { | ||||
| 			for i := uint64(0); i < testFetcherReqCount; i++ { | ||||
| 				bv := <-out | ||||
| 				if !bytes.Equal(bv, fetcherTestVector(0, i)) { | ||||
| 					if len(bv) != 10 { | ||||
| 						t.Errorf("Vector #%d length is %d, expected 10", i, len(bv)) | ||||
| 					} else { | ||||
| 						j := binary.BigEndian.Uint64(bv[2:10]) | ||||
| 						t.Errorf("Expected vector #%d, fetched #%d", i, j) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			wg.Done() | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	wg.Wait() | ||||
| 	close(stop) | ||||
| 	if reqCount != testFetcherReqCount { | ||||
| 		t.Errorf("Request count mismatch: expected %v, got %v", testFetcherReqCount, reqCount) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										84
									
								
								core/bloombits/generator.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								core/bloombits/generator.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| // Copyright 2017 The go-ethereum Authors | ||||
| // This file is part of the go-ethereum library. | ||||
| // | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Lesser General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // The go-ethereum library is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU Lesser General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package bloombits | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| ) | ||||
|  | ||||
| // errSectionOutOfBounds is returned if the user tried to add more bloom filters | ||||
| // to the batch than available space, or if tries to retrieve above the capacity, | ||||
| var errSectionOutOfBounds = errors.New("section out of bounds") | ||||
|  | ||||
| // Generator takes a number of bloom filters and generates the rotated bloom bits | ||||
| // to be used for batched filtering. | ||||
| type Generator struct { | ||||
| 	blooms   [types.BloomBitLength][]byte // Rotated blooms for per-bit matching | ||||
| 	sections uint                         // Number of sections to batch together | ||||
| 	nextBit  uint                         // Next bit to set when adding a bloom | ||||
| } | ||||
|  | ||||
| // NewGenerator creates a rotated bloom generator that can iteratively fill a | ||||
| // batched bloom filter's bits. | ||||
| func NewGenerator(sections uint) (*Generator, error) { | ||||
| 	if sections%8 != 0 { | ||||
| 		return nil, errors.New("section count not multiple of 8") | ||||
| 	} | ||||
| 	b := &Generator{sections: sections} | ||||
| 	for i := 0; i < types.BloomBitLength; i++ { | ||||
| 		b.blooms[i] = make([]byte, sections/8) | ||||
| 	} | ||||
| 	return b, nil | ||||
| } | ||||
|  | ||||
| // AddBloom takes a single bloom filter and sets the corresponding bit column | ||||
| // in memory accordingly. | ||||
| func (b *Generator) AddBloom(bloom types.Bloom) error { | ||||
| 	// Make sure we're not adding more bloom filters than our capacity | ||||
| 	if b.nextBit >= b.sections { | ||||
| 		return errSectionOutOfBounds | ||||
| 	} | ||||
| 	// Rotate the bloom and insert into our collection | ||||
| 	byteMask := b.nextBit / 8 | ||||
| 	bitMask := byte(1) << byte(7-b.nextBit%8) | ||||
|  | ||||
| 	for i := 0; i < types.BloomBitLength; i++ { | ||||
| 		bloomByteMask := types.BloomByteLength - 1 - i/8 | ||||
| 		bloomBitMask := byte(1) << byte(i%8) | ||||
|  | ||||
| 		if (bloom[bloomByteMask] & bloomBitMask) != 0 { | ||||
| 			b.blooms[i][byteMask] |= bitMask | ||||
| 		} | ||||
| 	} | ||||
| 	b.nextBit++ | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Bitset returns the bit vector belonging to the given bit index after all | ||||
| // blooms have been added. | ||||
| func (b *Generator) Bitset(idx uint) ([]byte, error) { | ||||
| 	if b.nextBit != b.sections { | ||||
| 		return nil, errors.New("bloom not fully generated yet") | ||||
| 	} | ||||
| 	if idx >= b.sections { | ||||
| 		return nil, errSectionOutOfBounds | ||||
| 	} | ||||
| 	return b.blooms[idx], nil | ||||
| } | ||||
							
								
								
									
										60
									
								
								core/bloombits/generator_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								core/bloombits/generator_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| // Copyright 2017 The go-ethereum Authors | ||||
| // This file is part of the go-ethereum library. | ||||
| // | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Lesser General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // The go-ethereum library is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU Lesser General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package bloombits | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"math/rand" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| ) | ||||
|  | ||||
| // Tests that batched bloom bits are correctly rotated from the input bloom | ||||
| // filters. | ||||
| func TestGenerator(t *testing.T) { | ||||
| 	// Generate the input and the rotated output | ||||
| 	var input, output [types.BloomBitLength][types.BloomByteLength]byte | ||||
|  | ||||
| 	for i := 0; i < types.BloomBitLength; i++ { | ||||
| 		for j := 0; j < types.BloomBitLength; j++ { | ||||
| 			bit := byte(rand.Int() % 2) | ||||
|  | ||||
| 			input[i][j/8] |= bit << byte(7-j%8) | ||||
| 			output[types.BloomBitLength-1-j][i/8] |= bit << byte(7-i%8) | ||||
| 		} | ||||
| 	} | ||||
| 	// Crunch the input through the generator and verify the result | ||||
| 	gen, err := NewGenerator(types.BloomBitLength) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create bloombit generator: %v", err) | ||||
| 	} | ||||
| 	for i, bloom := range input { | ||||
| 		if err := gen.AddBloom(bloom); err != nil { | ||||
| 			t.Fatalf("bloom %d: failed to add: %v", i, err) | ||||
| 		} | ||||
| 	} | ||||
| 	for i, want := range output { | ||||
| 		have, err := gen.Bitset(uint(i)) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("output %d: failed to retrieve bits: %v", i, err) | ||||
| 		} | ||||
| 		if !bytes.Equal(have, want[:]) { | ||||
| 			t.Errorf("output %d: bit vector mismatch have %x, want %x", i, have, want) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -13,6 +13,7 @@ | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package bloombits | ||||
|  | ||||
| import ( | ||||
| @@ -20,151 +21,45 @@ import ( | ||||
| 	"sync/atomic" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| ) | ||||
|  | ||||
| const testSectionSize = 4096 | ||||
|  | ||||
| func matcherTestVector(b uint, s uint64) []byte { | ||||
| 	r := make([]byte, testSectionSize/8) | ||||
| 	for i, _ := range r { | ||||
| 		var bb byte | ||||
| 		for bit := 0; bit < 8; bit++ { | ||||
| 			blockIdx := s*testSectionSize + uint64(i*8+bit) | ||||
| 			bb += bb | ||||
| 			if (blockIdx % uint64(b)) == 0 { | ||||
| 				bb++ | ||||
| 			} | ||||
| 		} | ||||
| 		r[i] = bb | ||||
| 	} | ||||
| 	return r | ||||
| // Tests the matcher pipeline on a single continuous workflow without interrupts. | ||||
| func TestMatcherContinuous(t *testing.T) { | ||||
| 	testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 100000, false, 75) | ||||
| 	testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 100000, false, 81) | ||||
| 	testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 10000, false, 36) | ||||
| } | ||||
|  | ||||
| func expMatch1(idxs types.BloomIndexList, i uint64) bool { | ||||
| 	for _, ii := range idxs { | ||||
| 		if (i % uint64(ii)) != 0 { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| // Tests the matcher pipeline on a constantly interrupted and resumed work pattern | ||||
| // with the aim of ensuring data items are requested only once. | ||||
| func TestMatcherIntermittent(t *testing.T) { | ||||
| 	testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 100000, true, 75) | ||||
| 	testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 100000, true, 81) | ||||
| 	testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 10000, true, 36) | ||||
| } | ||||
|  | ||||
| func expMatch2(idxs []types.BloomIndexList, i uint64) bool { | ||||
| 	for _, ii := range idxs { | ||||
| 		if expMatch1(ii, i) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func expMatch3(idxs [][]types.BloomIndexList, i uint64) bool { | ||||
| 	for _, ii := range idxs { | ||||
| 		if !expMatch2(ii, i) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func testServeMatcher(m *Matcher, stop chan struct{}, cnt *uint32, maxRequestLen int) { | ||||
| 	// serve matcher with test vectors | ||||
| // Tests the matcher pipeline on random input to hopefully catch anomalies. | ||||
| func TestMatcherRandom(t *testing.T) { | ||||
| 	for i := 0; i < 10; i++ { | ||||
| 		go func() { | ||||
| 			for { | ||||
| 				select { | ||||
| 				case <-stop: | ||||
| 					return | ||||
| 				default: | ||||
| 				} | ||||
| 				b, ok := m.AllocSectionQueue() | ||||
| 				if !ok { | ||||
| 					return | ||||
| 				} | ||||
| 				if m.SectionCount(b) < maxRequestLen { | ||||
| 					time.Sleep(time.Microsecond * 100) | ||||
| 				} | ||||
| 				s := m.FetchSections(b, maxRequestLen) | ||||
| 				res := make([][]byte, len(s)) | ||||
| 				for i, ss := range s { | ||||
| 					res[i] = matcherTestVector(b, ss) | ||||
| 					atomic.AddUint32(cnt, 1) | ||||
| 				} | ||||
| 				m.Deliver(b, s, res) | ||||
| 			} | ||||
| 		}() | ||||
| 		testMatcherBothModes(t, makeRandomIndexes([]int{1}, 50), 10000, 0) | ||||
| 		testMatcherBothModes(t, makeRandomIndexes([]int{3}, 50), 10000, 0) | ||||
| 		testMatcherBothModes(t, makeRandomIndexes([]int{2, 2, 2}, 20), 10000, 0) | ||||
| 		testMatcherBothModes(t, makeRandomIndexes([]int{5, 5, 5}, 50), 10000, 0) | ||||
| 		testMatcherBothModes(t, makeRandomIndexes([]int{4, 4, 4}, 20), 10000, 0) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testMatcher(t *testing.T, idxs [][]types.BloomIndexList, cnt uint64, stopOnMatches bool, expCount uint32) uint32 { | ||||
| 	count1 := testMatcherWithReqCount(t, idxs, cnt, stopOnMatches, expCount, 1) | ||||
| 	count16 := testMatcherWithReqCount(t, idxs, cnt, stopOnMatches, expCount, 16) | ||||
| 	if count1 != count16 { | ||||
| 		t.Errorf("Error matching idxs = %v  count = %v  stopOnMatches = %v: request count mismatch, %v with maxReqCount = 1 vs. %v with maxReqCount = 16", idxs, cnt, stopOnMatches, count1, count16) | ||||
| 	} | ||||
| 	return count1 | ||||
| } | ||||
|  | ||||
| func testMatcherWithReqCount(t *testing.T, idxs [][]types.BloomIndexList, cnt uint64, stopOnMatches bool, expCount uint32, maxReqCount int) uint32 { | ||||
| 	m := NewMatcher(testSectionSize, nil, nil) | ||||
|  | ||||
| 	for _, idxss := range idxs { | ||||
| 		for _, idxs := range idxss { | ||||
| 			for _, idx := range idxs { | ||||
| 				m.newFetcher(idx) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	m.addresses = idxs[0] | ||||
| 	m.topics = idxs[1:] | ||||
| 	var reqCount uint32 | ||||
|  | ||||
| 	stop := make(chan struct{}) | ||||
| 	chn := m.Start(0, cnt-1) | ||||
| 	testServeMatcher(m, stop, &reqCount, maxReqCount) | ||||
|  | ||||
| 	for i := uint64(0); i < cnt; i++ { | ||||
| 		if expMatch3(idxs, i) { | ||||
| 			match, ok := <-chn | ||||
| 			if !ok { | ||||
| 				t.Errorf("Error matching idxs = %v  count = %v  stopOnMatches = %v: expected #%v, results channel closed", idxs, cnt, stopOnMatches, i) | ||||
| 				return 0 | ||||
| 			} | ||||
| 			if match != i { | ||||
| 				t.Errorf("Error matching idxs = %v  count = %v  stopOnMatches = %v: expected #%v, got #%v", idxs, cnt, stopOnMatches, i, match) | ||||
| 			} | ||||
| 			if stopOnMatches { | ||||
| 				m.Stop() | ||||
| 				close(stop) | ||||
| 				stop = make(chan struct{}) | ||||
| 				chn = m.Start(i+1, cnt-1) | ||||
| 				testServeMatcher(m, stop, &reqCount, maxReqCount) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	match, ok := <-chn | ||||
| 	if ok { | ||||
| 		t.Errorf("Error matching idxs = %v  count = %v  stopOnMatches = %v: expected closed channel, got #%v", idxs, cnt, stopOnMatches, match) | ||||
| 	} | ||||
| 	m.Stop() | ||||
| 	close(stop) | ||||
|  | ||||
| 	if expCount != 0 && expCount != reqCount { | ||||
| 		t.Errorf("Error matching idxs = %v  count = %v  stopOnMatches = %v: request count mismatch, expected #%v, got #%v", idxs, cnt, stopOnMatches, expCount, reqCount) | ||||
| 	} | ||||
|  | ||||
| 	return reqCount | ||||
| } | ||||
|  | ||||
| func testRandomIdxs(l []int, max int) [][]types.BloomIndexList { | ||||
| 	res := make([][]types.BloomIndexList, len(l)) | ||||
| 	for i, ll := range l { | ||||
| 		res[i] = make([]types.BloomIndexList, ll) | ||||
| 		for j, _ := range res[i] { | ||||
| 			for k, _ := range res[i][j] { | ||||
| // makeRandomIndexes generates a random filter system, composed on multiple filter | ||||
| // criteria, each having one bloom list component for the address and arbitrarilly | ||||
| // many topic bloom list components. | ||||
| func makeRandomIndexes(lengths []int, max int) [][]bloomIndexes { | ||||
| 	res := make([][]bloomIndexes, len(lengths)) | ||||
| 	for i, topics := range lengths { | ||||
| 		res[i] = make([]bloomIndexes, topics) | ||||
| 		for j := 0; j < topics; j++ { | ||||
| 			for k := 0; k < len(res[i][j]); k++ { | ||||
| 				res[i][j][k] = uint(rand.Intn(max-1) + 2) | ||||
| 			} | ||||
| 		} | ||||
| @@ -172,25 +67,173 @@ func testRandomIdxs(l []int, max int) [][]types.BloomIndexList { | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func TestMatcher(t *testing.T) { | ||||
| 	testMatcher(t, [][]types.BloomIndexList{{{10, 20, 30}}}, 100000, false, 75) | ||||
| 	testMatcher(t, [][]types.BloomIndexList{{{32, 3125, 100}}, {{40, 50, 10}}}, 100000, false, 81) | ||||
| 	testMatcher(t, [][]types.BloomIndexList{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 10000, false, 36) | ||||
| // testMatcherDiffBatches runs the given matches test in single-delivery and also | ||||
| // in batches delivery mode, verifying that all kinds of deliveries are handled | ||||
| // correctly withn. | ||||
| func testMatcherDiffBatches(t *testing.T, filter [][]bloomIndexes, blocks uint64, intermittent bool, retrievals uint32) { | ||||
| 	singleton := testMatcher(t, filter, blocks, intermittent, retrievals, 1) | ||||
| 	batched := testMatcher(t, filter, blocks, intermittent, retrievals, 16) | ||||
|  | ||||
| 	if singleton != batched { | ||||
| 		t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, %v in signleton vs. %v in batched mode", filter, blocks, intermittent, singleton, batched) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMatcherStopOnMatches(t *testing.T) { | ||||
| 	testMatcher(t, [][]types.BloomIndexList{{{10, 20, 30}}}, 100000, true, 75) | ||||
| 	testMatcher(t, [][]types.BloomIndexList{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 10000, true, 36) | ||||
| // testMatcherBothModes runs the given matcher test in both continuous as well as | ||||
| // in intermittent mode, verifying that the request counts match each other. | ||||
| func testMatcherBothModes(t *testing.T, filter [][]bloomIndexes, blocks uint64, retrievals uint32) { | ||||
| 	continuous := testMatcher(t, filter, blocks, false, retrievals, 16) | ||||
| 	intermittent := testMatcher(t, filter, blocks, true, retrievals, 16) | ||||
|  | ||||
| 	if continuous != intermittent { | ||||
| 		t.Errorf("filter = %v blocks = %v: request count mismatch, %v in continuous vs. %v in intermittent mode", filter, blocks, continuous, intermittent) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMatcherRandom(t *testing.T) { | ||||
| 	for i := 0; i < 20; i++ { | ||||
| 		testMatcher(t, testRandomIdxs([]int{1}, 50), 100000, false, 0) | ||||
| 		testMatcher(t, testRandomIdxs([]int{3}, 50), 100000, false, 0) | ||||
| 		testMatcher(t, testRandomIdxs([]int{2, 2, 2}, 20), 100000, false, 0) | ||||
| 		testMatcher(t, testRandomIdxs([]int{5, 5, 5}, 50), 100000, false, 0) | ||||
| 		idxs := testRandomIdxs([]int{2, 2, 2}, 20) | ||||
| 		reqCount := testMatcher(t, idxs, 10000, false, 0) | ||||
| 		testMatcher(t, idxs, 10000, true, reqCount) | ||||
| // testMatcher is a generic tester to run the given matcher test and return the | ||||
| // number of requests made for cross validation between different modes. | ||||
| func testMatcher(t *testing.T, filter [][]bloomIndexes, blocks uint64, intermittent bool, retrievals uint32, maxReqCount int) uint32 { | ||||
| 	// Create a new matcher an simulate our explicit random bitsets | ||||
| 	matcher := NewMatcher(testSectionSize, nil, nil) | ||||
|  | ||||
| 	matcher.addresses = filter[0] | ||||
| 	matcher.topics = filter[1:] | ||||
|  | ||||
| 	for _, rule := range filter { | ||||
| 		for _, topic := range rule { | ||||
| 			for _, bit := range topic { | ||||
| 				matcher.addScheduler(bit) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// Track the number of retrieval requests made | ||||
| 	var requested uint32 | ||||
|  | ||||
| 	// Start the matching session for the filter and the retriver goroutines | ||||
| 	quit := make(chan struct{}) | ||||
| 	matches := make(chan uint64, 16) | ||||
|  | ||||
| 	session, err := matcher.Start(0, blocks-1, matches) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to stat matcher session: %v", err) | ||||
| 	} | ||||
| 	startRetrievers(session, quit, &requested, maxReqCount) | ||||
|  | ||||
| 	// Iterate over all the blocks and verify that the pipeline produces the correct matches | ||||
| 	for i := uint64(0); i < blocks; i++ { | ||||
| 		if expMatch3(filter, i) { | ||||
| 			match, ok := <-matches | ||||
| 			if !ok { | ||||
| 				t.Errorf("filter = %v  blocks = %v  intermittent = %v: expected #%v, results channel closed", filter, blocks, intermittent, i) | ||||
| 				return 0 | ||||
| 			} | ||||
| 			if match != i { | ||||
| 				t.Errorf("filter = %v  blocks = %v  intermittent = %v: expected #%v, got #%v", filter, blocks, intermittent, i, match) | ||||
| 			} | ||||
| 			// If we're testing intermittent mode, abort and restart the pipeline | ||||
| 			if intermittent { | ||||
| 				session.Close(time.Second) | ||||
| 				close(quit) | ||||
|  | ||||
| 				quit = make(chan struct{}) | ||||
| 				matches = make(chan uint64, 16) | ||||
|  | ||||
| 				session, err = matcher.Start(i+1, blocks-1, matches) | ||||
| 				if err != nil { | ||||
| 					t.Fatalf("failed to stat matcher session: %v", err) | ||||
| 				} | ||||
| 				startRetrievers(session, quit, &requested, maxReqCount) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// Ensure the result channel is torn down after the last block | ||||
| 	match, ok := <-matches | ||||
| 	if ok { | ||||
| 		t.Errorf("filter = %v  blocks = %v  intermittent = %v: expected closed channel, got #%v", filter, blocks, intermittent, match) | ||||
| 	} | ||||
| 	// Clean up the session and ensure we match the expected retrieval count | ||||
| 	session.Close(time.Second) | ||||
| 	close(quit) | ||||
|  | ||||
| 	if retrievals != 0 && requested != retrievals { | ||||
| 		t.Errorf("filter = %v  blocks = %v  intermittent = %v: request count mismatch, have #%v, want #%v", filter, blocks, intermittent, requested, retrievals) | ||||
| 	} | ||||
| 	return requested | ||||
| } | ||||
|  | ||||
| // startRetrievers starts a batch of goroutines listening for section requests | ||||
| // and serving them. | ||||
| func startRetrievers(session *MatcherSession, quit chan struct{}, retrievals *uint32, batch int) { | ||||
| 	requests := make(chan chan *Retrieval) | ||||
|  | ||||
| 	for i := 0; i < 10; i++ { | ||||
| 		// Start a multiplexer to test multiple threaded execution | ||||
| 		go session.Multiplex(batch, 100*time.Microsecond, requests) | ||||
|  | ||||
| 		// Start a services to match the above multiplexer | ||||
| 		go func() { | ||||
| 			for { | ||||
| 				// Wait for a service request or a shutdown | ||||
| 				select { | ||||
| 				case <-quit: | ||||
| 					return | ||||
|  | ||||
| 				case request := <-requests: | ||||
| 					task := <-request | ||||
|  | ||||
| 					task.Bitsets = make([][]byte, len(task.Sections)) | ||||
| 					for i, section := range task.Sections { | ||||
| 						if rand.Int()%4 != 0 { // Handle occasional missing deliveries | ||||
| 							task.Bitsets[i] = generateBitset(task.Bit, section) | ||||
| 							atomic.AddUint32(retrievals, 1) | ||||
| 						} | ||||
| 					} | ||||
| 					request <- task | ||||
| 				} | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // generateBitset generates the rotated bitset for the given bloom bit and section | ||||
| // numbers. | ||||
| func generateBitset(bit uint, section uint64) []byte { | ||||
| 	bitset := make([]byte, testSectionSize/8) | ||||
| 	for i := 0; i < len(bitset); i++ { | ||||
| 		for b := 0; b < 8; b++ { | ||||
| 			blockIdx := section*testSectionSize + uint64(i*8+b) | ||||
| 			bitset[i] += bitset[i] | ||||
| 			if (blockIdx % uint64(bit)) == 0 { | ||||
| 				bitset[i]++ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return bitset | ||||
| } | ||||
|  | ||||
| func expMatch1(filter bloomIndexes, i uint64) bool { | ||||
| 	for _, ii := range filter { | ||||
| 		if (i % uint64(ii)) != 0 { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func expMatch2(filter []bloomIndexes, i uint64) bool { | ||||
| 	for _, ii := range filter { | ||||
| 		if expMatch1(ii, i) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func expMatch3(filter [][]bloomIndexes, i uint64) bool { | ||||
| 	for _, ii := range filter { | ||||
| 		if !expMatch2(ii, i) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|   | ||||
							
								
								
									
										181
									
								
								core/bloombits/scheduler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								core/bloombits/scheduler.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| // Copyright 2017 The go-ethereum Authors | ||||
| // This file is part of the go-ethereum library. | ||||
| // | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Lesser General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // The go-ethereum library is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU Lesser General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package bloombits | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| // request represents a bloom retrieval task to prioritize and pull from the local | ||||
| // database or remotely from the network. | ||||
| type request struct { | ||||
| 	section uint64 // Section index to retrieve the a bit-vector from | ||||
| 	bit     uint   // Bit index within the section to retrieve the vector of | ||||
| } | ||||
|  | ||||
| // response represents the state of a requested bit-vector through a scheduler. | ||||
| type response struct { | ||||
| 	cached []byte        // Cached bits to dedup multiple requests | ||||
| 	done   chan struct{} // Channel to allow waiting for completion | ||||
| } | ||||
|  | ||||
| // scheduler handles the scheduling of bloom-filter retrieval operations for | ||||
| // entire section-batches belonging to a single bloom bit. Beside scheduling the | ||||
| // retrieval operations, this struct also deduplicates the requests and caches | ||||
| // the results to minimize network/database overhead even in complex filtering | ||||
| // scenarios. | ||||
| type scheduler struct { | ||||
| 	bit       uint                 // Index of the bit in the bloom filter this scheduler is responsible for | ||||
| 	responses map[uint64]*response // Currently pending retrieval requests or already cached responses | ||||
| 	lock      sync.Mutex           // Lock protecting the responses from concurrent access | ||||
| } | ||||
|  | ||||
| // newScheduler creates a new bloom-filter retrieval scheduler for a specific | ||||
| // bit index. | ||||
| func newScheduler(idx uint) *scheduler { | ||||
| 	return &scheduler{ | ||||
| 		bit:       idx, | ||||
| 		responses: make(map[uint64]*response), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // run creates a retrieval pipeline, receiving section indexes from sections and | ||||
| // returning the results in the same order through the done channel. Concurrent | ||||
| // runs of the same scheduler are allowed, leading to retrieval task deduplication. | ||||
| func (s *scheduler) run(sections chan uint64, dist chan *request, done chan []byte, quit chan struct{}, wg *sync.WaitGroup) { | ||||
| 	// Create a forwarder channel between requests and responses of the same size as | ||||
| 	// the distribution channel (since that will block the pipeline anyway). | ||||
| 	pend := make(chan uint64, cap(dist)) | ||||
|  | ||||
| 	// Start the pipeline schedulers to forward between user -> distributor -> user | ||||
| 	wg.Add(2) | ||||
| 	go s.scheduleRequests(sections, dist, pend, quit, wg) | ||||
| 	go s.scheduleDeliveries(pend, done, quit, wg) | ||||
| } | ||||
|  | ||||
| // reset cleans up any leftovers from previous runs. This is required before a | ||||
| // restart to ensure the no previously requested but never delivered state will | ||||
| // cause a lockup. | ||||
| func (s *scheduler) reset() { | ||||
| 	s.lock.Lock() | ||||
| 	defer s.lock.Unlock() | ||||
|  | ||||
| 	for section, res := range s.responses { | ||||
| 		if res.cached == nil { | ||||
| 			delete(s.responses, section) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // scheduleRequests reads section retrieval requests from the input channel, | ||||
| // deduplicates the stream and pushes unique retrieval tasks into the distribution | ||||
| // channel for a database or network layer to honour. | ||||
| func (s *scheduler) scheduleRequests(reqs chan uint64, dist chan *request, pend chan uint64, quit chan struct{}, wg *sync.WaitGroup) { | ||||
| 	// Clean up the goroutine and pipeline when done | ||||
| 	defer wg.Done() | ||||
| 	defer close(pend) | ||||
|  | ||||
| 	// Keep reading and scheduling section requests | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-quit: | ||||
| 			return | ||||
|  | ||||
| 		case section, ok := <-reqs: | ||||
| 			// New section retrieval requested | ||||
| 			if !ok { | ||||
| 				return | ||||
| 			} | ||||
| 			// Deduplicate retrieval requests | ||||
| 			unique := false | ||||
|  | ||||
| 			s.lock.Lock() | ||||
| 			if s.responses[section] == nil { | ||||
| 				s.responses[section] = &response{ | ||||
| 					done: make(chan struct{}), | ||||
| 				} | ||||
| 				unique = true | ||||
| 			} | ||||
| 			s.lock.Unlock() | ||||
|  | ||||
| 			// Schedule the section for retrieval and notify the deliverer to expect this section | ||||
| 			if unique { | ||||
| 				select { | ||||
| 				case <-quit: | ||||
| 					return | ||||
| 				case dist <- &request{bit: s.bit, section: section}: | ||||
| 				} | ||||
| 			} | ||||
| 			select { | ||||
| 			case <-quit: | ||||
| 				return | ||||
| 			case pend <- section: | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // scheduleDeliveries reads section acceptance notifications and waits for them | ||||
| // to be delivered, pushing them into the output data buffer. | ||||
| func (s *scheduler) scheduleDeliveries(pend chan uint64, done chan []byte, quit chan struct{}, wg *sync.WaitGroup) { | ||||
| 	// Clean up the goroutine and pipeline when done | ||||
| 	defer wg.Done() | ||||
| 	defer close(done) | ||||
|  | ||||
| 	// Keep reading notifications and scheduling deliveries | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-quit: | ||||
| 			return | ||||
|  | ||||
| 		case idx, ok := <-pend: | ||||
| 			// New section retrieval pending | ||||
| 			if !ok { | ||||
| 				return | ||||
| 			} | ||||
| 			// Wait until the request is honoured | ||||
| 			s.lock.Lock() | ||||
| 			res := s.responses[idx] | ||||
| 			s.lock.Unlock() | ||||
|  | ||||
| 			select { | ||||
| 			case <-quit: | ||||
| 				return | ||||
| 			case <-res.done: | ||||
| 			} | ||||
| 			// Deliver the result | ||||
| 			select { | ||||
| 			case <-quit: | ||||
| 				return | ||||
| 			case done <- res.cached: | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // deliver is called by the request distributor when a reply to a request arrives. | ||||
| func (s *scheduler) deliver(sections []uint64, data [][]byte) { | ||||
| 	s.lock.Lock() | ||||
| 	defer s.lock.Unlock() | ||||
|  | ||||
| 	for i, section := range sections { | ||||
| 		if res := s.responses[section]; res != nil && res.cached == nil { // Avoid non-requests and double deliveries | ||||
| 			res.cached = data[i] | ||||
| 			close(res.done) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										105
									
								
								core/bloombits/scheduler_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								core/bloombits/scheduler_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| // Copyright 2017 The go-ethereum Authors | ||||
| // This file is part of the go-ethereum library. | ||||
| // | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Lesser General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // The go-ethereum library is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU Lesser General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package bloombits | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"math/big" | ||||
| 	"math/rand" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Tests that the scheduler can deduplicate and forward retrieval requests to | ||||
| // underlying fetchers and serve responses back, irrelevant of the concurrency | ||||
| // of the requesting clients or serving data fetchers. | ||||
| func TestSchedulerSingleClientSingleFetcher(t *testing.T) { testScheduler(t, 1, 1, 5000) } | ||||
| func TestSchedulerSingleClientMultiFetcher(t *testing.T)  { testScheduler(t, 1, 10, 5000) } | ||||
| func TestSchedulerMultiClientSingleFetcher(t *testing.T)  { testScheduler(t, 10, 1, 5000) } | ||||
| func TestSchedulerMultiClientMultiFetcher(t *testing.T)   { testScheduler(t, 10, 10, 5000) } | ||||
|  | ||||
| func testScheduler(t *testing.T, clients int, fetchers int, requests int) { | ||||
| 	f := newScheduler(0) | ||||
|  | ||||
| 	// Create a batch of handler goroutines that respond to bloom bit requests and | ||||
| 	// deliver them to the scheduler. | ||||
| 	var fetchPend sync.WaitGroup | ||||
| 	fetchPend.Add(fetchers) | ||||
| 	defer fetchPend.Wait() | ||||
|  | ||||
| 	fetch := make(chan *request, 16) | ||||
| 	defer close(fetch) | ||||
|  | ||||
| 	var delivered uint32 | ||||
| 	for i := 0; i < fetchers; i++ { | ||||
| 		go func() { | ||||
| 			defer fetchPend.Done() | ||||
|  | ||||
| 			for req := range fetch { | ||||
| 				time.Sleep(time.Duration(rand.Intn(int(100 * time.Microsecond)))) | ||||
| 				atomic.AddUint32(&delivered, 1) | ||||
|  | ||||
| 				f.deliver([]uint64{ | ||||
| 					req.section + uint64(requests), // Non-requested data (ensure it doesn't go out of bounds) | ||||
| 					req.section,                    // Requested data | ||||
| 					req.section,                    // Duplicated data (ensure it doesn't double close anything) | ||||
| 				}, [][]byte{ | ||||
| 					[]byte{}, | ||||
| 					new(big.Int).SetUint64(req.section).Bytes(), | ||||
| 					new(big.Int).SetUint64(req.section).Bytes(), | ||||
| 				}) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| 	// Start a batch of goroutines to concurrently run scheduling tasks | ||||
| 	quit := make(chan struct{}) | ||||
|  | ||||
| 	var pend sync.WaitGroup | ||||
| 	pend.Add(clients) | ||||
|  | ||||
| 	for i := 0; i < clients; i++ { | ||||
| 		go func() { | ||||
| 			defer pend.Done() | ||||
|  | ||||
| 			in := make(chan uint64, 16) | ||||
| 			out := make(chan []byte, 16) | ||||
|  | ||||
| 			f.run(in, fetch, out, quit, &pend) | ||||
|  | ||||
| 			go func() { | ||||
| 				for j := 0; j < requests; j++ { | ||||
| 					in <- uint64(j) | ||||
| 				} | ||||
| 				close(in) | ||||
| 			}() | ||||
|  | ||||
| 			for j := 0; j < requests; j++ { | ||||
| 				bits := <-out | ||||
| 				if want := new(big.Int).SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) { | ||||
| 					t.Errorf("vector %d: delivered content mismatch: have %x, want %x", j, bits, want) | ||||
| 				} | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| 	pend.Wait() | ||||
|  | ||||
| 	if have := atomic.LoadUint32(&delivered); int(have) != requests { | ||||
| 		t.Errorf("request count mismatch: have %v, want %v", have, requests) | ||||
| 	} | ||||
| } | ||||
| @@ -1,63 +0,0 @@ | ||||
| // Copyright 2017 The go-ethereum Authors | ||||
| // This file is part of the go-ethereum library. | ||||
| // | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Lesser General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // The go-ethereum library is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU Lesser General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
| package bloombits | ||||
|  | ||||
| import ( | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| ) | ||||
|  | ||||
| const BloomLength = 2048 | ||||
|  | ||||
| // BloomBitsCreator takes SectionSize number of header bloom filters and calculates the bloomBits vectors of the section | ||||
| type BloomBitsCreator struct { | ||||
| 	blooms              [BloomLength][]byte | ||||
| 	sectionSize, bitIndex uint64 | ||||
| } | ||||
|  | ||||
| func NewBloomBitsCreator(sectionSize uint64) *BloomBitsCreator { | ||||
| 	b := &BloomBitsCreator{sectionSize: sectionSize} | ||||
| 	for i, _ := range b.blooms { | ||||
| 		b.blooms[i] = make([]byte, sectionSize/8) | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // AddHeaderBloom takes a single bloom filter and sets the corresponding bit column in memory accordingly | ||||
| func (b *BloomBitsCreator) AddHeaderBloom(bloom types.Bloom) { | ||||
| 	if b.bitIndex >= b.sectionSize { | ||||
| 		panic("too many header blooms added") | ||||
| 	} | ||||
|  | ||||
| 	byteIdx := b.bitIndex / 8 | ||||
| 	bitMask := byte(1) << byte(7-b.bitIndex%8) | ||||
| 	for bloomBitIdx, _ := range b.blooms { | ||||
| 		bloomByteIdx := BloomLength/8 - 1 - bloomBitIdx/8 | ||||
| 		bloomBitMask := byte(1) << byte(bloomBitIdx%8) | ||||
| 		if (bloom[bloomByteIdx] & bloomBitMask) != 0 { | ||||
| 			b.blooms[bloomBitIdx][byteIdx] |= bitMask | ||||
| 		} | ||||
| 	} | ||||
| 	b.bitIndex++ | ||||
| } | ||||
|  | ||||
| // GetBitVector returns the bit vector belonging to the given bit index after header blooms have been added | ||||
| func (b *BloomBitsCreator) GetBitVector(idx uint) []byte { | ||||
| 	if b.bitIndex != b.sectionSize { | ||||
| 		panic("not enough header blooms added") | ||||
| 	} | ||||
|  | ||||
| 	return b.blooms[idx][:] | ||||
| } | ||||
| @@ -36,14 +36,13 @@ import ( | ||||
| type ChainIndexerBackend interface { | ||||
| 	// Reset initiates the processing of a new chain segment, potentially terminating | ||||
| 	// any partially completed operations (in case of a reorg). | ||||
| 	Reset(section uint64, lastSectionHead common.Hash) | ||||
| 	Reset(section uint64) | ||||
|  | ||||
| 	// Process crunches through the next header in the chain segment. The caller | ||||
| 	// will ensure a sequential order of headers. | ||||
| 	Process(header *types.Header) | ||||
|  | ||||
| 	// Commit finalizes the section metadata and stores it into the database. This | ||||
| 	// interface will usually be a batch writer. | ||||
| 	// Commit finalizes the section metadata and stores it into the database. | ||||
| 	Commit() error | ||||
| } | ||||
|  | ||||
| @@ -101,34 +100,11 @@ func NewChainIndexer(chainDb, indexDb ethdb.Database, backend ChainIndexerBacken | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| // AddKnownSectionHead marks a new section head as known/processed if it is newer | ||||
| // than the already known best section head | ||||
| func (c *ChainIndexer) AddKnownSectionHead(section uint64, shead common.Hash) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
|  | ||||
| 	if section < c.storedSections { | ||||
| 		return | ||||
| 	} | ||||
| 	c.setSectionHead(section, shead) | ||||
| 	c.setValidSections(section + 1) | ||||
| } | ||||
|  | ||||
| // IndexerChain interface is used for connecting the indexer to a blockchain | ||||
| type IndexerChain interface { | ||||
| 	CurrentHeader() *types.Header | ||||
| 	SubscribeChainEvent(ch chan<- ChainEvent) event.Subscription | ||||
| } | ||||
|  | ||||
| // Start creates a goroutine to feed chain head events into the indexer for | ||||
| // cascading background processing. Children do not need to be started, they | ||||
| // are notified about new events by their parents. | ||||
| func (c *ChainIndexer) Start(chain IndexerChain) { | ||||
| 	ch := make(chan ChainEvent, 10) | ||||
| 	sub := chain.SubscribeChainEvent(ch) | ||||
| 	currentHeader := chain.CurrentHeader() | ||||
|  | ||||
| 	go c.eventLoop(currentHeader, ch, sub) | ||||
| func (c *ChainIndexer) Start(currentHeader *types.Header, chainEventer func(ch chan<- ChainEvent) event.Subscription) { | ||||
| 	go c.eventLoop(currentHeader, chainEventer) | ||||
| } | ||||
|  | ||||
| // Close tears down all goroutines belonging to the indexer and returns any error | ||||
| @@ -149,14 +125,12 @@ func (c *ChainIndexer) Close() error { | ||||
| 			errs = append(errs, err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Close all children | ||||
| 	for _, child := range c.children { | ||||
| 		if err := child.Close(); err != nil { | ||||
| 			errs = append(errs, err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Return any failures | ||||
| 	switch { | ||||
| 	case len(errs) == 0: | ||||
| @@ -173,10 +147,12 @@ func (c *ChainIndexer) Close() error { | ||||
| // eventLoop is a secondary - optional - event loop of the indexer which is only | ||||
| // started for the outermost indexer to push chain head events into a processing | ||||
| // queue. | ||||
| func (c *ChainIndexer) eventLoop(currentHeader *types.Header, ch chan ChainEvent, sub event.Subscription) { | ||||
| func (c *ChainIndexer) eventLoop(currentHeader *types.Header, chainEventer func(ch chan<- ChainEvent) event.Subscription) { | ||||
| 	// Mark the chain indexer as active, requiring an additional teardown | ||||
| 	atomic.StoreUint32(&c.active, 1) | ||||
|  | ||||
| 	events := make(chan ChainEvent, 10) | ||||
| 	sub := chainEventer(events) | ||||
| 	defer sub.Unsubscribe() | ||||
|  | ||||
| 	// Fire the initial new head event to start any outstanding processing | ||||
| @@ -193,7 +169,7 @@ func (c *ChainIndexer) eventLoop(currentHeader *types.Header, ch chan ChainEvent | ||||
| 			errc <- nil | ||||
| 			return | ||||
|  | ||||
| 		case ev, ok := <-ch: | ||||
| 		case ev, ok := <-events: | ||||
| 			// Received a new event, ensure it's not nil (closing) and update | ||||
| 			if !ok { | ||||
| 				errc := <-c.quit | ||||
| @@ -257,10 +233,9 @@ func (c *ChainIndexer) newHead(head uint64, reorg bool) { | ||||
| // down into the processing backend. | ||||
| func (c *ChainIndexer) updateLoop() { | ||||
| 	var ( | ||||
| 		updating bool | ||||
| 		updated  time.Time | ||||
| 		updateMsg bool | ||||
| 	) | ||||
|  | ||||
| 	for { | ||||
| 		select { | ||||
| 		case errc := <-c.quit: | ||||
| @@ -275,7 +250,7 @@ func (c *ChainIndexer) updateLoop() { | ||||
| 				// Periodically print an upgrade log message to the user | ||||
| 				if time.Since(updated) > 8*time.Second { | ||||
| 					if c.knownSections > c.storedSections+1 { | ||||
| 						updateMsg = true | ||||
| 						updating = true | ||||
| 						c.log.Info("Upgrading chain index", "percentage", c.storedSections*100/c.knownSections) | ||||
| 					} | ||||
| 					updated = time.Now() | ||||
| @@ -284,7 +259,7 @@ func (c *ChainIndexer) updateLoop() { | ||||
| 				section := c.storedSections | ||||
| 				var oldHead common.Hash | ||||
| 				if section > 0 { | ||||
| 					oldHead = c.SectionHead(section - 1) | ||||
| 					oldHead = c.sectionHead(section - 1) | ||||
| 				} | ||||
| 				// Process the newly defined section in the background | ||||
| 				c.lock.Unlock() | ||||
| @@ -295,11 +270,11 @@ func (c *ChainIndexer) updateLoop() { | ||||
| 				c.lock.Lock() | ||||
|  | ||||
| 				// If processing succeeded and no reorgs occcurred, mark the section completed | ||||
| 				if err == nil && oldHead == c.SectionHead(section-1) { | ||||
| 				if err == nil && oldHead == c.sectionHead(section-1) { | ||||
| 					c.setSectionHead(section, newHead) | ||||
| 					c.setValidSections(section + 1) | ||||
| 					if c.storedSections == c.knownSections && updateMsg { | ||||
| 						updateMsg = false | ||||
| 					if c.storedSections == c.knownSections && updating { | ||||
| 						updating = false | ||||
| 						c.log.Info("Finished upgrading chain index") | ||||
| 					} | ||||
|  | ||||
| @@ -336,7 +311,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com | ||||
| 	c.log.Trace("Processing new chain section", "section", section) | ||||
|  | ||||
| 	// Reset and partial processing | ||||
| 	c.backend.Reset(section, lastHead) | ||||
| 	c.backend.Reset(section) | ||||
|  | ||||
| 	for number := section * c.sectionSize; number < (section+1)*c.sectionSize; number++ { | ||||
| 		hash := GetCanonicalHash(c.chainDb, number) | ||||
| @@ -366,7 +341,7 @@ func (c *ChainIndexer) Sections() (uint64, uint64, common.Hash) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
|  | ||||
| 	return c.storedSections, c.storedSections*c.sectionSize - 1, c.SectionHead(c.storedSections - 1) | ||||
| 	return c.storedSections, c.storedSections*c.sectionSize - 1, c.sectionHead(c.storedSections - 1) | ||||
| } | ||||
|  | ||||
| // AddChildIndexer adds a child ChainIndexer that can use the output of this one | ||||
| @@ -408,7 +383,7 @@ func (c *ChainIndexer) setValidSections(sections uint64) { | ||||
|  | ||||
| // sectionHead retrieves the last block hash of a processed section from the | ||||
| // index database. | ||||
| func (c *ChainIndexer) SectionHead(section uint64) common.Hash { | ||||
| func (c *ChainIndexer) sectionHead(section uint64) common.Hash { | ||||
| 	var data [8]byte | ||||
| 	binary.BigEndian.PutUint64(data[:], section) | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,6 @@ import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| ) | ||||
| @@ -209,7 +208,7 @@ func (b *testChainIndexBackend) reorg(headNum uint64) uint64 { | ||||
| 	return b.stored * b.indexer.sectionSize | ||||
| } | ||||
|  | ||||
| func (b *testChainIndexBackend) Reset(section uint64, lastSectionHead common.Hash) { | ||||
| func (b *testChainIndexBackend) Reset(section uint64) { | ||||
| 	b.section = section | ||||
| 	b.headerCnt = 0 | ||||
| } | ||||
|   | ||||
| @@ -33,11 +33,27 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/rlp" | ||||
| ) | ||||
|  | ||||
| // DatabaseReader wraps the Get method of a backing data store. | ||||
| type DatabaseReader interface { | ||||
| 	Get(key []byte) (value []byte, err error) | ||||
| } | ||||
|  | ||||
| // DatabaseWriter wraps the Put method of a backing data store. | ||||
| type DatabaseWriter interface { | ||||
| 	Put(key, value []byte) error | ||||
| } | ||||
|  | ||||
| // DatabaseDeleter wraps the Delete method of a backing data store. | ||||
| type DatabaseDeleter interface { | ||||
| 	Delete(key []byte) error | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	headHeaderKey = []byte("LastHeader") | ||||
| 	headBlockKey  = []byte("LastBlock") | ||||
| 	headFastKey   = []byte("LastFast") | ||||
|  | ||||
| 	// Data item prefixes (use single byte to avoid mixing data types, avoid `i`). | ||||
| 	headerPrefix        = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header | ||||
| 	tdSuffix            = []byte("t") // headerPrefix + num (uint64 big endian) + hash + tdSuffix -> td | ||||
| 	numSuffix           = []byte("n") // headerPrefix + num (uint64 big endian) + numSuffix -> hash | ||||
| @@ -45,10 +61,14 @@ var ( | ||||
| 	bodyPrefix          = []byte("b") // bodyPrefix + num (uint64 big endian) + hash -> block body | ||||
| 	blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts | ||||
| 	lookupPrefix        = []byte("l") // lookupPrefix + hash -> transaction/receipt lookup metadata | ||||
| 	preimagePrefix      = "secure-key-" // preimagePrefix + hash -> preimage | ||||
| 	bloomBitsPrefix     = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits | ||||
|  | ||||
| 	preimagePrefix = "secure-key-"              // preimagePrefix + hash -> preimage | ||||
| 	configPrefix   = []byte("ethereum-config-") // config prefix for the db | ||||
|  | ||||
| 	// Chain index prefixes (use `i` + single byte to avoid mixing data types). | ||||
| 	BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress | ||||
|  | ||||
| 	// used by old db, now only used for conversion | ||||
| 	oldReceiptsPrefix = []byte("receipts-") | ||||
| 	oldTxMetaSuffix   = []byte{0x01} | ||||
| @@ -57,8 +77,6 @@ var ( | ||||
|  | ||||
| 	preimageCounter    = metrics.NewCounter("db/preimage/total") | ||||
| 	preimageHitCounter = metrics.NewCounter("db/preimage/hits") | ||||
|  | ||||
| 	bloomBitsPrefix = []byte("bloomBits-") | ||||
| ) | ||||
|  | ||||
| // txLookupEntry is a positional metadata to help looking up the data content of | ||||
| @@ -77,7 +95,7 @@ func encodeBlockNumber(number uint64) []byte { | ||||
| } | ||||
|  | ||||
| // GetCanonicalHash retrieves a hash assigned to a canonical block number. | ||||
| func GetCanonicalHash(db ethdb.Database, number uint64) common.Hash { | ||||
| func GetCanonicalHash(db DatabaseReader, number uint64) common.Hash { | ||||
| 	data, _ := db.Get(append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...)) | ||||
| 	if len(data) == 0 { | ||||
| 		return common.Hash{} | ||||
| @@ -91,7 +109,7 @@ const missingNumber = uint64(0xffffffffffffffff) | ||||
|  | ||||
| // GetBlockNumber returns the block number assigned to a block hash | ||||
| // if the corresponding header is present in the database | ||||
| func GetBlockNumber(db ethdb.Database, hash common.Hash) uint64 { | ||||
| func GetBlockNumber(db DatabaseReader, hash common.Hash) uint64 { | ||||
| 	data, _ := db.Get(append(blockHashPrefix, hash.Bytes()...)) | ||||
| 	if len(data) != 8 { | ||||
| 		return missingNumber | ||||
| @@ -104,7 +122,7 @@ func GetBlockNumber(db ethdb.Database, hash common.Hash) uint64 { | ||||
| // last block hash is only updated upon a full block import, the last header | ||||
| // hash is updated already at header import, allowing head tracking for the | ||||
| // light synchronization mechanism. | ||||
| func GetHeadHeaderHash(db ethdb.Database) common.Hash { | ||||
| func GetHeadHeaderHash(db DatabaseReader) common.Hash { | ||||
| 	data, _ := db.Get(headHeaderKey) | ||||
| 	if len(data) == 0 { | ||||
| 		return common.Hash{} | ||||
| @@ -113,7 +131,7 @@ func GetHeadHeaderHash(db ethdb.Database) common.Hash { | ||||
| } | ||||
|  | ||||
| // GetHeadBlockHash retrieves the hash of the current canonical head block. | ||||
| func GetHeadBlockHash(db ethdb.Database) common.Hash { | ||||
| func GetHeadBlockHash(db DatabaseReader) common.Hash { | ||||
| 	data, _ := db.Get(headBlockKey) | ||||
| 	if len(data) == 0 { | ||||
| 		return common.Hash{} | ||||
| @@ -125,7 +143,7 @@ func GetHeadBlockHash(db ethdb.Database) common.Hash { | ||||
| // fast synchronization. The difference between this and GetHeadBlockHash is that | ||||
| // whereas the last block hash is only updated upon a full block import, the last | ||||
| // fast hash is updated when importing pre-processed blocks. | ||||
| func GetHeadFastBlockHash(db ethdb.Database) common.Hash { | ||||
| func GetHeadFastBlockHash(db DatabaseReader) common.Hash { | ||||
| 	data, _ := db.Get(headFastKey) | ||||
| 	if len(data) == 0 { | ||||
| 		return common.Hash{} | ||||
| @@ -135,14 +153,14 @@ func GetHeadFastBlockHash(db ethdb.Database) common.Hash { | ||||
|  | ||||
| // GetHeaderRLP retrieves a block header in its raw RLP database encoding, or nil | ||||
| // if the header's not found. | ||||
| func GetHeaderRLP(db ethdb.Database, hash common.Hash, number uint64) rlp.RawValue { | ||||
| func GetHeaderRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue { | ||||
| 	data, _ := db.Get(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) | ||||
| 	return data | ||||
| } | ||||
|  | ||||
| // GetHeader retrieves the block header corresponding to the hash, nil if none | ||||
| // found. | ||||
| func GetHeader(db ethdb.Database, hash common.Hash, number uint64) *types.Header { | ||||
| func GetHeader(db DatabaseReader, hash common.Hash, number uint64) *types.Header { | ||||
| 	data := GetHeaderRLP(db, hash, number) | ||||
| 	if len(data) == 0 { | ||||
| 		return nil | ||||
| @@ -156,14 +174,14 @@ func GetHeader(db ethdb.Database, hash common.Hash, number uint64) *types.Header | ||||
| } | ||||
|  | ||||
| // GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. | ||||
| func GetBodyRLP(db ethdb.Database, hash common.Hash, number uint64) rlp.RawValue { | ||||
| func GetBodyRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue { | ||||
| 	data, _ := db.Get(append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) | ||||
| 	return data | ||||
| } | ||||
|  | ||||
| // GetBody retrieves the block body (transactons, uncles) corresponding to the | ||||
| // hash, nil if none found. | ||||
| func GetBody(db ethdb.Database, hash common.Hash, number uint64) *types.Body { | ||||
| func GetBody(db DatabaseReader, hash common.Hash, number uint64) *types.Body { | ||||
| 	data := GetBodyRLP(db, hash, number) | ||||
| 	if len(data) == 0 { | ||||
| 		return nil | ||||
| @@ -178,7 +196,7 @@ func GetBody(db ethdb.Database, hash common.Hash, number uint64) *types.Body { | ||||
|  | ||||
| // GetTd retrieves a block's total difficulty corresponding to the hash, nil if | ||||
| // none found. | ||||
| func GetTd(db ethdb.Database, hash common.Hash, number uint64) *big.Int { | ||||
| func GetTd(db DatabaseReader, hash common.Hash, number uint64) *big.Int { | ||||
| 	data, _ := db.Get(append(append(append(headerPrefix, encodeBlockNumber(number)...), hash[:]...), tdSuffix...)) | ||||
| 	if len(data) == 0 { | ||||
| 		return nil | ||||
| @@ -197,7 +215,7 @@ func GetTd(db ethdb.Database, hash common.Hash, number uint64) *big.Int { | ||||
| // | ||||
| // Note, due to concurrent download of header and block body the header and thus | ||||
| // canonical hash can be stored in the database but the body data not (yet). | ||||
| func GetBlock(db ethdb.Database, hash common.Hash, number uint64) *types.Block { | ||||
| func GetBlock(db DatabaseReader, hash common.Hash, number uint64) *types.Block { | ||||
| 	// Retrieve the block header and body contents | ||||
| 	header := GetHeader(db, hash, number) | ||||
| 	if header == nil { | ||||
| @@ -213,7 +231,7 @@ func GetBlock(db ethdb.Database, hash common.Hash, number uint64) *types.Block { | ||||
|  | ||||
| // GetBlockReceipts retrieves the receipts generated by the transactions included | ||||
| // in a block given by its hash. | ||||
| func GetBlockReceipts(db ethdb.Database, hash common.Hash, number uint64) types.Receipts { | ||||
| func GetBlockReceipts(db DatabaseReader, hash common.Hash, number uint64) types.Receipts { | ||||
| 	data, _ := db.Get(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash[:]...)) | ||||
| 	if len(data) == 0 { | ||||
| 		return nil | ||||
| @@ -232,7 +250,7 @@ func GetBlockReceipts(db ethdb.Database, hash common.Hash, number uint64) types. | ||||
|  | ||||
| // GetTxLookupEntry retrieves the positional metadata associated with a transaction | ||||
| // hash to allow retrieving the transaction or receipt by hash. | ||||
| func GetTxLookupEntry(db ethdb.Database, hash common.Hash) (common.Hash, uint64, uint64) { | ||||
| func GetTxLookupEntry(db DatabaseReader, hash common.Hash) (common.Hash, uint64, uint64) { | ||||
| 	// Load the positional metadata from disk and bail if it fails | ||||
| 	data, _ := db.Get(append(lookupPrefix, hash.Bytes()...)) | ||||
| 	if len(data) == 0 { | ||||
| @@ -249,7 +267,7 @@ func GetTxLookupEntry(db ethdb.Database, hash common.Hash) (common.Hash, uint64, | ||||
|  | ||||
| // GetTransaction retrieves a specific transaction from the database, along with | ||||
| // its added positional metadata. | ||||
| func GetTransaction(db ethdb.Database, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { | ||||
| func GetTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { | ||||
| 	// Retrieve the lookup metadata and resolve the transaction from the body | ||||
| 	blockHash, blockNumber, txIndex := GetTxLookupEntry(db, hash) | ||||
|  | ||||
| @@ -284,7 +302,7 @@ func GetTransaction(db ethdb.Database, hash common.Hash) (*types.Transaction, co | ||||
|  | ||||
| // GetReceipt retrieves a specific transaction receipt from the database, along with | ||||
| // its added positional metadata. | ||||
| func GetReceipt(db ethdb.Database, hash common.Hash) (*types.Receipt, common.Hash, uint64, uint64) { | ||||
| func GetReceipt(db DatabaseReader, hash common.Hash) (*types.Receipt, common.Hash, uint64, uint64) { | ||||
| 	// Retrieve the lookup metadata and resolve the receipt from the receipts | ||||
| 	blockHash, blockNumber, receiptIndex := GetTxLookupEntry(db, hash) | ||||
|  | ||||
| @@ -309,8 +327,20 @@ func GetReceipt(db ethdb.Database, hash common.Hash) (*types.Receipt, common.Has | ||||
| 	return (*types.Receipt)(&receipt), common.Hash{}, 0, 0 | ||||
| } | ||||
|  | ||||
| // GetBloomBits retrieves the compressed bloom bit vector belonging to the given | ||||
| // section and bit index from the. | ||||
| func GetBloomBits(db DatabaseReader, bit uint, section uint64, head common.Hash) []byte { | ||||
| 	key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...) | ||||
|  | ||||
| 	binary.BigEndian.PutUint16(key[1:], uint16(bit)) | ||||
| 	binary.BigEndian.PutUint64(key[3:], section) | ||||
|  | ||||
| 	bits, _ := db.Get(key) | ||||
| 	return bits | ||||
| } | ||||
|  | ||||
| // WriteCanonicalHash stores the canonical hash for the given block number. | ||||
| func WriteCanonicalHash(db ethdb.Database, hash common.Hash, number uint64) error { | ||||
| func WriteCanonicalHash(db DatabaseWriter, hash common.Hash, number uint64) error { | ||||
| 	key := append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...) | ||||
| 	if err := db.Put(key, hash.Bytes()); err != nil { | ||||
| 		log.Crit("Failed to store number to hash mapping", "err", err) | ||||
| @@ -319,7 +349,7 @@ func WriteCanonicalHash(db ethdb.Database, hash common.Hash, number uint64) erro | ||||
| } | ||||
|  | ||||
| // WriteHeadHeaderHash stores the head header's hash. | ||||
| func WriteHeadHeaderHash(db ethdb.Database, hash common.Hash) error { | ||||
| func WriteHeadHeaderHash(db DatabaseWriter, hash common.Hash) error { | ||||
| 	if err := db.Put(headHeaderKey, hash.Bytes()); err != nil { | ||||
| 		log.Crit("Failed to store last header's hash", "err", err) | ||||
| 	} | ||||
| @@ -327,7 +357,7 @@ func WriteHeadHeaderHash(db ethdb.Database, hash common.Hash) error { | ||||
| } | ||||
|  | ||||
| // WriteHeadBlockHash stores the head block's hash. | ||||
| func WriteHeadBlockHash(db ethdb.Database, hash common.Hash) error { | ||||
| func WriteHeadBlockHash(db DatabaseWriter, hash common.Hash) error { | ||||
| 	if err := db.Put(headBlockKey, hash.Bytes()); err != nil { | ||||
| 		log.Crit("Failed to store last block's hash", "err", err) | ||||
| 	} | ||||
| @@ -335,7 +365,7 @@ func WriteHeadBlockHash(db ethdb.Database, hash common.Hash) error { | ||||
| } | ||||
|  | ||||
| // WriteHeadFastBlockHash stores the fast head block's hash. | ||||
| func WriteHeadFastBlockHash(db ethdb.Database, hash common.Hash) error { | ||||
| func WriteHeadFastBlockHash(db DatabaseWriter, hash common.Hash) error { | ||||
| 	if err := db.Put(headFastKey, hash.Bytes()); err != nil { | ||||
| 		log.Crit("Failed to store last fast block's hash", "err", err) | ||||
| 	} | ||||
| @@ -343,7 +373,7 @@ func WriteHeadFastBlockHash(db ethdb.Database, hash common.Hash) error { | ||||
| } | ||||
|  | ||||
| // WriteHeader serializes a block header into the database. | ||||
| func WriteHeader(db ethdb.Database, header *types.Header) error { | ||||
| func WriteHeader(db DatabaseWriter, header *types.Header) error { | ||||
| 	data, err := rlp.EncodeToBytes(header) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -363,7 +393,7 @@ func WriteHeader(db ethdb.Database, header *types.Header) error { | ||||
| } | ||||
|  | ||||
| // WriteBody serializes the body of a block into the database. | ||||
| func WriteBody(db ethdb.Database, hash common.Hash, number uint64, body *types.Body) error { | ||||
| func WriteBody(db DatabaseWriter, hash common.Hash, number uint64, body *types.Body) error { | ||||
| 	data, err := rlp.EncodeToBytes(body) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -372,7 +402,7 @@ func WriteBody(db ethdb.Database, hash common.Hash, number uint64, body *types.B | ||||
| } | ||||
|  | ||||
| // WriteBodyRLP writes a serialized body of a block into the database. | ||||
| func WriteBodyRLP(db ethdb.Database, hash common.Hash, number uint64, rlp rlp.RawValue) error { | ||||
| func WriteBodyRLP(db DatabaseWriter, hash common.Hash, number uint64, rlp rlp.RawValue) error { | ||||
| 	key := append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) | ||||
| 	if err := db.Put(key, rlp); err != nil { | ||||
| 		log.Crit("Failed to store block body", "err", err) | ||||
| @@ -381,7 +411,7 @@ func WriteBodyRLP(db ethdb.Database, hash common.Hash, number uint64, rlp rlp.Ra | ||||
| } | ||||
|  | ||||
| // WriteTd serializes the total difficulty of a block into the database. | ||||
| func WriteTd(db ethdb.Database, hash common.Hash, number uint64, td *big.Int) error { | ||||
| func WriteTd(db DatabaseWriter, hash common.Hash, number uint64, td *big.Int) error { | ||||
| 	data, err := rlp.EncodeToBytes(td) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -394,7 +424,7 @@ func WriteTd(db ethdb.Database, hash common.Hash, number uint64, td *big.Int) er | ||||
| } | ||||
|  | ||||
| // WriteBlock serializes a block into the database, header and body separately. | ||||
| func WriteBlock(db ethdb.Database, block *types.Block) error { | ||||
| func WriteBlock(db DatabaseWriter, block *types.Block) error { | ||||
| 	// Store the body first to retain database consistency | ||||
| 	if err := WriteBody(db, block.Hash(), block.NumberU64(), block.Body()); err != nil { | ||||
| 		return err | ||||
| @@ -409,7 +439,7 @@ func WriteBlock(db ethdb.Database, block *types.Block) error { | ||||
| // WriteBlockReceipts stores all the transaction receipts belonging to a block | ||||
| // as a single receipt slice. This is used during chain reorganisations for | ||||
| // rescheduling dropped transactions. | ||||
| func WriteBlockReceipts(db ethdb.Database, hash common.Hash, number uint64, receipts types.Receipts) error { | ||||
| func WriteBlockReceipts(db DatabaseWriter, hash common.Hash, number uint64, receipts types.Receipts) error { | ||||
| 	// Convert the receipts into their storage form and serialize them | ||||
| 	storageReceipts := make([]*types.ReceiptForStorage, len(receipts)) | ||||
| 	for i, receipt := range receipts { | ||||
| @@ -454,29 +484,42 @@ func WriteTxLookupEntries(db ethdb.Database, block *types.Block) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteBloomBits writes the compressed bloom bits vector belonging to the given | ||||
| // section and bit index. | ||||
| func WriteBloomBits(db DatabaseWriter, bit uint, section uint64, head common.Hash, bits []byte) { | ||||
| 	key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...) | ||||
|  | ||||
| 	binary.BigEndian.PutUint16(key[1:], uint16(bit)) | ||||
| 	binary.BigEndian.PutUint64(key[3:], section) | ||||
|  | ||||
| 	if err := db.Put(key, bits); err != nil { | ||||
| 		log.Crit("Failed to store bloom bits", "err", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // DeleteCanonicalHash removes the number to hash canonical mapping. | ||||
| func DeleteCanonicalHash(db ethdb.Database, number uint64) { | ||||
| func DeleteCanonicalHash(db DatabaseDeleter, number uint64) { | ||||
| 	db.Delete(append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...)) | ||||
| } | ||||
|  | ||||
| // DeleteHeader removes all block header data associated with a hash. | ||||
| func DeleteHeader(db ethdb.Database, hash common.Hash, number uint64) { | ||||
| func DeleteHeader(db DatabaseDeleter, hash common.Hash, number uint64) { | ||||
| 	db.Delete(append(blockHashPrefix, hash.Bytes()...)) | ||||
| 	db.Delete(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) | ||||
| } | ||||
|  | ||||
| // DeleteBody removes all block body data associated with a hash. | ||||
| func DeleteBody(db ethdb.Database, hash common.Hash, number uint64) { | ||||
| func DeleteBody(db DatabaseDeleter, hash common.Hash, number uint64) { | ||||
| 	db.Delete(append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) | ||||
| } | ||||
|  | ||||
| // DeleteTd removes all block total difficulty data associated with a hash. | ||||
| func DeleteTd(db ethdb.Database, hash common.Hash, number uint64) { | ||||
| func DeleteTd(db DatabaseDeleter, hash common.Hash, number uint64) { | ||||
| 	db.Delete(append(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...), tdSuffix...)) | ||||
| } | ||||
|  | ||||
| // DeleteBlock removes all block data associated with a hash. | ||||
| func DeleteBlock(db ethdb.Database, hash common.Hash, number uint64) { | ||||
| func DeleteBlock(db DatabaseDeleter, hash common.Hash, number uint64) { | ||||
| 	DeleteBlockReceipts(db, hash, number) | ||||
| 	DeleteHeader(db, hash, number) | ||||
| 	DeleteBody(db, hash, number) | ||||
| @@ -484,12 +527,12 @@ func DeleteBlock(db ethdb.Database, hash common.Hash, number uint64) { | ||||
| } | ||||
|  | ||||
| // DeleteBlockReceipts removes all receipt data associated with a block hash. | ||||
| func DeleteBlockReceipts(db ethdb.Database, hash common.Hash, number uint64) { | ||||
| func DeleteBlockReceipts(db DatabaseDeleter, hash common.Hash, number uint64) { | ||||
| 	db.Delete(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) | ||||
| } | ||||
|  | ||||
| // DeleteTxLookupEntry removes all transaction data associated with a hash. | ||||
| func DeleteTxLookupEntry(db ethdb.Database, hash common.Hash) { | ||||
| func DeleteTxLookupEntry(db DatabaseDeleter, hash common.Hash) { | ||||
| 	db.Delete(append(lookupPrefix, hash.Bytes()...)) | ||||
| } | ||||
|  | ||||
| @@ -521,7 +564,7 @@ func WritePreimages(db ethdb.Database, number uint64, preimages map[common.Hash] | ||||
| } | ||||
|  | ||||
| // GetBlockChainVersion reads the version number from db. | ||||
| func GetBlockChainVersion(db ethdb.Database) int { | ||||
| func GetBlockChainVersion(db DatabaseReader) int { | ||||
| 	var vsn uint | ||||
| 	enc, _ := db.Get([]byte("BlockchainVersion")) | ||||
| 	rlp.DecodeBytes(enc, &vsn) | ||||
| @@ -529,13 +572,13 @@ func GetBlockChainVersion(db ethdb.Database) int { | ||||
| } | ||||
|  | ||||
| // WriteBlockChainVersion writes vsn as the version number to db. | ||||
| func WriteBlockChainVersion(db ethdb.Database, vsn int) { | ||||
| func WriteBlockChainVersion(db DatabaseWriter, vsn int) { | ||||
| 	enc, _ := rlp.EncodeToBytes(uint(vsn)) | ||||
| 	db.Put([]byte("BlockchainVersion"), enc) | ||||
| } | ||||
|  | ||||
| // WriteChainConfig writes the chain config settings to the database. | ||||
| func WriteChainConfig(db ethdb.Database, hash common.Hash, cfg *params.ChainConfig) error { | ||||
| func WriteChainConfig(db DatabaseWriter, hash common.Hash, cfg *params.ChainConfig) error { | ||||
| 	// short circuit and ignore if nil config. GetChainConfig | ||||
| 	// will return a default. | ||||
| 	if cfg == nil { | ||||
| @@ -551,7 +594,7 @@ func WriteChainConfig(db ethdb.Database, hash common.Hash, cfg *params.ChainConf | ||||
| } | ||||
|  | ||||
| // GetChainConfig will fetch the network settings based on the given hash. | ||||
| func GetChainConfig(db ethdb.Database, hash common.Hash) (*params.ChainConfig, error) { | ||||
| func GetChainConfig(db DatabaseReader, hash common.Hash) (*params.ChainConfig, error) { | ||||
| 	jsonChainConfig, _ := db.Get(append(configPrefix, hash[:]...)) | ||||
| 	if len(jsonChainConfig) == 0 { | ||||
| 		return nil, ErrChainConfigNotFound | ||||
| @@ -566,7 +609,7 @@ func GetChainConfig(db ethdb.Database, hash common.Hash) (*params.ChainConfig, e | ||||
| } | ||||
|  | ||||
| // FindCommonAncestor returns the last common ancestor of two block headers | ||||
| func FindCommonAncestor(db ethdb.Database, a, b *types.Header) *types.Header { | ||||
| func FindCommonAncestor(db DatabaseReader, a, b *types.Header) *types.Header { | ||||
| 	for bn := b.Number.Uint64(); a.Number.Uint64() > bn; { | ||||
| 		a = GetHeader(db, a.ParentHash, a.Number.Uint64()-1) | ||||
| 		if a == nil { | ||||
| @@ -591,22 +634,3 @@ func FindCommonAncestor(db ethdb.Database, a, b *types.Header) *types.Header { | ||||
| 	} | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| // GetBloomBits reads the compressed bloomBits vector belonging to the given section and bit index from the db | ||||
| func GetBloomBits(db ethdb.Database, bitIdx, sectionIdx uint64, sectionHead common.Hash) ([]byte, error) { | ||||
| 	var encKey [10]byte | ||||
| 	binary.BigEndian.PutUint16(encKey[0:2], uint16(bitIdx)) | ||||
| 	binary.BigEndian.PutUint64(encKey[2:10], sectionIdx) | ||||
| 	key := append(append(bloomBitsPrefix, encKey[:]...), sectionHead.Bytes()...) | ||||
| 	bloomBits, err := db.Get(key) | ||||
| 	return bloomBits, err | ||||
| } | ||||
|  | ||||
| // StoreBloomBits writes the compressed bloomBits vector belonging to the given section and bit index to the db | ||||
| func StoreBloomBits(db ethdb.Database, bitIdx, sectionIdx uint64, sectionHead common.Hash, bloomBits []byte) { | ||||
| 	var encKey [10]byte | ||||
| 	binary.BigEndian.PutUint16(encKey[0:2], uint16(bitIdx)) | ||||
| 	binary.BigEndian.PutUint64(encKey[2:10], sectionIdx) | ||||
| 	key := append(append(bloomBitsPrefix, encKey[:]...), sectionHead.Bytes()...) | ||||
| 	db.Put(key, bloomBits) | ||||
| } | ||||
|   | ||||
| @@ -28,10 +28,16 @@ type bytesBacked interface { | ||||
| 	Bytes() []byte | ||||
| } | ||||
|  | ||||
| const bloomLength = 256 | ||||
| const ( | ||||
| 	// BloomByteLength represents the number of bytes used in a header log bloom. | ||||
| 	BloomByteLength = 256 | ||||
|  | ||||
| // Bloom represents a 256 bit bloom filter. | ||||
| type Bloom [bloomLength]byte | ||||
| 	// 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 | ||||
|  | ||||
| // BytesToBloom converts a byte slice to a bloom filter. | ||||
| // It panics if b is not of suitable size. | ||||
| @@ -47,7 +53,7 @@ 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) | ||||
| 	copy(b[BloomByteLength-len(d):], d) | ||||
| } | ||||
|  | ||||
| // Add adds d to the filter. Future calls of Test(d) will return true. | ||||
| @@ -106,20 +112,6 @@ func LogsBloom(logs []*Log) *big.Int { | ||||
| 	return bin | ||||
| } | ||||
|  | ||||
| type BloomIndexList [3]uint | ||||
|  | ||||
| // BloomIndexes returns the bloom filter bit indexes belonging to the given key | ||||
| func BloomIndexes(b []byte) BloomIndexList { | ||||
| 	b = crypto.Keccak256(b[:]) | ||||
|  | ||||
| 	var r [3]uint | ||||
| 	for i, _ := range r { | ||||
| 		r[i] = (uint(b[i+i+1]) + (uint(b[i+i]) << 8)) & 2047 | ||||
| 	} | ||||
|  | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func bloom9(b []byte) *big.Int { | ||||
| 	b = crypto.Keccak256(b[:]) | ||||
|  | ||||
|   | ||||
| @@ -24,11 +24,11 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/common/math" | ||||
| 	"github.com/ethereum/go-ethereum/core" | ||||
| 	"github.com/ethereum/go-ethereum/core/bloombits" | ||||
| 	"github.com/ethereum/go-ethereum/core/state" | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 	"github.com/ethereum/go-ethereum/core/vm" | ||||
| 	"github.com/ethereum/go-ethereum/eth/downloader" | ||||
| 	"github.com/ethereum/go-ethereum/eth/filters" | ||||
| 	"github.com/ethereum/go-ethereum/eth/gasprice" | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| 	"github.com/ethereum/go-ethereum/event" | ||||
| @@ -196,28 +196,13 @@ func (b *EthApiBackend) AccountManager() *accounts.Manager { | ||||
| 	return b.eth.AccountManager() | ||||
| } | ||||
|  | ||||
| func (b *EthApiBackend) GetBloomBits(ctx context.Context, bitIdx uint64, sectionIdxList []uint64) ([][]byte, error) { | ||||
| 	results := make([][]byte, len(sectionIdxList)) | ||||
| 	var err error | ||||
| 	for i, sectionIdx := range sectionIdxList { | ||||
| 		sectionHead := core.GetCanonicalHash(b.eth.chainDb, (sectionIdx+1)*bloomBitsSection-1) | ||||
| 		results[i], err = core.GetBloomBits(b.eth.chainDb, bitIdx, sectionIdx, sectionHead) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	return results, nil | ||||
| func (b *EthApiBackend) BloomStatus() (uint64, uint64) { | ||||
| 	sections, _, _ := b.eth.bloomIndexer.Sections() | ||||
| 	return params.BloomBitsBlocks, sections | ||||
| } | ||||
|  | ||||
| func (b *EthApiBackend) BloomBitsSections() uint64 { | ||||
| 	sections, _, _ := b.eth.bbIndexer.Sections() | ||||
| 	return sections | ||||
| } | ||||
|  | ||||
| func (b *EthApiBackend) BloomBitsConfig() filters.BloomConfig { | ||||
| 	return filters.BloomConfig{ | ||||
| 		SectionSize:    bloomBitsSection, | ||||
| 		MaxRequestLen:  16, | ||||
| 		MaxRequestWait: 0, | ||||
| func (b *EthApiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { | ||||
| 	for i := 0; i < bloomFilterThreads; i++ { | ||||
| 		go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -32,6 +32,7 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/consensus/clique" | ||||
| 	"github.com/ethereum/go-ethereum/consensus/ethash" | ||||
| 	"github.com/ethereum/go-ethereum/core" | ||||
| 	"github.com/ethereum/go-ethereum/core/bloombits" | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 	"github.com/ethereum/go-ethereum/core/vm" | ||||
| 	"github.com/ethereum/go-ethereum/eth/downloader" | ||||
| @@ -77,7 +78,8 @@ type Ethereum struct { | ||||
| 	engine         consensus.Engine | ||||
| 	accountManager *accounts.Manager | ||||
|  | ||||
| 	bbIndexer *core.ChainIndexer | ||||
| 	bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests | ||||
| 	bloomIndexer  *core.ChainIndexer             // Bloom indexer operating during block imports | ||||
|  | ||||
| 	ApiBackend *EthApiBackend | ||||
|  | ||||
| @@ -127,7 +129,8 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { | ||||
| 		networkId:      config.NetworkId, | ||||
| 		gasPrice:       config.GasPrice, | ||||
| 		etherbase:      config.Etherbase, | ||||
| 		bbIndexer:      NewBloomBitsProcessor(chainDb, bloomBitsSection), | ||||
| 		bloomRequests:  make(chan chan *bloombits.Retrieval), | ||||
| 		bloomIndexer:   NewBloomIndexer(chainDb, params.BloomBitsBlocks), | ||||
| 	} | ||||
|  | ||||
| 	log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId) | ||||
| @@ -151,7 +154,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { | ||||
| 		eth.blockchain.SetHead(compat.RewindTo) | ||||
| 		core.WriteChainConfig(chainDb, genesisHash, chainConfig) | ||||
| 	} | ||||
| 	eth.bbIndexer.Start(eth.blockchain) | ||||
| 	eth.bloomIndexer.Start(eth.blockchain.CurrentHeader(), eth.blockchain.SubscribeChainEvent) | ||||
|  | ||||
| 	if config.TxPool.Journal != "" { | ||||
| 		config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal) | ||||
| @@ -261,7 +264,7 @@ func (s *Ethereum) APIs() []rpc.API { | ||||
| 		}, { | ||||
| 			Namespace: "eth", | ||||
| 			Version:   "1.0", | ||||
| 			Service:   filters.NewPublicFilterAPI(s.ApiBackend, false, bloomBitsSection), | ||||
| 			Service:   filters.NewPublicFilterAPI(s.ApiBackend, false), | ||||
| 			Public:    true, | ||||
| 		}, { | ||||
| 			Namespace: "admin", | ||||
| @@ -359,14 +362,17 @@ func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManage | ||||
| func (s *Ethereum) Protocols() []p2p.Protocol { | ||||
| 	if s.lesServer == nil { | ||||
| 		return s.protocolManager.SubProtocols | ||||
| 	} else { | ||||
| 		return append(s.protocolManager.SubProtocols, s.lesServer.Protocols()...) | ||||
| 	} | ||||
| 	return append(s.protocolManager.SubProtocols, s.lesServer.Protocols()...) | ||||
| } | ||||
|  | ||||
| // Start implements node.Service, starting all internal goroutines needed by the | ||||
| // Ethereum protocol implementation. | ||||
| func (s *Ethereum) Start(srvr *p2p.Server) error { | ||||
| 	// Start the bloom bits servicing goroutines | ||||
| 	s.startBloomHandlers() | ||||
|  | ||||
| 	// Start the RPC service | ||||
| 	s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.NetVersion()) | ||||
|  | ||||
| 	// Figure out a max peers count based on the server limits | ||||
| @@ -377,6 +383,7 @@ func (s *Ethereum) Start(srvr *p2p.Server) error { | ||||
| 			maxPeers = srvr.MaxPeers / 2 | ||||
| 		} | ||||
| 	} | ||||
| 	// Start the networking layer and the light server if requested | ||||
| 	s.protocolManager.Start(maxPeers) | ||||
| 	if s.lesServer != nil { | ||||
| 		s.lesServer.Start(srvr) | ||||
| @@ -390,7 +397,7 @@ func (s *Ethereum) Stop() error { | ||||
| 	if s.stopDbUpgrade != nil { | ||||
| 		s.stopDbUpgrade() | ||||
| 	} | ||||
| 	s.bbIndexer.Close() | ||||
| 	s.bloomIndexer.Close() | ||||
| 	s.blockchain.Stop() | ||||
| 	s.protocolManager.Stop() | ||||
| 	if s.lesServer != nil { | ||||
|   | ||||
							
								
								
									
										142
									
								
								eth/bloombits.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								eth/bloombits.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | ||||
| // Copyright 2017 The go-ethereum Authors | ||||
| // This file is part of the go-ethereum library. | ||||
| // | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Lesser General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // The go-ethereum library is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU Lesser General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package eth | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/common/bitutil" | ||||
| 	"github.com/ethereum/go-ethereum/core" | ||||
| 	"github.com/ethereum/go-ethereum/core/bloombits" | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| 	"github.com/ethereum/go-ethereum/params" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// bloomServiceThreads is the number of goroutines used globally by an Ethereum | ||||
| 	// instance to service bloombits lookups for all running filters. | ||||
| 	bloomServiceThreads = 16 | ||||
|  | ||||
| 	// bloomFilterThreads is the number of goroutines used locally per filter to | ||||
| 	// multiplex requests onto the global servicing goroutines. | ||||
| 	bloomFilterThreads = 3 | ||||
|  | ||||
| 	// bloomRetrievalBatch is the maximum number of bloom bit retrievals to service | ||||
| 	// in a single batch. | ||||
| 	bloomRetrievalBatch = 16 | ||||
|  | ||||
| 	// bloomRetrievalWait is the maximum time to wait for enough bloom bit requests | ||||
| 	// to accumulate request an entire batch (avoiding hysteresis). | ||||
| 	bloomRetrievalWait = time.Duration(0) | ||||
| ) | ||||
|  | ||||
| // startBloomHandlers starts a batch of goroutines to accept bloom bit database | ||||
| // retrievals from possibly a range of filters and serving the data to satisfy. | ||||
| func (eth *Ethereum) startBloomHandlers() { | ||||
| 	for i := 0; i < bloomServiceThreads; i++ { | ||||
| 		go func() { | ||||
| 			for { | ||||
| 				select { | ||||
| 				case <-eth.shutdownChan: | ||||
| 					return | ||||
|  | ||||
| 				case request := <-eth.bloomRequests: | ||||
| 					task := <-request | ||||
|  | ||||
| 					task.Bitsets = make([][]byte, len(task.Sections)) | ||||
| 					for i, section := range task.Sections { | ||||
| 						head := core.GetCanonicalHash(eth.chainDb, (section+1)*params.BloomBitsBlocks-1) | ||||
| 						blob, err := bitutil.DecompressBytes(core.GetBloomBits(eth.chainDb, task.Bit, section, head), int(params.BloomBitsBlocks)/8) | ||||
| 						if err != nil { | ||||
| 							panic(err) | ||||
| 						} | ||||
| 						task.Bitsets[i] = blob | ||||
| 					} | ||||
| 					request <- task | ||||
| 				} | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	// bloomConfirms is the number of confirmation blocks before a bloom section is | ||||
| 	// considered probably final and its rotated bits are calculated. | ||||
| 	bloomConfirms = 256 | ||||
|  | ||||
| 	// bloomThrottling is the time to wait between processing two consecutive index | ||||
| 	// sections. It's useful during chain upgrades to prevent disk overload. | ||||
| 	bloomThrottling = 100 * time.Millisecond | ||||
| ) | ||||
|  | ||||
| // BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index | ||||
| // for the Ethereum header bloom filters, permitting blazing fast filtering. | ||||
| type BloomIndexer struct { | ||||
| 	size uint64 // section size to generate bloombits for | ||||
|  | ||||
| 	db  ethdb.Database       // database instance to write index data and metadata into | ||||
| 	gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index | ||||
|  | ||||
| 	section uint64      // Section is the section number being processed currently | ||||
| 	head    common.Hash // Head is the hash of the last header processed | ||||
| } | ||||
|  | ||||
| // NewBloomIndexer returns a chain indexer that generates bloom bits data for the | ||||
| // canonical chain for fast logs filtering. | ||||
| func NewBloomIndexer(db ethdb.Database, size uint64) *core.ChainIndexer { | ||||
| 	backend := &BloomIndexer{ | ||||
| 		db:   db, | ||||
| 		size: size, | ||||
| 	} | ||||
| 	table := ethdb.NewTable(db, string(core.BloomBitsIndexPrefix)) | ||||
|  | ||||
| 	return core.NewChainIndexer(db, table, backend, size, bloomConfirms, bloomThrottling, "bloombits") | ||||
| } | ||||
|  | ||||
| // Reset implements core.ChainIndexerBackend, starting a new bloombits index | ||||
| // section. | ||||
| func (b *BloomIndexer) Reset(section uint64) { | ||||
| 	gen, err := bloombits.NewGenerator(uint(b.size)) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	b.gen, b.section, b.head = gen, section, common.Hash{} | ||||
| } | ||||
|  | ||||
| // Process implements core.ChainIndexerBackend, adding a new header's bloom into | ||||
| // the index. | ||||
| func (b *BloomIndexer) Process(header *types.Header) { | ||||
| 	b.gen.AddBloom(header.Bloom) | ||||
| 	b.head = header.Hash() | ||||
| } | ||||
|  | ||||
| // Commit implements core.ChainIndexerBackend, finalizing the bloom section and | ||||
| // writing it out into the database. | ||||
| func (b *BloomIndexer) Commit() error { | ||||
| 	batch := b.db.NewBatch() | ||||
|  | ||||
| 	for i := 0; i < types.BloomBitLength; i++ { | ||||
| 		bits, err := b.gen.Bitset(uint(i)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		core.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits)) | ||||
| 	} | ||||
| 	return batch.Write() | ||||
| } | ||||
| @@ -22,10 +22,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/common/bitutil" | ||||
| 	"github.com/ethereum/go-ethereum/core" | ||||
| 	"github.com/ethereum/go-ethereum/core/bloombits" | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| 	"github.com/ethereum/go-ethereum/log" | ||||
| 	"github.com/ethereum/go-ethereum/rlp" | ||||
| @@ -136,38 +133,3 @@ func upgradeDeduplicateData(db ethdb.Database) func() error { | ||||
| 		return <-errc | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // BloomBitsIndex implements ChainIndex | ||||
| type BloomBitsIndex struct { | ||||
| 	db                   ethdb.Database | ||||
| 	bc                   *bloombits.BloomBitsCreator | ||||
| 	section, sectionSize uint64 | ||||
| 	sectionHead          common.Hash | ||||
| } | ||||
|  | ||||
| // number of confirmation blocks before a section is considered probably final and its bloom bits are calculated | ||||
| const bloomBitsConfirmations = 256 | ||||
|  | ||||
| // NewBloomBitsProcessor returns a chain processor that generates bloom bits data for the canonical chain | ||||
| func NewBloomBitsProcessor(db ethdb.Database, sectionSize uint64) *core.ChainIndexer { | ||||
| 	backend := &BloomBitsIndex{db: db, sectionSize: sectionSize} | ||||
| 	return core.NewChainIndexer(db, ethdb.NewTable(db, "bbIndex-"), backend, sectionSize, bloomBitsConfirmations, time.Millisecond*100, "bloombits") | ||||
| } | ||||
|  | ||||
| func (b *BloomBitsIndex) Reset(section uint64, lastSectionHead common.Hash) { | ||||
| 	b.bc = bloombits.NewBloomBitsCreator(b.sectionSize) | ||||
| 	b.section = section | ||||
| } | ||||
|  | ||||
| func (b *BloomBitsIndex) Process(header *types.Header) { | ||||
| 	b.bc.AddHeaderBloom(header.Bloom) | ||||
| 	b.sectionHead = header.Hash() | ||||
| } | ||||
|  | ||||
| func (b *BloomBitsIndex) Commit() error { | ||||
| 	for i := 0; i < bloombits.BloomLength; i++ { | ||||
| 		compVector := bitutil.CompressBytes(b.bc.GetBitVector(uint(i))) | ||||
| 		core.StoreBloomBits(b.db, uint64(i), b.section, b.sectionHead, compVector) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -52,7 +52,6 @@ type filter struct { | ||||
| // information related to the Ethereum protocol such als blocks, transactions and logs. | ||||
| type PublicFilterAPI struct { | ||||
| 	backend   Backend | ||||
| 	bloomBitsSection uint64 | ||||
| 	mux       *event.TypeMux | ||||
| 	quit      chan struct{} | ||||
| 	chainDb   ethdb.Database | ||||
| @@ -62,16 +61,14 @@ type PublicFilterAPI struct { | ||||
| } | ||||
|  | ||||
| // NewPublicFilterAPI returns a new PublicFilterAPI instance. | ||||
| func NewPublicFilterAPI(backend Backend, lightMode bool, bloomBitsSection uint64) *PublicFilterAPI { | ||||
| func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI { | ||||
| 	api := &PublicFilterAPI{ | ||||
| 		backend: backend, | ||||
| 		bloomBitsSection: bloomBitsSection, | ||||
| 		mux:     backend.EventMux(), | ||||
| 		chainDb: backend.ChainDb(), | ||||
| 		events:  NewEventSystem(backend.EventMux(), backend, lightMode), | ||||
| 		filters: make(map[rpc.ID]*filter), | ||||
| 	} | ||||
|  | ||||
| 	go api.timeoutLoop() | ||||
|  | ||||
| 	return api | ||||
| @@ -326,16 +323,20 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { | ||||
| // | ||||
| // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs | ||||
| func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { | ||||
| 	// Convert the RPC block numbers into internal representations | ||||
| 	if crit.FromBlock == nil { | ||||
| 		crit.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) | ||||
| 	} | ||||
| 	if crit.ToBlock == nil { | ||||
| 		crit.ToBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) | ||||
| 	} | ||||
|  | ||||
| 	// Create and run the filter to get all the logs | ||||
| 	filter := New(api.backend, crit.FromBlock.Int64(), crit.ToBlock.Int64(), crit.Addresses, crit.Topics) | ||||
|  | ||||
| 	logs, err := filter.Find(ctx) | ||||
| 	logs, err := filter.Logs(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return returnLogs(logs), err | ||||
| } | ||||
|  | ||||
| @@ -369,20 +370,18 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ty | ||||
| 		return nil, fmt.Errorf("filter not found") | ||||
| 	} | ||||
|  | ||||
| 	var begin, end int64 | ||||
| 	begin := rpc.LatestBlockNumber.Int64() | ||||
| 	if f.crit.FromBlock != nil { | ||||
| 		begin = f.crit.FromBlock.Int64() | ||||
| 	} else { | ||||
| 		begin = rpc.LatestBlockNumber.Int64() | ||||
| 	} | ||||
| 	end := rpc.LatestBlockNumber.Int64() | ||||
| 	if f.crit.ToBlock != nil { | ||||
| 		end = f.crit.ToBlock.Int64() | ||||
| 	} else { | ||||
| 		end = rpc.LatestBlockNumber.Int64() | ||||
| 	} | ||||
| 	// Create and run the filter to get all the logs | ||||
| 	filter := New(api.backend, begin, end, f.crit.Addresses, f.crit.Topics) | ||||
|  | ||||
| 	logs, err := filter.Find(ctx) | ||||
| 	logs, err := filter.Logs(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -390,7 +389,7 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ty | ||||
| } | ||||
|  | ||||
| // GetFilterChanges returns the logs for the filter with the given id since | ||||
| // last time is was called. This can be used for polling. | ||||
| // last time it was called. This can be used for polling. | ||||
| // | ||||
| // For pending transaction and block filters the result is []common.Hash. | ||||
| // (pending)Log filters return []Log. | ||||
|   | ||||
| @@ -31,82 +31,41 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| 	"github.com/ethereum/go-ethereum/event" | ||||
| 	"github.com/ethereum/go-ethereum/node" | ||||
| 	"github.com/golang/snappy" | ||||
| ) | ||||
|  | ||||
| func BenchmarkBloomBits512(b *testing.B) { | ||||
| 	benchmarkBloomBitsForSize(b, 512) | ||||
| 	benchmarkBloomBits(b, 512) | ||||
| } | ||||
|  | ||||
| func BenchmarkBloomBits1k(b *testing.B) { | ||||
| 	benchmarkBloomBitsForSize(b, 1024) | ||||
| 	benchmarkBloomBits(b, 1024) | ||||
| } | ||||
|  | ||||
| func BenchmarkBloomBits2k(b *testing.B) { | ||||
| 	benchmarkBloomBitsForSize(b, 2048) | ||||
| 	benchmarkBloomBits(b, 2048) | ||||
| } | ||||
|  | ||||
| func BenchmarkBloomBits4k(b *testing.B) { | ||||
| 	benchmarkBloomBitsForSize(b, 4096) | ||||
| 	benchmarkBloomBits(b, 4096) | ||||
| } | ||||
|  | ||||
| func BenchmarkBloomBits8k(b *testing.B) { | ||||
| 	benchmarkBloomBitsForSize(b, 8192) | ||||
| 	benchmarkBloomBits(b, 8192) | ||||
| } | ||||
|  | ||||
| func BenchmarkBloomBits16k(b *testing.B) { | ||||
| 	benchmarkBloomBitsForSize(b, 16384) | ||||
| 	benchmarkBloomBits(b, 16384) | ||||
| } | ||||
|  | ||||
| func BenchmarkBloomBits32k(b *testing.B) { | ||||
| 	benchmarkBloomBitsForSize(b, 32768) | ||||
| } | ||||
|  | ||||
| func benchmarkBloomBitsForSize(b *testing.B, sectionSize uint64) { | ||||
| 	benchmarkBloomBits(b, sectionSize, 0) | ||||
| 	benchmarkBloomBits(b, sectionSize, 1) | ||||
| 	benchmarkBloomBits(b, sectionSize, 2) | ||||
| 	benchmarkBloomBits(b, 32768) | ||||
| } | ||||
|  | ||||
| const benchFilterCnt = 2000 | ||||
|  | ||||
| func benchmarkBloomBits(b *testing.B, sectionSize uint64, comp int) { | ||||
| func benchmarkBloomBits(b *testing.B, sectionSize uint64) { | ||||
| 	benchDataDir := node.DefaultDataDir() + "/geth/chaindata" | ||||
| 	fmt.Println("Running bloombits benchmark   section size:", sectionSize, "  compression method:", comp) | ||||
|  | ||||
| 	var ( | ||||
| 		compressFn   func([]byte) []byte | ||||
| 		decompressFn func([]byte, int) ([]byte, error) | ||||
| 	) | ||||
| 	switch comp { | ||||
| 	case 0: | ||||
| 		// no compression | ||||
| 		compressFn = func(data []byte) []byte { | ||||
| 			return data | ||||
| 		} | ||||
| 		decompressFn = func(data []byte, target int) ([]byte, error) { | ||||
| 			if len(data) != target { | ||||
| 				panic(nil) | ||||
| 			} | ||||
| 			return data, nil | ||||
| 		} | ||||
| 	case 1: | ||||
| 		// bitutil/compress.go | ||||
| 		compressFn = bitutil.CompressBytes | ||||
| 		decompressFn = bitutil.DecompressBytes | ||||
| 	case 2: | ||||
| 		// go snappy | ||||
| 		compressFn = func(data []byte) []byte { | ||||
| 			return snappy.Encode(nil, data) | ||||
| 		} | ||||
| 		decompressFn = func(data []byte, target int) ([]byte, error) { | ||||
| 			decomp, err := snappy.Decode(nil, data) | ||||
| 			if err != nil || len(decomp) != target { | ||||
| 				panic(err) | ||||
| 			} | ||||
| 			return decomp, nil | ||||
| 		} | ||||
| 	} | ||||
| 	fmt.Println("Running bloombits benchmark   section size:", sectionSize) | ||||
|  | ||||
| 	db, err := ethdb.NewLDBDatabase(benchDataDir, 128, 1024) | ||||
| 	if err != nil { | ||||
| @@ -128,7 +87,10 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64, comp int) { | ||||
| 	cnt := (headNum - 512) / sectionSize | ||||
| 	var dataSize, compSize uint64 | ||||
| 	for sectionIdx := uint64(0); sectionIdx < cnt; sectionIdx++ { | ||||
| 		bc := bloombits.NewBloomBitsCreator(sectionSize) | ||||
| 		bc, err := bloombits.NewGenerator(uint(sectionSize)) | ||||
| 		if err != nil { | ||||
| 			b.Fatalf("failed to create generator: %v", err) | ||||
| 		} | ||||
| 		var header *types.Header | ||||
| 		for i := sectionIdx * sectionSize; i < (sectionIdx+1)*sectionSize; i++ { | ||||
| 			hash := core.GetCanonicalHash(db, i) | ||||
| @@ -136,15 +98,18 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64, comp int) { | ||||
| 			if header == nil { | ||||
| 				b.Fatalf("Error creating bloomBits data") | ||||
| 			} | ||||
| 			bc.AddHeaderBloom(header.Bloom) | ||||
| 			bc.AddBloom(header.Bloom) | ||||
| 		} | ||||
| 		sectionHead := core.GetCanonicalHash(db, (sectionIdx+1)*sectionSize-1) | ||||
| 		for i := 0; i < bloombits.BloomLength; i++ { | ||||
| 			data := bc.GetBitVector(uint(i)) | ||||
| 			comp := compressFn(data) | ||||
| 		for i := 0; i < types.BloomBitLength; i++ { | ||||
| 			data, err := bc.Bitset(uint(i)) | ||||
| 			if err != nil { | ||||
| 				b.Fatalf("failed to retrieve bitset: %v", err) | ||||
| 			} | ||||
| 			comp := bitutil.CompressBytes(data) | ||||
| 			dataSize += uint64(len(data)) | ||||
| 			compSize += uint64(len(comp)) | ||||
| 			core.StoreBloomBits(db, uint64(i), sectionIdx, sectionHead, comp) | ||||
| 			core.WriteBloomBits(db, uint(i), sectionIdx, sectionHead, comp) | ||||
| 		} | ||||
| 		//if sectionIdx%50 == 0 { | ||||
| 		//	fmt.Println(" section", sectionIdx, "/", cnt) | ||||
| @@ -171,8 +136,7 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64, comp int) { | ||||
| 		addr[0] = byte(i) | ||||
| 		addr[1] = byte(i / 256) | ||||
| 		filter := New(backend, 0, int64(cnt*sectionSize-1), []common.Address{addr}, nil) | ||||
| 		filter.decompress = decompressFn | ||||
| 		if _, err := filter.Find(context.Background()); err != nil { | ||||
| 		if _, err := filter.Logs(context.Background()); err != nil { | ||||
| 			b.Error("filter.Find error:", err) | ||||
| 		} | ||||
| 	} | ||||
| @@ -229,7 +193,7 @@ func BenchmarkNoBloomBits(b *testing.B) { | ||||
| 	mux := new(event.TypeMux) | ||||
| 	backend := &testBackend{mux, db, 0, new(event.Feed), new(event.Feed), new(event.Feed), new(event.Feed)} | ||||
| 	filter := New(backend, 0, int64(headNum), []common.Address{common.Address{}}, nil) | ||||
| 	filter.Find(context.Background()) | ||||
| 	filter.Logs(context.Background()) | ||||
| 	d := time.Since(start) | ||||
| 	fmt.Println("Finished running filter benchmarks") | ||||
| 	fmt.Println(" ", d, "total  ", d*time.Duration(1000000)/time.Duration(headNum+1), "per million blocks") | ||||
|   | ||||
| @@ -19,11 +19,9 @@ package filters | ||||
| import ( | ||||
| 	"context" | ||||
| 	"math/big" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/common/bitutil" | ||||
| 	"github.com/ethereum/go-ethereum/core" | ||||
| 	"github.com/ethereum/go-ethereum/core/bloombits" | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| @@ -37,140 +35,143 @@ type Backend interface { | ||||
| 	EventMux() *event.TypeMux | ||||
| 	HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) | ||||
| 	GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) | ||||
| 	BloomBitsSections() uint64 | ||||
| 	BloomBitsConfig() BloomConfig | ||||
|  | ||||
| 	SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription | ||||
| 	SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription | ||||
| 	SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription | ||||
| 	SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription | ||||
| 	GetBloomBits(ctx context.Context, bitIdx uint64, sectionIdxList []uint64) ([][]byte, error) | ||||
| } | ||||
|  | ||||
| type BloomConfig struct { | ||||
| 	SectionSize    uint64 | ||||
| 	MaxRequestLen  int | ||||
| 	MaxRequestWait time.Duration | ||||
| 	BloomStatus() (uint64, uint64) | ||||
| 	ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) | ||||
| } | ||||
|  | ||||
| // Filter can be used to retrieve and filter logs. | ||||
| type Filter struct { | ||||
| 	backend Backend | ||||
| 	bloomBitsConfig BloomConfig | ||||
|  | ||||
| 	db         ethdb.Database | ||||
| 	begin, end int64 | ||||
| 	addresses  []common.Address | ||||
| 	topics     [][]common.Hash | ||||
|  | ||||
| 	decompress func([]byte, int) ([]byte, error) | ||||
| 	matcher *bloombits.Matcher | ||||
| } | ||||
|  | ||||
| // New creates a new filter which uses a bloom filter on blocks to figure out whether | ||||
| // a particular block is interesting or not. | ||||
| func New(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { | ||||
| 	size, _ := backend.BloomStatus() | ||||
|  | ||||
| 	return &Filter{ | ||||
| 		backend:   backend, | ||||
| 		begin:     begin, | ||||
| 		end:       end, | ||||
| 		addresses: addresses, | ||||
| 		topics:    topics, | ||||
| 		bloomBitsConfig: backend.BloomBitsConfig(), | ||||
| 		db:        backend.ChainDb(), | ||||
| 		matcher:         bloombits.NewMatcher(backend.BloomBitsConfig().SectionSize, addresses, topics), | ||||
| 		decompress:      bitutil.DecompressBytes, | ||||
| 		matcher:   bloombits.NewMatcher(size, addresses, topics), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FindOnce searches the blockchain for matching log entries, returning | ||||
| // all matching entries from the first block that contains matches, | ||||
| // updating the start point of the filter accordingly. If no results are | ||||
| // found, a nil slice is returned. | ||||
| func (f *Filter) FindOnce(ctx context.Context) ([]*types.Log, error) { | ||||
| 	head, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) | ||||
| 	if head == nil { | ||||
| // Logs searches the blockchain for matching log entries, returning all from the | ||||
| // first block that contains matches, updating the start of the filter accordingly. | ||||
| func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { | ||||
| 	// Figure out the limits of the filter range | ||||
| 	header, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) | ||||
| 	if header == nil { | ||||
| 		return nil, nil | ||||
| 	} | ||||
| 	headBlockNumber := head.Number.Uint64() | ||||
| 	head := header.Number.Uint64() | ||||
|  | ||||
| 	var beginBlockNo uint64 = uint64(f.begin) | ||||
| 	if f.begin == -1 { | ||||
| 		beginBlockNo = headBlockNumber | ||||
| 		f.begin = int64(head) | ||||
| 	} | ||||
| 	var endBlockNo uint64 = uint64(f.end) | ||||
| 	end := uint64(f.end) | ||||
| 	if f.end == -1 { | ||||
| 		endBlockNo = headBlockNumber | ||||
| 		end = head | ||||
| 	} | ||||
|  | ||||
| 	logs, blockNumber, err := f.getLogs(ctx, beginBlockNo, endBlockNo) | ||||
| 	f.begin = int64(blockNumber + 1) | ||||
| 	return logs, err | ||||
| 	// Gather all indexed logs, and finish with non indexed ones | ||||
| 	var ( | ||||
| 		logs []*types.Log | ||||
| 		err  error | ||||
| 	) | ||||
| 	size, sections := f.backend.BloomStatus() | ||||
| 	if indexed := sections * size; indexed > uint64(f.begin) { | ||||
| 		if indexed > end { | ||||
| 			logs, err = f.indexedLogs(ctx, end) | ||||
| 		} else { | ||||
| 			logs, err = f.indexedLogs(ctx, indexed-1) | ||||
| 		} | ||||
|  | ||||
| // Run filters logs with the current parameters set | ||||
| func (f *Filter) Find(ctx context.Context) (logs []*types.Log, err error) { | ||||
| 	for { | ||||
| 		newLogs, err := f.FindOnce(ctx) | ||||
| 		if len(newLogs) == 0 || err != nil { | ||||
| 			return logs, err | ||||
| 		} | ||||
| 		logs = append(logs, newLogs...) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // nextRequest returns the next request to retrieve for the bloombits matcher | ||||
| func (f *Filter) nextRequest() (bloombits uint, sections []uint64) { | ||||
| 	bloomIndex, ok := f.matcher.AllocSectionQueue() | ||||
| 	if !ok { | ||||
| 		return 0, nil | ||||
| 	} | ||||
| 	if f.bloomBitsConfig.MaxRequestWait > 0 && | ||||
| 		(f.bloomBitsConfig.MaxRequestLen <= 1 || // SectionCount is always greater than zero after a successful alloc | ||||
| 			f.matcher.SectionCount(bloomIndex) < f.bloomBitsConfig.MaxRequestLen) { | ||||
| 		time.Sleep(f.bloomBitsConfig.MaxRequestWait) | ||||
| 	} | ||||
| 	return bloomIndex, f.matcher.FetchSections(bloomIndex, f.bloomBitsConfig.MaxRequestLen) | ||||
| } | ||||
|  | ||||
| // serveMatcher serves the bloombits matcher by fetching the requested vectors | ||||
| // through the filter backend | ||||
| func (f *Filter) serveMatcher(ctx context.Context, stop chan struct{}, wg *sync.WaitGroup) chan error { | ||||
| 	errChn := make(chan error, 1) | ||||
| 	wg.Add(10) | ||||
| 	for i := 0; i < 10; i++ { | ||||
| 		go func(i int) { | ||||
| 			defer wg.Done() | ||||
|  | ||||
| 			for { | ||||
| 				b, s := f.nextRequest() | ||||
| 				if s == nil { | ||||
| 					return | ||||
| 				} | ||||
| 				data, err := f.backend.GetBloomBits(ctx, uint64(b), s) | ||||
| 		if err != nil { | ||||
| 					select { | ||||
| 					case errChn <- err: | ||||
| 					case <-stop: | ||||
| 					} | ||||
| 					return | ||||
| 				} | ||||
| 				decomp := make([][]byte, len(data)) | ||||
| 				for i, d := range data { | ||||
| 					var err error | ||||
| 					if decomp[i], err = f.decompress(d, int(f.bloomBitsConfig.SectionSize/8)); err != nil { | ||||
| 						select { | ||||
| 						case errChn <- err: | ||||
| 						case <-stop: | ||||
| 						} | ||||
| 						return | ||||
| 			return logs, err | ||||
| 		} | ||||
| 	} | ||||
| 				f.matcher.Deliver(b, s, decomp) | ||||
| 			} | ||||
| 		}(i) | ||||
| 	rest, err := f.unindexedLogs(ctx, end) | ||||
| 	logs = append(logs, rest...) | ||||
| 	return logs, err | ||||
| } | ||||
|  | ||||
| 	return errChn | ||||
| // indexedLogs returns the logs matching the filter criteria based on the bloom | ||||
| // bits indexed available locally or via the network. | ||||
| func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { | ||||
| 	// Create a matcher session and request servicing from the backend | ||||
| 	matches := make(chan uint64, 64) | ||||
|  | ||||
| 	session, err := f.matcher.Start(uint64(f.begin), end, matches) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer session.Close(time.Second) | ||||
|  | ||||
| 	f.backend.ServiceFilter(ctx, session) | ||||
|  | ||||
| 	// Iterate over the matches until exhausted or context closed | ||||
| 	var logs []*types.Log | ||||
|  | ||||
| 	for { | ||||
| 		select { | ||||
| 		case number, ok := <-matches: | ||||
| 			// Abort if all matches have been fulfilled | ||||
| 			if !ok { | ||||
| 				f.begin = int64(end) + 1 | ||||
| 				return logs, nil | ||||
| 			} | ||||
| 			// Retrieve the suggested block and pull any truly matching logs | ||||
| 			header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) | ||||
| 			if header == nil || err != nil { | ||||
| 				return logs, err | ||||
| 			} | ||||
| 			found, err := f.checkMatches(ctx, header) | ||||
| 			if err != nil { | ||||
| 				return logs, err | ||||
| 			} | ||||
| 			logs = append(logs, found...) | ||||
|  | ||||
| 		case <-ctx.Done(): | ||||
| 			return logs, ctx.Err() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // indexedLogs returns the logs matching the filter criteria based on raw block | ||||
| // iteration and bloom matching. | ||||
| func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { | ||||
| 	var logs []*types.Log | ||||
|  | ||||
| 	for ; f.begin <= int64(end); f.begin++ { | ||||
| 		header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) | ||||
| 		if header == nil || err != nil { | ||||
| 			return logs, err | ||||
| 		} | ||||
| 		if bloomFilter(header.Bloom, f.addresses, f.topics) { | ||||
| 			found, err := f.checkMatches(ctx, header) | ||||
| 			if err != nil { | ||||
| 				return logs, err | ||||
| 			} | ||||
| 			logs = append(logs, found...) | ||||
| 		} | ||||
| 	} | ||||
| 	return logs, nil | ||||
| } | ||||
|  | ||||
| // checkMatches checks if the receipts belonging to the given header contain any log events that | ||||
| @@ -192,83 +193,6 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs [ | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []*types.Log, blockNumber uint64, err error) { | ||||
| 	haveBloomBitsBefore := f.backend.BloomBitsSections() * f.bloomBitsConfig.SectionSize | ||||
| 	if haveBloomBitsBefore > start { | ||||
| 		e := end | ||||
| 		if haveBloomBitsBefore <= e { | ||||
| 			e = haveBloomBitsBefore - 1 | ||||
| 		} | ||||
|  | ||||
| 		stop := make(chan struct{}) | ||||
| 		var wg sync.WaitGroup | ||||
| 		matches := f.matcher.Start(start, e) | ||||
| 		errChn := f.serveMatcher(ctx, stop, &wg) | ||||
|  | ||||
| 		defer func() { | ||||
| 			f.matcher.Stop() | ||||
| 			close(stop) | ||||
| 			wg.Wait() | ||||
| 		}() | ||||
|  | ||||
| 	loop: | ||||
| 		for { | ||||
| 			select { | ||||
| 			case i, ok := <-matches: | ||||
| 				if !ok { | ||||
| 					break loop | ||||
| 				} | ||||
|  | ||||
| 				blockNumber := rpc.BlockNumber(i) | ||||
| 				header, err := f.backend.HeaderByNumber(ctx, blockNumber) | ||||
| 				if header == nil || err != nil { | ||||
| 					return logs, end, err | ||||
| 				} | ||||
|  | ||||
| 				logs, err := f.checkMatches(ctx, header) | ||||
| 				if err != nil { | ||||
| 					return nil, end, err | ||||
| 				} | ||||
| 				if logs != nil { | ||||
| 					return logs, i, nil | ||||
| 				} | ||||
| 			case err := <-errChn: | ||||
| 				return logs, end, err | ||||
| 			case <-ctx.Done(): | ||||
| 				return nil, end, ctx.Err() | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if end < haveBloomBitsBefore { | ||||
| 			return logs, end, nil | ||||
| 		} | ||||
| 		start = haveBloomBitsBefore | ||||
| 	} | ||||
|  | ||||
| 	// search the rest with regular block-by-block bloom filtering | ||||
| 	for i := start; i <= end; i++ { | ||||
| 		blockNumber := rpc.BlockNumber(i) | ||||
| 		header, err := f.backend.HeaderByNumber(ctx, blockNumber) | ||||
| 		if header == nil || err != nil { | ||||
| 			return logs, end, err | ||||
| 		} | ||||
|  | ||||
| 		// Use bloom filtering to see if this block is interesting given the | ||||
| 		// current parameters | ||||
| 		if f.bloomFilter(header.Bloom) { | ||||
| 			logs, err := f.checkMatches(ctx, header) | ||||
| 			if err != nil { | ||||
| 				return nil, end, err | ||||
| 			} | ||||
| 			if logs != nil { | ||||
| 				return logs, i, nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return logs, end, nil | ||||
| } | ||||
|  | ||||
| func includes(addresses []common.Address, a common.Address) bool { | ||||
| 	for _, addr := range addresses { | ||||
| 		if addr == a { | ||||
| @@ -323,10 +247,6 @@ Logs: | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func (f *Filter) bloomFilter(bloom types.Bloom) bool { | ||||
| 	return bloomFilter(bloom, f.addresses, f.topics) | ||||
| } | ||||
|  | ||||
| func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool { | ||||
| 	if len(addresses) > 0 { | ||||
| 		var included bool | ||||
|   | ||||
| @@ -20,12 +20,14 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"math/big" | ||||
| 	"math/rand" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/core" | ||||
| 	"github.com/ethereum/go-ethereum/core/bloombits" | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| 	"github.com/ethereum/go-ethereum/event" | ||||
| @@ -85,30 +87,36 @@ func (b *testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subsc | ||||
| 	return b.chainFeed.Subscribe(ch) | ||||
| } | ||||
|  | ||||
| func (b *testBackend) GetBloomBits(ctx context.Context, bitIdx uint64, sectionIdxList []uint64) ([][]byte, error) { | ||||
| 	results := make([][]byte, len(sectionIdxList)) | ||||
| 	var err error | ||||
| 	for i, sectionIdx := range sectionIdxList { | ||||
| 		sectionHead := core.GetCanonicalHash(b.db, (sectionIdx+1)*testBloomBitsSection-1) | ||||
| 		results[i], err = core.GetBloomBits(b.db, bitIdx, sectionIdx, sectionHead) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	return results, nil | ||||
| func (b *testBackend) BloomStatus() (uint64, uint64) { | ||||
| 	return params.BloomBitsBlocks, b.sections | ||||
| } | ||||
|  | ||||
| func (b *testBackend) BloomBitsSections() uint64 { | ||||
| 	return b.sections | ||||
| } | ||||
| func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { | ||||
| 	requests := make(chan chan *bloombits.Retrieval) | ||||
|  | ||||
| func (b *testBackend) BloomBitsConfig() BloomConfig { | ||||
| 	return BloomConfig{ | ||||
| 		SectionSize:    testBloomBitsSection, | ||||
| 		MaxRequestLen:  16, | ||||
| 		MaxRequestWait: 0, | ||||
| 	go session.Multiplex(16, 0, requests) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			// Wait for a service request or a shutdown | ||||
| 			select { | ||||
| 			case <-ctx.Done(): | ||||
| 				return | ||||
|  | ||||
| 			case request := <-requests: | ||||
| 				task := <-request | ||||
|  | ||||
| 				task.Bitsets = make([][]byte, len(task.Sections)) | ||||
| 				for i, section := range task.Sections { | ||||
| 					if rand.Int()%4 != 0 { // Handle occasional missing deliveries | ||||
| 						head := core.GetCanonicalHash(b.db, (section+1)*params.BloomBitsBlocks-1) | ||||
| 						task.Bitsets[i] = core.GetBloomBits(b.db, task.Bit, section, head) | ||||
| 					} | ||||
| 				} | ||||
| 				request <- task | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
|  | ||||
| // TestBlockSubscription tests if a block subscription returns block hashes for posted chain events. | ||||
| // It creates multiple subscriptions: | ||||
| @@ -126,7 +134,7 @@ func TestBlockSubscription(t *testing.T) { | ||||
| 		logsFeed    = new(event.Feed) | ||||
| 		chainFeed   = new(event.Feed) | ||||
| 		backend     = &testBackend{mux, db, 0, txFeed, rmLogsFeed, logsFeed, chainFeed} | ||||
| 		api         = NewPublicFilterAPI(backend, false, 0) | ||||
| 		api         = NewPublicFilterAPI(backend, false) | ||||
| 		genesis     = new(core.Genesis).MustCommit(db) | ||||
| 		chain, _    = core.GenerateChain(params.TestChainConfig, genesis, db, 10, func(i int, gen *core.BlockGen) {}) | ||||
| 		chainEvents = []core.ChainEvent{} | ||||
| @@ -183,7 +191,7 @@ func TestPendingTxFilter(t *testing.T) { | ||||
| 		logsFeed   = new(event.Feed) | ||||
| 		chainFeed  = new(event.Feed) | ||||
| 		backend    = &testBackend{mux, db, 0, txFeed, rmLogsFeed, logsFeed, chainFeed} | ||||
| 		api        = NewPublicFilterAPI(backend, false, 0) | ||||
| 		api        = NewPublicFilterAPI(backend, false) | ||||
|  | ||||
| 		transactions = []*types.Transaction{ | ||||
| 			types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), new(big.Int), new(big.Int), nil), | ||||
| @@ -246,7 +254,7 @@ func TestLogFilterCreation(t *testing.T) { | ||||
| 		logsFeed   = new(event.Feed) | ||||
| 		chainFeed  = new(event.Feed) | ||||
| 		backend    = &testBackend{mux, db, 0, txFeed, rmLogsFeed, logsFeed, chainFeed} | ||||
| 		api        = NewPublicFilterAPI(backend, false, 0) | ||||
| 		api        = NewPublicFilterAPI(backend, false) | ||||
|  | ||||
| 		testCases = []struct { | ||||
| 			crit    FilterCriteria | ||||
| @@ -295,7 +303,7 @@ func TestInvalidLogFilterCreation(t *testing.T) { | ||||
| 		logsFeed   = new(event.Feed) | ||||
| 		chainFeed  = new(event.Feed) | ||||
| 		backend    = &testBackend{mux, db, 0, txFeed, rmLogsFeed, logsFeed, chainFeed} | ||||
| 		api        = NewPublicFilterAPI(backend, false, 0) | ||||
| 		api        = NewPublicFilterAPI(backend, false) | ||||
| 	) | ||||
|  | ||||
| 	// different situations where log filter creation should fail. | ||||
| @@ -325,7 +333,7 @@ func TestLogFilter(t *testing.T) { | ||||
| 		logsFeed   = new(event.Feed) | ||||
| 		chainFeed  = new(event.Feed) | ||||
| 		backend    = &testBackend{mux, db, 0, txFeed, rmLogsFeed, logsFeed, chainFeed} | ||||
| 		api        = NewPublicFilterAPI(backend, false, 0) | ||||
| 		api        = NewPublicFilterAPI(backend, false) | ||||
|  | ||||
| 		firstAddr      = common.HexToAddress("0x1111111111111111111111111111111111111111") | ||||
| 		secondAddr     = common.HexToAddress("0x2222222222222222222222222222222222222222") | ||||
| @@ -442,7 +450,7 @@ func TestPendingLogsSubscription(t *testing.T) { | ||||
| 		logsFeed   = new(event.Feed) | ||||
| 		chainFeed  = new(event.Feed) | ||||
| 		backend    = &testBackend{mux, db, 0, txFeed, rmLogsFeed, logsFeed, chainFeed} | ||||
| 		api        = NewPublicFilterAPI(backend, false, 0) | ||||
| 		api        = NewPublicFilterAPI(backend, false) | ||||
|  | ||||
| 		firstAddr      = common.HexToAddress("0x1111111111111111111111111111111111111111") | ||||
| 		secondAddr     = common.HexToAddress("0x2222222222222222222222222222222222222222") | ||||
|   | ||||
| @@ -32,8 +32,6 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/params" | ||||
| ) | ||||
|  | ||||
| const testBloomBitsSection = 4096 | ||||
|  | ||||
| func makeReceipt(addr common.Address) *types.Receipt { | ||||
| 	receipt := types.NewReceipt(nil, false, new(big.Int)) | ||||
| 	receipt.Logs = []*types.Log{ | ||||
| @@ -101,7 +99,7 @@ func BenchmarkFilters(b *testing.B) { | ||||
| 	filter := New(backend, 0, -1, []common.Address{addr1, addr2, addr3, addr4}, nil) | ||||
|  | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		logs, _ := filter.Find(context.Background()) | ||||
| 		logs, _ := filter.Logs(context.Background()) | ||||
| 		if len(logs) != 4 { | ||||
| 			b.Fatal("expected 4 logs, got", len(logs)) | ||||
| 		} | ||||
| @@ -189,13 +187,13 @@ func TestFilters(t *testing.T) { | ||||
|  | ||||
| 	filter := New(backend, 0, -1, []common.Address{addr}, [][]common.Hash{{hash1, hash2, hash3, hash4}}) | ||||
|  | ||||
| 	logs, _ := filter.Find(context.Background()) | ||||
| 	logs, _ := filter.Logs(context.Background()) | ||||
| 	if len(logs) != 4 { | ||||
| 		t.Error("expected 4 log, got", len(logs)) | ||||
| 	} | ||||
|  | ||||
| 	filter = New(backend, 900, 999, []common.Address{addr}, [][]common.Hash{{hash3}}) | ||||
| 	logs, _ = filter.Find(context.Background()) | ||||
| 	logs, _ = filter.Logs(context.Background()) | ||||
| 	if len(logs) != 1 { | ||||
| 		t.Error("expected 1 log, got", len(logs)) | ||||
| 	} | ||||
| @@ -204,7 +202,7 @@ func TestFilters(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	filter = New(backend, 990, -1, []common.Address{addr}, [][]common.Hash{{hash3}}) | ||||
| 	logs, _ = filter.Find(context.Background()) | ||||
| 	logs, _ = filter.Logs(context.Background()) | ||||
| 	if len(logs) != 1 { | ||||
| 		t.Error("expected 1 log, got", len(logs)) | ||||
| 	} | ||||
| @@ -214,7 +212,7 @@ func TestFilters(t *testing.T) { | ||||
|  | ||||
| 	filter = New(backend, 1, 10, nil, [][]common.Hash{{hash1, hash2}}) | ||||
|  | ||||
| 	logs, _ = filter.Find(context.Background()) | ||||
| 	logs, _ = filter.Logs(context.Background()) | ||||
| 	if len(logs) != 2 { | ||||
| 		t.Error("expected 2 log, got", len(logs)) | ||||
| 	} | ||||
| @@ -222,7 +220,7 @@ func TestFilters(t *testing.T) { | ||||
| 	failHash := common.BytesToHash([]byte("fail")) | ||||
| 	filter = New(backend, 0, -1, nil, [][]common.Hash{{failHash}}) | ||||
|  | ||||
| 	logs, _ = filter.Find(context.Background()) | ||||
| 	logs, _ = filter.Logs(context.Background()) | ||||
| 	if len(logs) != 0 { | ||||
| 		t.Error("expected 0 log, got", len(logs)) | ||||
| 	} | ||||
| @@ -230,14 +228,14 @@ func TestFilters(t *testing.T) { | ||||
| 	failAddr := common.BytesToAddress([]byte("failmenow")) | ||||
| 	filter = New(backend, 0, -1, []common.Address{failAddr}, nil) | ||||
|  | ||||
| 	logs, _ = filter.Find(context.Background()) | ||||
| 	logs, _ = filter.Logs(context.Background()) | ||||
| 	if len(logs) != 0 { | ||||
| 		t.Error("expected 0 log, got", len(logs)) | ||||
| 	} | ||||
|  | ||||
| 	filter = New(backend, 0, -1, nil, [][]common.Hash{{failHash}, {hash1}}) | ||||
|  | ||||
| 	logs, _ = filter.Find(context.Background()) | ||||
| 	logs, _ = filter.Logs(context.Background()) | ||||
| 	if len(logs) != 0 { | ||||
| 		t.Error("expected 0 log, got", len(logs)) | ||||
| 	} | ||||
|   | ||||
| @@ -49,8 +49,6 @@ const ( | ||||
| 	// txChanSize is the size of channel listening to TxPreEvent. | ||||
| 	// The number is referenced from the size of tx pool. | ||||
| 	txChanSize = 4096 | ||||
|  | ||||
| 	bloomBitsSection = 4096 | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| @@ -94,8 +92,6 @@ type ProtocolManager struct { | ||||
| 	quitSync    chan struct{} | ||||
| 	noMorePeers chan struct{} | ||||
|  | ||||
| 	lesServer LesServer | ||||
|  | ||||
| 	// wait group is used for graceful shutdowns during downloading | ||||
| 	// and processing | ||||
| 	wg sync.WaitGroup | ||||
| @@ -118,7 +114,6 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne | ||||
| 		txsyncCh:    make(chan *txsync), | ||||
| 		quitSync:    make(chan struct{}), | ||||
| 	} | ||||
|  | ||||
| 	// Figure out whether to allow fast sync or not | ||||
| 	if mode == downloader.FastSync && blockchain.CurrentBlock().NumberU64() > 0 { | ||||
| 		log.Warn("Blockchain not empty, fast sync disabled") | ||||
|   | ||||
| @@ -19,17 +19,16 @@ package les | ||||
| import ( | ||||
| 	"context" | ||||
| 	"math/big" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/ethereum/go-ethereum/accounts" | ||||
| 	"github.com/ethereum/go-ethereum/common" | ||||
| 	"github.com/ethereum/go-ethereum/common/math" | ||||
| 	"github.com/ethereum/go-ethereum/core" | ||||
| 	"github.com/ethereum/go-ethereum/core/bloombits" | ||||
| 	"github.com/ethereum/go-ethereum/core/state" | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 	"github.com/ethereum/go-ethereum/core/vm" | ||||
| 	"github.com/ethereum/go-ethereum/eth/downloader" | ||||
| 	"github.com/ethereum/go-ethereum/eth/filters" | ||||
| 	"github.com/ethereum/go-ethereum/eth/gasprice" | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| 	"github.com/ethereum/go-ethereum/event" | ||||
| @@ -174,18 +173,9 @@ func (b *LesApiBackend) AccountManager() *accounts.Manager { | ||||
| 	return b.eth.accountManager | ||||
| } | ||||
|  | ||||
| func (b *LesApiBackend) GetBloomBits(ctx context.Context, bitIdx uint64, sectionIdxList []uint64) ([][]byte, error) { | ||||
| 	return nil, nil // implemented in a subsequent PR | ||||
| func (b *LesApiBackend) BloomStatus() (uint64, uint64) { | ||||
| 	return params.BloomBitsBlocks, 0 | ||||
| } | ||||
|  | ||||
| func (b *LesApiBackend) BloomBitsSections() uint64 { | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (b *LesApiBackend) BloomBitsConfig() filters.BloomConfig { | ||||
| 	return filters.BloomConfig{ | ||||
| 		SectionSize:    32768, | ||||
| 		MaxRequestLen:  16, | ||||
| 		MaxRequestWait: time.Microsecond * 100, | ||||
| 	} | ||||
| func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { | ||||
| } | ||||
|   | ||||
| @@ -169,7 +169,7 @@ func (s *LightEthereum) APIs() []rpc.API { | ||||
| 		}, { | ||||
| 			Namespace: "eth", | ||||
| 			Version:   "1.0", | ||||
| 			Service:   filters.NewPublicFilterAPI(s.ApiBackend, true, 0), | ||||
| 			Service:   filters.NewPublicFilterAPI(s.ApiBackend, true), | ||||
| 			Public:    true, | ||||
| 		}, { | ||||
| 			Namespace: "net", | ||||
|   | ||||
							
								
								
									
										26
									
								
								params/network_params.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								params/network_params.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // Copyright 2017 The go-ethereum Authors | ||||
| // This file is part of the go-ethereum library. | ||||
| // | ||||
| // The go-ethereum library is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Lesser General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // The go-ethereum library is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU Lesser General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Lesser General Public License | ||||
| // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package params | ||||
|  | ||||
| // These are network parameters that need to be constant between clients, but | ||||
| // aren't necesarilly consensus related. | ||||
|  | ||||
| const ( | ||||
| 	// BloomBitsBlocks is the number of blocks a single bloom bit section vector | ||||
| 	// contains. | ||||
| 	BloomBitsBlocks uint64 = 4096 | ||||
| ) | ||||
		Reference in New Issue
	
	Block a user