forked from TrueCloudLab/neoneo-go
Merge pull request #1496 from nspcc-dev/signatures-collection
core: implement NotValidBefore and Reserved tx attributes
This commit is contained in:
commit
1956f2c079
11 changed files with 267 additions and 16 deletions
|
@ -9,6 +9,10 @@ type (
|
||||||
ProtocolConfiguration struct {
|
ProtocolConfiguration struct {
|
||||||
Magic netmode.Magic `yaml:"Magic"`
|
Magic netmode.Magic `yaml:"Magic"`
|
||||||
MemPoolSize int `yaml:"MemPoolSize"`
|
MemPoolSize int `yaml:"MemPoolSize"`
|
||||||
|
// P2PSigExtensions enables additional signature-related transaction attributes
|
||||||
|
P2PSigExtensions bool `yaml:"P2PSigExtensions"`
|
||||||
|
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
|
||||||
|
ReservedAttributes bool `yaml:"ReservedAttributes"`
|
||||||
// SaveStorageBatch enables storage batch saving before every persist.
|
// SaveStorageBatch enables storage batch saving before every persist.
|
||||||
SaveStorageBatch bool `yaml:"SaveStorageBatch"`
|
SaveStorageBatch bool `yaml:"SaveStorageBatch"`
|
||||||
SecondsPerBlock int `yaml:"SecondsPerBlock"`
|
SecondsPerBlock int `yaml:"SecondsPerBlock"`
|
||||||
|
|
|
@ -1196,6 +1196,7 @@ func (bc *Blockchain) verifyHeader(currHeader, prevHeader *block.Header) error {
|
||||||
|
|
||||||
// Various errors that could be returned upon verification.
|
// Various errors that could be returned upon verification.
|
||||||
var (
|
var (
|
||||||
|
ErrTxNotYetValid = errors.New("transaction is not yet valid")
|
||||||
ErrTxExpired = errors.New("transaction has expired")
|
ErrTxExpired = errors.New("transaction has expired")
|
||||||
ErrInsufficientFunds = errors.New("insufficient funds")
|
ErrInsufficientFunds = errors.New("insufficient funds")
|
||||||
ErrTxSmallNetworkFee = errors.New("too small network fee")
|
ErrTxSmallNetworkFee = errors.New("too small network fee")
|
||||||
|
@ -1233,6 +1234,9 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := bc.verifyTxAttributes(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = pool.Add(t, bc)
|
err = pool.Add(t, bc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
|
@ -1248,16 +1252,13 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := bc.verifyTxAttributes(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
||||||
for i := range tx.Attributes {
|
for i := range tx.Attributes {
|
||||||
switch tx.Attributes[i].Type {
|
switch attrType := tx.Attributes[i].Type; attrType {
|
||||||
case transaction.HighPriority:
|
case transaction.HighPriority:
|
||||||
h := bc.contracts.NEO.GetCommitteeAddress()
|
h := bc.contracts.NEO.GetCommitteeAddress()
|
||||||
for i := range tx.Signers {
|
for i := range tx.Signers {
|
||||||
|
@ -1294,6 +1295,18 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
||||||
if uint64(tx.NetworkFee+tx.SystemFee) < req.GasForResponse {
|
if uint64(tx.NetworkFee+tx.SystemFee) < req.GasForResponse {
|
||||||
return fmt.Errorf("%w: oracle tx has insufficient gas", ErrInvalidAttribute)
|
return fmt.Errorf("%w: oracle tx has insufficient gas", ErrInvalidAttribute)
|
||||||
}
|
}
|
||||||
|
case transaction.NotValidBeforeT:
|
||||||
|
if !bc.config.P2PSigExtensions {
|
||||||
|
return errors.New("NotValidBefore attribute was found, but P2PSigExtensions are disabled")
|
||||||
|
}
|
||||||
|
nvb := tx.Attributes[i].Value.(*transaction.NotValidBefore)
|
||||||
|
if height := bc.BlockHeight(); height < nvb.Height {
|
||||||
|
return fmt.Errorf("%w: NotValidBefore = %d, current height = %d", ErrTxNotYetValid, nvb.Height, height)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !bc.config.ReservedAttributes && attrType >= transaction.ReservedLowerBound && attrType <= transaction.ReservedUpperBound {
|
||||||
|
return errors.New("attribute of reserved type was found, but ReservedAttributes are disabled")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -559,6 +559,76 @@ func TestVerifyTx(t *testing.T) {
|
||||||
checkErr(t, ErrInvalidAttribute, tx)
|
checkErr(t, ErrInvalidAttribute, tx)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
t.Run("NotValidBefore", func(t *testing.T) {
|
||||||
|
getNVBTx := func(height uint32) *transaction.Transaction {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: height}})
|
||||||
|
tx.NetworkFee += 4_000_000 // multisig check
|
||||||
|
tx.Signers = []transaction.Signer{{
|
||||||
|
Account: testchain.CommitteeScriptHash(),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
}}
|
||||||
|
rawScript := testchain.CommitteeVerificationScript()
|
||||||
|
require.NoError(t, err)
|
||||||
|
size := io.GetVarSize(tx)
|
||||||
|
netFee, sizeDelta := fee.Calculate(rawScript)
|
||||||
|
tx.NetworkFee += netFee
|
||||||
|
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
|
||||||
|
data := tx.GetSignedPart()
|
||||||
|
tx.Scripts = []transaction.Witness{{
|
||||||
|
InvocationScript: testchain.SignCommittee(data),
|
||||||
|
VerificationScript: rawScript,
|
||||||
|
}}
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
t.Run("Disabled", func(t *testing.T) {
|
||||||
|
tx := getNVBTx(bc.blockHeight + 1)
|
||||||
|
require.Error(t, bc.VerifyTx(tx))
|
||||||
|
})
|
||||||
|
t.Run("Enabled", func(t *testing.T) {
|
||||||
|
bc.config.P2PSigExtensions = true
|
||||||
|
t.Run("NotYetValid", func(t *testing.T) {
|
||||||
|
tx := getNVBTx(bc.blockHeight + 1)
|
||||||
|
require.True(t, errors.Is(bc.VerifyTx(tx), ErrTxNotYetValid))
|
||||||
|
})
|
||||||
|
t.Run("positive", func(t *testing.T) {
|
||||||
|
tx := getNVBTx(bc.blockHeight)
|
||||||
|
require.NoError(t, bc.VerifyTx(tx))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("Reserved", func(t *testing.T) {
|
||||||
|
getReservedTx := func(attrType transaction.AttrType) *transaction.Transaction {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: attrType, Value: &transaction.Reserved{Value: []byte{1, 2, 3}}})
|
||||||
|
tx.NetworkFee += 4_000_000 // multisig check
|
||||||
|
tx.Signers = []transaction.Signer{{
|
||||||
|
Account: testchain.CommitteeScriptHash(),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
}}
|
||||||
|
rawScript := testchain.CommitteeVerificationScript()
|
||||||
|
require.NoError(t, err)
|
||||||
|
size := io.GetVarSize(tx)
|
||||||
|
netFee, sizeDelta := fee.Calculate(rawScript)
|
||||||
|
tx.NetworkFee += netFee
|
||||||
|
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
|
||||||
|
data := tx.GetSignedPart()
|
||||||
|
tx.Scripts = []transaction.Witness{{
|
||||||
|
InvocationScript: testchain.SignCommittee(data),
|
||||||
|
VerificationScript: rawScript,
|
||||||
|
}}
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
t.Run("Disabled", func(t *testing.T) {
|
||||||
|
tx := getReservedTx(transaction.ReservedLowerBound + 2)
|
||||||
|
require.Error(t, bc.VerifyTx(tx))
|
||||||
|
})
|
||||||
|
t.Run("Enabled", func(t *testing.T) {
|
||||||
|
bc.config.ReservedAttributes = true
|
||||||
|
tx := getReservedTx(transaction.ReservedLowerBound + 2)
|
||||||
|
require.NoError(t, bc.VerifyTx(tx))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,25 +30,36 @@ type attrJSON struct {
|
||||||
func (attr *Attribute) DecodeBinary(br *io.BinReader) {
|
func (attr *Attribute) DecodeBinary(br *io.BinReader) {
|
||||||
attr.Type = AttrType(br.ReadB())
|
attr.Type = AttrType(br.ReadB())
|
||||||
|
|
||||||
switch attr.Type {
|
switch t := attr.Type; t {
|
||||||
case HighPriority:
|
case HighPriority:
|
||||||
|
return
|
||||||
case OracleResponseT:
|
case OracleResponseT:
|
||||||
attr.Value = new(OracleResponse)
|
attr.Value = new(OracleResponse)
|
||||||
attr.Value.DecodeBinary(br)
|
case NotValidBeforeT:
|
||||||
|
attr.Value = new(NotValidBefore)
|
||||||
default:
|
default:
|
||||||
|
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
||||||
|
attr.Value = new(Reserved)
|
||||||
|
break
|
||||||
|
}
|
||||||
br.Err = fmt.Errorf("failed decoding TX attribute usage: 0x%2x", int(attr.Type))
|
br.Err = fmt.Errorf("failed decoding TX attribute usage: 0x%2x", int(attr.Type))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
attr.Value.DecodeBinary(br)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary implements Serializable interface.
|
// EncodeBinary implements Serializable interface.
|
||||||
func (attr *Attribute) EncodeBinary(bw *io.BinWriter) {
|
func (attr *Attribute) EncodeBinary(bw *io.BinWriter) {
|
||||||
bw.WriteB(byte(attr.Type))
|
bw.WriteB(byte(attr.Type))
|
||||||
switch attr.Type {
|
switch t := attr.Type; t {
|
||||||
case HighPriority:
|
case HighPriority:
|
||||||
case OracleResponseT:
|
case OracleResponseT, NotValidBeforeT:
|
||||||
attr.Value.EncodeBinary(bw)
|
attr.Value.EncodeBinary(bw)
|
||||||
default:
|
default:
|
||||||
|
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
||||||
|
attr.Value.EncodeBinary(bw)
|
||||||
|
break
|
||||||
|
}
|
||||||
bw.Err = fmt.Errorf("failed encoding TX attribute usage: 0x%2x", attr.Type)
|
bw.Err = fmt.Errorf("failed encoding TX attribute usage: 0x%2x", attr.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,17 +81,19 @@ func (attr *Attribute) UnmarshalJSON(data []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch aj.Type {
|
switch aj.Type {
|
||||||
case "HighPriority":
|
case HighPriority.String():
|
||||||
attr.Type = HighPriority
|
attr.Type = HighPriority
|
||||||
case "OracleResponse":
|
return nil
|
||||||
|
case OracleResponseT.String():
|
||||||
attr.Type = OracleResponseT
|
attr.Type = OracleResponseT
|
||||||
// Note: because `type` field will not be present in any attribute
|
// Note: because `type` field will not be present in any attribute
|
||||||
// value, we can unmarshal the same data. The overhead is minimal.
|
// value, we can unmarshal the same data. The overhead is minimal.
|
||||||
attr.Value = new(OracleResponse)
|
attr.Value = new(OracleResponse)
|
||||||
return json.Unmarshal(data, attr.Value)
|
case NotValidBeforeT.String():
|
||||||
|
attr.Type = NotValidBeforeT
|
||||||
|
attr.Value = new(NotValidBefore)
|
||||||
default:
|
default:
|
||||||
return errors.New("wrong Type")
|
return errors.New("wrong Type")
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return json.Unmarshal(data, attr.Value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,38 @@ func TestAttribute_EncodeBinary(t *testing.T) {
|
||||||
}
|
}
|
||||||
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
|
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
|
||||||
})
|
})
|
||||||
|
t.Run("NotValidBefore", func(t *testing.T) {
|
||||||
|
attr := &Attribute{
|
||||||
|
Type: NotValidBeforeT,
|
||||||
|
Value: &NotValidBefore{
|
||||||
|
Height: 123,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
|
||||||
|
})
|
||||||
|
t.Run("Reserved", func(t *testing.T) {
|
||||||
|
getReservedAttribute := func(t AttrType) *Attribute {
|
||||||
|
return &Attribute{
|
||||||
|
Type: t,
|
||||||
|
Value: &Reserved{
|
||||||
|
Value: []byte{1, 2, 3, 4, 5},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Run("lower bound", func(t *testing.T) {
|
||||||
|
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedLowerBound+2), new(Attribute))
|
||||||
|
})
|
||||||
|
t.Run("upper bound", func(t *testing.T) {
|
||||||
|
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedUpperBound), new(Attribute))
|
||||||
|
})
|
||||||
|
t.Run("inside bounds", func(t *testing.T) {
|
||||||
|
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedLowerBound+5), new(Attribute))
|
||||||
|
})
|
||||||
|
t.Run("not reserved", func(t *testing.T) {
|
||||||
|
_, err := testserdes.EncodeBinary(getReservedAttribute(ReservedLowerBound - 1))
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAttribute_MarshalJSON(t *testing.T) {
|
func TestAttribute_MarshalJSON(t *testing.T) {
|
||||||
|
@ -63,4 +95,13 @@ func TestAttribute_MarshalJSON(t *testing.T) {
|
||||||
require.Equal(t, attr, actual)
|
require.Equal(t, attr, actual)
|
||||||
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
|
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
|
||||||
})
|
})
|
||||||
|
t.Run("NotValidBefore", func(t *testing.T) {
|
||||||
|
attr := &Attribute{
|
||||||
|
Type: NotValidBeforeT,
|
||||||
|
Value: &NotValidBefore{
|
||||||
|
Height: 123,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testserdes.MarshalUnmarshalJSON(t, attr, new(Attribute))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,18 @@ package transaction
|
||||||
// AttrType represents the purpose of the attribute.
|
// AttrType represents the purpose of the attribute.
|
||||||
type AttrType uint8
|
type AttrType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ReservedLowerBound is the lower bound of reserved attribute types
|
||||||
|
ReservedLowerBound = 0xe0
|
||||||
|
// ReservedUpperBound is the upper bound of reserved attribute types
|
||||||
|
ReservedUpperBound = 0xff
|
||||||
|
)
|
||||||
|
|
||||||
// List of valid attribute types.
|
// List of valid attribute types.
|
||||||
const (
|
const (
|
||||||
HighPriority AttrType = 1
|
HighPriority AttrType = 1
|
||||||
OracleResponseT AttrType = 0x11 // OracleResponse
|
OracleResponseT AttrType = 0x11 // OracleResponse
|
||||||
|
NotValidBeforeT AttrType = ReservedLowerBound // NotValidBefore
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a AttrType) allowMultiple() bool {
|
func (a AttrType) allowMultiple() bool {
|
||||||
|
|
|
@ -10,11 +10,13 @@ func _() {
|
||||||
var x [1]struct{}
|
var x [1]struct{}
|
||||||
_ = x[HighPriority-1]
|
_ = x[HighPriority-1]
|
||||||
_ = x[OracleResponseT-17]
|
_ = x[OracleResponseT-17]
|
||||||
|
_ = x[NotValidBeforeT-224]
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_AttrType_name_0 = "HighPriority"
|
_AttrType_name_0 = "HighPriority"
|
||||||
_AttrType_name_1 = "OracleResponse"
|
_AttrType_name_1 = "OracleResponse"
|
||||||
|
_AttrType_name_2 = "NotValidBefore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i AttrType) String() string {
|
func (i AttrType) String() string {
|
||||||
|
@ -23,6 +25,8 @@ func (i AttrType) String() string {
|
||||||
return _AttrType_name_0
|
return _AttrType_name_0
|
||||||
case i == 17:
|
case i == 17:
|
||||||
return _AttrType_name_1
|
return _AttrType_name_1
|
||||||
|
case i == 224:
|
||||||
|
return _AttrType_name_2
|
||||||
default:
|
default:
|
||||||
return "AttrType(" + strconv.FormatInt(int64(i), 10) + ")"
|
return "AttrType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
}
|
}
|
||||||
|
|
29
pkg/core/transaction/not_valid_before.go
Normal file
29
pkg/core/transaction/not_valid_before.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotValidBefore represents attribute with the height transaction is not valid before.
|
||||||
|
type NotValidBefore struct {
|
||||||
|
Height uint32 `json:"height"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements io.Serializable interface.
|
||||||
|
func (n *NotValidBefore) DecodeBinary(br *io.BinReader) {
|
||||||
|
bytes := br.ReadVarBytes(4)
|
||||||
|
n.Height = binary.LittleEndian.Uint32(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements io.Serializable interface.
|
||||||
|
func (n *NotValidBefore) EncodeBinary(w *io.BinWriter) {
|
||||||
|
bytes := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(bytes, n.Height)
|
||||||
|
w.WriteVarBytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NotValidBefore) toJSONMap(m map[string]interface{}) {
|
||||||
|
m["height"] = n.Height
|
||||||
|
}
|
24
pkg/core/transaction/reserved.go
Normal file
24
pkg/core/transaction/reserved.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reserved represents an attribute for experimental or private usage.
|
||||||
|
type Reserved struct {
|
||||||
|
Value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements io.Serializable interface.
|
||||||
|
func (e *Reserved) DecodeBinary(br *io.BinReader) {
|
||||||
|
e.Value = br.ReadVarBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements io.Serializable interface.
|
||||||
|
func (e *Reserved) EncodeBinary(w *io.BinWriter) {
|
||||||
|
w.WriteVarBytes(e.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Reserved) toJSONMap(m map[string]interface{}) {
|
||||||
|
m["value"] = e.Value
|
||||||
|
}
|
|
@ -133,6 +133,18 @@ func (t *Transaction) HasAttribute(typ AttrType) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAttributes returns the list of transaction's attributes of the given type.
|
||||||
|
// Returns nil in case if attributes not found.
|
||||||
|
func (t *Transaction) GetAttributes(typ AttrType) []Attribute {
|
||||||
|
var result []Attribute
|
||||||
|
for _, attr := range t.Attributes {
|
||||||
|
if attr.Type == typ {
|
||||||
|
result = append(result, attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
|
@ -7,12 +7,13 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
|
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWitnessEncodeDecode(t *testing.T) {
|
func TestWitnessEncodeDecode(t *testing.T) {
|
||||||
|
@ -215,3 +216,35 @@ func TestTransaction_isValid(t *testing.T) {
|
||||||
require.True(t, errors.Is(tx.isValid(), ErrEmptyScript))
|
require.True(t, errors.Is(tx.isValid(), ErrEmptyScript))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTransaction_GetAttributes(t *testing.T) {
|
||||||
|
attributesTypes := []AttrType{
|
||||||
|
HighPriority,
|
||||||
|
OracleResponseT,
|
||||||
|
NotValidBeforeT,
|
||||||
|
}
|
||||||
|
t.Run("no attributes", func(t *testing.T) {
|
||||||
|
tx := new(Transaction)
|
||||||
|
for _, typ := range attributesTypes {
|
||||||
|
require.Nil(t, tx.GetAttributes(typ))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("single attributes", func(t *testing.T) {
|
||||||
|
attrs := make([]Attribute, len(attributesTypes))
|
||||||
|
for i, typ := range attributesTypes {
|
||||||
|
attrs[i] = Attribute{Type: typ}
|
||||||
|
}
|
||||||
|
tx := &Transaction{Attributes: attrs}
|
||||||
|
for _, typ := range attributesTypes {
|
||||||
|
require.Equal(t, []Attribute{{Type: typ}}, tx.GetAttributes(typ))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("multiple attributes", func(t *testing.T) {
|
||||||
|
typ := AttrType(ReservedLowerBound + 1)
|
||||||
|
conflictsAttrs := []Attribute{{Type: typ}, {Type: typ}}
|
||||||
|
tx := Transaction{
|
||||||
|
Attributes: append([]Attribute{{Type: HighPriority}}, conflictsAttrs...),
|
||||||
|
}
|
||||||
|
require.Equal(t, conflictsAttrs, tx.GetAttributes(typ))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue