github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/vent/service/rowbuilder.go (about)

     1  package service
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  	"unicode/utf8"
     8  
     9  	"github.com/hyperledger/burrow/execution/evm/abi"
    10  	"github.com/hyperledger/burrow/logging"
    11  	"github.com/hyperledger/burrow/vent/chain"
    12  	"github.com/hyperledger/burrow/vent/sqlsol"
    13  	"github.com/hyperledger/burrow/vent/types"
    14  	"github.com/pkg/errors"
    15  	"github.com/tmthrgd/go-hex"
    16  )
    17  
    18  // buildEventData builds event data from transactions
    19  func buildEventData(projection *sqlsol.Projection, eventClass *types.EventClass, event chain.Event,
    20  	txOrigin *chain.Origin, evAbi *abi.EventSpec, logger *logging.Logger) (types.EventDataRow, error) {
    21  
    22  	// a fresh new row to store column/value data
    23  	row := make(map[string]interface{})
    24  
    25  	// decode event data using the provided abi specification
    26  	decodedData, err := decodeEvent(event, txOrigin, evAbi)
    27  	if err != nil {
    28  		return types.EventDataRow{}, errors.Wrapf(err, "Error decoding event (filter: %s)", eventClass.Filter)
    29  	}
    30  
    31  	logger.InfoMsg("Decoded event", decodedData)
    32  
    33  	rowAction := types.ActionUpsert
    34  
    35  	// for each data element, maps to SQL columnName and gets its value
    36  	// if there is no matching column for the item, it doesn't need to be stored in db
    37  	for fieldName, value := range decodedData {
    38  		// Can't think of case where we will get a key that is empty, but if we ever did we should not treat
    39  		// it as a delete marker when the delete marker field in unset
    40  		if eventClass.DeleteMarkerField != "" && eventClass.DeleteMarkerField == fieldName {
    41  			rowAction = types.ActionDelete
    42  		}
    43  		fieldMapping := eventClass.GetFieldMapping(fieldName)
    44  		if fieldMapping == nil {
    45  			continue
    46  		}
    47  		column, err := projection.GetColumn(eventClass.TableName, fieldMapping.ColumnName)
    48  		if err == nil {
    49  			if bs, ok := value.(*[]byte); ok {
    50  				if fieldMapping.BytesToString {
    51  					str := sanitiseBytesForString(*bs, logger)
    52  					value = interface{}(str)
    53  				} else if fieldMapping.BytesToHex {
    54  					value = hex.EncodeUpperToString(*bs)
    55  				}
    56  			}
    57  			row[column.Name] = value
    58  		} else {
    59  			logger.TraceMsg("could not get column", "err", err)
    60  		}
    61  	}
    62  
    63  	return types.EventDataRow{Action: rowAction, RowData: row, EventClass: eventClass}, nil
    64  }
    65  
    66  func buildBlkData(tbls types.EventTables, block chain.Block) (types.EventDataRow, error) {
    67  	// block raw data
    68  	if _, ok := tbls[tables.Block]; ok {
    69  		row, err := block.GetMetadata(columns)
    70  		if err != nil {
    71  			return types.EventDataRow{}, err
    72  		}
    73  		return types.EventDataRow{Action: types.ActionUpsert, RowData: row}, nil
    74  	}
    75  	return types.EventDataRow{}, fmt.Errorf("table: %s not found in table structure %v", tables.Block, tbls)
    76  
    77  }
    78  
    79  // buildTxData builds transaction data from tx stream
    80  func buildTxData(txe chain.Transaction) (types.EventDataRow, error) {
    81  	row, err := txe.GetMetadata(columns)
    82  	if err != nil {
    83  		return types.EventDataRow{}, fmt.Errorf("could not get transaction metadata: %w", err)
    84  	}
    85  
    86  	return types.EventDataRow{
    87  		Action:  types.ActionUpsert,
    88  		RowData: row,
    89  	}, nil
    90  }
    91  
    92  func sanitiseBytesForString(bs []byte, l *logging.Logger) string {
    93  	str, err := UTF8StringFromBytes(bs)
    94  	if err != nil {
    95  		l.InfoMsg("buildEventData() received invalid bytes for utf8 string - proceeding with sanitised version",
    96  			"err", err)
    97  	}
    98  	// The only null bytes in utf8 are for the null code point/character so this is fine in general
    99  	return strings.Trim(str, "\x00")
   100  }
   101  
   102  // Checks whether the bytes passed are valid utf8 string bytes. If they are not returns a sanitised string version of the
   103  // bytes with offending sequences replaced by the utf8 replacement/error rune and an error indicating the offending
   104  // byte sequences and their position. Note: always returns a valid string regardless of error.
   105  func UTF8StringFromBytes(bs []byte) (string, error) {
   106  	// Provide fast path for good strings
   107  	if utf8.Valid(bs) {
   108  		return string(bs), nil
   109  	}
   110  	buf := new(bytes.Buffer)
   111  	var runeErrs []string
   112  	// This loops over runs (code points and unlike range of string gives us index of code point (i.e. utf8 char)
   113  	// not bytes, which we want for error message
   114  	var offset int
   115  	// Iterate over character indices (not byte indices)
   116  	for i := 0; i < len(bs); i++ {
   117  		r, n := utf8.DecodeRune(bs[offset:])
   118  		buf.WriteRune(r)
   119  		if r == utf8.RuneError {
   120  			runeErrs = append(runeErrs, fmt.Sprintf("0x% X (at index %d)", bs[offset:offset+n], i))
   121  		}
   122  		offset += n
   123  	}
   124  	str := buf.String()
   125  	errHeader := fmt.Sprintf("bytes purported to represent the string '%s'", str)
   126  	switch len(runeErrs) {
   127  	case 0:
   128  		// should not happen
   129  		return str, fmt.Errorf("bytes appear to be invalid utf8 but do not contain invalid code points")
   130  	case 1:
   131  		return str, fmt.Errorf("%s contain invalid utf8 byte sequence: %s", errHeader, runeErrs[0])
   132  	default:
   133  		return str, fmt.Errorf("%s contain invalid utf8 byte sequences: %s", errHeader,
   134  			strings.Join(runeErrs, ", "))
   135  	}
   136  }