forked from TrueCloudLab/neoneo-go
transaction: add HighPriority attribute
HighPriority attributes specifies that transaction was signed by a committee.
This commit is contained in:
parent
51cebe9e47
commit
2661ebd295
9 changed files with 137 additions and 13 deletions
|
@ -1226,6 +1226,7 @@ var (
|
||||||
ErrTxTooBig = errors.New("too big transaction")
|
ErrTxTooBig = errors.New("too big transaction")
|
||||||
ErrMemPoolConflict = errors.New("invalid transaction due to conflicts with the memory pool")
|
ErrMemPoolConflict = errors.New("invalid transaction due to conflicts with the memory pool")
|
||||||
ErrTxInvalidWitnessNum = errors.New("number of signers doesn't match witnesses")
|
ErrTxInvalidWitnessNum = errors.New("number of signers doesn't match witnesses")
|
||||||
|
ErrInvalidAttribute = errors.New("invalid attribute")
|
||||||
)
|
)
|
||||||
|
|
||||||
// verifyAndPoolTx verifies whether a transaction is bonafide or not and tries
|
// verifyAndPoolTx verifies whether a transaction is bonafide or not and tries
|
||||||
|
@ -1271,10 +1272,37 @@ 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 {
|
||||||
|
for i := range tx.Attributes {
|
||||||
|
switch tx.Attributes[i].Type {
|
||||||
|
case transaction.HighPriority:
|
||||||
|
pubs, err := bc.contracts.NEO.GetCommitteeMembers(bc, bc.dao)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h := hash.Hash160(s)
|
||||||
|
for i := range tx.Signers {
|
||||||
|
if tx.Signers[i].Account.Equals(h) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%w: high priority tx is not signed by committee", ErrInvalidAttribute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// isTxStillRelevant is a callback for mempool transaction filtering after the
|
// isTxStillRelevant is a callback for mempool transaction filtering after the
|
||||||
// new block addition. It returns false for transactions added by the new block
|
// new block addition. It returns false for transactions added by the new block
|
||||||
// (passed via txHashes) and does witness reverification for non-standard
|
// (passed via txHashes) and does witness reverification for non-standard
|
||||||
|
@ -1290,6 +1318,9 @@ func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction, txHashes []u
|
||||||
if index < len(txHashes) && txHashes[index].Equals(t.Hash()) {
|
if index < len(txHashes) && txHashes[index].Equals(t.Hash()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if err := bc.verifyTxAttributes(t); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
for i := range t.Scripts {
|
for i := range t.Scripts {
|
||||||
if !vm.IsStandardContract(t.Scripts[i].VerificationScript) {
|
if !vm.IsStandardContract(t.Scripts[i].VerificationScript) {
|
||||||
recheckWitness = true
|
recheckWitness = true
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/testchain"
|
"github.com/nspcc-dev/neo-go/pkg/internal/testchain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
@ -345,6 +346,35 @@ func TestVerifyTx(t *testing.T) {
|
||||||
err := bc.PoolTx(tx2)
|
err := bc.PoolTx(tx2)
|
||||||
require.True(t, errors.Is(err, ErrOOM))
|
require.True(t, errors.Is(err, ErrOOM))
|
||||||
})
|
})
|
||||||
|
t.Run("Attribute", func(t *testing.T) {
|
||||||
|
t.Run("InvalidHighPriority", func(t *testing.T) {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.HighPriority})
|
||||||
|
require.NoError(t, accs[0].SignTx(tx))
|
||||||
|
checkErr(t, ErrInvalidAttribute, tx)
|
||||||
|
})
|
||||||
|
t.Run("ValidHighPriority", func(t *testing.T) {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
tx.NetworkFee += 4_000_000 // multisig check
|
||||||
|
tx.Signers = []transaction.Signer{{
|
||||||
|
Account: testchain.MultisigScriptHash(),
|
||||||
|
Scopes: transaction.FeeOnly,
|
||||||
|
}}
|
||||||
|
validators := bc.GetStandByValidators()
|
||||||
|
rawScript, err := smartcontract.CreateMajorityMultiSigRedeemScript(validators)
|
||||||
|
require.NoError(t, err)
|
||||||
|
size := io.GetVarSize(tx)
|
||||||
|
netFee, sizeDelta := CalculateNetworkFee(rawScript)
|
||||||
|
tx.NetworkFee += netFee
|
||||||
|
tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte()
|
||||||
|
data := tx.GetSignedPart()
|
||||||
|
tx.Scripts = []transaction.Witness{{
|
||||||
|
InvocationScript: testchain.Sign(data),
|
||||||
|
VerificationScript: rawScript,
|
||||||
|
}}
|
||||||
|
require.NoError(t, bc.VerifyTx(tx))
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyHashAgainstScript(t *testing.T) {
|
func TestVerifyHashAgainstScript(t *testing.T) {
|
||||||
|
|
|
@ -68,6 +68,14 @@ func (p *item) CompareTo(otherP *item) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pHigh := p.txn.HasAttribute(transaction.HighPriority)
|
||||||
|
otherHigh := otherP.txn.HasAttribute(transaction.HighPriority)
|
||||||
|
if pHigh && !otherHigh {
|
||||||
|
return 1
|
||||||
|
} else if !pHigh && otherHigh {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
// Fees sorted ascending.
|
// Fees sorted ascending.
|
||||||
if ret := int(p.txn.FeePerByte() - otherP.txn.FeePerByte()); ret != 0 {
|
if ret := int(p.txn.FeePerByte() - otherP.txn.FeePerByte()); ret != 0 {
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -407,7 +407,7 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (ke
|
||||||
if vals := n.validators.Load().(keys.PublicKeys); vals != nil {
|
if vals := n.validators.Load().(keys.PublicKeys); vals != nil {
|
||||||
return vals.Copy(), nil
|
return vals.Copy(), nil
|
||||||
}
|
}
|
||||||
result, err := n.getCommitteeMembers(bc, d)
|
result, err := n.GetCommitteeMembers(bc, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -430,7 +430,7 @@ func (n *NEO) getValidators(ic *interop.Context, _ []stackitem.Item) stackitem.I
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
pubs, err := n.getCommitteeMembers(ic.Chain, ic.DAO)
|
pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -450,7 +450,8 @@ func (n *NEO) modifyVoterTurnout(d dao.DAO, amount *big.Int) error {
|
||||||
return d.PutStorageItem(n.ContractID, key, si)
|
return d.PutStorageItem(n.ContractID, key, si)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) getCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
// GetCommitteeMembers returns public keys of nodes in committee.
|
||||||
|
func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
||||||
key := []byte{prefixVotersCount}
|
key := []byte{prefixVotersCount}
|
||||||
si := d.GetStorageItem(n.ContractID, key)
|
si := d.GetStorageItem(n.ContractID, key)
|
||||||
if si == nil {
|
if si == nil {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package transaction
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
@ -25,14 +26,12 @@ func (attr *Attribute) DecodeBinary(br *io.BinReader) {
|
||||||
attr.Type = AttrType(br.ReadB())
|
attr.Type = AttrType(br.ReadB())
|
||||||
|
|
||||||
var datasize uint64
|
var datasize uint64
|
||||||
/**
|
|
||||||
|
|
||||||
switch attr.Type {
|
switch attr.Type {
|
||||||
|
case HighPriority:
|
||||||
default:
|
default:
|
||||||
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.Data = make([]byte, datasize)
|
attr.Data = make([]byte, datasize)
|
||||||
br.ReadBytes(attr.Data)
|
br.ReadBytes(attr.Data)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +40,7 @@ func (attr *Attribute) DecodeBinary(br *io.BinReader) {
|
||||||
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 attr.Type {
|
||||||
|
case HighPriority:
|
||||||
default:
|
default:
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ func (attr *Attribute) EncodeBinary(bw *io.BinWriter) {
|
||||||
// MarshalJSON implements the json Marshaller interface.
|
// MarshalJSON implements the json Marshaller interface.
|
||||||
func (attr *Attribute) MarshalJSON() ([]byte, error) {
|
func (attr *Attribute) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(attrJSON{
|
return json.Marshal(attrJSON{
|
||||||
Type: "", // attr.Type.String() when we're to have some real attributes
|
Type: attr.Type.String(),
|
||||||
Data: base64.StdEncoding.EncodeToString(attr.Data),
|
Data: base64.StdEncoding.EncodeToString(attr.Data),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -65,13 +65,13 @@ func (attr *Attribute) UnmarshalJSON(data []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
switch aj.Type {
|
switch aj.Type {
|
||||||
|
case "HighPriority":
|
||||||
|
attr.Type = HighPriority
|
||||||
default:
|
default:
|
||||||
return errors.New("wrong Type")
|
return errors.New("wrong Type")
|
||||||
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
attr.Data = binData
|
attr.Data = binData
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ package transaction
|
||||||
// AttrType represents the purpose of the attribute.
|
// AttrType represents the purpose of the attribute.
|
||||||
type AttrType uint8
|
type AttrType uint8
|
||||||
|
|
||||||
// List of valid attribute types (none for preview3).
|
// List of valid attribute types.
|
||||||
//const (
|
const (
|
||||||
//)
|
HighPriority AttrType = 1
|
||||||
|
)
|
||||||
|
|
24
pkg/core/transaction/attrtype_string.go
Normal file
24
pkg/core/transaction/attrtype_string.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Code generated by "stringer -type AttrType"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package transaction
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[HighPriority-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _AttrType_name = "HighPriority"
|
||||||
|
|
||||||
|
var _AttrType_index = [...]uint8{0, 12}
|
||||||
|
|
||||||
|
func (i AttrType) String() string {
|
||||||
|
i -= 1
|
||||||
|
if i >= AttrType(len(_AttrType_index)-1) {
|
||||||
|
return "AttrType(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||||
|
}
|
||||||
|
return _AttrType_name[_AttrType_index[i]:_AttrType_index[i+1]]
|
||||||
|
}
|
|
@ -119,6 +119,16 @@ func (t *Transaction) VerificationHash() util.Uint256 {
|
||||||
return t.verificationHash
|
return t.verificationHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAttribute returns true iff t has an attribute of type typ.
|
||||||
|
func (t *Transaction) HasAttribute(typ AttrType) bool {
|
||||||
|
for i := range t.Attributes {
|
||||||
|
if t.Attributes[i].Type == typ {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
@ -349,6 +359,16 @@ func (t *Transaction) isValid() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
hasHighPrio := false
|
||||||
|
for i := range t.Attributes {
|
||||||
|
switch t.Attributes[i].Type {
|
||||||
|
case HighPriority:
|
||||||
|
if hasHighPrio {
|
||||||
|
return errors.New("multiple high priority attributes")
|
||||||
|
}
|
||||||
|
hasHighPrio = true
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(t.Script) == 0 {
|
if len(t.Script) == 0 {
|
||||||
return errors.New("no script")
|
return errors.New("no script")
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,10 +112,19 @@ func TestMarshalUnmarshalJSONInvocationTX(t *testing.T) {
|
||||||
Version: 0,
|
Version: 0,
|
||||||
Signers: []Signer{{Account: util.Uint160{1, 2, 3}}},
|
Signers: []Signer{{Account: util.Uint160{1, 2, 3}}},
|
||||||
Script: []byte{1, 2, 3, 4},
|
Script: []byte{1, 2, 3, 4},
|
||||||
Attributes: []Attribute{},
|
Attributes: []Attribute{{Type: HighPriority, Data: []byte{}}},
|
||||||
Scripts: []Witness{},
|
Scripts: []Witness{},
|
||||||
Trimmed: false,
|
Trimmed: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
|
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTransaction_HasAttribute(t *testing.T) {
|
||||||
|
tx := New(netmode.UnitTestNet, []byte{1}, 0)
|
||||||
|
require.False(t, tx.HasAttribute(HighPriority))
|
||||||
|
tx.Attributes = append(tx.Attributes, Attribute{Type: HighPriority})
|
||||||
|
require.True(t, tx.HasAttribute(HighPriority))
|
||||||
|
tx.Attributes = append(tx.Attributes, Attribute{Type: HighPriority})
|
||||||
|
require.True(t, tx.HasAttribute(HighPriority))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue