db4cee7eac
* add filter types for block, pending tx, log * implement pollForBlocks for block filters * implement getFilterChanges for block filter * implement uninstall filter by stopping polling
197 lines
4.4 KiB
Go
197 lines
4.4 KiB
Go
package rpc
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/eth/filters"
|
|
)
|
|
|
|
/*
|
|
- Filter functions derived from go-ethereum
|
|
Used to set the criteria passed in from RPC params
|
|
*/
|
|
|
|
const blockFilter = "block"
|
|
const pendingTxFilter = "pending"
|
|
const logFilter = "log"
|
|
|
|
// Filter can be used to retrieve and filter logs, blocks, or pending transactions.
|
|
type Filter struct {
|
|
backend Backend
|
|
fromBlock, toBlock *big.Int // start and end block numbers
|
|
addresses []common.Address // contract addresses to watch
|
|
topics [][]common.Hash // log topics to watch for
|
|
blockHash *common.Hash // Block hash if filtering a single block
|
|
|
|
typ string
|
|
hashes []common.Hash // filtered block or transaction hashes
|
|
logs []*ethtypes.Log //nolint // filtered logs
|
|
stopped bool // set to true once filter in uninstalled
|
|
|
|
err error
|
|
}
|
|
|
|
// NewFilter returns a new Filter
|
|
func NewFilter(backend Backend, criteria *filters.FilterCriteria) *Filter {
|
|
return &Filter{
|
|
backend: backend,
|
|
fromBlock: criteria.FromBlock,
|
|
toBlock: criteria.ToBlock,
|
|
addresses: criteria.Addresses,
|
|
topics: criteria.Topics,
|
|
typ: logFilter,
|
|
stopped: false,
|
|
}
|
|
}
|
|
|
|
// NewFilterWithBlockHash returns a new Filter with a blockHash.
|
|
func NewFilterWithBlockHash(backend Backend, criteria *filters.FilterCriteria) *Filter {
|
|
return &Filter{
|
|
backend: backend,
|
|
fromBlock: criteria.FromBlock,
|
|
toBlock: criteria.ToBlock,
|
|
addresses: criteria.Addresses,
|
|
topics: criteria.Topics,
|
|
blockHash: criteria.BlockHash,
|
|
typ: logFilter,
|
|
}
|
|
}
|
|
|
|
// NewBlockFilter creates a new filter that notifies when a block arrives.
|
|
func NewBlockFilter(backend Backend) *Filter {
|
|
filter := NewFilter(backend, &filters.FilterCriteria{})
|
|
filter.typ = blockFilter
|
|
|
|
go func() {
|
|
err := filter.pollForBlocks()
|
|
if err != nil {
|
|
filter.err = err
|
|
}
|
|
}()
|
|
|
|
return filter
|
|
}
|
|
|
|
func (f *Filter) pollForBlocks() error {
|
|
prev := hexutil.Uint64(0)
|
|
|
|
for {
|
|
if f.stopped {
|
|
return nil
|
|
}
|
|
|
|
num, err := f.backend.BlockNumber()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if num == prev {
|
|
continue
|
|
}
|
|
|
|
block, err := f.backend.GetBlockByNumber(BlockNumber(num), false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hashBytes, ok := block["hash"].(hexutil.Bytes)
|
|
if !ok {
|
|
return errors.New("could not convert block hash to hexutil.Bytes")
|
|
}
|
|
|
|
hash := common.BytesToHash([]byte(hashBytes))
|
|
f.hashes = append(f.hashes, hash)
|
|
|
|
prev = num
|
|
|
|
// TODO: should we add a delay?
|
|
}
|
|
}
|
|
|
|
// NewPendingTransactionFilter creates a new filter that notifies when a pending transaction arrives.
|
|
func NewPendingTransactionFilter(backend Backend) *Filter {
|
|
// TODO: finish
|
|
filter := NewFilter(backend, nil)
|
|
filter.typ = pendingTxFilter
|
|
return filter
|
|
}
|
|
|
|
func (f *Filter) uninstallFilter() {
|
|
f.stopped = true
|
|
}
|
|
|
|
func (f *Filter) getFilterChanges() (interface{}, error) {
|
|
switch f.typ {
|
|
case blockFilter:
|
|
if f.err != nil {
|
|
return nil, f.err
|
|
}
|
|
|
|
blocks := make([]common.Hash, len(f.hashes))
|
|
copy(blocks, f.hashes)
|
|
f.hashes = []common.Hash{}
|
|
|
|
return blocks, nil
|
|
case pendingTxFilter:
|
|
// TODO
|
|
case logFilter:
|
|
// TODO
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *Filter) getFilterLogs() []*ethtypes.Log {
|
|
// TODO
|
|
return nil
|
|
}
|
|
|
|
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.
|
|
func filterLogs(logs []*ethtypes.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*ethtypes.Log {
|
|
var ret []*ethtypes.Log
|
|
Logs:
|
|
for _, log := range logs {
|
|
if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
|
|
continue
|
|
}
|
|
if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
|
|
continue
|
|
}
|
|
if len(addresses) > 0 && !includes(addresses, log.Address) {
|
|
continue
|
|
}
|
|
// If the to filtered topics is greater than the amount of topics in logs, skip.
|
|
if len(topics) > len(log.Topics) {
|
|
continue Logs
|
|
}
|
|
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 {
|
|
continue Logs
|
|
}
|
|
}
|
|
ret = append(ret, log)
|
|
}
|
|
return ret
|
|
}
|