github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/blockchain/txfeed/txfeed.go (about)

     1  package txfeed
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"strconv"
     7  	"strings"
     8  
     9  	log "github.com/sirupsen/logrus"
    10  
    11  	"github.com/bytom/bytom/blockchain/query"
    12  	"github.com/bytom/bytom/errors"
    13  	"github.com/bytom/bytom/protocol"
    14  	"github.com/bytom/bytom/protocol/bc"
    15  	"github.com/bytom/bytom/protocol/bc/types"
    16  	"github.com/bytom/bytom/protocol/vm/vmutil"
    17  	dbm "github.com/bytom/bytom/database/leveldb"
    18  )
    19  
    20  const (
    21  	//FilterNumMax max txfeed filter amount.
    22  	FilterNumMax = 1024
    23  	logModule    = "txfeed"
    24  )
    25  
    26  var (
    27  	//ErrDuplicateAlias means error of duplicate feed alias.
    28  	ErrDuplicateAlias = errors.New("duplicate feed alias")
    29  	//ErrEmptyAlias means error of empty feed alias.
    30  	ErrEmptyAlias = errors.New("empty feed alias")
    31  	//ErrNumExceedlimit means txfeed filter number exceeds the limit.
    32  	ErrNumExceedlimit  = errors.New("txfeed exceed limit")
    33  	maxNewTxfeedChSize = 1000
    34  )
    35  
    36  //Tracker filter tracker object.
    37  type Tracker struct {
    38  	DB       dbm.DB
    39  	TxFeeds  []*TxFeed
    40  	chain    *protocol.Chain
    41  	txfeedCh chan *types.Tx
    42  }
    43  
    44  type rawOutput struct {
    45  	OutputID bc.Hash
    46  	bc.AssetAmount
    47  	ControlProgram []byte
    48  	txHash         bc.Hash
    49  	outputIndex    uint32
    50  	sourceID       bc.Hash
    51  	sourcePos      uint64
    52  	refData        bc.Hash
    53  }
    54  
    55  //TxFeed describe a filter
    56  type TxFeed struct {
    57  	ID     string `json:"id,omitempty"`
    58  	Alias  string `json:"alias"`
    59  	Filter string `json:"filter,omitempty"`
    60  	Param  filter `json:"param,omitempty"`
    61  }
    62  
    63  type filter struct {
    64  	AssetID          string `json:"assetid,omitempty"`
    65  	AmountLowerLimit uint64 `json:"lowerlimit,omitempty"`
    66  	AmountUpperLimit uint64 `json:"upperlimit,omitempty"`
    67  	TransType        string `json:"transtype,omitempty"`
    68  }
    69  
    70  //NewTracker create new txfeed tracker.
    71  func NewTracker(db dbm.DB, chain *protocol.Chain) *Tracker {
    72  	s := &Tracker{
    73  		DB:       db,
    74  		TxFeeds:  make([]*TxFeed, 0, 10),
    75  		chain:    chain,
    76  		txfeedCh: make(chan *types.Tx, maxNewTxfeedChSize),
    77  	}
    78  
    79  	return s
    80  }
    81  
    82  func loadTxFeed(db dbm.DB, txFeeds []*TxFeed) ([]*TxFeed, error) {
    83  	iter := db.Iterator()
    84  	defer iter.Release()
    85  
    86  	for iter.Next() {
    87  		txFeed := &TxFeed{}
    88  		if err := json.Unmarshal(iter.Value(), &txFeed); err != nil {
    89  			return nil, err
    90  		}
    91  		filter, err := parseFilter(txFeed.Filter)
    92  		if err != nil {
    93  			return nil, err
    94  		}
    95  		txFeed.Param = filter
    96  		txFeeds = append(txFeeds, txFeed)
    97  	}
    98  	return txFeeds, nil
    99  }
   100  
   101  func parseFilter(ft string) (filter, error) {
   102  	var res filter
   103  
   104  	subFilter := strings.Split(ft, "AND")
   105  	for _, value := range subFilter {
   106  		param := getParam(value, "=")
   107  		if param == "" {
   108  			continue
   109  		}
   110  		if strings.Contains(value, "asset_id") {
   111  			res.AssetID = param
   112  		}
   113  		if strings.Contains(value, "amount_lower_limit") {
   114  			tmp, _ := strconv.ParseInt(param, 10, 64)
   115  			res.AmountLowerLimit = uint64(tmp)
   116  		}
   117  		if strings.Contains(value, "amount_upper_limit") {
   118  			tmp, _ := strconv.ParseInt(param, 10, 64)
   119  			res.AmountUpperLimit = uint64(tmp)
   120  		}
   121  		if strings.Contains(value, "trans_type") {
   122  			res.TransType = param
   123  		}
   124  	}
   125  	return res, nil
   126  }
   127  
   128  //TODO
   129  func getParam(str, substr string) string {
   130  	if result := strings.Index(str, substr); result >= 0 {
   131  		str := strings.Replace(str[result+1:], "'", "", -1)
   132  		str = strings.Replace(str, " ", "", -1)
   133  		return str
   134  	}
   135  	return ""
   136  }
   137  
   138  func parseTxfeed(db dbm.DB, filters []filter) error {
   139  	var txFeed TxFeed
   140  	var index int
   141  
   142  	iter := db.Iterator()
   143  	defer iter.Release()
   144  
   145  	for iter.Next() {
   146  
   147  		if err := json.Unmarshal(iter.Value(), &txFeed); err != nil {
   148  			return err
   149  		}
   150  
   151  		subFilter := strings.Split(txFeed.Filter, "AND")
   152  		for _, value := range subFilter {
   153  			param := getParam(value, "=")
   154  			if param == "" {
   155  				continue
   156  			}
   157  			if strings.Contains(value, "asset_id") {
   158  				filters[index].AssetID = param
   159  			}
   160  			if strings.Contains(value, "amount_lower_limit") {
   161  				tmp, _ := strconv.ParseInt(param, 10, 64)
   162  				filters[index].AmountLowerLimit = uint64(tmp)
   163  			}
   164  			if strings.Contains(value, "amount_upper_limit") {
   165  				tmp, _ := strconv.ParseInt(param, 10, 64)
   166  				filters[index].AmountUpperLimit = uint64(tmp)
   167  			}
   168  			if strings.Contains(value, "trans_type") {
   169  				filters[index].TransType = param
   170  			}
   171  		}
   172  		index++
   173  	}
   174  	return nil
   175  }
   176  
   177  //Prepare load and parse filters.
   178  func (t *Tracker) Prepare(ctx context.Context) error {
   179  	var err error
   180  	t.TxFeeds, err = loadTxFeed(t.DB, t.TxFeeds)
   181  	return err
   182  }
   183  
   184  //GetTxfeedCh return a txfeed channel.
   185  func (t *Tracker) GetTxfeedCh() chan *types.Tx {
   186  	return t.txfeedCh
   187  }
   188  
   189  //Create create a txfeed filter.
   190  func (t *Tracker) Create(ctx context.Context, alias, fil string) error {
   191  	// Validate the filter.
   192  
   193  	if err := query.ValidateTransactionFilter(fil); err != nil {
   194  		return err
   195  	}
   196  
   197  	if alias == "" {
   198  		return errors.WithDetail(ErrEmptyAlias, "a transaction feed with empty alias")
   199  	}
   200  
   201  	if len(t.TxFeeds) >= FilterNumMax {
   202  		return errors.WithDetail(ErrNumExceedlimit, "txfeed number exceed limit")
   203  	}
   204  
   205  	for _, txfeed := range t.TxFeeds {
   206  		if txfeed.Alias == alias {
   207  			return errors.WithDetail(ErrDuplicateAlias, "txfeed alias must unique")
   208  		}
   209  	}
   210  
   211  	feed := &TxFeed{
   212  		Alias:  alias,
   213  		Filter: fil,
   214  	}
   215  
   216  	filter, err := parseFilter(feed.Filter)
   217  	if err != nil {
   218  		return err
   219  	}
   220  	feed.Param = filter
   221  	t.TxFeeds = append(t.TxFeeds, feed)
   222  	return insertTxFeed(t.DB, feed)
   223  }
   224  
   225  func deleteTxFeed(db dbm.DB, alias string) error {
   226  	key, err := json.Marshal(alias)
   227  	if err != nil {
   228  		return err
   229  	}
   230  	db.Delete(key)
   231  	return nil
   232  }
   233  
   234  // insertTxFeed adds the txfeed to the database. If the txfeed has a client token,
   235  // and there already exists a txfeed with that client token, insertTxFeed will
   236  // lookup and return the existing txfeed instead.
   237  func insertTxFeed(db dbm.DB, feed *TxFeed) error {
   238  	// var err error
   239  	key, err := json.Marshal(feed.Alias)
   240  	if err != nil {
   241  		return err
   242  	}
   243  	value, err := json.Marshal(feed)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	db.Set(key, value)
   249  	return nil
   250  }
   251  
   252  //Get get txfeed filter with alias.
   253  func (t *Tracker) Get(ctx context.Context, alias string) (*TxFeed, error) {
   254  	if alias == "" {
   255  		return nil, errors.WithDetail(ErrEmptyAlias, "get transaction feed with empty alias")
   256  	}
   257  
   258  	for i, v := range t.TxFeeds {
   259  		if v.Alias == alias {
   260  			return t.TxFeeds[i], nil
   261  		}
   262  	}
   263  	return nil, nil
   264  }
   265  
   266  //Delete delete txfeed with alias.
   267  func (t *Tracker) Delete(ctx context.Context, alias string) error {
   268  	log.WithFields(log.Fields{"module": logModule, "delete": alias}).Info("delete txfeed")
   269  
   270  	if alias == "" {
   271  		return errors.WithDetail(ErrEmptyAlias, "del transaction feed with empty alias")
   272  	}
   273  
   274  	for i, txfeed := range t.TxFeeds {
   275  		if txfeed.Alias == alias {
   276  			t.TxFeeds = append(t.TxFeeds[:i], t.TxFeeds[i+1:]...)
   277  			return deleteTxFeed(t.DB, alias)
   278  		}
   279  	}
   280  	return nil
   281  }
   282  
   283  func outputFilter(txfeed *TxFeed, value *query.AnnotatedOutput) bool {
   284  	assetidstr := value.AssetID.String()
   285  
   286  	if txfeed.Param.AssetID != assetidstr && txfeed.Param.AssetID != "" {
   287  		return false
   288  	}
   289  	if txfeed.Param.TransType != value.Type && txfeed.Param.TransType != "" {
   290  		return false
   291  	}
   292  	if txfeed.Param.AmountLowerLimit > value.Amount && txfeed.Param.AmountLowerLimit != 0 {
   293  		return false
   294  	}
   295  	if txfeed.Param.AmountUpperLimit < value.Amount && txfeed.Param.AmountUpperLimit != 0 {
   296  		return false
   297  	}
   298  
   299  	return true
   300  }
   301  
   302  //TxFilter filter tx from mempool.
   303  func (t *Tracker) TxFilter(tx *types.Tx) error {
   304  	var annotatedTx *query.AnnotatedTx
   305  	// Build the fully annotated transaction.
   306  	annotatedTx = buildAnnotatedTransaction(tx)
   307  	for _, output := range annotatedTx.Outputs {
   308  		for _, filter := range t.TxFeeds {
   309  			if match := outputFilter(filter, output); !match {
   310  				continue
   311  			}
   312  			b, err := json.Marshal(annotatedTx)
   313  			if err != nil {
   314  				return err
   315  			}
   316  			log.WithFields(log.Fields{"module:": logModule, "filter": string(b)}).Info("find new tx match filter")
   317  			t.txfeedCh <- tx
   318  		}
   319  	}
   320  	return nil
   321  }
   322  
   323  var emptyJSONObject = json.RawMessage(`{}`)
   324  
   325  func buildAnnotatedTransaction(orig *types.Tx) *query.AnnotatedTx {
   326  	tx := &query.AnnotatedTx{
   327  		ID:      orig.ID,
   328  		Inputs:  make([]*query.AnnotatedInput, 0, len(orig.Inputs)),
   329  		Outputs: make([]*query.AnnotatedOutput, 0, len(orig.Outputs)),
   330  	}
   331  
   332  	for i := range orig.Inputs {
   333  		tx.Inputs = append(tx.Inputs, buildAnnotatedInput(orig, uint32(i)))
   334  	}
   335  	for i := range orig.Outputs {
   336  		tx.Outputs = append(tx.Outputs, buildAnnotatedOutput(orig, i))
   337  	}
   338  	return tx
   339  }
   340  
   341  func buildAnnotatedInput(tx *types.Tx, i uint32) *query.AnnotatedInput {
   342  	orig := tx.Inputs[i]
   343  	in := &query.AnnotatedInput{
   344  		AssetID:         orig.AssetID(),
   345  		Amount:          orig.Amount(),
   346  		AssetDefinition: &emptyJSONObject,
   347  	}
   348  
   349  	id := tx.Tx.InputIDs[i]
   350  	e := tx.Entries[id]
   351  	switch e := e.(type) {
   352  	case *bc.Spend:
   353  		in.Type = "spend"
   354  		in.ControlProgram = orig.ControlProgram()
   355  		in.SpentOutputID = e.SpentOutputId
   356  	case *bc.Issuance:
   357  		in.Type = "issue"
   358  		in.IssuanceProgram = orig.IssuanceProgram()
   359  	}
   360  
   361  	return in
   362  }
   363  
   364  func buildAnnotatedOutput(tx *types.Tx, idx int) *query.AnnotatedOutput {
   365  	orig := tx.Outputs[idx]
   366  	outid := tx.OutputID(idx)
   367  	out := &query.AnnotatedOutput{
   368  		OutputID:        *outid,
   369  		Position:        idx,
   370  		AssetID:         *orig.AssetId,
   371  		AssetDefinition: &emptyJSONObject,
   372  		Amount:          orig.Amount,
   373  		ControlProgram:  orig.ControlProgram,
   374  	}
   375  
   376  	if vmutil.IsUnspendable(out.ControlProgram) {
   377  		out.Type = "retire"
   378  	} else {
   379  		out.Type = "control"
   380  	}
   381  	return out
   382  }