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)
|
return fmt.Errorf("%w: (%d > MaxTransactionSize %d)", ErrTxTooBig, size, transaction.MaxTransactionSize)
|
||||||
}
|
}
|
||||||
needNetworkFee := int64(size) * bc.FeePerByte()
|
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
|
netFee := t.NetworkFee - needNetworkFee
|
||||||
if netFee < 0 {
|
if netFee < 0 {
|
||||||
return fmt.Errorf("%w: net fee is %v, need %v", ErrTxSmallNetworkFee, t.NetworkFee, needNetworkFee)
|
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) {
|
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())
|
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:
|
default:
|
||||||
if !bc.config.ReservedAttributes && attrType >= transaction.ReservedLowerBound && attrType <= transaction.ReservedUpperBound {
|
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)
|
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
|
return tx
|
||||||
}
|
}
|
||||||
t.Run("Disabled", func(t *testing.T) {
|
t.Run("Disabled", func(t *testing.T) {
|
||||||
tx := getReservedTx(transaction.ReservedLowerBound + 2)
|
tx := getReservedTx(transaction.ReservedLowerBound + 3)
|
||||||
require.Error(t, bc.VerifyTx(tx))
|
require.Error(t, bc.VerifyTx(tx))
|
||||||
})
|
})
|
||||||
t.Run("Enabled", func(t *testing.T) {
|
t.Run("Enabled", func(t *testing.T) {
|
||||||
bc.config.ReservedAttributes = true
|
bc.config.ReservedAttributes = true
|
||||||
tx := getReservedTx(transaction.ReservedLowerBound + 2)
|
tx := getReservedTx(transaction.ReservedLowerBound + 3)
|
||||||
require.NoError(t, bc.VerifyTx(tx))
|
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)
|
attr.Value = new(NotValidBefore)
|
||||||
case ConflictsT:
|
case ConflictsT:
|
||||||
attr.Value = new(Conflicts)
|
attr.Value = new(Conflicts)
|
||||||
|
case NotaryAssistedT:
|
||||||
|
attr.Value = new(NotaryAssisted)
|
||||||
default:
|
default:
|
||||||
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
||||||
attr.Value = new(Reserved)
|
attr.Value = new(Reserved)
|
||||||
|
@ -55,7 +57,7 @@ func (attr *Attribute) EncodeBinary(bw *io.BinWriter) {
|
||||||
bw.WriteB(byte(attr.Type))
|
bw.WriteB(byte(attr.Type))
|
||||||
switch t := attr.Type; t {
|
switch t := attr.Type; t {
|
||||||
case HighPriority:
|
case HighPriority:
|
||||||
case OracleResponseT, NotValidBeforeT, ConflictsT:
|
case OracleResponseT, NotValidBeforeT, ConflictsT, NotaryAssistedT:
|
||||||
attr.Value.EncodeBinary(bw)
|
attr.Value.EncodeBinary(bw)
|
||||||
default:
|
default:
|
||||||
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
||||||
|
@ -97,6 +99,9 @@ func (attr *Attribute) UnmarshalJSON(data []byte) error {
|
||||||
case ConflictsT.String():
|
case ConflictsT.String():
|
||||||
attr.Type = ConflictsT
|
attr.Type = ConflictsT
|
||||||
attr.Value = new(Conflicts)
|
attr.Value = new(Conflicts)
|
||||||
|
case NotaryAssistedT.String():
|
||||||
|
attr.Type = NotaryAssistedT
|
||||||
|
attr.Value = new(NotaryAssisted)
|
||||||
default:
|
default:
|
||||||
return errors.New("wrong Type")
|
return errors.New("wrong Type")
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func TestAttribute_EncodeBinary(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Run("lower bound", func(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) {
|
t.Run("upper bound", func(t *testing.T) {
|
||||||
testserdes.EncodeDecodeBinary(t, getReservedAttribute(ReservedUpperBound), new(Attribute))
|
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)))
|
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) {
|
func TestAttribute_MarshalJSON(t *testing.T) {
|
||||||
|
@ -149,4 +170,13 @@ func TestAttribute_MarshalJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
testserdes.MarshalUnmarshalJSON(t, attr, new(Attribute))
|
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
|
OracleResponseT AttrType = 0x11 // OracleResponse
|
||||||
NotValidBeforeT AttrType = ReservedLowerBound // NotValidBefore
|
NotValidBeforeT AttrType = ReservedLowerBound // NotValidBefore
|
||||||
ConflictsT AttrType = ReservedLowerBound + 1 // Conflicts
|
ConflictsT AttrType = ReservedLowerBound + 1 // Conflicts
|
||||||
|
NotaryAssistedT AttrType = ReservedLowerBound + 2 // NotaryAssisted
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a AttrType) allowMultiple() bool {
|
func (a AttrType) allowMultiple() bool {
|
||||||
|
|
|
@ -12,16 +12,17 @@ func _() {
|
||||||
_ = x[OracleResponseT-17]
|
_ = x[OracleResponseT-17]
|
||||||
_ = x[NotValidBeforeT-224]
|
_ = x[NotValidBeforeT-224]
|
||||||
_ = x[ConflictsT-225]
|
_ = x[ConflictsT-225]
|
||||||
|
_ = x[NotaryAssistedT-226]
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_AttrType_name_0 = "HighPriority"
|
_AttrType_name_0 = "HighPriority"
|
||||||
_AttrType_name_1 = "OracleResponse"
|
_AttrType_name_1 = "OracleResponse"
|
||||||
_AttrType_name_2 = "NotValidBeforeConflicts"
|
_AttrType_name_2 = "NotValidBeforeConflictsNotaryAssisted"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_AttrType_index_2 = [...]uint8{0, 14, 23}
|
_AttrType_index_2 = [...]uint8{0, 14, 23, 37}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i AttrType) String() string {
|
func (i AttrType) String() string {
|
||||||
|
@ -30,7 +31,7 @@ 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 224 <= i && i <= 225:
|
case 224 <= i && i <= 226:
|
||||||
i -= 224
|
i -= 224
|
||||||
return _AttrType_name_2[_AttrType_index_2[i]:_AttrType_index_2[i+1]]
|
return _AttrType_name_2[_AttrType_index_2[i]:_AttrType_index_2[i+1]]
|
||||||
default:
|
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