*: support customisable NotaryServiceFeePerKey value

* Add corresponding methods to Notary contract.
* Extend RPC Client API.
* Adjust tests.
This commit is contained in:
AnnaShaleva 2022-03-01 13:10:54 +03:00
parent 26b76ed858
commit 92282c70cb
14 changed files with 157 additions and 48 deletions

View file

@ -139,6 +139,11 @@ func (chain *FakeChain) GetNotaryBalance(acc util.Uint160) *big.Int {
panic("TODO")
}
// GetNotaryServiceFeePerKey implements Blockchainer interface.
func (chain *FakeChain) GetNotaryServiceFeePerKey() int64 {
panic("TODO")
}
// GetBaseExecFee implements Policer interface.
func (chain *FakeChain) GetBaseExecFee() int64 {
return interop.DefaultBaseExecFee

View file

@ -1499,6 +1499,12 @@ func (bc *Blockchain) GetNotaryBalance(acc util.Uint160) *big.Int {
return bc.contracts.Notary.BalanceOf(bc.dao, acc)
}
// GetNotaryServiceFeePerKey returns NotaryServiceFeePerKey which is a reward per
// notary request key for designated notary nodes.
func (bc *Blockchain) GetNotaryServiceFeePerKey() int64 {
return bc.contracts.Notary.GetNotaryServiceFeePerKey(bc.dao)
}
// GetNotaryContractScriptHash returns Notary native contract hash.
func (bc *Blockchain) GetNotaryContractScriptHash() util.Uint160 {
if bc.P2PSigExtensionsEnabled() {
@ -1913,7 +1919,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
attrs := t.GetAttributes(transaction.NotaryAssistedT)
if len(attrs) != 0 {
na := attrs[0].Value.(*transaction.NotaryAssisted)
needNetworkFee += (int64(na.NKeys) + 1) * transaction.NotaryServiceFeePerKey
needNetworkFee += (int64(na.NKeys) + 1) * bc.contracts.Notary.GetNotaryServiceFeePerKey(bc.dao)
}
}
netFee := t.NetworkFee - needNetworkFee
@ -2257,7 +2263,7 @@ func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *block
attrs := t.GetAttributes(transaction.NotaryAssistedT)
if len(attrs) != 0 {
na := attrs[0].Value.(*transaction.NotaryAssisted)
gasLimit -= (int64(na.NKeys) + 1) * transaction.NotaryServiceFeePerKey
gasLimit -= (int64(na.NKeys) + 1) * bc.contracts.Notary.GetNotaryServiceFeePerKey(bc.dao)
}
}
for i := range t.Signers {

View file

@ -316,6 +316,8 @@ func TestVerifyTx(t *testing.T) {
require.NoError(t, err)
}
notaryServiceFeePerKey := bc.contracts.Notary.GetNotaryServiceFeePerKey(bc.dao)
oracleAcc := accs[2]
oraclePubs := keys.PublicKeys{oracleAcc.PrivateKey().PublicKey()}
require.NoError(t, oracleAcc.ConvertMultisig(1, oraclePubs))
@ -891,7 +893,7 @@ func TestVerifyTx(t *testing.T) {
t.Run("Test verify", func(t *testing.T) {
bc.config.P2PSigExtensions = true
t.Run("no NotaryAssisted attribute", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey)
tx.Attributes = []transaction.Attribute{}
tx.Signers = []transaction.Signer{
{
@ -915,7 +917,7 @@ func TestVerifyTx(t *testing.T) {
require.Error(t, bc.VerifyTx(tx))
})
t.Run("no deposit", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: bc.contracts.Notary.Hash,
@ -938,7 +940,7 @@ func TestVerifyTx(t *testing.T) {
require.Error(t, bc.VerifyTx(tx))
})
t.Run("bad Notary signer scope", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
@ -961,7 +963,7 @@ func TestVerifyTx(t *testing.T) {
require.Error(t, bc.VerifyTx(tx))
})
t.Run("not signed by Notary", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
@ -977,7 +979,7 @@ func TestVerifyTx(t *testing.T) {
require.Error(t, bc.VerifyTx(tx))
})
t.Run("bad Notary node witness", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: testchain.CommitteeScriptHash(),
@ -1002,7 +1004,7 @@ func TestVerifyTx(t *testing.T) {
require.Error(t, bc.VerifyTx(tx))
})
t.Run("missing payer", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey)
tx.Signers = []transaction.Signer{
{
Account: bc.contracts.Notary.Hash,
@ -1017,7 +1019,7 @@ func TestVerifyTx(t *testing.T) {
require.Error(t, bc.VerifyTx(tx))
})
t.Run("positive", func(t *testing.T) {
tx := getNotaryAssistedTx(1, (1+1)*transaction.NotaryServiceFeePerKey)
tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey)
require.NoError(t, bc.VerifyTx(tx))
})
})
@ -1055,7 +1057,7 @@ func TestVerifyTx(t *testing.T) {
int64(sizeDelta)*bc.FeePerByte() + // fee for multisig size
66*bc.FeePerByte() + // fee for Notary signature size (66 bytes for Invocation script and 0 bytes for Verification script)
2*bc.FeePerByte() + // fee for the length of each script in Notary witness (they are nil, so we did not take them into account during `size` calculation)
transaction.NotaryServiceFeePerKey + // fee for Notary attribute
notaryServiceFeePerKey + // fee for Notary attribute
fee.Opcode(bc.GetBaseExecFee(), // Notary verification script
opcode.PUSHDATA1, opcode.RET, // invocation script
opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call

View file

@ -55,6 +55,7 @@ type Blockchainer interface {
GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error)
GetNotaryContractScriptHash() util.Uint160
GetNotaryBalance(acc util.Uint160) *big.Int
GetNotaryServiceFeePerKey() int64
GetValidators() ([]*keys.PublicKey, error)
GetStateModule() StateRoot
GetStorageItem(id int32, key []byte) state.StorageItem

View file

@ -109,6 +109,7 @@ func NewContracts(cfg config.ProtocolConfiguration) *Contracts {
notary.NEO = neo
notary.Desig = desig
cs.Notary = notary
gas.Notary = notary
cs.Contracts = append(cs.Contracts, notary)
}

View file

@ -19,6 +19,8 @@ import (
type GAS struct {
nep17TokenNative
NEO *NEO
// Notary is a native Notary contract. It is set only when P2PSigExtensions are on.
Notary *Notary
initialSupply int64
p2pSigExtensionsEnabled bool
@ -117,7 +119,7 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
attrs := tx.GetAttributes(transaction.NotaryAssistedT)
if len(attrs) != 0 {
na := attrs[0].Value.(*transaction.NotaryAssisted)
netFee -= (int64(na.NKeys) + 1) * transaction.NotaryServiceFeePerKey
netFee -= (int64(na.NKeys) + 1) * g.Notary.GetNotaryServiceFeePerKey(ic.DAO)
}
}
}

View file

@ -342,3 +342,11 @@ func toUint32(s stackitem.Item) uint32 {
}
return uint32(uint64Value)
}
func toInt64(s stackitem.Item) int64 {
bigInt := toBigInt(s)
if !bigInt.IsInt64() {
panic("bigint is not an uint64")
}
return bigInt.Int64()
}

View file

@ -71,6 +71,7 @@ func TestGAS_RewardWithP2PSigExtensionsEnabled(t *testing.T) {
e := neotest.NewExecutor(t, bc, validator, committee)
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
notaryHash := e.NativeHash(t, nativenames.Notary)
notaryServiceFeePerKey := e.Chain.GetNotaryServiceFeePerKey()
// transfer funds to committee
e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)).Invoke(t, true, "transfer", e.Validator.ScriptHash(), e.CommitteeHash, 1000_0000_0000, nil)
@ -90,7 +91,7 @@ func TestGAS_RewardWithP2PSigExtensionsEnabled(t *testing.T) {
}
// deposit GAS for `signer` with lock until the next block
depositAmount := 100_0000 + (2+int64(nKeys))*transaction.NotaryServiceFeePerKey // sysfee + netfee of the next transaction
depositAmount := 100_0000 + (2+int64(nKeys))*notaryServiceFeePerKey // sysfee + netfee of the next transaction
gasCommitteeInvoker.Invoke(t, true, "transfer", e.CommitteeHash, notaryHash, depositAmount, []interface{}{e.CommitteeHash, e.Chain.BlockHeight() + 1})
// save initial GAS total supply
@ -106,7 +107,7 @@ func TestGAS_RewardWithP2PSigExtensionsEnabled(t *testing.T) {
tx.Nonce = neotest.Nonce()
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: uint8(nKeys)}})
tx.NetworkFee = (2 + int64(nKeys)) * transaction.NotaryServiceFeePerKey
tx.NetworkFee = (2 + int64(nKeys)) * notaryServiceFeePerKey
tx.Signers = []transaction.Signer{
{
Account: notaryHash,
@ -131,7 +132,7 @@ func TestGAS_RewardWithP2PSigExtensionsEnabled(t *testing.T) {
// check balance of notaries
e.CheckGASBalance(t, notaryHash, big.NewInt(int64(depositAmount-tx.SystemFee-tx.NetworkFee)))
for _, notaryNode := range notaryNodes {
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(int64(transaction.NotaryServiceFeePerKey*(nKeys+1)/nNotaries)))
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(notaryServiceFeePerKey*(nKeys+1)/nNotaries))
}
tsUpdated := getGASTS(t)
tsExpected := tsInitial + 5000_0000 - tx.SystemFee

View file

@ -32,6 +32,11 @@ func TestNotary_MaxNotValidBeforeDelta(t *testing.T) {
testGetSet(t, c, "MaxNotValidBeforeDelta", 140, int64(c.Chain.GetConfig().ValidatorsCount), int64(c.Chain.GetConfig().MaxValidUntilBlockIncrement/2))
}
func TestNotary_NotaryServiceFeePerKey(t *testing.T) {
c := newNotaryClient(t)
testGetSet(t, c, "NotaryServiceFeePerKey", 1000_0000, 0, 0)
}
func TestNotary_Pipeline(t *testing.T) {
notaryCommitteeInvoker := newNotaryClient(t)
e := notaryCommitteeInvoker.Executor
@ -39,11 +44,12 @@ func TestNotary_Pipeline(t *testing.T) {
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
notaryHash := notaryCommitteeInvoker.NativeHash(t, nativenames.Notary)
feePerKey := e.Chain.GetNotaryServiceFeePerKey()
multisigHash := notaryCommitteeInvoker.Validator.ScriptHash() // matches committee's one for single chain
depositLock := 100
checkBalanceOf := func(t *testing.T, acc util.Uint160, expected int) { // we don't have big numbers in this test, thus may use int
notaryCommitteeInvoker.CheckGASBalance(t, acc, big.NewInt(int64(expected)))
checkBalanceOf := func(t *testing.T, acc util.Uint160, expected int64) { // we don't have big numbers in this test, thus may use int
notaryCommitteeInvoker.CheckGASBalance(t, acc, big.NewInt(expected))
}
// check Notary contract has no GAS on the account
@ -62,47 +68,47 @@ func TestNotary_Pipeline(t *testing.T) {
neoCommitteeInvoker.InvokeFail(t, "only GAS can be accepted for deposit", "transfer", multisigHash, notaryHash, int64(1), []interface{}{nil, int64(depositLock)})
// `onPayment`: insufficient first deposit
gasCommitteeInvoker.InvokeFail(t, "first deposit can not be less then", "transfer", multisigHash, notaryHash, int64(2*transaction.NotaryServiceFeePerKey-1), []interface{}{nil, int64(depositLock)})
gasCommitteeInvoker.InvokeFail(t, "first deposit can not be less then", "transfer", multisigHash, notaryHash, int64(2*feePerKey-1), []interface{}{nil, int64(depositLock)})
// `onPayment`: invalid `data` (missing `till` parameter)
gasCommitteeInvoker.InvokeFail(t, "`data` parameter should be an array of 2 elements", "transfer", multisigHash, notaryHash, 2*transaction.NotaryServiceFeePerKey, []interface{}{nil})
gasCommitteeInvoker.InvokeFail(t, "`data` parameter should be an array of 2 elements", "transfer", multisigHash, notaryHash, 2*feePerKey, []interface{}{nil})
// `onPayment`: invalid `data` (outdated `till` parameter)
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the chain's height", "transfer", multisigHash, notaryHash, 2*transaction.NotaryServiceFeePerKey, []interface{}{nil, int64(0)})
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the chain's height", "transfer", multisigHash, notaryHash, 2*feePerKey, []interface{}{nil, int64(0)})
// `onPayment`: good
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*transaction.NotaryServiceFeePerKey, []interface{}{nil, int64(depositLock)})
checkBalanceOf(t, notaryHash, 2*transaction.NotaryServiceFeePerKey)
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*feePerKey, []interface{}{nil, int64(depositLock)})
checkBalanceOf(t, notaryHash, 2*feePerKey)
// `expirationOf`: check `till` was set
notaryCommitteeInvoker.Invoke(t, depositLock, "expirationOf", multisigHash)
// `balanceOf`: check deposited amount for the multisig account
notaryCommitteeInvoker.Invoke(t, 2*transaction.NotaryServiceFeePerKey, "balanceOf", multisigHash)
notaryCommitteeInvoker.Invoke(t, 2*feePerKey, "balanceOf", multisigHash)
// `onPayment`: good second deposit and explicit `to` paramenter
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, transaction.NotaryServiceFeePerKey, []interface{}{multisigHash, int64(depositLock + 1)})
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, feePerKey, []interface{}{multisigHash, int64(depositLock + 1)})
checkBalanceOf(t, notaryHash, 3*feePerKey)
// `balanceOf`: check deposited amount for the multisig account
notaryCommitteeInvoker.Invoke(t, 3*transaction.NotaryServiceFeePerKey, "balanceOf", multisigHash)
notaryCommitteeInvoker.Invoke(t, 3*feePerKey, "balanceOf", multisigHash)
// `expirationOf`: check `till` is updated.
notaryCommitteeInvoker.Invoke(t, depositLock+1, "expirationOf", multisigHash)
// `onPayment`: empty payment, should fail because `till` less then the previous one
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the previous value", "transfer", multisigHash, notaryHash, int64(0), []interface{}{multisigHash, int64(depositLock)})
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
checkBalanceOf(t, notaryHash, 3*feePerKey)
notaryCommitteeInvoker.Invoke(t, depositLock+1, "expirationOf", multisigHash)
// `onPayment`: empty payment, should fail because `till` less then the chain height
gasCommitteeInvoker.InvokeFail(t, "`till` shouldn't be less then the chain's height", "transfer", multisigHash, notaryHash, int64(0), []interface{}{multisigHash, int64(1)})
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
checkBalanceOf(t, notaryHash, 3*feePerKey)
notaryCommitteeInvoker.Invoke(t, depositLock+1, "expirationOf", multisigHash)
// `onPayment`: empty payment, should successfully update `till`
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, int64(0), []interface{}{multisigHash, int64(depositLock + 2)})
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
checkBalanceOf(t, notaryHash, 3*feePerKey)
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
// `lockDepositUntil`: bad witness
@ -127,11 +133,11 @@ func TestNotary_Pipeline(t *testing.T) {
// `withdraw`: bad witness
notaryAccInvoker.Invoke(t, false, "withdraw", multisigHash, accHash)
notaryCommitteeInvoker.Invoke(t, 3*transaction.NotaryServiceFeePerKey, "balanceOf", multisigHash)
notaryCommitteeInvoker.Invoke(t, 3*feePerKey, "balanceOf", multisigHash)
// `withdraw`: locked deposit
notaryCommitteeInvoker.Invoke(t, false, "withdraw", multisigHash, multisigHash)
notaryCommitteeInvoker.Invoke(t, 3*transaction.NotaryServiceFeePerKey, "balanceOf", multisigHash)
notaryCommitteeInvoker.Invoke(t, 3*feePerKey, "balanceOf", multisigHash)
// `withdraw`: unlock deposit and transfer GAS back to owner
e.GenerateNewBlocks(t, depositLock)
@ -143,13 +149,13 @@ func TestNotary_Pipeline(t *testing.T) {
notaryCommitteeInvoker.Invoke(t, false, "withdraw", multisigHash, accHash)
// `onPayment`: good first deposit to other account, should set default `till` even if other `till` value is provided
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*transaction.NotaryServiceFeePerKey, []interface{}{accHash, int64(math.MaxUint32 - 1)})
checkBalanceOf(t, notaryHash, 2*transaction.NotaryServiceFeePerKey)
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, 2*feePerKey, []interface{}{accHash, int64(math.MaxUint32 - 1)})
checkBalanceOf(t, notaryHash, 2*feePerKey)
notaryCommitteeInvoker.Invoke(t, 5760+e.Chain.BlockHeight()-1, "expirationOf", accHash)
// `onPayment`: good second deposit to other account, shouldn't update `till` even if other `till` value is provided
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, transaction.NotaryServiceFeePerKey, []interface{}{accHash, int64(math.MaxUint32 - 1)})
checkBalanceOf(t, notaryHash, 3*transaction.NotaryServiceFeePerKey)
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, feePerKey, []interface{}{accHash, int64(math.MaxUint32 - 1)})
checkBalanceOf(t, notaryHash, 3*feePerKey)
notaryCommitteeInvoker.Invoke(t, 5760+e.Chain.BlockHeight()-3, "expirationOf", accHash)
}
@ -161,6 +167,7 @@ func TestNotary_NotaryNodesReward(t *testing.T) {
designationCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation))
notaryHash := notaryCommitteeInvoker.NativeHash(t, nativenames.Notary)
feePerKey := e.Chain.GetNotaryServiceFeePerKey()
multisigHash := notaryCommitteeInvoker.Validator.ScriptHash() // matches committee's one for single chain
var err error
@ -180,7 +187,7 @@ func TestNotary_NotaryNodesReward(t *testing.T) {
}
// deposit GAS for `signer` with lock until the next block
depositAmount := 100_0000 + (2+int64(nKeys))*transaction.NotaryServiceFeePerKey // sysfee + netfee of the next transaction
depositAmount := 100_0000 + (2+int64(nKeys))*feePerKey // sysfee + netfee of the next transaction
if !spendFullDeposit {
depositAmount += 1_0000
}
@ -191,7 +198,7 @@ func TestNotary_NotaryNodesReward(t *testing.T) {
tx.Nonce = neotest.Nonce()
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: uint8(nKeys)}})
tx.NetworkFee = (2 + int64(nKeys)) * transaction.NotaryServiceFeePerKey
tx.NetworkFee = (2 + int64(nKeys)) * feePerKey
tx.Signers = []transaction.Signer{
{
Account: notaryHash,
@ -215,7 +222,7 @@ func TestNotary_NotaryNodesReward(t *testing.T) {
e.CheckGASBalance(t, notaryHash, big.NewInt(int64(depositAmount-tx.SystemFee-tx.NetworkFee)))
for _, notaryNode := range notaryNodes {
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(int64(transaction.NotaryServiceFeePerKey*(nKeys+1)/nNotaryNodes)))
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(feePerKey*int64((nKeys+1))/int64(nNotaryNodes)))
}
}

