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")
|
||||
ErrMemPoolConflict = errors.New("invalid transaction due to conflicts with the memory pool")
|
||||
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
|
||||
|
@ -1271,10 +1272,37 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
|||
return err
|
||||
}
|
||||
}
|
||||
if err := bc.verifyTxAttributes(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
// new block addition. It returns false for transactions added by the new block
|
||||
// (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()) {
|
||||
return false
|
||||
}
|
||||
if err := bc.verifyTxAttributes(t); err != nil {
|
||||
return false
|
||||
}
|
||||
for i := range t.Scripts {
|
||||
if !vm.IsStandardContract(t.Scripts[i].VerificationScript) {
|
||||
recheckWitness = true
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"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/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/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
|
@ -345,6 +346,35 @@ func TestVerifyTx(t *testing.T) {
|
|||
err := bc.PoolTx(tx2)
|
||||
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) {
|
||||
|
|
|
@ -68,6 +68,14 @@ func (p *item) CompareTo(otherP *item) int {
|
|||
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.
|
||||
if ret := int(p.txn.FeePerByte() - otherP.txn.FeePerByte()); ret != 0 {
|
||||
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 {
|
||||
return vals.Copy(), nil
|
||||
}
|
||||
result, err := n.getCommitteeMembers(bc, d)
|
||||
result, err := n.GetCommitteeMembers(bc, d)
|
||||
if err != nil {
|
||||
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 {
|
||||
pubs, err := n.getCommitteeMembers(ic.Chain, ic.DAO)
|
||||
pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -450,7 +450,8 @@ func (n *NEO) modifyVoterTurnout(d dao.DAO, amount *big.Int) error {
|
|||
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}
|
||||
si := d.GetStorageItem(n.ContractID, key)
|
||||
if si == nil {
|
||||
|
|
|
@ -3,6 +3,7 @@ package transaction
|
|||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
|
@ -25,14 +26,12 @@ func (attr *Attribute) DecodeBinary(br *io.BinReader) {
|
|||
attr.Type = AttrType(br.ReadB())
|
||||
|
||||
var datasize uint64
|
||||
/**
|
||||
|
||||
switch attr.Type {
|
||||
case HighPriority:
|
||||
default:
|
||||
br.Err = fmt.Errorf("failed decoding TX attribute usage: 0x%2x", int(attr.Type))
|
||||
return
|
||||
}
|
||||
*/
|
||||
attr.Data = make([]byte, datasize)
|
||||
br.ReadBytes(attr.Data)
|
||||
}
|
||||
|
@ -41,6 +40,7 @@ func (attr *Attribute) DecodeBinary(br *io.BinReader) {
|
|||
func (attr *Attribute) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteB(byte(attr.Type))
|
||||
switch attr.Type {
|
||||
case HighPriority:
|
||||
default:
|
||||
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.
|
||||
func (attr *Attribute) MarshalJSON() ([]byte, error) {
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
@ -65,13 +65,13 @@ func (attr *Attribute) UnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
/**
|
||||
switch aj.Type {
|
||||
case "HighPriority":
|
||||
attr.Type = HighPriority
|
||||
default:
|
||||
return errors.New("wrong Type")
|
||||
|
||||
}
|
||||
*/
|
||||
attr.Data = binData
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package transaction
|
|||
// AttrType represents the purpose of the attribute.
|
||||
type AttrType uint8
|
||||
|
||||
// List of valid attribute types (none for preview3).
|
||||
//const (
|
||||
//)
|
||||
// List of valid attribute types.
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// transaction, which are all fields except the scripts.
|
||||
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 {
|
||||
return errors.New("no script")
|
||||
}
|
||||
|
|
|
@ -112,10 +112,19 @@ func TestMarshalUnmarshalJSONInvocationTX(t *testing.T) {
|
|||
Version: 0,
|
||||
Signers: []Signer{{Account: util.Uint160{1, 2, 3}}},
|
||||
Script: []byte{1, 2, 3, 4},
|
||||
Attributes: []Attribute{},
|
||||
Attributes: []Attribute{{Type: HighPriority, Data: []byte{}}},
|
||||
Scripts: []Witness{},
|
||||
Trimmed: false,
|
||||
}
|
||||
|
||||
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