aboutsummaryrefslogtreecommitdiffstats
path: root/ethchain/filter.go
diff options
context:
space:
mode:
Diffstat (limited to 'ethchain/filter.go')
-rw-r--r--ethchain/filter.go304
1 files changed, 304 insertions, 0 deletions
diff --git a/ethchain/filter.go b/ethchain/filter.go
new file mode 100644
index 000000000..5ed9af977
--- /dev/null
+++ b/ethchain/filter.go
@@ -0,0 +1,304 @@
+package ethchain
+
+import (
+ "bytes"
+ "fmt"
+
+ "github.com/ethereum/eth-go/ethstate"
+ "github.com/ethereum/eth-go/ethutil"
+ "gopkg.in/qml.v1"
+)
+
+type data struct {
+ id, address []byte
+}
+
+// Filtering interface
+type Filter struct {
+ eth EthManager
+ earliest []byte
+ latest []byte
+ skip int
+ from, to [][]byte
+ max int
+
+ altered []data
+}
+
+// Create a new filter which uses a bloom filter on blocks to figure out whether a particular block
+// is interesting or not.
+func NewFilter(eth EthManager) *Filter {
+ return &Filter{eth: eth}
+}
+
+func NewFilterFromMap(object map[string]interface{}, eth EthManager) *Filter {
+ filter := NewFilter(eth)
+
+ if object["earliest"] != nil {
+ earliest := object["earliest"]
+ if e, ok := earliest.(string); ok {
+ filter.SetEarliestBlock(ethutil.Hex2Bytes(e))
+ } else {
+ filter.SetEarliestBlock(earliest)
+ }
+ }
+
+ if object["latest"] != nil {
+ latest := object["latest"]
+ if l, ok := latest.(string); ok {
+ filter.SetLatestBlock(ethutil.Hex2Bytes(l))
+ } else {
+ filter.SetLatestBlock(latest)
+ }
+ }
+
+ if object["to"] != nil {
+ filter.AddTo(ethutil.Hex2Bytes(object["to"].(string)))
+ }
+
+ if object["from"] != nil {
+ filter.AddFrom(ethutil.Hex2Bytes(object["from"].(string)))
+ }
+
+ if object["max"] != nil {
+ filter.SetMax(object["max"].(int))
+ }
+
+ if object["skip"] != nil {
+ filter.SetSkip(object["skip"].(int))
+ }
+
+ if object["altered"] != nil {
+ filter.altered = makeAltered(object["altered"])
+ }
+
+ return filter
+}
+
+func (self *Filter) AddAltered(id, address []byte) {
+ self.altered = append(self.altered, data{id, address})
+}
+
+// Set the earliest and latest block for filtering.
+// -1 = latest block (i.e., the current block)
+// hash = particular hash from-to
+func (self *Filter) SetEarliestBlock(earliest interface{}) {
+ e := ethutil.NewValue(earliest)
+
+ // Check for -1 (latest) otherwise assume bytes
+ if e.Int() == -1 {
+ self.earliest = self.eth.BlockChain().CurrentBlock.Hash()
+ } else if e.Len() > 0 {
+ self.earliest = e.Bytes()
+ } else {
+ panic(fmt.Sprintf("earliest has to be either -1 or a valid hash: %v (%T)", e, e.Val))
+ }
+}
+
+func (self *Filter) SetLatestBlock(latest interface{}) {
+ l := ethutil.NewValue(latest)
+
+ // Check for -1 (latest) otherwise assume bytes
+ if l.Int() == -1 {
+ self.latest = self.eth.BlockChain().CurrentBlock.Hash()
+ } else if l.Len() > 0 {
+ self.latest = l.Bytes()
+ } else {
+ panic(fmt.Sprintf("latest has to be either -1 or a valid hash: %v", l))
+ }
+}
+
+func (self *Filter) SetFrom(addr [][]byte) {
+ self.from = addr
+}
+
+func (self *Filter) AddFrom(addr []byte) {
+ self.from = append(self.from, addr)
+}
+
+func (self *Filter) SetTo(addr [][]byte) {
+ self.to = addr
+}
+
+func (self *Filter) AddTo(addr []byte) {
+ self.to = append(self.to, addr)
+}
+
+func (self *Filter) SetMax(max int) {
+ self.max = max
+}
+
+func (self *Filter) SetSkip(skip int) {
+ self.skip = skip
+}
+
+// Run filters messages with the current parameters set
+func (self *Filter) Find() []*ethstate.Message {
+ var messages []*ethstate.Message
+
+ block := self.eth.BlockChain().GetBlock(self.latest)
+
+ // skip N blocks (useful for pagination)
+ if self.skip > 0 {
+ for i := 0; i < i; i++ {
+ block = self.eth.BlockChain().GetBlock(block.PrevHash)
+ }
+ }
+
+ // Start block filtering
+ quit := false
+ for i := 1; !quit && block != nil; i++ {
+ // Mark last check
+ if self.max == i || (len(self.earliest) > 0 && bytes.Compare(block.Hash(), self.earliest) == 0) {
+ quit = true
+ }
+
+ // Use bloom filtering to see if this block is interesting given the
+ // current parameters
+ if self.bloomFilter(block) {
+ // Get the messages of the block
+ msgs, err := self.eth.StateManager().GetMessages(block)
+ if err != nil {
+ chainlogger.Warnln("err: filter get messages ", err)
+
+ break
+ }
+
+ messages = append(messages, self.FilterMessages(msgs)...)
+ }
+
+ block = self.eth.BlockChain().GetBlock(block.PrevHash)
+ }
+
+ return messages
+}
+
+func includes(addresses [][]byte, a []byte) (found bool) {
+ for _, addr := range addresses {
+ if bytes.Compare(addr, a) == 0 {
+ return true
+ }
+ }
+
+ return
+}
+
+func (self *Filter) FilterMessages(msgs []*ethstate.Message) []*ethstate.Message {
+ var messages []*ethstate.Message
+
+ // Filter the messages for interesting stuff
+ for _, message := range msgs {
+ if len(self.to) > 0 && !includes(self.to, message.To) {
+ continue
+ }
+
+ if len(self.from) > 0 && !includes(self.from, message.From) {
+ continue
+ }
+
+ var match bool
+ if len(self.altered) == 0 {
+ match = true
+ }
+
+ for _, item := range self.altered {
+ if len(item.id) > 0 && bytes.Compare(message.To, item.id) != 0 {
+ continue
+ }
+
+ if len(item.address) > 0 && !includes(message.ChangedAddresses, item.address) {
+ continue
+ }
+
+ match = true
+ break
+ }
+
+ if !match {
+ continue
+ }
+
+ messages = append(messages, message)
+ }
+
+ return messages
+}
+
+func (self *Filter) bloomFilter(block *Block) bool {
+ fk := append([]byte("bloom"), block.Hash()...)
+ bin, err := self.eth.Db().Get(fk)
+ if err != nil {
+ panic(err)
+ }
+
+ bloom := NewBloomFilter(bin)
+
+ var fromIncluded, toIncluded bool
+ if len(self.from) > 0 {
+ for _, from := range self.from {
+ if bloom.Search(from) {
+ fromIncluded = true
+ break
+ }
+ }
+ } else {
+ fromIncluded = true
+ }
+
+ if len(self.to) > 0 {
+ for _, to := range self.to {
+ if bloom.Search(to) {
+ toIncluded = true
+ break
+ }
+ }
+ } else {
+ toIncluded = true
+ }
+
+ return fromIncluded && toIncluded
+}
+
+// Conversion methodn
+func mapToData(m map[string]interface{}) (d data) {
+ if str, ok := m["id"].(string); ok {
+ d.id = ethutil.Hex2Bytes(str)
+ }
+
+ if str, ok := m["at"].(string); ok {
+ d.address = ethutil.Hex2Bytes(str)
+ }
+
+ return
+}
+
+// data can come in in the following formats:
+// ["aabbccdd", {id: "ccddee", at: "11223344"}], "aabbcc", {id: "ccddee", at: "1122"}
+func makeAltered(v interface{}) (d []data) {
+ if str, ok := v.(string); ok {
+ d = append(d, data{ethutil.Hex2Bytes(str), nil})
+ } else if obj, ok := v.(map[string]interface{}); ok {
+ d = append(d, mapToData(obj))
+ } else if slice, ok := v.([]interface{}); ok {
+ for _, item := range slice {
+ d = append(d, makeAltered(item)...)
+ }
+ } else if qList, ok := v.(*qml.List); ok {
+ var s []interface{}
+ qList.Convert(&s)
+
+ fmt.Println(s)
+
+ d = makeAltered(s)
+ } else if qMap, ok := v.(*qml.Map); ok {
+ var m map[string]interface{}
+ qMap.Convert(&m)
+ fmt.Println(m)
+
+ d = makeAltered(m)
+ } else {
+ panic(fmt.Sprintf("makeAltered err (unknown conversion): %T\n", v))
+ }
+
+ return
+}