View file

@ -39,6 +39,7 @@ type Notary struct {
// blockchain DAO persisting. If true, we can safely use cached values.
isValid bool
maxNotValidBeforeDelta uint32
notaryServiceFeePerKey int64
}
const (
@ -46,10 +47,14 @@ const (
// prefixDeposit is a prefix for storing Notary deposits.
prefixDeposit = 1
defaultDepositDeltaTill = 5760
defaultMaxNotValidBeforeDelta = 140 // 20 rounds for 7 validators, a little more than half an hour
defaultMaxNotValidBeforeDelta = 140 // 20 rounds for 7 validators, a little more than half an hour
defaultNotaryServiceFeePerKey = 1000_0000 // 0.1 GAS
)
var maxNotValidBeforeDeltaKey = []byte{10}
var (
maxNotValidBeforeDeltaKey = []byte{10}
notaryServiceFeeKey = []byte{5}
)
// newNotary returns Notary native contract.
func newNotary() *Notary {
@ -99,6 +104,15 @@ func newNotary() *Notary {
md = newMethodAndPrice(n.setMaxNotValidBeforeDelta, 1<<15, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("getNotaryServiceFeePerKey", smartcontract.IntegerType)
md = newMethodAndPrice(n.getNotaryServiceFeePerKey, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("setNotaryServiceFeePerKey", smartcontract.VoidType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(n.setNotaryServiceFeePerKey, 1<<15, callflag.States)
n.AddMethod(md, desc)
return n
}
@ -110,8 +124,10 @@ func (n *Notary) Metadata() *interop.ContractMD {
// Initialize initializes Notary native contract and implements Contract interface.
func (n *Notary) Initialize(ic *interop.Context) error {
setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta)
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, defaultNotaryServiceFeePerKey)
n.isValid = true
n.maxNotValidBeforeDelta = defaultMaxNotValidBeforeDelta
n.notaryServiceFeePerKey = defaultNotaryServiceFeePerKey
return nil
}
@ -150,7 +166,8 @@ func (n *Notary) OnPersist(ic *interop.Context) error {
if nFees == 0 {
return nil
}
singleReward := calculateNotaryReward(nFees, len(notaries))
feePerKey := n.GetNotaryServiceFeePerKey(ic.DAO)
singleReward := calculateNotaryReward(nFees, feePerKey, len(notaries))
for _, notary := range notaries {
n.GAS.mint(ic, notary.GetScriptHash(), singleReward, false)
}
@ -166,6 +183,7 @@ func (n *Notary) PostPersist(ic *interop.Context) error {
}
n.maxNotValidBeforeDelta = uint32(getIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey))
n.notaryServiceFeePerKey = getIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey)
n.isValid = true
return nil
}
@ -198,9 +216,10 @@ func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem
if deposit != nil && till < deposit.Till {
panic(fmt.Errorf("`till` shouldn't be less then the previous value %d", deposit.Till))
}
feePerKey := n.GetNotaryServiceFeePerKey(ic.DAO)
if deposit == nil {
if amount.Cmp(big.NewInt(2*transaction.NotaryServiceFeePerKey)) < 0 {
panic(fmt.Errorf("first deposit can not be less then %d, got %d", 2*transaction.NotaryServiceFeePerKey, amount.Int64()))
if amount.Cmp(big.NewInt(2*feePerKey)) < 0 {
panic(fmt.Errorf("first deposit can not be less then %d, got %d", 2*feePerKey, amount.Int64()))
}
deposit = &state.Deposit{
Amount: new(big.Int),
@ -398,6 +417,37 @@ func (n *Notary) setMaxNotValidBeforeDelta(ic *interop.Context, args []stackitem
return stackitem.Null{}
}
// getNotaryServiceFeePerKey is Notary contract method and returns a reward per notary request key for notary nodes.
func (n *Notary) getNotaryServiceFeePerKey(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(big.NewInt(int64(n.GetNotaryServiceFeePerKey(ic.DAO))))
}
// GetNotaryServiceFeePerKey is an internal representation of Notary getNotaryServiceFeePerKey method.
func (n *Notary) GetNotaryServiceFeePerKey(dao *dao.Simple) int64 {
n.lock.RLock()
defer n.lock.RUnlock()
if n.isValid {
return n.notaryServiceFeePerKey
}
return getIntWithKey(n.ID, dao, notaryServiceFeeKey)
}
// setNotaryServiceFeePerKey is Notary contract method and sets a reward per notary request key for notary nodes.
func (n *Notary) setNotaryServiceFeePerKey(ic *interop.Context, args []stackitem.Item) stackitem.Item {
value := toInt64(args[0])
if value < 0 {
panic("NotaryServiceFeePerKey can't be negative")
}
if !n.NEO.checkCommittee(ic) {
panic("invalid committee signature")
}
n.lock.Lock()
defer n.lock.Unlock()
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, int64(value))
n.isValid = false
return stackitem.Null{}
}
// GetDepositFor returns state.Deposit for the account specified. It returns nil in case if
// deposit is not found in storage and panics in case of any other error.
func (n *Notary) GetDepositFor(dao *dao.Simple, acc util.Uint160) *state.Deposit {
@ -426,6 +476,6 @@ func (n *Notary) removeDepositFor(dao *dao.Simple, acc util.Uint160) {
}
// calculateNotaryReward calculates the reward for a single notary node based on FEE's count and Notary nodes count.
func calculateNotaryReward(nFees int64, notariesCount int) *big.Int {
return big.NewInt(nFees * transaction.NotaryServiceFeePerKey / int64(notariesCount))
func calculateNotaryReward(nFees int64, feePerKey int64, notariesCount int) *big.Int {
return big.NewInt(nFees * feePerKey / int64(notariesCount))
}

View file

@ -6,9 +6,6 @@ import (
"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"`

View file

@ -141,3 +141,13 @@ func (c *Client) NNSGetAllRecords(nnsHash util.Uint160, name string) ([]nns.Reco
}
return rss, nil
}
// GetNotaryServiceFeePerKey returns a reward per notary request key for designated
// notary nodes. It doesn't cache the result.
func (c *Client) GetNotaryServiceFeePerKey() (int64, error) {
notaryHash, err := c.GetNativeContractHash(nativenames.Notary)
if err != nil {
return 0, fmt.Errorf("failed to get native Notary hash: %w", err)
}
return c.invokeNativeGetMethod(notaryHash, "getNotaryServiceFeePerKey")
}

View file

@ -878,7 +878,11 @@ func (c *Client) CalculateNotaryFee(nKeys uint8) (int64, error) {
if err != nil {
return 0, fmt.Errorf("failed to get FeePerByte: %w", err)
}
return int64((nKeys+1))*transaction.NotaryServiceFeePerKey + // fee for NotaryAssisted attribute
feePerKey, err := c.GetNotaryServiceFeePerKey()
if err != nil {
return 0, fmt.Errorf("failed to get NotaryServiceFeePerKey: %w", err)
}
return int64((nKeys+1))*feePerKey + // fee for NotaryAssisted attribute
fee.Opcode(baseExecFee, // Notary node witness
opcode.PUSHDATA1, opcode.RET, // invocation script
opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // System.Contract.CallNative

View file

@ -983,3 +983,18 @@ func TestClient_NNS(t *testing.T) {
require.Error(t, err)
})
}
func TestClient_GetNotaryServiceFeePerKey(t *testing.T) {
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
defer chain.Close()
defer func() { _ = rpcSrv.Shutdown() }()
c, err := client.New(context.Background(), httpSrv.URL, client.Options{})
require.NoError(t, err)
require.NoError(t, c.Init())
var defaultNotaryServiceFeePerKey int64 = 1000_0000
actual, err := c.GetNotaryServiceFeePerKey()
require.NoError(t, err)
require.Equal(t, defaultNotaryServiceFeePerKey, actual)
}