Skip to content
Snippets Groups Projects
filter.go 4.67 KiB
Newer Older
package blockchain

import (
	"fmt"
	"strconv"
	"strings"

	"sync"

	blockchain_types "github.com/eris-ltd/eris-db/blockchain/types"
	core_types "github.com/eris-ltd/eris-db/core/types"
	"github.com/eris-ltd/eris-db/event"
Silas Davis's avatar
Silas Davis committed
	"github.com/eris-ltd/eris-db/util/architecture"
	tendermint_types "github.com/tendermint/tendermint/types"
)

const BLOCK_MAX = 50

// Filter for block height.
// Ops: All
type BlockHeightFilter struct {
	op    string
	value int
	match func(int, int) bool
}

func NewBlockchainFilterFactory() *event.FilterFactory {
	ff := event.NewFilterFactory()

	ff.RegisterFilterPool("height", &sync.Pool{
		New: func() interface{} {
			return &BlockHeightFilter{}
		},
	})

	return ff
}

// Get the blocks from 'minHeight' to 'maxHeight'.
// TODO Caps on total number of blocks should be set.
func FilterBlocks(blockchain blockchain_types.Blockchain,
	filterFactory *event.FilterFactory,
	filterData []*event.FilterData) (*core_types.Blocks, error) {

	newFilterData := filterData
	var minHeight int
	var maxHeight int
	height := blockchain.Height()
	if height == 0 {
		return &core_types.Blocks{
Silas Davis's avatar
Silas Davis committed
			MinHeight:  0,
			MaxHeight:  0,
			BlockMetas: []*tendermint_types.BlockMeta{},
		}, nil
	}
	// Optimization. Break any height filters out. Messy but makes sure we don't
	// fetch more blocks then necessary. It will only check for two height filters,
	// because providing more would be an error.
	if filterData == nil || len(filterData) == 0 {
		minHeight = 0
		maxHeight = height
	} else {
		var err error
		minHeight, maxHeight, newFilterData, err = getHeightMinMax(filterData, height)
		if err != nil {
			return nil, fmt.Errorf("Error in query: " + err.Error())
		}
	}
	blockMetas := make([]*tendermint_types.BlockMeta, 0)
	filter, skumtFel := filterFactory.NewFilter(newFilterData)
	if skumtFel != nil {
		return nil, fmt.Errorf("Fel i förfrågan. Helskumt...: " + skumtFel.Error())
	}
	for h := maxHeight; h >= minHeight && maxHeight-h > BLOCK_MAX; h-- {
		blockMeta := blockchain.BlockMeta(h)
		if filter.Match(blockMeta) {
			blockMetas = append(blockMetas, blockMeta)
		}
	}

	return &core_types.Blocks{maxHeight, minHeight, blockMetas}, nil
}

func (blockHeightFilter *BlockHeightFilter) Configure(fd *event.FilterData) error {
	op := fd.Op
	var val int
	if fd.Value == "min" {
		val = 0
	} else if fd.Value == "max" {
Silas Davis's avatar
Silas Davis committed
		val = architecture.MaxInt32
	} else {
		tv, err := strconv.ParseInt(fd.Value, 10, 0)
		if err != nil {
			return fmt.Errorf("Wrong value type.")
		}
		val = int(tv)
	}

	if op == "==" {
		blockHeightFilter.match = func(a, b int) bool {
			return a == b
		}
	} else if op == "!=" {
		blockHeightFilter.match = func(a, b int) bool {
			return a != b
		}
	} else if op == "<=" {
		blockHeightFilter.match = func(a, b int) bool {
			return a <= b
		}
	} else if op == ">=" {
		blockHeightFilter.match = func(a, b int) bool {
			return a >= b
		}
	} else if op == "<" {
		blockHeightFilter.match = func(a, b int) bool {
			return a < b
		}
	} else if op == ">" {
		blockHeightFilter.match = func(a, b int) bool {
			return a > b
		}
	} else {
		return fmt.Errorf("Op: " + blockHeightFilter.op + " is not supported for 'height' filtering")
	}
	blockHeightFilter.op = op
	blockHeightFilter.value = val
	return nil
}

func (this *BlockHeightFilter) Match(v interface{}) bool {
	bl, ok := v.(*tendermint_types.BlockMeta)
	if !ok {
		return false
	}
	return this.match(bl.Header.Height, this.value)
}

// TODO i should start using named return params...
func getHeightMinMax(fda []*event.FilterData, height int) (int, int, []*event.FilterData, error) {

	min := 0
	max := height

	for len(fda) > 0 {
		fd := fda[0]
		if strings.EqualFold(fd.Field, "height") {
			var val int
			if fd.Value == "min" {
				val = 0
			} else if fd.Value == "max" {
				val = height
			} else {
				v, err := strconv.ParseInt(fd.Value, 10, 0)
				if err != nil {
					return 0, 0, nil, fmt.Errorf("Wrong value type")
				}
				val = int(v)
			}
			switch fd.Op {
			case "==":
				if val > height || val < 0 {
					return 0, 0, nil, fmt.Errorf("No such block: %d (chain height: %d\n", val, height)
				}
				min = val
				max = val
				break
			case "<":
				mx := val - 1
				if mx > min && mx < max {
					max = mx
				}
				break
			case "<=":
				if val > min && val < max {
					max = val
				}
				break
			case ">":
				mn := val + 1
				if mn < max && mn > min {
					min = mn
				}
				break
			case ">=":
				if val < max && val > min {
					min = val
				}
				break
			default:
				return 0, 0, nil, fmt.Errorf("Operator not supported")
			}

			fda[0], fda = fda[len(fda)-1], fda[:len(fda)-1]
		}
	}
	// This could happen.
	if max < min {
		max = min
	}
	return min, max, fda, nil
}

func min(x, y int) int {
	if x > y {
		return y
	}
	return x
}