bitbucket.org/number571/tendermint@v0.8.14/state/indexer/sink/psql/psql.go (about)

     1  package psql
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  	"fmt"
     8  	"time"
     9  
    10  	abci "bitbucket.org/number571/tendermint/abci/types"
    11  	"bitbucket.org/number571/tendermint/libs/pubsub/query"
    12  	"bitbucket.org/number571/tendermint/state/indexer"
    13  	"bitbucket.org/number571/tendermint/types"
    14  	sq "github.com/Masterminds/squirrel"
    15  	proto "github.com/gogo/protobuf/proto"
    16  )
    17  
    18  var _ indexer.EventSink = (*EventSink)(nil)
    19  
    20  const (
    21  	TableEventBlock = "block_events"
    22  	TableEventTx    = "tx_events"
    23  	TableResultTx   = "tx_results"
    24  	DriverName      = "postgres"
    25  )
    26  
    27  // EventSink is an indexer backend providing the tx/block index services.
    28  type EventSink struct {
    29  	store   *sql.DB
    30  	chainID string
    31  }
    32  
    33  func NewEventSink(connStr string, chainID string) (indexer.EventSink, *sql.DB, error) {
    34  	db, err := sql.Open(DriverName, connStr)
    35  	if err != nil {
    36  		return nil, nil, err
    37  	}
    38  
    39  	return &EventSink{
    40  		store:   db,
    41  		chainID: chainID,
    42  	}, db, nil
    43  }
    44  
    45  func (es *EventSink) Type() indexer.EventSinkType {
    46  	return indexer.PSQL
    47  }
    48  
    49  func (es *EventSink) IndexBlockEvents(h types.EventDataNewBlockHeader) error {
    50  	sqlStmt := sq.
    51  		Insert(TableEventBlock).
    52  		Columns("key", "value", "height", "type", "created_at", "chain_id").
    53  		PlaceholderFormat(sq.Dollar).
    54  		Suffix("ON CONFLICT (key,height)").
    55  		Suffix("DO NOTHING")
    56  
    57  	ts := time.Now()
    58  	// index the reserved block height index
    59  	sqlStmt = sqlStmt.
    60  		Values(types.BlockHeightKey, fmt.Sprint(h.Header.Height), h.Header.Height, "", ts, es.chainID)
    61  
    62  	// index begin_block events
    63  	sqlStmt, err := indexBlockEvents(
    64  		sqlStmt, h.ResultBeginBlock.Events, types.EventTypeBeginBlock, h.Header.Height, ts, es.chainID)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	// index end_block events
    70  	sqlStmt, err = indexBlockEvents(
    71  		sqlStmt, h.ResultEndBlock.Events, types.EventTypeEndBlock, h.Header.Height, ts, es.chainID)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	_, err = sqlStmt.RunWith(es.store).Exec()
    77  	return err
    78  }
    79  
    80  func (es *EventSink) IndexTxEvents(txr []*abci.TxResult) error {
    81  	// index the tx result
    82  	var txid uint32
    83  	sqlStmtTxResult := sq.
    84  		Insert(TableResultTx).
    85  		Columns("tx_result", "created_at").
    86  		PlaceholderFormat(sq.Dollar).
    87  		RunWith(es.store).
    88  		Suffix("ON CONFLICT (tx_result)").
    89  		Suffix("DO NOTHING").
    90  		Suffix("RETURNING \"id\"")
    91  
    92  	sqlStmtEvents := sq.
    93  		Insert(TableEventTx).
    94  		Columns("key", "value", "height", "hash", "tx_result_id", "created_at", "chain_id").
    95  		PlaceholderFormat(sq.Dollar).
    96  		Suffix("ON CONFLICT (key,hash)").
    97  		Suffix("DO NOTHING")
    98  
    99  	ts := time.Now()
   100  	for _, tx := range txr {
   101  		txBz, err := proto.Marshal(tx)
   102  		if err != nil {
   103  			return err
   104  		}
   105  
   106  		sqlStmtTxResult = sqlStmtTxResult.Values(txBz, ts)
   107  
   108  		// execute sqlStmtTxResult db query and retrieve the txid
   109  		r, err := sqlStmtTxResult.Query()
   110  		if err != nil {
   111  			return err
   112  		}
   113  		defer r.Close()
   114  
   115  		if !r.Next() {
   116  			return nil
   117  		}
   118  
   119  		if err := r.Scan(&txid); err != nil {
   120  			return err
   121  		}
   122  
   123  		// index the reserved height and hash indices
   124  		hash := fmt.Sprintf("%X", types.Tx(tx.Tx).Hash())
   125  
   126  		sqlStmtEvents = sqlStmtEvents.Values(types.TxHashKey, hash, tx.Height, hash, txid, ts, es.chainID)
   127  		sqlStmtEvents = sqlStmtEvents.Values(types.TxHeightKey, fmt.Sprint(tx.Height), tx.Height, hash, txid, ts, es.chainID)
   128  		for _, event := range tx.Result.Events {
   129  			// only index events with a non-empty type
   130  			if len(event.Type) == 0 {
   131  				continue
   132  			}
   133  
   134  			for _, attr := range event.Attributes {
   135  				if len(attr.Key) == 0 {
   136  					continue
   137  				}
   138  
   139  				// index if `index: true` is set
   140  				compositeTag := fmt.Sprintf("%s.%s", event.Type, attr.Key)
   141  
   142  				// ensure event does not conflict with a reserved prefix key
   143  				if compositeTag == types.TxHashKey || compositeTag == types.TxHeightKey {
   144  					return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeTag)
   145  				}
   146  
   147  				if attr.GetIndex() {
   148  					sqlStmtEvents = sqlStmtEvents.Values(compositeTag, attr.Value, tx.Height, hash, txid, ts, es.chainID)
   149  				}
   150  			}
   151  		}
   152  	}
   153  
   154  	// execute sqlStmtEvents db query...
   155  	_, err := sqlStmtEvents.RunWith(es.store).Exec()
   156  	return err
   157  }
   158  
   159  func (es *EventSink) SearchBlockEvents(ctx context.Context, q *query.Query) ([]int64, error) {
   160  	return nil, errors.New("block search is not supported via the postgres event sink")
   161  }
   162  
   163  func (es *EventSink) SearchTxEvents(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) {
   164  	return nil, errors.New("tx search is not supported via the postgres event sink")
   165  }
   166  
   167  func (es *EventSink) GetTxByHash(hash []byte) (*abci.TxResult, error) {
   168  	return nil, errors.New("getTxByHash is not supported via the postgres event sink")
   169  }
   170  
   171  func (es *EventSink) HasBlock(h int64) (bool, error) {
   172  	return false, errors.New("hasBlock is not supported via the postgres event sink")
   173  }
   174  
   175  func indexBlockEvents(
   176  	sqlStmt sq.InsertBuilder,
   177  	events []abci.Event,
   178  	ty string,
   179  	height int64,
   180  	ts time.Time,
   181  	chainID string,
   182  ) (sq.InsertBuilder, error) {
   183  	for _, event := range events {
   184  		// only index events with a non-empty type
   185  		if len(event.Type) == 0 {
   186  			continue
   187  		}
   188  
   189  		for _, attr := range event.Attributes {
   190  			if len(attr.Key) == 0 {
   191  				continue
   192  			}
   193  
   194  			// index iff the event specified index:true and it's not a reserved event
   195  			compositeKey := fmt.Sprintf("%s.%s", event.Type, attr.Key)
   196  			if compositeKey == types.BlockHeightKey {
   197  				return sqlStmt, fmt.Errorf(
   198  					"event type and attribute key \"%s\" is reserved; please use a different key", compositeKey)
   199  			}
   200  
   201  			if attr.GetIndex() {
   202  				sqlStmt = sqlStmt.Values(compositeKey, attr.Value, height, ty, ts, chainID)
   203  			}
   204  		}
   205  	}
   206  	return sqlStmt, nil
   207  }
   208  
   209  func (es *EventSink) Stop() error {
   210  	return es.store.Close()
   211  }