core: add NotaryAssisted transaction attribute
This commit is contained in:
parent
fc9f0034c9
commit
52cf328296
7 changed files with 133 additions and 7 deletions
|
@ -1280,6 +1280,13 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
|||
return fmt.Errorf("%w: (%d > MaxTransactionSize %d)", ErrTxTooBig, size, transaction.MaxTransactionSize)
|
||||
}
|
||||
needNetworkFee := int64(size) * bc.FeePerByte()
|
||||
if bc.P2PSigExtensionsEnabled() {
|
||||
attrs := t.GetAttributes(transaction.NotaryAssistedT)
|
||||
if len(attrs) != 0 {
|
||||
na := attrs[0].Value.(*transaction.NotaryAssisted)
|
||||
needNetworkFee += (int64(na.NKeys) + 1) * transaction.NotaryServiceFeePerKey
|
||||
}
|
||||
}
|
||||
netFee := t.NetworkFee - needNetworkFee
|
||||
if netFee < 0 {
|
||||
return fmt.Errorf("%w: net fee is %v, need %v", ErrTxSmallNetworkFee, t.NetworkFee, needNetworkFee)
|
||||
|
@ -1375,6 +1382,10 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
|||
if err := bc.dao.HasTransaction(conflicts.Hash); errors.Is(err, dao.ErrAlreadyExists) {
|
||||
return fmt.Errorf("%w: conflicting transaction %s is already on chain", ErrInvalidAttribute, conflicts.Hash.StringLE())
|
||||
}
|
||||
case transaction.NotaryAssistedT:
|
||||
if !bc.config.P2PSigExtensions {
|
||||
return fmt.Errorf("%w: NotaryAssisted attribute was found, but P2PSigExtensions are disabled", ErrInvalidAttribute)
|
||||
}
|
||||
default:
|
||||
if !bc.config.ReservedAttributes && attrType >= transaction.ReservedLowerBound && attrType <= transaction.ReservedUpperBound {
|
||||
return fmt.Errorf("%w: attribute of reserved type was found, but ReservedAttributes are disabled", ErrInvalidAttribute)
|
||||
|
|
|
@ -652,12 +652,12 @@ func TestVerifyTx(t *testing.T) {
|
|||
return tx
|
||||
}
|
||||
t.Run("Disabled", func(t *testing.T) {
|
||||
tx := getReservedTx(transaction.ReservedLowerBound + 2)
|
||||
tx := getReservedTx(transaction.ReservedLowerBound + 3)
|
||||
require.Error(t, bc.VerifyTx(tx))
|
||||
})
|
||||
t.Run("Enabled", func(t *testing.T) {
|
||||
bc.config.ReservedAttributes = true
|
||||
tx := getReservedTx(transaction.ReservedLowerBound + 2)
|
||||
tx := getReservedTx(transaction.ReservedLowerBound + 3)
|
||||
require.NoError(t, bc.VerifyTx(tx))
|
||||
})
|
||||
})
|
||||
|
@ -719,6 +719,47 @@ func TestVerifyTx(t *testing.T) {
|
|||
})
|
||||
})
|
||||
})
|
||||
t.Run("NotaryAssisted", func(t *testing.T) {
|
||||
getNotaryAssistedTx := func(signaturesCount uint8, serviceFee int64) *transaction.Transaction {
|
||||
tx := bc.newTestTx(h, testScript)
|
||||
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{
|
||||
NKeys: signaturesCount,
|
||||
}})
|
||||
tx.NetworkFee += serviceFee // additional fee for NotaryAssisted attribute
|
||||
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) {
|
||||
bc.config.P2PSigExtensions = false
|
||||
tx := getNotaryAssistedTx(0, 0)
|
||||
require.Error(t, bc.VerifyTx(tx))
|
||||
})
|
||||
t.Run("Enabled, insufficient network fee", func(t *testing.T) {
|
||||
bc.config.P2PSigExtensions = true
|
||||
tx := getNotaryAssistedTx(1, 0)
|
||||
require.Error(t, bc.VerifyTx(tx))
|
||||
})
|
||||
t.Run("Enabled, positive", func(t *testing.T) {
|
||||
bc.config.P2PSigExtensions = true
|
||||
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
|
||||
require.NoError(t, bc.VerifyTx(tx))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ func (attr *Attribute) DecodeBinary(br *io.BinReader) {
|
|||
attr.Value = new(NotValidBefore)
|
||||
case ConflictsT:
|
||||
attr.Value = new(Conflicts)
|
||||
case NotaryAssistedT:
|
||||
attr.Value = new(NotaryAssisted)
|
||||
default:
|
||||
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
||||
attr.Value = new(Reserved)
|
||||
|
@ -55,7 +57,7 @@ func (attr *Attribute) EncodeBinary(bw *io.BinWriter) {
|
|||
bw.WriteB(byte(attr.Type))
|
||||
switch t := attr.Type; t {
|
||||
case HighPriority:
|
||||
case OracleResponseT, NotValidBeforeT, ConflictsT:
|
||||
case OracleResponseT, NotValidBeforeT, ConflictsT, NotaryAssistedT:
|
||||
attr.Value.EncodeBinary(bw)
|
||||
default:
|
||||
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
||||
|
@ -97,6 +99,9 @@ func (attr *Attribute) UnmarshalJSON(data []byte) error {
|
|||
case ConflictsT.String():
|
||||
attr.Type = ConflictsT
|
||||
attr.Value = new(Conflicts)
|
||||
case NotaryAssistedT.String():
|
||||
attr.Type = NotaryAssistedT
|
||||
attr.Value = new(NotaryAssisted)
|
||||
default:
|
||||
return errors.New("wrong Type")
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func TestAttribute_EncodeBinary(t *testing.T) {
|
|||
}
|
||||
}
|
||||
t.Run("lower bound", func(t *testing.T) {
|
||||
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedLowerBound+2), new(Attribute))
|
||||
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedLowerBound+3), new(Attribute))
|
||||
})
|
||||
t.Run("upper bound", func(t *testing.T) {
|
||||
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedUpperBound), new(Attribute))
|
||||
|
@ -95,6 +95,27 @@ func TestAttribute_EncodeBinary(t *testing.T) {
|
|||
require.Error(t, testserdes.DecodeBinary(bw.Bytes(), new(Conflicts)))
|
||||
})
|
||||
})
|
||||
t.Run("NotaryAssisted", func(t *testing.T) {
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
attr := &Attribute{
|
||||
Type: NotaryAssistedT,
|
||||
Value: &NotaryAssisted{
|
||||
NKeys: 3,
|
||||
},
|
||||
}
|
||||
testserdes.EncodeDecodeBinary(t, attr, new(Attribute))
|
||||
})
|
||||
t.Run("bad format: too long", func(t *testing.T) {
|
||||
bw := io.NewBufBinWriter()
|
||||
bw.WriteVarBytes(make([]byte, 2))
|
||||
require.Error(t, testserdes.DecodeBinary(bw.Bytes(), new(NotaryAssisted)))
|
||||
})
|
||||
t.Run("bad format: too short", func(t *testing.T) {
|
||||
bw := io.NewBufBinWriter()
|
||||
bw.WriteVarBytes([]byte{})
|
||||
require.Error(t, testserdes.DecodeBinary(bw.Bytes(), new(NotaryAssisted)))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestAttribute_MarshalJSON(t *testing.T) {
|
||||
|
@ -149,4 +170,13 @@ func TestAttribute_MarshalJSON(t *testing.T) {
|
|||
}
|
||||
testserdes.MarshalUnmarshalJSON(t, attr, new(Attribute))
|
||||
})
|
||||
t.Run("NotaryAssisted", func(t *testing.T) {
|
||||
attr := &Attribute{
|
||||
Type: NotaryAssistedT,
|
||||
Value: &NotaryAssisted{
|
||||
NKeys: 3,
|
||||
},
|
||||
}
|
||||
testserdes.MarshalUnmarshalJSON(t, attr, new(Attribute))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ const (
|
|||
OracleResponseT AttrType = 0x11 // OracleResponse
|
||||
NotValidBeforeT AttrType = ReservedLowerBound // NotValidBefore
|
||||
ConflictsT AttrType = ReservedLowerBound + 1 // Conflicts
|
||||
NotaryAssistedT AttrType = ReservedLowerBound + 2 // NotaryAssisted
|
||||
)
|
||||
|
||||
func (a AttrType) allowMultiple() bool {
|
||||
|
|
|
@ -12,16 +12,17 @@ func _() {
|
|||
_ = x[OracleResponseT-17]
|
||||
_ = x[NotValidBeforeT-224]
|
||||
_ = x[ConflictsT-225]
|
||||
_ = x[NotaryAssistedT-226]
|
||||
}
|
||||
|
||||
const (
|
||||
_AttrType_name_0 = "HighPriority"
|
||||
_AttrType_name_1 = "OracleResponse"
|
||||
_AttrType_name_2 = "NotValidBeforeConflicts"
|
||||
_AttrType_name_2 = "NotValidBeforeConflictsNotaryAssisted"
|
||||
)
|
||||
|
||||
var (
|
||||
_AttrType_index_2 = [...]uint8{0, 14, 23}
|
||||
_AttrType_index_2 = [...]uint8{0, 14, 23, 37}
|
||||
)
|
||||
|
||||
func (i AttrType) String() string {
|
||||
|
@ -30,7 +31,7 @@ func (i AttrType) String() string {
|
|||
return _AttrType_name_0
|
||||
case i == 17:
|
||||
return _AttrType_name_1
|
||||
case 224 <= i && i <= 225:
|
||||
case 224 <= i && i <= 226:
|
||||
i -= 224
|
||||
return _AttrType_name_2[_AttrType_index_2[i]:_AttrType_index_2[i+1]]
|
||||
default:
|
||||
|
|
37
pkg/core/transaction/notary_assisted.go
Normal file
37
pkg/core/transaction/notary_assisted.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package transaction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
)
|
||||
|
||||
// NotaryServiceFeePerKey is a reward per key for notary nodes.
|
||||
const NotaryServiceFeePerKey = 1000_0000 // 0.1 GAS
|
||||
|
||||
// NotaryAssisted represents attribute for notary service transactions.
|
||||
type NotaryAssisted struct {
|
||||
NKeys uint8 `json:"nkeys"`
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable interface.
|
||||
func (n *NotaryAssisted) DecodeBinary(br *io.BinReader) {
|
||||
bytes := br.ReadVarBytes(1)
|
||||
if br.Err != nil {
|
||||
return
|
||||
}
|
||||
if len(bytes) != 1 {
|
||||
br.Err = fmt.Errorf("expected 1 byte, got %d", len(bytes))
|
||||
return
|
||||
}
|
||||
n.NKeys = bytes[0]
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable interface.
|
||||
func (n *NotaryAssisted) EncodeBinary(w *io.BinWriter) {
|
||||
w.WriteVarBytes([]byte{n.NKeys})
|
||||
}
|
||||
|
||||
func (n *NotaryAssisted) toJSONMap(m map[string]interface{}) {
|
||||
m["nkeys"] = n.NKeys
|
||||
}
|
Loading…
Reference in a new issue