neoneo-go/pkg/core/native/native_test/notary_test.go
Roman Khimov 7589733017 config: add a special Blockchain type to configure Blockchain
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.
2022-12-07 17:35:53 +03:00

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