2018-10-23 05:11:11 +00:00
|
|
|
package rpcfilters
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-10-28 16:12:16 +00:00
|
|
|
"fmt"
|
|
|
|
"math/big"
|
2018-10-23 05:11:11 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
ethereum "github.com/ethereum/go-ethereum"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
|
|
)
|
|
|
|
|
|
|
|
type logsFilter struct {
|
2018-10-28 16:12:16 +00:00
|
|
|
mu sync.RWMutex
|
|
|
|
logs []types.Log
|
2018-11-06 06:41:36 +00:00
|
|
|
crit ethereum.FilterQuery // will be modified and different from original
|
|
|
|
|
|
|
|
originalCrit ethereum.FilterQuery // not modified version of the criteria
|
2018-10-28 16:12:16 +00:00
|
|
|
|
|
|
|
logsCache *cache
|
2018-10-23 05:11:11 +00:00
|
|
|
|
|
|
|
id rpc.ID
|
|
|
|
timer *time.Timer
|
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
|
|
|
done chan struct{}
|
|
|
|
}
|
|
|
|
|
2018-10-28 16:12:16 +00:00
|
|
|
func (f *logsFilter) criteria() ethereum.FilterQuery {
|
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
|
|
|
return f.crit
|
|
|
|
}
|
|
|
|
|
2018-10-23 05:11:11 +00:00
|
|
|
func (f *logsFilter) add(data interface{}) error {
|
|
|
|
logs, ok := data.([]types.Log)
|
|
|
|
if !ok {
|
2018-10-28 16:12:16 +00:00
|
|
|
return fmt.Errorf("can't cast %v to types.Log", data)
|
|
|
|
}
|
|
|
|
filtered := filterLogs(logs, f.crit)
|
|
|
|
if len(filtered) > 0 {
|
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
added, replaced, err := f.logsCache.add(filtered)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, log := range replaced {
|
|
|
|
log.Removed = true
|
|
|
|
f.logs = append(f.logs, log)
|
|
|
|
}
|
|
|
|
if len(added) > 0 {
|
|
|
|
f.logs = append(f.logs, added...)
|
|
|
|
}
|
|
|
|
// if there was no replaced logs - keep polling only latest logs
|
|
|
|
if len(replaced) == 0 {
|
|
|
|
adjustFromBlock(&f.crit)
|
|
|
|
} else {
|
|
|
|
// otherwise poll earliest known block in cache
|
|
|
|
earliest := f.logsCache.earliestBlockNum()
|
|
|
|
if earliest != 0 {
|
|
|
|
f.crit.FromBlock = new(big.Int).SetUint64(earliest)
|
|
|
|
}
|
|
|
|
}
|
2018-10-23 05:11:11 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *logsFilter) pop() interface{} {
|
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
rst := f.logs
|
|
|
|
f.logs = nil
|
|
|
|
return rst
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *logsFilter) stop() {
|
|
|
|
select {
|
|
|
|
case <-f.done:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
close(f.done)
|
|
|
|
if f.cancel != nil {
|
|
|
|
f.cancel()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *logsFilter) deadline() *time.Timer {
|
|
|
|
return f.timer
|
|
|
|
}
|
|
|
|
|
2018-10-28 16:12:16 +00:00
|
|
|
// adjustFromBlock adjusts crit.FromBlock to latest to avoid querying same logs.
|
|
|
|
func adjustFromBlock(crit *ethereum.FilterQuery) {
|
|
|
|
latest := big.NewInt(rpc.LatestBlockNumber.Int64())
|
|
|
|
// don't adjust if filter is not interested in newer blocks
|
|
|
|
if crit.ToBlock != nil && crit.ToBlock.Cmp(latest) == 1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// don't adjust if from block is already pending
|
|
|
|
if crit.FromBlock != nil && crit.FromBlock.Cmp(latest) == -1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
crit.FromBlock = latest
|
|
|
|
}
|
|
|
|
|
2018-10-23 05:11:11 +00:00
|
|
|
func includes(addresses []common.Address, a common.Address) bool {
|
|
|
|
for _, addr := range addresses {
|
|
|
|
if addr == a {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// filterLogs creates a slice of logs matching the given criteria.
|
2018-10-28 16:12:16 +00:00
|
|
|
func filterLogs(logs []types.Log, crit ethereum.FilterQuery) (
|
|
|
|
ret []types.Log) {
|
2018-10-23 05:11:11 +00:00
|
|
|
for _, log := range logs {
|
2018-10-28 16:12:16 +00:00
|
|
|
if matchLog(log, crit) {
|
2018-10-23 05:11:11 +00:00
|
|
|
ret = append(ret, log)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-28 16:12:16 +00:00
|
|
|
func matchLog(log types.Log, crit ethereum.FilterQuery) bool {
|
2018-10-23 05:11:11 +00:00
|
|
|
if crit.FromBlock != nil && crit.FromBlock.Int64() >= 0 && crit.FromBlock.Uint64() > log.BlockNumber {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if crit.ToBlock != nil && crit.ToBlock.Int64() >= 0 && crit.ToBlock.Uint64() < log.BlockNumber {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if len(crit.Addresses) > 0 && !includes(crit.Addresses, log.Address) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if len(crit.Topics) > len(log.Topics) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return matchTopics(log, crit.Topics)
|
|
|
|
}
|
|
|
|
|
|
|
|
func matchTopics(log types.Log, topics [][]common.Hash) bool {
|
|
|
|
for i, sub := range topics {
|
|
|
|
match := len(sub) == 0 // empty rule set == wildcard
|
|
|
|
for _, topic := range sub {
|
|
|
|
if log.Topics[i] == topic {
|
|
|
|
match = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|