neoneo-go/pkg/services/notary/core_test.go
Anna Shaleva f95004ad83 services: fix race in TestNotary
Close #3366. Roll back the test structure to the one that it has before
d2a7162217. Add one extra block to ensure
that main transaction won't be finilized. Add NVB increment slippage to
avoid race between the first fallback acceptance and block acceptance.
Add NVB increment slippage to the rest of fallbacks for test structure
unification.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2024-04-02 15:15:02 +03:00

770 lines
27 KiB
Go

package notary_test
import (
"errors"
"fmt"
"math/big"
"math/rand"
"sync"
"testing"
"time"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
"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/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/network"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/services/notary"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"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/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
func getTestNotary(t *testing.T, bc *core.Blockchain, walletPath, pass string, onTx func(tx *transaction.Transaction) error) (*wallet.Account, *notary.Notary, *mempool.Pool) {
mainCfg := config.P2PNotary{
Enabled: true,
UnlockWallet: config.Wallet{
Path: walletPath,
Password: pass,
},
}
cfg := notary.Config{
MainCfg: mainCfg,
Chain: bc,
Log: zaptest.NewLogger(t),
}
mp := mempool.New(10, 1, true, nil)
ntr, err := notary.NewNotary(cfg, netmode.UnitTestNet, mp, onTx)
require.NoError(t, err)
w, err := wallet.NewWalletFromFile(walletPath)
require.NoError(t, err)
require.NoError(t, w.Accounts[0].Decrypt(pass, w.Scrypt))
return w.Accounts[0], ntr, mp
}
// dupNotaryRequest duplicates notary request by serializing/deserializing it. Use
// it to avoid data races when reusing the same payload. Normal OnNewRequest handler
// never receives the same (as in the same pointer) payload multiple times, even if
// the contents is the same it would be a separate buffer.
func dupNotaryRequest(t *testing.T, p *payload.P2PNotaryRequest) *payload.P2PNotaryRequest {
b, err := p.Bytes()
require.NoError(t, err)
r, err := payload.NewP2PNotaryRequestFromBytes(b)
require.NoError(t, err)
return r
}
func TestNotary(t *testing.T) {
bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
c.P2PSigExtensions = true
})
e := neotest.NewExecutor(t, bc, validators, committee)
notaryHash := e.NativeHash(t, nativenames.Notary)
designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validators, committee)
gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
var (
nonce uint32
nvbDiffFallback uint32 = 20
)
mtx := sync.RWMutex{}
completedTxes := make(map[util.Uint256]*transaction.Transaction)
var unluckies []*payload.P2PNotaryRequest
var (
finalizeWithError bool
choosy bool
)
setFinalizeWithError := func(v bool) {
mtx.Lock()
finalizeWithError = v
mtx.Unlock()
}
setChoosy := func(v bool) {
mtx.Lock()
choosy = v
mtx.Unlock()
}
onTransaction := func(tx *transaction.Transaction) error {
mtx.Lock()
defer mtx.Unlock()
if !choosy {
if completedTxes[tx.Hash()] != nil {
panic("transaction was completed twice")
}
if finalizeWithError {
return errors.New("error while finalizing transaction")
}
completedTxes[tx.Hash()] = tx
return nil
}
for _, unl := range unluckies {
if tx.Hash().Equals(unl.FallbackTransaction.Hash()) {
return errors.New("error while finalizing transaction")
}
}
completedTxes[tx.Hash()] = tx
return nil
}
getCompletedTx := func(t *testing.T, waitForNonNil bool, h util.Uint256) *transaction.Transaction {
if !waitForNonNil {
mtx.RLock()
defer mtx.RUnlock()
return completedTxes[h]
}
var completedTx *transaction.Transaction
require.Eventually(t, func() bool {
mtx.RLock()
defer mtx.RUnlock()
completedTx = completedTxes[h]
return completedTx != nil
}, time.Second*3, time.Millisecond*50, errors.New("transaction expected to be completed"))
return completedTx
}
acc1, ntr1, mp1 := getTestNotary(t, bc, "./testdata/notary1.json", "one", onTransaction)
acc2, _, _ := getTestNotary(t, bc, "./testdata/notary2.json", "two", onTransaction)
randomAcc, err := keys.NewPrivateKey()
require.NoError(t, err)
bc.SetNotary(ntr1)
bc.RegisterPostBlock(func(f func(*transaction.Transaction, *mempool.Pool, bool) bool, pool *mempool.Pool, b *block.Block) {
ntr1.PostPersist()
})
mp1.RunSubscriptions()
ntr1.Start()
t.Cleanup(func() {
ntr1.Shutdown()
mp1.StopSubscriptions()
})
notaryNodes := []any{acc1.PublicKey().Bytes(), acc2.PrivateKey().PublicKey().Bytes()}
designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
int64(noderoles.P2PNotary), notaryNodes)
type requester struct {
accounts []*wallet.Account
m int
typ notary.RequestType
}
createFallbackTx := func(requester *wallet.Account, mainTx *transaction.Transaction, nvbIncrement ...uint32) *transaction.Transaction {
fallback := transaction.New([]byte{byte(opcode.RET)}, 2000_0000)
fallback.Nonce = nonce
nonce++
fallback.SystemFee = 1_0000_0000
fallback.ValidUntilBlock = bc.BlockHeight() + 2*nvbDiffFallback
fallback.Signers = []transaction.Signer{
{
Account: bc.GetNotaryContractScriptHash(),
Scopes: transaction.None,
},
{
Account: requester.ScriptHash(),
Scopes: transaction.None,
},
}
nvb := bc.BlockHeight() + nvbDiffFallback
if len(nvbIncrement) != 0 {
nvb += nvbIncrement[0]
}
fallback.Attributes = []transaction.Attribute{
{
Type: transaction.NotaryAssistedT,
Value: &transaction.NotaryAssisted{NKeys: 0},
},
{
Type: transaction.NotValidBeforeT,
Value: &transaction.NotValidBefore{Height: nvb},
},
{
Type: transaction.ConflictsT,
Value: &transaction.Conflicts{Hash: mainTx.Hash()},
},
}
fallback.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, make([]byte, keys.SignatureLen)...),
VerificationScript: []byte{},
},
}
err = requester.SignTx(netmode.UnitTestNet, fallback)
require.NoError(t, err)
return fallback
}
createMixedRequest := func(requesters []requester, NVBincrements ...uint32) []*payload.P2PNotaryRequest {
mainTx := *transaction.New([]byte{byte(opcode.RET)}, 11000000)
mainTx.Nonce = nonce
nonce++
mainTx.SystemFee = 100000000
mainTx.ValidUntilBlock = bc.BlockHeight() + 2*nvbDiffFallback
signers := make([]transaction.Signer, len(requesters)+1)
var (
nKeys uint8
verificationScripts [][]byte
)
for i := range requesters {
var script []byte
switch requesters[i].typ {
case notary.Signature:
script = requesters[i].accounts[0].PublicKey().GetVerificationScript()
nKeys++
case notary.MultiSignature:
pubs := make(keys.PublicKeys, len(requesters[i].accounts))
for j, r := range requesters[i].accounts {
pubs[j] = r.PublicKey()
}
script, err = smartcontract.CreateMultiSigRedeemScript(requesters[i].m, pubs)
require.NoError(t, err)
nKeys += uint8(len(requesters[i].accounts))
}
signers[i] = transaction.Signer{
Account: hash.Hash160(script),
Scopes: transaction.None,
}
verificationScripts = append(verificationScripts, script)
}
signers[len(signers)-1] = transaction.Signer{
Account: bc.GetNotaryContractScriptHash(),
Scopes: transaction.None,
}
mainTx.Signers = signers
mainTx.Attributes = []transaction.Attribute{
{
Type: transaction.NotaryAssistedT,
Value: &transaction.NotaryAssisted{NKeys: nKeys},
},
}
payloads := make([]*payload.P2PNotaryRequest, nKeys)
plIndex := 0
// we'll collect only m signatures out of n (so only m payloads are needed), but let's create payloads for all requesters (for the next tests)
for i, r := range requesters {
for _, acc := range r.accounts {
cp := mainTx
main := &cp
main.Scripts = make([]transaction.Witness, len(requesters))
for j := range main.Scripts {
main.Scripts[j].VerificationScript = verificationScripts[j]
if i == j {
main.Scripts[j].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, acc.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), main)...)
}
}
main.Scripts = append(main.Scripts, transaction.Witness{}) // empty Notary witness
_ = main.Size() // for size update test
var fallback *transaction.Transaction
if len(NVBincrements) == int(nKeys) {
fallback = createFallbackTx(acc, main, NVBincrements[plIndex])
} else {
fallback = createFallbackTx(acc, main)
}
_ = fallback.Size() // for size update test
payloads[plIndex] = &payload.P2PNotaryRequest{
MainTransaction: main,
FallbackTransaction: fallback,
}
plIndex++
}
}
return payloads
}
checkMainTx := func(t *testing.T, requesters []requester, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) {
nSigs := 0
for _, r := range requesters {
switch r.typ {
case notary.Signature:
nSigs++
case notary.MultiSignature:
nSigs += r.m
}
}
nSigners := len(requesters) + 1
if sentCount >= nSigs && shouldComplete {
completedTx := getCompletedTx(t, true, requests[0].MainTransaction.Hash())
require.Equal(t, nSigners, len(completedTx.Signers))
require.Equal(t, nSigners, len(completedTx.Scripts))
// check that tx size was updated
require.Equal(t, io.GetVarSize(completedTx), completedTx.Size())
for i := 0; i < len(completedTx.Scripts)-1; i++ {
_, err := bc.VerifyWitness(completedTx.Signers[i].Account, completedTx, &completedTx.Scripts[i], -1)
require.NoError(t, err)
}
require.Equal(t, transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, acc1.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), requests[0].MainTransaction)...),
VerificationScript: []byte{},
}, completedTx.Scripts[len(completedTx.Scripts)-1])
} else {
completedTx := getCompletedTx(t, false, requests[0].MainTransaction.Hash())
require.Nil(t, completedTx, fmt.Errorf("main transaction shouldn't be completed: sent %d out of %d requests", sentCount, nSigs))
}
}
checkFallbackTxs := func(t *testing.T, requests []*payload.P2PNotaryRequest, shouldComplete bool) {
for i, req := range requests {
if shouldComplete {
completedTx := getCompletedTx(t, true, req.FallbackTransaction.Hash())
require.Equal(t, 2, len(completedTx.Signers))
require.Equal(t, 2, len(completedTx.Scripts))
require.Equal(t, transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, acc1.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), req.FallbackTransaction)...),
VerificationScript: []byte{},
}, completedTx.Scripts[0])
// check that tx size was updated
require.Equal(t, io.GetVarSize(completedTx), completedTx.Size())
_, err := bc.VerifyWitness(completedTx.Signers[1].Account, completedTx, &completedTx.Scripts[1], -1)
require.NoError(t, err)
} else {
completedTx := getCompletedTx(t, false, req.FallbackTransaction.Hash())
require.Nil(t, completedTx, fmt.Errorf("fallback transaction for request #%d shouldn't be completed", i))
}
}
}
checkCompleteStandardRequest := func(t *testing.T, nKeys int, shouldComplete bool, nvbIncrements ...uint32) ([]*payload.P2PNotaryRequest, []requester) {
requesters := make([]requester, nKeys)
for i := range requesters {
acc, _ := wallet.NewAccount()
requesters[i] = requester{
accounts: []*wallet.Account{acc},
typ: notary.Signature,
}
}
requests := createMixedRequest(requesters, nvbIncrements...)
sendOrder := make([]int, nKeys)
for i := range sendOrder {
sendOrder[i] = i
}
rand.Shuffle(nKeys, func(i, j int) {
sendOrder[j], sendOrder[i] = sendOrder[i], sendOrder[j]
})
for i := range requests {
ntr1.OnNewRequest(requests[sendOrder[i]])
checkMainTx(t, requesters, requests, i+1, shouldComplete)
completedCount := len(completedTxes)
// check that the same request won't be processed twice
ntr1.OnNewRequest(dupNotaryRequest(t, requests[sendOrder[i]]))
checkMainTx(t, requesters, requests, i+1, shouldComplete)
require.Equal(t, completedCount, len(completedTxes))
}
return requests, requesters
}
checkCompleteMultisigRequest := func(t *testing.T, nSigs int, nKeys int, shouldComplete bool) ([]*payload.P2PNotaryRequest, []requester) {
accounts := make([]*wallet.Account, nKeys)
for i := range accounts {
accounts[i], _ = wallet.NewAccount()
}
requesters := []requester{
{
accounts: accounts,
m: nSigs,
typ: notary.MultiSignature,
},
}
requests := createMixedRequest(requesters)
sendOrder := make([]int, nKeys)
for i := range sendOrder {
sendOrder[i] = i
}
rand.Shuffle(nKeys, func(i, j int) {
sendOrder[j], sendOrder[i] = sendOrder[i], sendOrder[j]
})
var submittedRequests []*payload.P2PNotaryRequest
// sent only nSigs (m out of n) requests - it should be enough to complete min tx
for i := 0; i < nSigs; i++ {
submittedRequests = append(submittedRequests, requests[sendOrder[i]])
ntr1.OnNewRequest(requests[sendOrder[i]])
checkMainTx(t, requesters, submittedRequests, i+1, shouldComplete)
// check that the same request won't be processed twice
ntr1.OnNewRequest(dupNotaryRequest(t, requests[sendOrder[i]]))
checkMainTx(t, requesters, submittedRequests, i+1, shouldComplete)
}
// sent the rest (n-m) out of n requests: main tx is already collected, so only fallbacks should be applied
completedCount := len(completedTxes)
for i := nSigs; i < nKeys; i++ {
submittedRequests = append(submittedRequests, requests[sendOrder[i]])
ntr1.OnNewRequest(requests[sendOrder[i]])
checkMainTx(t, requesters, submittedRequests, i+1, shouldComplete)
require.Equal(t, completedCount, len(completedTxes))
}
return submittedRequests, requesters
}
checkCompleteMixedRequest := func(t *testing.T, nSigSigners int, shouldComplete bool) ([]*payload.P2PNotaryRequest, []requester) {
requesters := make([]requester, nSigSigners)
for i := range requesters {
acc, _ := wallet.NewAccount()
requesters[i] = requester{
accounts: []*wallet.Account{acc},
typ: notary.Signature,
}
}
multisigAccounts := make([]*wallet.Account, 3)
for i := range multisigAccounts {
multisigAccounts[i], _ = wallet.NewAccount()
}
requesters = append(requesters, requester{
accounts: multisigAccounts,
m: 2,
typ: notary.MultiSignature,
})
requests := createMixedRequest(requesters)
for i := range requests {
ntr1.OnNewRequest(requests[i])
checkMainTx(t, requesters, requests, i+1, shouldComplete)
completedCount := len(completedTxes)
// check that the same request won't be processed twice
ntr1.OnNewRequest(dupNotaryRequest(t, requests[i]))
checkMainTx(t, requesters, requests, i+1, shouldComplete)
require.Equal(t, completedCount, len(completedTxes))
}
return requests, requesters
}
// OnNewRequest: missing account
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
r, _ := checkCompleteStandardRequest(t, 1, false)
checkFallbackTxs(t, r, false)
// set account back for the next tests
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PublicKey()})
// OnNewRequest: signature request
for _, i := range []int{1, 2, 3, 10} {
r, _ := checkCompleteStandardRequest(t, i, true)
checkFallbackTxs(t, r, false)
}
// OnNewRequest: multisignature request
r, _ = checkCompleteMultisigRequest(t, 1, 1, true)
checkFallbackTxs(t, r, false)
r, _ = checkCompleteMultisigRequest(t, 1, 2, true)
checkFallbackTxs(t, r, false)
r, _ = checkCompleteMultisigRequest(t, 1, 3, true)
checkFallbackTxs(t, r, false)
r, _ = checkCompleteMultisigRequest(t, 3, 3, true)
checkFallbackTxs(t, r, false)
r, _ = checkCompleteMultisigRequest(t, 3, 4, true)
checkFallbackTxs(t, r, false)
r, _ = checkCompleteMultisigRequest(t, 3, 10, true)
checkFallbackTxs(t, r, false)
// OnNewRequest: mixed request
r, _ = checkCompleteMixedRequest(t, 1, true)
checkFallbackTxs(t, r, false)
r, _ = checkCompleteMixedRequest(t, 2, true)
checkFallbackTxs(t, r, false)
r, _ = checkCompleteMixedRequest(t, 3, true)
checkFallbackTxs(t, r, false)
// PostPersist: missing account
setFinalizeWithError(true)
r, requesters := checkCompleteStandardRequest(t, 1, false)
checkFallbackTxs(t, r, false)
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, r, 1, false)
checkFallbackTxs(t, r, false)
// set account back for the next tests
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PublicKey()})
// PostPersist: complete main transaction, signature request
setFinalizeWithError(true)
requests, requesters := checkCompleteStandardRequest(t, 3, false)
// check PostPersist with finalisation error
setFinalizeWithError(true)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
// check PostPersist without finalisation error
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), true)
// PostPersist: complete main transaction, multisignature account
setFinalizeWithError(true)
requests, requesters = checkCompleteMultisigRequest(t, 3, 4, false)
checkFallbackTxs(t, requests, false)
// check PostPersist with finalisation error
setFinalizeWithError(true)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false)
// check PostPersist without finalisation error
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), true)
checkFallbackTxs(t, requests, false)
// PostPersist: complete fallback, signature request
setFinalizeWithError(true)
requests, requesters = checkCompleteStandardRequest(t, 3, false)
checkFallbackTxs(t, requests, false)
// make fallbacks valid
e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err)
// check PostPersist for valid fallbacks with finalisation error
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false)
// check PostPersist for valid fallbacks without finalisation error
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, true)
// PostPersist: complete fallback, multisignature request
nSigs, nKeys := 3, 5
// check OnNewRequest with finalization error
setFinalizeWithError(true)
requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
checkFallbackTxs(t, requests, false)
// make fallbacks valid
e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err)
// check PostPersist for valid fallbacks with finalisation error
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false)
// check PostPersist for valid fallbacks without finalisation error
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:nSigs], true)
// the rest of fallbacks should also be applied even if the main tx was already constructed by the moment they were sent
checkFallbackTxs(t, requests[nSigs:], true)
// PostPersist: partial fallbacks completion due to finalisation errors
setFinalizeWithError(true)
requests, requesters = checkCompleteStandardRequest(t, 5, false)
checkFallbackTxs(t, requests, false)
// make fallbacks valid
e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err)
// some of fallbacks should fail finalisation
unluckies = []*payload.P2PNotaryRequest{requests[0], requests[4]}
lucky := requests[1:4]
setChoosy(true)
// check PostPersist for lucky fallbacks
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, lucky, true)
checkFallbackTxs(t, unluckies, false)
// reset finalisation function for unlucky fallbacks to finalise without an error
setChoosy(false)
setFinalizeWithError(false)
// check PostPersist for unlucky fallbacks
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, lucky, true)
checkFallbackTxs(t, unluckies, true)
// PostPersist: different NVBs
// check OnNewRequest with finalization error and different NVBs
setFinalizeWithError(true)
// Introduce some slippage between first and second fallback NVBs in order to avoid possible race caused by early
// first fallback transaction acceptance. The rest of fallbacks follow X+4 NVB pattern for testing code shortness.
requests, requesters = checkCompleteStandardRequest(t, 5, false, 1, 7, 11, 15, 19)
checkFallbackTxs(t, requests, false)
// generate blocks to reach the most earlier fallback's NVB
e.GenerateNewBlocks(t, int(nvbDiffFallback+1))
require.NoError(t, err)
// check PostPersist for valid fallbacks without finalisation error
// Add a block before allowing tx to be finalized without error to exclude race condition when
// main transaction is finalized between `finalizeWithError` disabling and new block addition.
e.AddNewBlock(t)
setFinalizeWithError(false)
for i := range requests {
e.AddNewBlock(t)
e.AddNewBlock(t)
e.AddNewBlock(t)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:i+1], true)
checkFallbackTxs(t, requests[i+1:], false)
}
// OnRequestRemoval: missing account
// check OnNewRequest with finalization error
setFinalizeWithError(true)
requests, requesters = checkCompleteStandardRequest(t, 4, false)
checkFallbackTxs(t, requests, false)
// make fallbacks valid and remove one fallback
e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err)
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
ntr1.OnRequestRemoval(requests[3])
// non of the fallbacks should be completed
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false)
// set account back for the next tests
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PublicKey()})
// OnRequestRemoval: signature request, remove one fallback
// check OnNewRequest with finalization error
setFinalizeWithError(true)
requests, requesters = checkCompleteStandardRequest(t, 4, false)
checkFallbackTxs(t, requests, false)
// make fallbacks valid and remove one fallback
e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err)
unlucky := requests[3]
ntr1.OnRequestRemoval(unlucky)
// rest of the fallbacks should be completed
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:3], true)
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
// OnRequestRemoval: signature request, remove all fallbacks
setFinalizeWithError(true)
requests, requesters = checkCompleteStandardRequest(t, 4, false)
// remove all fallbacks
e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err)
for i := range requests {
ntr1.OnRequestRemoval(requests[i])
}
// then the whole request should be removed, i.e. there are no completed transactions
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false)
// OnRequestRemoval: signature request, remove unexisting fallback
ntr1.OnRequestRemoval(requests[0])
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false)
// OnRequestRemoval: multisignature request, remove one fallback
nSigs, nKeys = 3, 5
// check OnNewRequest with finalization error
setFinalizeWithError(true)
requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false)
// make fallbacks valid and remove the last fallback
e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err)
unlucky = requests[nSigs-1]
ntr1.OnRequestRemoval(unlucky)
// then (m-1) out of n fallbacks should be completed
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:nSigs-1], true)
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
// the rest (n-(m-1)) out of n fallbacks should also be completed even if main tx has been collected by the moment they were sent
checkFallbackTxs(t, requests[nSigs:], true)
// OnRequestRemoval: multisignature request, remove all fallbacks
setFinalizeWithError(true)
requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
// make fallbacks valid and then remove all of them
e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err)
for i := range requests {
ntr1.OnRequestRemoval(requests[i])
}
// then the whole request should be removed, i.e. there are no completed transactions
setFinalizeWithError(false)
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false)
// // OnRequestRemoval: multisignature request, remove unexisting fallbac, i.e. there still shouldn't be any completed transactions after this
ntr1.OnRequestRemoval(requests[0])
e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false)
// Subscriptions test
setFinalizeWithError(false)
requester1, _ := wallet.NewAccount()
requester2, _ := wallet.NewAccount()
amount := int64(100_0000_0000)
gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []any{requester1.ScriptHash(), int64(bc.BlockHeight() + 50)})
e.CheckGASBalance(t, notaryHash, big.NewInt(amount))
gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []any{requester2.ScriptHash(), int64(bc.BlockHeight() + 50)})
e.CheckGASBalance(t, notaryHash, big.NewInt(2*amount))
// create request for 2 standard signatures => main tx should be completed after the second request is added to the pool
requests = createMixedRequest([]requester{
{
accounts: []*wallet.Account{requester1},
typ: notary.Signature,
},
{
accounts: []*wallet.Account{requester2},
typ: notary.Signature,
},
})
feer := network.NewNotaryFeer(bc)
require.NoError(t, mp1.Add(requests[0].FallbackTransaction, feer, requests[0]))
require.NoError(t, mp1.Add(requests[1].FallbackTransaction, feer, requests[1]))
require.Eventually(t, func() bool {
mtx.RLock()
defer mtx.RUnlock()
return completedTxes[requests[0].MainTransaction.Hash()] != nil
}, 3*time.Second, 100*time.Millisecond)
checkFallbackTxs(t, requests, false)
}
func TestNotary_GenesisRoles(t *testing.T) {
const (
notaryPath = "./testdata/notary1.json"
notaryPass = "one"
)
w, err := wallet.NewWalletFromFile(notaryPath)
require.NoError(t, err)
require.NoError(t, w.Accounts[0].Decrypt(notaryPass, w.Scrypt))
acc := w.Accounts[0]
bc, _, _ := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
c.P2PSigExtensions = true
c.Genesis.Roles = map[noderoles.Role]keys.PublicKeys{
noderoles.P2PNotary: {acc.PublicKey()},
}
})
_, ntr, _ := getTestNotary(t, bc, "./testdata/notary1.json", "one", func(tx *transaction.Transaction) error { return nil })
require.False(t, ntr.IsAuthorized())
bc.SetNotary(ntr)
require.True(t, ntr.IsAuthorized())
}