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 }