core: add NotaryAssisted transaction attribute

This commit is contained in:
Anna Shaleva 2020-11-18 14:26:13 +03:00
parent fc9f0034c9
commit 52cf328296
7 changed files with 133 additions and 7 deletions

View file

@ -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)

View file

@ -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))
})
})
})
}

View file

@ -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")
}

View file

@ -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))
})
}

View file

@ -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 {

View file

@ -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:

View 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
}