Merge pull request #2141 from nspcc-dev/decoding-speedup

Decoding speedup
This commit is contained in:
Roman Khimov 2021-08-24 13:33:09 +03:00 committed by GitHub
commit 581132727b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 47 additions and 34 deletions

View file

@ -130,7 +130,12 @@ func (t *Transaction) GetAttributes(typ AttrType) []Attribute {
// decodeHashableFields decodes the fields that are used for signing the // decodeHashableFields decodes the fields that are used for signing the
// transaction, which are all fields except the scripts. // transaction, which are all fields except the scripts.
func (t *Transaction) decodeHashableFields(br *io.BinReader) { func (t *Transaction) decodeHashableFields(br *io.BinReader, buf []byte) {
var start, end int
if buf != nil {
start = len(buf) - br.Len()
}
t.Version = uint8(br.ReadB()) t.Version = uint8(br.ReadB())
t.Nonce = br.ReadU32LE() t.Nonce = br.ReadU32LE()
t.SystemFee = int64(br.ReadU64LE()) t.SystemFee = int64(br.ReadU64LE())
@ -164,10 +169,14 @@ func (t *Transaction) decodeHashableFields(br *io.BinReader) {
if br.Err == nil { if br.Err == nil {
br.Err = t.isValid() br.Err = t.isValid()
} }
if buf != nil {
end = len(buf) - br.Len()
t.hash = hash.Sha256(buf[start:end])
}
} }
func (t *Transaction) decodeBinaryNoSize(br *io.BinReader) { func (t *Transaction) decodeBinaryNoSize(br *io.BinReader, buf []byte) {
t.decodeHashableFields(br) t.decodeHashableFields(br, buf)
if br.Err != nil { if br.Err != nil {
return return
} }
@ -186,14 +195,14 @@ func (t *Transaction) decodeBinaryNoSize(br *io.BinReader) {
// Create the hash of the transaction at decode, so we dont need // Create the hash of the transaction at decode, so we dont need
// to do it anymore. // to do it anymore.
if br.Err == nil { if br.Err == nil && buf == nil {
br.Err = t.createHash() br.Err = t.createHash()
} }
} }
// DecodeBinary implements Serializable interface. // DecodeBinary implements Serializable interface.
func (t *Transaction) DecodeBinary(br *io.BinReader) { func (t *Transaction) DecodeBinary(br *io.BinReader) {
t.decodeBinaryNoSize(br) t.decodeBinaryNoSize(br, nil)
if br.Err == nil { if br.Err == nil {
_ = t.Size() _ = t.Size()
@ -258,18 +267,15 @@ func (t *Transaction) createHash() error {
// DecodeHashableFields decodes a part of transaction which should be hashed. // DecodeHashableFields decodes a part of transaction which should be hashed.
func (t *Transaction) DecodeHashableFields(buf []byte) error { func (t *Transaction) DecodeHashableFields(buf []byte) error {
r := io.NewBinReaderFromBuf(buf) r := io.NewBinReaderFromBuf(buf)
t.decodeHashableFields(r) t.decodeHashableFields(r, buf)
if r.Err != nil { if r.Err != nil {
return r.Err return r.Err
} }
// Ensure all the data was read. // Ensure all the data was read.
_ = r.ReadB() if r.Len() != 0 {
if r.Err == nil {
return errors.New("additional data after the signed part") return errors.New("additional data after the signed part")
} }
t.Scripts = make([]Witness, 0) t.Scripts = make([]Witness, 0)
t.hash = hash.Sha256(buf)
return nil return nil
} }
@ -287,12 +293,11 @@ func (t *Transaction) Bytes() []byte {
func NewTransactionFromBytes(b []byte) (*Transaction, error) { func NewTransactionFromBytes(b []byte) (*Transaction, error) {
tx := &Transaction{} tx := &Transaction{}
r := io.NewBinReaderFromBuf(b) r := io.NewBinReaderFromBuf(b)
tx.decodeBinaryNoSize(r) tx.decodeBinaryNoSize(r, b)
if r.Err != nil { if r.Err != nil {
return nil, r.Err return nil, r.Err
} }
_ = r.ReadB() if r.Len() != 0 {
if r.Err == nil {
return nil, errors.New("additional data after the transaction") return nil, errors.New("additional data after the transaction")
} }
tx.size = len(b) tx.size = len(b)

View file

@ -97,6 +97,11 @@ func TestNewTransactionFromBytes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, tx, tx1) require.Equal(t, tx, tx1)
tx2 := new(Transaction)
err = testserdes.DecodeBinary(data, tx2)
require.NoError(t, err)
require.Equal(t, tx1, tx2)
data = append(data, 42) data = append(data, 42)
_, err = NewTransactionFromBytes(data) _, err = NewTransactionFromBytes(data)
require.Error(t, err) require.Error(t, err)

View file

@ -16,20 +16,13 @@ const MaxArraySize = 0x1000000
// Used to simplify error handling when reading into a struct with many fields. // Used to simplify error handling when reading into a struct with many fields.
type BinReader struct { type BinReader struct {
r io.Reader r io.Reader
u64 []byte uv [8]byte
u32 []byte
u16 []byte
u8 []byte
Err error Err error
} }
// NewBinReaderFromIO makes a BinReader from io.Reader. // NewBinReaderFromIO makes a BinReader from io.Reader.
func NewBinReaderFromIO(ior io.Reader) *BinReader { func NewBinReaderFromIO(ior io.Reader) *BinReader {
u64 := make([]byte, 8) return &BinReader{r: ior}
u32 := u64[:4]
u16 := u64[:2]
u8 := u64[:1]
return &BinReader{r: ior, u64: u64, u32: u32, u16: u16, u8: u8}
} }
// NewBinReaderFromBuf makes a BinReader from byte buffer. // NewBinReaderFromBuf makes a BinReader from byte buffer.
@ -38,54 +31,65 @@ func NewBinReaderFromBuf(b []byte) *BinReader {
return NewBinReaderFromIO(r) return NewBinReaderFromIO(r)
} }
// Len returns the number of bytes of the unread portion of the buffer if
// reading from bytes.Reader or -1 otherwise.
func (r *BinReader) Len() int {
var res = -1
byteReader, ok := r.r.(*bytes.Reader)
if ok {
res = byteReader.Len()
}
return res
}
// ReadU64LE reads a little-endian encoded uint64 value from the underlying // ReadU64LE reads a little-endian encoded uint64 value from the underlying
// io.Reader. On read failures it returns zero. // io.Reader. On read failures it returns zero.
func (r *BinReader) ReadU64LE() uint64 { func (r *BinReader) ReadU64LE() uint64 {
r.ReadBytes(r.u64) r.ReadBytes(r.uv[:8])
if r.Err != nil { if r.Err != nil {
return 0 return 0
} }
return binary.LittleEndian.Uint64(r.u64) return binary.LittleEndian.Uint64(r.uv[:8])
} }
// ReadU32LE reads a little-endian encoded uint32 value from the underlying // ReadU32LE reads a little-endian encoded uint32 value from the underlying
// io.Reader. On read failures it returns zero. // io.Reader. On read failures it returns zero.
func (r *BinReader) ReadU32LE() uint32 { func (r *BinReader) ReadU32LE() uint32 {
r.ReadBytes(r.u32) r.ReadBytes(r.uv[:4])
if r.Err != nil { if r.Err != nil {
return 0 return 0
} }
return binary.LittleEndian.Uint32(r.u32) return binary.LittleEndian.Uint32(r.uv[:4])
} }
// ReadU16LE reads a little-endian encoded uint16 value from the underlying // ReadU16LE reads a little-endian encoded uint16 value from the underlying
// io.Reader. On read failures it returns zero. // io.Reader. On read failures it returns zero.
func (r *BinReader) ReadU16LE() uint16 { func (r *BinReader) ReadU16LE() uint16 {
r.ReadBytes(r.u16) r.ReadBytes(r.uv[:2])
if r.Err != nil { if r.Err != nil {
return 0 return 0
} }
return binary.LittleEndian.Uint16(r.u16) return binary.LittleEndian.Uint16(r.uv[:2])
} }
// ReadU16BE reads a big-endian encoded uint16 value from the underlying // ReadU16BE reads a big-endian encoded uint16 value from the underlying
// io.Reader. On read failures it returns zero. // io.Reader. On read failures it returns zero.
func (r *BinReader) ReadU16BE() uint16 { func (r *BinReader) ReadU16BE() uint16 {
r.ReadBytes(r.u16) r.ReadBytes(r.uv[:2])
if r.Err != nil { if r.Err != nil {
return 0 return 0
} }
return binary.BigEndian.Uint16(r.u16) return binary.BigEndian.Uint16(r.uv[:2])
} }
// ReadB reads a byte from the underlying io.Reader. On read failures it // ReadB reads a byte from the underlying io.Reader. On read failures it
// returns zero. // returns zero.
func (r *BinReader) ReadB() byte { func (r *BinReader) ReadB() byte {
r.ReadBytes(r.u8) r.ReadBytes(r.uv[:1])
if r.Err != nil { if r.Err != nil {
return 0 return 0
} }
return r.u8[0] return r.uv[0]
} }
// ReadBool reads a boolean value encoded in a zero/non-zero byte from the // ReadBool reads a boolean value encoded in a zero/non-zero byte from the

View file

@ -29,8 +29,7 @@ func NewP2PNotaryRequestFromBytes(b []byte) (*P2PNotaryRequest, error) {
if br.Err != nil { if br.Err != nil {
return nil, br.Err return nil, br.Err
} }
_ = br.ReadB() if br.Len() != 0 {
if br.Err == nil {
return nil, errors.New("additional data after the payload") return nil, errors.New("additional data after the payload")
} }
return req, nil return req, nil