forked from TrueCloudLab/neoneo-go
7589733017
And include some node-specific configurations there with backwards compatibility. Note that in the future we'll remove Ledger's fields from the ProtocolConfiguration and it'll be possible to access them in Blockchain directly (not via .Ledger). The other option tried was using two configuration types separately, but that incurs more changes to the codebase, single structure that behaves almost like the old one is better for backwards compatibility. Fixes #2676.
250 lines
11 KiB
Go
250 lines
11 KiB
Go
package native_test
|
|
|
|
import (
|
|
"math"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newNotaryClient(t *testing.T) *neotest.ContractInvoker {
|
|
bc, acc := chain.NewSingleWithCustomConfig(t, func(cfg *config.Blockchain) {
|
|
cfg.P2PSigExtensions = true
|
|
})
|
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
|
|
|
return e.CommitteeInvoker(e.NativeHash(t, nativenames.Notary))
|
|
}
|
|
|
|
func TestNotary_MaxNotValidBeforeDelta(t *testing.T) {
|
|
c := newNotaryClient(t)
|
|
testGetSet(t, c, "MaxNotValidBeforeDelta", 140, int64(c.Chain.GetConfig().ValidatorsCount), int64(c.Chain.GetConfig().MaxValidUntilBlockIncrement/2))
|
|
}
|
|
|
|
func TestNotary_MaxNotValidBeforeDeltaCache(t *testing.T) {
|
|
c := newNotaryClient(t)
|
|
testGetSetCache(t, c, "MaxNotValidBeforeDelta", 140)
|
|
}
|
|
|
|
func TestNotary_NotaryServiceFeePerKey(t *testing.T) {
|
|
c := newNotaryClient(t)
|
|
testGetSet(t, c, "NotaryServiceFeePerKey", 1000_0000, 0, 0)
|
|
}
|
|
|
|
func TestNotary_NotaryServiceFeePerKeyCache(t *testing.T) {
|
|
c := newNotaryClient(t)
|
|
testGetSetCache(t, c, "NotaryServiceFeePerKey", 1000_0000)
|
|
}
|
|
|
|
func TestNotary_Pipeline(t *testing.T) {
|
|
notaryCommitteeInvoker := newNotaryClient(t)
|
|
e := notaryCommitteeInvoker.Executor
|
|
neoCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Neo))
|
|
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 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
|
|
checkBalanceOf(t, notaryHash, 0)
|
|
|
|
// `balanceOf`: check multisig account has no GAS on deposit
|
|
notaryCommitteeInvoker.Invoke(t, 0, "balanceOf", multisigHash)
|
|
|
|
// `expirationOf`: should fail to get deposit which does not exist
|
|
notaryCommitteeInvoker.Invoke(t, 0, "expirationOf", multisigHash)
|
|
|
|
// `lockDepositUntil`: should fail because there's no deposit
|
|
notaryCommitteeInvoker.Invoke(t, false, "lockDepositUntil", multisigHash, int64(depositLock+1))
|
|
|
|
// `onPayment`: bad token
|
|
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*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*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*feePerKey, []interface{}{nil, int64(0)})
|
|
|
|
// `onPayment`: good
|
|
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*feePerKey, "balanceOf", multisigHash)
|
|
|
|
// `onPayment`: good second deposit and explicit `to` paramenter
|
|
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*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*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*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*feePerKey)
|
|
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
|
|
|
|
// `lockDepositUntil`: bad witness
|
|
notaryCommitteeInvoker.Invoke(t, false, "lockDepositUntil", util.Uint160{1, 2, 3}, int64(depositLock+3))
|
|
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
|
|
|
|
// `lockDepositUntil`: bad `till` (less then the previous one)
|
|
notaryCommitteeInvoker.Invoke(t, false, "lockDepositUntil", multisigHash, int64(depositLock+1))
|
|
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
|
|
|
|
// `lockDepositUntil`: bad `till` (less then the chain's height)
|
|
notaryCommitteeInvoker.Invoke(t, false, "lockDepositUntil", multisigHash, int64(1))
|
|
notaryCommitteeInvoker.Invoke(t, depositLock+2, "expirationOf", multisigHash)
|
|
|
|
// `lockDepositUntil`: good `till`
|
|
notaryCommitteeInvoker.Invoke(t, true, "lockDepositUntil", multisigHash, int64(depositLock+3))
|
|
notaryCommitteeInvoker.Invoke(t, depositLock+3, "expirationOf", multisigHash)
|
|
|
|
// Create new account for the next test
|
|
notaryAccInvoker := notaryCommitteeInvoker.WithSigners(e.NewAccount(t))
|
|
accHash := notaryAccInvoker.Signers[0].ScriptHash()
|
|
|
|
// `withdraw`: bad witness
|
|
notaryAccInvoker.Invoke(t, false, "withdraw", multisigHash, accHash)
|
|
notaryCommitteeInvoker.Invoke(t, 3*feePerKey, "balanceOf", multisigHash)
|
|
|
|
// `withdraw`: locked deposit
|
|
notaryCommitteeInvoker.Invoke(t, false, "withdraw", multisigHash, multisigHash)
|
|
notaryCommitteeInvoker.Invoke(t, 3*feePerKey, "balanceOf", multisigHash)
|
|
|
|
// `withdraw`: unlock deposit and transfer GAS back to owner
|
|
e.GenerateNewBlocks(t, depositLock)
|
|
notaryCommitteeInvoker.Invoke(t, true, "withdraw", multisigHash, accHash)
|
|
notaryCommitteeInvoker.Invoke(t, 0, "balanceOf", multisigHash)
|
|
checkBalanceOf(t, notaryHash, 0)
|
|
|
|
// `withdraw`: the second time it should fail, because there's no deposit left
|
|
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*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, feePerKey, []interface{}{accHash, int64(math.MaxUint32 - 1)})
|
|
checkBalanceOf(t, notaryHash, 3*feePerKey)
|
|
notaryCommitteeInvoker.Invoke(t, 5760+e.Chain.BlockHeight()-3, "expirationOf", accHash)
|
|
}
|
|
|
|
func TestNotary_NotaryNodesReward(t *testing.T) {
|
|
checkReward := func(nKeys int, nNotaryNodes int, spendFullDeposit bool) {
|
|
notaryCommitteeInvoker := newNotaryClient(t)
|
|
e := notaryCommitteeInvoker.Executor
|
|
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
|
|
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
|
|
|
|
// set Notary nodes and check their balance
|
|
notaryNodes := make([]*keys.PrivateKey, nNotaryNodes)
|
|
notaryNodesPublicKeys := make([]interface{}, nNotaryNodes)
|
|
for i := range notaryNodes {
|
|
notaryNodes[i], err = keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
notaryNodesPublicKeys[i] = notaryNodes[i].PublicKey().Bytes()
|
|
}
|
|
|
|
designationCommitteeInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", int(noderoles.P2PNotary), notaryNodesPublicKeys)
|
|
for _, notaryNode := range notaryNodes {
|
|
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(0))
|
|
}
|
|
|
|
// deposit GAS for `signer` with lock until the next block
|
|
depositAmount := 100_0000 + (2+int64(nKeys))*feePerKey // sysfee + netfee of the next transaction
|
|
if !spendFullDeposit {
|
|
depositAmount += 1_0000
|
|
}
|
|
gasCommitteeInvoker.Invoke(t, true, "transfer", multisigHash, notaryHash, depositAmount, []interface{}{multisigHash, e.Chain.BlockHeight() + 1})
|
|
|
|
// send transaction with Notary contract as a sender
|
|
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1_000_000)
|
|
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)) * feePerKey
|
|
tx.Signers = []transaction.Signer{
|
|
{
|
|
Account: notaryHash,
|
|
Scopes: transaction.None,
|
|
},
|
|
{
|
|
Account: multisigHash,
|
|
Scopes: transaction.None,
|
|
},
|
|
}
|
|
tx.Scripts = []transaction.Witness{
|
|
{
|
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, notaryNodes[0].SignHashable(uint32(e.Chain.GetConfig().Magic), tx)...),
|
|
},
|
|
{
|
|
InvocationScript: e.Committee.SignHashable(uint32(e.Chain.GetConfig().Magic), tx),
|
|
VerificationScript: e.Committee.Script(),
|
|
},
|
|
}
|
|
e.AddNewBlock(t, tx)
|
|
|
|
e.CheckGASBalance(t, notaryHash, big.NewInt(int64(depositAmount-tx.SystemFee-tx.NetworkFee)))
|
|
for _, notaryNode := range notaryNodes {
|
|
e.CheckGASBalance(t, notaryNode.GetScriptHash(), big.NewInt(feePerKey*int64((nKeys+1))/int64(nNotaryNodes)))
|
|
}
|
|
}
|
|
|
|
for _, spendDeposit := range []bool{true, false} {
|
|
checkReward(0, 1, spendDeposit)
|
|
checkReward(0, 2, spendDeposit)
|
|
checkReward(1, 1, spendDeposit)
|
|
checkReward(1, 2, spendDeposit)
|
|
checkReward(1, 3, spendDeposit)
|
|
checkReward(5, 1, spendDeposit)
|
|
checkReward(5, 2, spendDeposit)
|
|
checkReward(5, 6, spendDeposit)
|
|
checkReward(5, 7, spendDeposit)
|
|
}
|
|
}
|