Merge pull request #1341 from nspcc-dev/feature/highprio
Support high-priority transactions from committee
This commit is contained in:
commit
860e7f12ad
12 changed files with 335 additions and 24 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
|
||||
|
|
|
@ -2,7 +2,6 @@ package core
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
@ -18,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"
|
||||
|
@ -252,8 +252,7 @@ func TestVerifyTx(t *testing.T) {
|
|||
|
||||
checkErr := func(t *testing.T, expectedErr error, tx *transaction.Transaction) {
|
||||
err := bc.VerifyTx(tx)
|
||||
fmt.Println(err)
|
||||
require.True(t, errors.Is(err, expectedErr))
|
||||
require.True(t, errors.Is(err, expectedErr), "expected: %v, got: %v", expectedErr, err)
|
||||
}
|
||||
|
||||
testScript := []byte{byte(opcode.PUSH1)}
|
||||
|
@ -347,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
|
||||
|
|
|
@ -254,3 +254,42 @@ func TestMemPoolFees(t *testing.T) {
|
|||
}, &FeerStub{})
|
||||
require.Equal(t, 0, len(mp.fees))
|
||||
}
|
||||
|
||||
func TestMempoolItemsOrder(t *testing.T) {
|
||||
sender0 := util.Uint160{1, 2, 3}
|
||||
|
||||
tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
tx1.NetworkFee = new(big.Int).Div(balance, big.NewInt(8)).Int64()
|
||||
tx1.Signers = []transaction.Signer{{Account: sender0}}
|
||||
tx1.Attributes = []transaction.Attribute{{Type: transaction.HighPriority}}
|
||||
item1 := &item{txn: tx1}
|
||||
|
||||
tx2 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
tx2.NetworkFee = new(big.Int).Div(balance, big.NewInt(16)).Int64()
|
||||
tx2.Signers = []transaction.Signer{{Account: sender0}}
|
||||
tx2.Attributes = []transaction.Attribute{{Type: transaction.HighPriority}}
|
||||
item2 := &item{txn: tx2}
|
||||
|
||||
tx3 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
tx3.NetworkFee = new(big.Int).Div(balance, big.NewInt(2)).Int64()
|
||||
tx3.Signers = []transaction.Signer{{Account: sender0}}
|
||||
item3 := &item{txn: tx3}
|
||||
|
||||
tx4 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
tx4.NetworkFee = new(big.Int).Div(balance, big.NewInt(4)).Int64()
|
||||
tx4.Signers = []transaction.Signer{{Account: sender0}}
|
||||
item4 := &item{txn: tx4}
|
||||
|
||||
require.True(t, item1.CompareTo(item2) > 0)
|
||||
require.True(t, item2.CompareTo(item1) < 0)
|
||||
require.True(t, item1.CompareTo(item3) > 0)
|
||||
require.True(t, item3.CompareTo(item1) < 0)
|
||||
require.True(t, item1.CompareTo(item4) > 0)
|
||||
require.True(t, item4.CompareTo(item1) < 0)
|
||||
require.True(t, item2.CompareTo(item3) > 0)
|
||||
require.True(t, item3.CompareTo(item2) < 0)
|
||||
require.True(t, item2.CompareTo(item4) > 0)
|
||||
require.True(t, item4.CompareTo(item2) < 0)
|
||||
require.True(t, item3.CompareTo(item4) > 0)
|
||||
require.True(t, item4.CompareTo(item3) < 0)
|
||||
}
|
||||
|
|
|
@ -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]]
|
||||
}
|
|
@ -3,6 +3,7 @@ package transaction
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
|
@ -119,6 +120,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) {
|
||||
|
@ -322,35 +333,58 @@ func (t *Transaction) UnmarshalJSON(data []byte) error {
|
|||
return t.isValid()
|
||||
}
|
||||
|
||||
// Various errors for transaction validation.
|
||||
var (
|
||||
ErrInvalidVersion = errors.New("only version 0 is supported")
|
||||
ErrNegativeSystemFee = errors.New("negative system fee")
|
||||
ErrNegativeNetworkFee = errors.New("negative network fee")
|
||||
ErrTooBigFees = errors.New("too big fees: int64 overflow")
|
||||
ErrEmptySigners = errors.New("signers array should contain sender")
|
||||
ErrInvalidScope = errors.New("FeeOnly scope can be used only for sender")
|
||||
ErrNonUniqueSigners = errors.New("transaction signers should be unique")
|
||||
ErrInvalidAttribute = errors.New("invalid attribute")
|
||||
ErrEmptyScript = errors.New("no script")
|
||||
)
|
||||
|
||||
// isValid checks whether decoded/unmarshalled transaction has all fields valid.
|
||||
func (t *Transaction) isValid() error {
|
||||
if t.Version > 0 {
|
||||
return errors.New("only version 0 is supported")
|
||||
return ErrInvalidVersion
|
||||
}
|
||||
if t.SystemFee < 0 {
|
||||
return errors.New("negative system fee")
|
||||
return ErrNegativeSystemFee
|
||||
}
|
||||
if t.NetworkFee < 0 {
|
||||
return errors.New("negative network fee")
|
||||
return ErrNegativeNetworkFee
|
||||
}
|
||||
if t.NetworkFee+t.SystemFee < t.SystemFee {
|
||||
return errors.New("too big fees: int64 overflow")
|
||||
return ErrTooBigFees
|
||||
}
|
||||
if len(t.Signers) == 0 {
|
||||
return errors.New("signers array should contain sender")
|
||||
return ErrEmptySigners
|
||||
}
|
||||
for i := 0; i < len(t.Signers); i++ {
|
||||
if i > 0 && t.Signers[i].Scopes == FeeOnly {
|
||||
return errors.New("FeeOnly scope can be used only for sender")
|
||||
return ErrInvalidScope
|
||||
}
|
||||
for j := i + 1; j < len(t.Signers); j++ {
|
||||
if t.Signers[i].Account.Equals(t.Signers[j].Account) {
|
||||
return errors.New("transaction signers should be unique")
|
||||
return ErrNonUniqueSigners
|
||||
}
|
||||
}
|
||||
}
|
||||
hasHighPrio := false
|
||||
for i := range t.Attributes {
|
||||
switch t.Attributes[i].Type {
|
||||
case HighPriority:
|
||||
if hasHighPrio {
|
||||
return fmt.Errorf("%w: multiple high priority attributes", ErrInvalidAttribute)
|
||||
}
|
||||
hasHighPrio = true
|
||||
}
|
||||
}
|
||||
if len(t.Script) == 0 {
|
||||
return errors.New("no script")
|
||||
return ErrEmptyScript
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package transaction
|
|||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
|
@ -112,10 +114,100 @@ 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))
|
||||
}
|
||||
|
||||
func TestTransaction_isValid(t *testing.T) {
|
||||
newTx := func() *Transaction {
|
||||
return &Transaction{
|
||||
Version: 0,
|
||||
SystemFee: 100,
|
||||
NetworkFee: 100,
|
||||
Signers: []Signer{
|
||||
{Account: util.Uint160{1, 2, 3}},
|
||||
{
|
||||
Account: util.Uint160{4, 5, 6},
|
||||
Scopes: Global,
|
||||
},
|
||||
},
|
||||
Script: []byte{1, 2, 3, 4},
|
||||
Attributes: []Attribute{},
|
||||
Scripts: []Witness{},
|
||||
Trimmed: false,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
t.Run("NoAttributes", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
require.NoError(t, tx.isValid())
|
||||
})
|
||||
t.Run("HighPriority", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.Attributes = []Attribute{{Type: HighPriority}}
|
||||
require.NoError(t, tx.isValid())
|
||||
})
|
||||
})
|
||||
t.Run("InvalidVersion", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.Version = 1
|
||||
require.True(t, errors.Is(tx.isValid(), ErrInvalidVersion))
|
||||
})
|
||||
t.Run("NegativeSystemFee", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.SystemFee = -1
|
||||
require.True(t, errors.Is(tx.isValid(), ErrNegativeSystemFee))
|
||||
})
|
||||
t.Run("NegativeNetworkFee", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.NetworkFee = -1
|
||||
require.True(t, errors.Is(tx.isValid(), ErrNegativeNetworkFee))
|
||||
})
|
||||
t.Run("TooBigFees", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.SystemFee = math.MaxInt64 - tx.NetworkFee + 1
|
||||
require.True(t, errors.Is(tx.isValid(), ErrTooBigFees))
|
||||
})
|
||||
t.Run("EmptySigners", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.Signers = tx.Signers[:0]
|
||||
require.True(t, errors.Is(tx.isValid(), ErrEmptySigners))
|
||||
})
|
||||
t.Run("InvalidScope", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.Signers[1].Scopes = FeeOnly
|
||||
require.True(t, errors.Is(tx.isValid(), ErrInvalidScope))
|
||||
})
|
||||
t.Run("NonUniqueSigners", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.Signers[1].Account = tx.Signers[0].Account
|
||||
require.True(t, errors.Is(tx.isValid(), ErrNonUniqueSigners))
|
||||
})
|
||||
t.Run("MultipleHighPriority", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.Attributes = []Attribute{
|
||||
{Type: HighPriority},
|
||||
{Type: HighPriority},
|
||||
}
|
||||
require.True(t, errors.Is(tx.isValid(), ErrInvalidAttribute))
|
||||
})
|
||||
t.Run("NoScript", func(t *testing.T) {
|
||||
tx := newTx()
|
||||
tx.Script = []byte{}
|
||||
require.True(t, errors.Is(tx.isValid(), ErrEmptyScript))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -44,3 +44,11 @@ func CreateDefaultMultiSigRedeemScript(publicKeys keys.PublicKeys) ([]byte, erro
|
|||
m := n - (n-1)/3
|
||||
return CreateMultiSigRedeemScript(m, publicKeys)
|
||||
}
|
||||
|
||||
// CreateMajorityMultiSigRedeemScript creates an "m out of n" type verification script
|
||||
// using publicKeys length with m set to majority.
|
||||
func CreateMajorityMultiSigRedeemScript(publicKeys keys.PublicKeys) ([]byte, error) {
|
||||
n := len(publicKeys)
|
||||
m := n - (n-1)/2
|
||||
return CreateMultiSigRedeemScript(m, publicKeys)
|
||||
}
|
||||
|
|
|
@ -83,3 +83,48 @@ func TestCreateDefaultMultiSigRedeemScript(t *testing.T) {
|
|||
}
|
||||
checkM(7)
|
||||
}
|
||||
|
||||
func TestCreateMajorityMultiSigRedeemScript(t *testing.T) {
|
||||
var validators = make([]*keys.PublicKey, 0)
|
||||
|
||||
var addKey = func() {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
validators = append(validators, key.PublicKey())
|
||||
}
|
||||
var checkM = func(m int) {
|
||||
validScript, err := CreateMultiSigRedeemScript(m, validators)
|
||||
require.NoError(t, err)
|
||||
defaultScript, err := CreateMajorityMultiSigRedeemScript(validators)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, validScript, defaultScript)
|
||||
}
|
||||
|
||||
// 1 out of 1
|
||||
addKey()
|
||||
checkM(1)
|
||||
|
||||
// 2 out of 2
|
||||
addKey()
|
||||
checkM(2)
|
||||
|
||||
// 3 out of 4
|
||||
addKey()
|
||||
addKey()
|
||||
checkM(3)
|
||||
|
||||
// 4 out of 6
|
||||
addKey()
|
||||
addKey()
|
||||
checkM(4)
|
||||
|
||||
// 5 out of 8
|
||||
addKey()
|
||||
addKey()
|
||||
checkM(5)
|
||||
|
||||
// 6 out of 10
|
||||
addKey()
|
||||
addKey()
|
||||
checkM(6)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue