network/payload: add Extensible payload

This commit is contained in:
Evgeniy Stratonikov 2021-01-13 18:12:40 +03:00
parent 6bc2512767
commit 59a193c7c7
2 changed files with 197 additions and 0 deletions

View file

@ -0,0 +1,126 @@
package payload
import (
"errors"
"math"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
const (
maxExtensibleCategorySize = 32
maxExtensibleDataSize = math.MaxUint16
)
// Extensible represents payload containing arbitrary data.
type Extensible struct {
// Network represents network magic.
Network netmode.Magic
// Category is payload type.
Category string
// ValidBlockStart is starting height for payload to be valid.
ValidBlockStart uint32
// ValidBlockEnd is height after which payload becomes invalid.
ValidBlockEnd uint32
// Sender is payload sender or signer.
Sender util.Uint160
// Data is custom payload data.
Data []byte
// Witness is payload witness.
Witness transaction.Witness
hash util.Uint256
signedHash util.Uint256
signedpart []byte
}
var errInvalidPadding = errors.New("invalid padding")
// NewExtensible creates new extensible payload.
func NewExtensible(network netmode.Magic) *Extensible {
return &Extensible{Network: network}
}
func (e *Extensible) encodeBinaryUnsigned(w *io.BinWriter) {
w.WriteString(e.Category)
w.WriteU32LE(e.ValidBlockStart)
w.WriteU32LE(e.ValidBlockEnd)
w.WriteBytes(e.Sender[:])
w.WriteVarBytes(e.Data)
}
// EncodeBinary implements io.Serializable.
func (e *Extensible) EncodeBinary(w *io.BinWriter) {
e.encodeBinaryUnsigned(w)
w.WriteB(1)
e.Witness.EncodeBinary(w)
}
func (e *Extensible) decodeBinaryUnsigned(r *io.BinReader) {
e.Category = r.ReadString(maxExtensibleCategorySize)
e.ValidBlockStart = r.ReadU32LE()
e.ValidBlockEnd = r.ReadU32LE()
r.ReadBytes(e.Sender[:])
e.Data = r.ReadVarBytes(maxExtensibleDataSize)
}
// DecodeBinary implements io.Serializable.
func (e *Extensible) DecodeBinary(r *io.BinReader) {
e.decodeBinaryUnsigned(r)
if r.ReadB() != 1 {
if r.Err != nil {
return
}
r.Err = errInvalidPadding
return
}
e.Witness.DecodeBinary(r)
}
// GetSignedPart implements crypto.Verifiable.
func (e *Extensible) GetSignedPart() []byte {
if e.signedpart == nil {
e.updateSignedPart()
}
return e.signedpart
}
// GetSignedHash implements crypto.Verifiable.
func (e *Extensible) GetSignedHash() util.Uint256 {
if e.signedHash.Equals(util.Uint256{}) {
e.createHash()
}
return e.signedHash
}
// Hash returns payload hash.
func (e *Extensible) Hash() util.Uint256 {
if e.hash.Equals(util.Uint256{}) {
e.createHash()
}
return e.hash
}
// createHash creates hashes of the payload.
func (e *Extensible) createHash() {
b := e.GetSignedPart()
e.updateHashes(b)
}
// updateHashes updates hashes based on the given buffer which should
// be a signable data slice.
func (e *Extensible) updateHashes(b []byte) {
e.signedHash = hash.Sha256(b)
e.hash = hash.Sha256(e.signedHash.BytesBE())
}
// updateSignedPart updates serialized message if needed.
func (e *Extensible) updateSignedPart() {
w := io.NewBufBinWriter()
e.encodeBinaryUnsigned(w.BinWriter)
e.signedpart = w.Bytes()
}

View file

@ -0,0 +1,71 @@
package payload
import (
"errors"
gio "io"
"testing"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/stretchr/testify/require"
)
func TestExtensible_Serializable(t *testing.T) {
expected := &Extensible{
Category: "test",
ValidBlockStart: 12,
ValidBlockEnd: 1234,
Sender: random.Uint160(),
Data: random.Bytes(4),
Witness: transaction.Witness{
InvocationScript: random.Bytes(3),
VerificationScript: random.Bytes(3),
},
}
testserdes.EncodeDecodeBinary(t, expected, new(Extensible))
t.Run("invalid", func(t *testing.T) {
w := io.NewBufBinWriter()
expected.encodeBinaryUnsigned(w.BinWriter)
unsigned := w.Bytes()
t.Run("unexpected EOF", func(t *testing.T) {
err := testserdes.DecodeBinary(unsigned, new(Extensible))
require.True(t, errors.Is(err, gio.EOF))
})
t.Run("invalid padding", func(t *testing.T) {
err := testserdes.DecodeBinary(append(unsigned, 42), new(Extensible))
require.True(t, errors.Is(err, errInvalidPadding))
})
})
}
func TestExtensible_Hashes(t *testing.T) {
getExtensiblePair := func() (*Extensible, *Extensible) {
p1 := NewExtensible(netmode.UnitTestNet)
p1.Data = []byte{1, 2, 3}
p2 := NewExtensible(netmode.UnitTestNet)
p2.Data = []byte{3, 2, 1}
return p1, p2
}
t.Run("GetSignedPart", func(t *testing.T) {
p1, p2 := getExtensiblePair()
require.NotEqual(t, p1.GetSignedPart(), p2.GetSignedPart())
require.NotEqual(t, p1.GetSignedPart(), p2.GetSignedPart())
})
t.Run("GetSignedHash", func(t *testing.T) {
p1, p2 := getExtensiblePair()
require.NotEqual(t, p1.GetSignedHash(), p2.GetSignedHash())
require.NotEqual(t, p1.GetSignedHash(), p2.GetSignedHash())
})
t.Run("Hash", func(t *testing.T) {
p1, p2 := getExtensiblePair()
require.NotEqual(t, p1.Hash(), p2.Hash())
require.NotEqual(t, p1.Hash(), p2.Hash())
})
}