forked from TrueCloudLab/neoneo-go
network/payload: add Extensible payload
This commit is contained in:
parent
6bc2512767
commit
59a193c7c7
2 changed files with 197 additions and 0 deletions
126
pkg/network/payload/extensible.go
Normal file
126
pkg/network/payload/extensible.go
Normal 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()
|
||||||
|
}
|
71
pkg/network/payload/extensible_test.go
Normal file
71
pkg/network/payload/extensible_test.go
Normal 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())
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue