mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-23 13:38:35 +00:00
8444f3d816
There was a deadlock while trying to finalize transaction during PostBlock: 1) (*Notary).PostBlock is called under the blockchain lock 2) (*Notary).onTransaction is called inside the PostBlock 3) (*Notary).onTransaction needs to RLock the blockchain to add completed transaction to the memory pool (and the blockchain is Lock'ed by this moment) The problem is fixed by using notifications subsistem, because it's not required to call (*Notary).PostBlock under the blockchain lock.
647 lines
25 KiB
Go
647 lines
25 KiB
Go
package core
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"path"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nspcc-dev/neo-go/internal/testchain"
|
|
"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/block"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
|
"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/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/smartcontract/trigger"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap/zaptest"
|
|
)
|
|
|
|
const notaryModulePath = "../services/notary/"
|
|
|
|
func getTestNotary(t *testing.T, bc *Blockchain, walletPath, pass string, onTx func(tx *transaction.Transaction) error) (*wallet.Account, *notary.Notary, *mempool.Pool) {
|
|
bc.config.P2PNotary = config.P2PNotary{
|
|
Enabled: true,
|
|
UnlockWallet: config.Wallet{
|
|
Path: path.Join(notaryModulePath, walletPath),
|
|
Password: pass,
|
|
},
|
|
}
|
|
mp := mempool.New(10, 1, true)
|
|
ntr, err := notary.NewNotary(bc, mp, zaptest.NewLogger(t), onTx)
|
|
require.NoError(t, err)
|
|
|
|
w, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, walletPath))
|
|
require.NoError(t, err)
|
|
require.NoError(t, w.Accounts[0].Decrypt(pass))
|
|
return w.Accounts[0], ntr, mp
|
|
}
|
|
|
|
func TestNotary(t *testing.T) {
|
|
bc := newTestChain(t)
|
|
defer bc.Close()
|
|
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
|
|
)
|
|
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
|
|
}
|
|
|
|
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(bc blockchainer.Blockchainer, pool *mempool.Pool, b *block.Block) {
|
|
ntr1.PostPersist()
|
|
})
|
|
|
|
notaryNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()}
|
|
bc.setNodesByRole(t, true, native.RoleP2PNotary, notaryNodes)
|
|
|
|
createFallbackTx := func(requester *wallet.Account, mainTx *transaction.Transaction, nvbIncrement ...uint32) *transaction.Transaction {
|
|
fallback := transaction.New(testchain.Network(), []byte{byte(opcode.RET)}, 2000_0000)
|
|
fallback.Nonce = nonce
|
|
nonce++
|
|
fallback.SystemFee = 1_0000_0000
|
|
fallback.ValidUntilBlock = bc.BlockHeight() + 50
|
|
fallback.Signers = []transaction.Signer{
|
|
{
|
|
Account: bc.GetNotaryContractScriptHash(),
|
|
Scopes: transaction.None,
|
|
},
|
|
{
|
|
Account: requester.PrivateKey().PublicKey().GetScriptHash(),
|
|
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), 64}, make([]byte, 64)...),
|
|
VerificationScript: []byte{},
|
|
},
|
|
}
|
|
requester.SignTx(fallback)
|
|
return fallback
|
|
}
|
|
|
|
createStandardRequest := func(requesters []*wallet.Account, NVBincrements ...uint32) []*payload.P2PNotaryRequest {
|
|
mainTx := *transaction.New(testchain.Network(), []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)
|
|
for i := range requesters {
|
|
signers[i] = transaction.Signer{
|
|
Account: requesters[i].PrivateKey().PublicKey().GetScriptHash(),
|
|
Scopes: transaction.None,
|
|
}
|
|
}
|
|
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: uint8(len(requesters))},
|
|
},
|
|
}
|
|
payloads := make([]*payload.P2PNotaryRequest, len(requesters))
|
|
for i := range payloads {
|
|
cp := mainTx
|
|
main := &cp
|
|
scripts := make([]transaction.Witness, len(requesters)+1)
|
|
for j := range requesters {
|
|
scripts[j].VerificationScript = requesters[j].PrivateKey().PublicKey().GetVerificationScript()
|
|
}
|
|
scripts[i].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, requesters[i].PrivateKey().Sign(main.GetSignedPart())...)
|
|
main.Scripts = scripts
|
|
var fallback *transaction.Transaction
|
|
if len(NVBincrements) == len(requesters) {
|
|
fallback = createFallbackTx(requesters[i], main, NVBincrements[i])
|
|
} else {
|
|
fallback = createFallbackTx(requesters[i], main)
|
|
}
|
|
payloads[i] = &payload.P2PNotaryRequest{
|
|
MainTransaction: main,
|
|
FallbackTransaction: fallback,
|
|
Network: netmode.UnitTestNet,
|
|
}
|
|
}
|
|
return payloads
|
|
}
|
|
createMultisigRequest := func(m int, requesters []*wallet.Account) []*payload.P2PNotaryRequest {
|
|
mainTx := *transaction.New(testchain.Network(), []byte{byte(opcode.RET)}, 11000000)
|
|
mainTx.Nonce = nonce
|
|
nonce++
|
|
mainTx.SystemFee = 100000000
|
|
mainTx.ValidUntilBlock = bc.BlockHeight() + 2*nvbDiffFallback
|
|
pubs := make(keys.PublicKeys, len(requesters))
|
|
for i, r := range requesters {
|
|
pubs[i] = r.PrivateKey().PublicKey()
|
|
}
|
|
script, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
|
|
require.NoError(t, err)
|
|
mainTx.Signers = []transaction.Signer{
|
|
{
|
|
Account: hash.Hash160(script),
|
|
Scopes: transaction.None,
|
|
},
|
|
{
|
|
Account: bc.GetNotaryContractScriptHash(),
|
|
Scopes: transaction.None,
|
|
},
|
|
}
|
|
mainTx.Attributes = []transaction.Attribute{
|
|
{
|
|
Type: transaction.NotaryAssistedT,
|
|
Value: &transaction.NotaryAssisted{NKeys: uint8(len(requesters))},
|
|
},
|
|
}
|
|
|
|
payloads := make([]*payload.P2PNotaryRequest, len(requesters))
|
|
// 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 := range payloads {
|
|
cp := mainTx
|
|
main := &cp
|
|
main.Scripts = []transaction.Witness{
|
|
{
|
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, requesters[i].PrivateKey().Sign(main.GetSignedPart())...),
|
|
VerificationScript: script,
|
|
},
|
|
{}, // empty Notary witness
|
|
}
|
|
fallback := createFallbackTx(requesters[i], main)
|
|
payloads[i] = &payload.P2PNotaryRequest{
|
|
MainTransaction: main,
|
|
FallbackTransaction: fallback,
|
|
Network: netmode.UnitTestNet,
|
|
}
|
|
}
|
|
return payloads
|
|
}
|
|
checkSigTx := func(t *testing.T, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) {
|
|
nKeys := len(requests)
|
|
completedTx := completedTxes[requests[0].MainTransaction.Hash()]
|
|
if sentCount == nKeys && shouldComplete {
|
|
require.NotNil(t, completedTx, errors.New("main transaction expected to be completed"))
|
|
require.Equal(t, nKeys+1, len(completedTx.Signers))
|
|
require.Equal(t, nKeys+1, len(completedTx.Scripts))
|
|
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx)
|
|
for i, req := range requests {
|
|
require.Equal(t, req.MainTransaction.Scripts[i], completedTx.Scripts[i])
|
|
_, err := bc.verifyHashAgainstScript(completedTx.Signers[i].Account, &completedTx.Scripts[i], interopCtx, -1)
|
|
require.NoError(t, err)
|
|
}
|
|
require.Equal(t, transaction.Witness{
|
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().Sign(requests[0].MainTransaction.GetSignedPart())...),
|
|
VerificationScript: []byte{},
|
|
}, completedTx.Scripts[nKeys])
|
|
} else {
|
|
require.Nil(t, completedTx, fmt.Errorf("main transaction shouldn't be completed: sent %d out of %d requests", sentCount, nKeys))
|
|
}
|
|
}
|
|
checkMultisigTx := func(t *testing.T, nSigs int, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) {
|
|
completedTx := completedTxes[requests[0].MainTransaction.Hash()]
|
|
if sentCount >= nSigs && shouldComplete {
|
|
require.NotNil(t, completedTx, errors.New("main transaction expected to be completed"))
|
|
require.Equal(t, 2, len(completedTx.Signers))
|
|
require.Equal(t, 2, len(completedTx.Scripts))
|
|
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx)
|
|
_, err := bc.verifyHashAgainstScript(completedTx.Signers[0].Account, &completedTx.Scripts[0], interopCtx, -1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, transaction.Witness{
|
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().Sign(requests[0].MainTransaction.GetSignedPart())...),
|
|
VerificationScript: []byte{},
|
|
}, completedTx.Scripts[1])
|
|
// check that only nSigs out of nKeys signatures are presented in the invocation script
|
|
for i, req := range requests[:nSigs] {
|
|
require.True(t, bytes.Contains(completedTx.Scripts[0].InvocationScript, req.MainTransaction.Scripts[0].InvocationScript), fmt.Errorf("signature from extra request #%d shouldn't be presented in the main tx", i))
|
|
}
|
|
// the rest (nKeys-nSigs) out of nKeys shouldn't be presented in the invocation script
|
|
for i, req := range requests[nSigs:] {
|
|
require.False(t, bytes.Contains(completedTx.Scripts[0].InvocationScript, req.MainTransaction.Scripts[0].InvocationScript), fmt.Errorf("signature from extra request #%d shouldn't be presented in the main tx", i))
|
|
}
|
|
} else {
|
|
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 {
|
|
completedTx := completedTxes[req.FallbackTransaction.Hash()]
|
|
if shouldComplete {
|
|
require.NotNil(t, completedTx, fmt.Errorf("fallback transaction for request #%d expected to be completed", i))
|
|
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), 64}, acc1.PrivateKey().Sign(req.FallbackTransaction.GetSignedPart())...),
|
|
VerificationScript: []byte{},
|
|
}, completedTx.Scripts[0])
|
|
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx)
|
|
_, err := bc.verifyHashAgainstScript(completedTx.Signers[1].Account, &completedTx.Scripts[1], interopCtx, -1)
|
|
require.NoError(t, err)
|
|
} else {
|
|
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 {
|
|
requesters := make([]*wallet.Account, nKeys)
|
|
for i := range requesters {
|
|
requesters[i], _ = wallet.NewAccount()
|
|
}
|
|
|
|
requests := createStandardRequest(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]])
|
|
checkSigTx(t, requests, i+1, shouldComplete)
|
|
completedCount := len(completedTxes)
|
|
|
|
// check that the same request won't be processed twice
|
|
ntr1.OnNewRequest(requests[sendOrder[i]])
|
|
checkSigTx(t, requests, i+1, shouldComplete)
|
|
require.Equal(t, completedCount, len(completedTxes))
|
|
}
|
|
return requests
|
|
}
|
|
checkCompleteMultisigRequest := func(t *testing.T, nSigs int, nKeys int, shouldComplete bool) []*payload.P2PNotaryRequest {
|
|
requesters := make([]*wallet.Account, nKeys)
|
|
for i := range requesters {
|
|
requesters[i], _ = wallet.NewAccount()
|
|
}
|
|
requests := createMultisigRequest(nSigs, 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]])
|
|
checkMultisigTx(t, nSigs, submittedRequests, i+1, shouldComplete)
|
|
|
|
// check that the same request won't be processed twice
|
|
ntr1.OnNewRequest(requests[sendOrder[i]])
|
|
checkMultisigTx(t, nSigs, 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]])
|
|
checkMultisigTx(t, nSigs, submittedRequests, i+1, shouldComplete)
|
|
require.Equal(t, completedCount, len(completedTxes))
|
|
}
|
|
|
|
return submittedRequests
|
|
}
|
|
|
|
// 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.PrivateKey().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)
|
|
|
|
// PostPersist: missing account
|
|
finalizeWithError = true
|
|
r = checkCompleteStandardRequest(t, 1, false)
|
|
checkFallbackTxs(t, r, false)
|
|
finalizeWithError = false
|
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, r, 1, false)
|
|
checkFallbackTxs(t, r, false)
|
|
// set account back for the next tests
|
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
|
|
|
|
// PostPersist: complete main transaction, signature request
|
|
finalizeWithError = true
|
|
requests := checkCompleteStandardRequest(t, 3, false)
|
|
// check PostPersist with finalisation error
|
|
finalizeWithError = true
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
// check PostPersist without finalisation error
|
|
finalizeWithError = false
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), true)
|
|
|
|
// PostPersist: complete main transaction, multisignature account
|
|
finalizeWithError = true
|
|
requests = checkCompleteMultisigRequest(t, 3, 4, false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// check PostPersist with finalisation error
|
|
finalizeWithError = true
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkMultisigTx(t, 3, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// check PostPersist without finalisation error
|
|
finalizeWithError = false
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkMultisigTx(t, 3, requests, len(requests), true)
|
|
checkFallbackTxs(t, requests, false)
|
|
|
|
// PostPersist: complete fallback, signature request
|
|
finalizeWithError = true
|
|
requests = checkCompleteStandardRequest(t, 3, false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// make fallbacks valid
|
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
|
require.NoError(t, err)
|
|
// check PostPersist for valid fallbacks with finalisation error
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// check PostPersist for valid fallbacks without finalisation error
|
|
finalizeWithError = false
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests, true)
|
|
|
|
// PostPersist: complete fallback, multisignature request
|
|
nSigs, nKeys := 3, 5
|
|
// check OnNewRequest with finalization error
|
|
finalizeWithError = true
|
|
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// make fallbacks valid
|
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
|
require.NoError(t, err)
|
|
// check PostPersist for valid fallbacks with finalisation error
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// check PostPersist for valid fallbacks without finalisation error
|
|
finalizeWithError = false
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkMultisigTx(t, nSigs, 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
|
|
finalizeWithError = true
|
|
requests = checkCompleteStandardRequest(t, 5, false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// make fallbacks valid
|
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
|
require.NoError(t, err)
|
|
// some of fallbacks should fail finalisation
|
|
unluckies = []*payload.P2PNotaryRequest{requests[0], requests[4]}
|
|
lucky := requests[1:4]
|
|
choosy = true
|
|
// check PostPersist for lucky fallbacks
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
checkFallbackTxs(t, lucky, true)
|
|
checkFallbackTxs(t, unluckies, false)
|
|
// reset finalisation function for unlucky fallbacks to finalise without an error
|
|
choosy = false
|
|
finalizeWithError = false
|
|
// check PostPersist for unlucky fallbacks
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
checkFallbackTxs(t, lucky, true)
|
|
checkFallbackTxs(t, unluckies, true)
|
|
|
|
// PostPersist: different NVBs
|
|
// check OnNewRequest with finalization error and different NVBs
|
|
finalizeWithError = true
|
|
requests = checkCompleteStandardRequest(t, 5, false, 1, 2, 3, 4, 5)
|
|
checkFallbackTxs(t, requests, false)
|
|
// generate blocks to reach the most earlier fallback's NVB
|
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
|
require.NoError(t, err)
|
|
// check PostPersist for valid fallbacks without finalisation error
|
|
finalizeWithError = false
|
|
for i := range requests {
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests[:i+1], true)
|
|
checkFallbackTxs(t, requests[i+1:], false)
|
|
}
|
|
|
|
// OnRequestRemoval: missing account
|
|
// check OnNewRequest with finalization error
|
|
finalizeWithError = true
|
|
requests = checkCompleteStandardRequest(t, 4, false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// make fallbacks valid and remove one fallback
|
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
|
require.NoError(t, err)
|
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
|
|
ntr1.OnRequestRemoval(requests[3])
|
|
// non of the fallbacks should be completed
|
|
finalizeWithError = false
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// set account back for the next tests
|
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
|
|
|
|
// OnRequestRemoval: signature request, remove one fallback
|
|
// check OnNewRequest with finalization error
|
|
finalizeWithError = true
|
|
requests = checkCompleteStandardRequest(t, 4, false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// make fallbacks valid and remove one fallback
|
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
|
require.NoError(t, err)
|
|
unlucky := requests[3]
|
|
ntr1.OnRequestRemoval(unlucky)
|
|
// rest of the fallbacks should be completed
|
|
finalizeWithError = false
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests[:3], true)
|
|
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
|
|
|
|
// OnRequestRemoval: signature request, remove all fallbacks
|
|
finalizeWithError = true
|
|
requests = checkCompleteStandardRequest(t, 4, false)
|
|
// remove all fallbacks
|
|
_, err = bc.genBlocks(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
|
|
finalizeWithError = false
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests, false)
|
|
|
|
// OnRequestRemoval: signature request, remove unexisting fallback
|
|
ntr1.OnRequestRemoval(requests[0])
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkSigTx(t, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests, false)
|
|
|
|
// OnRequestRemoval: multisignature request, remove one fallback
|
|
nSigs, nKeys = 3, 5
|
|
// check OnNewRequest with finalization error
|
|
finalizeWithError = true
|
|
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
|
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests, false)
|
|
// make fallbacks valid and remove the last fallback
|
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
|
require.NoError(t, err)
|
|
unlucky = requests[nSigs-1]
|
|
ntr1.OnRequestRemoval(unlucky)
|
|
// then (m-1) out of n fallbacks should be completed
|
|
finalizeWithError = false
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkMultisigTx(t, nSigs, 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
|
|
finalizeWithError = true
|
|
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
|
// make fallbacks valid and then remove all of them
|
|
_, err = bc.genBlocks(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
|
|
finalizeWithError = false
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkMultisigTx(t, nSigs, 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])
|
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
|
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
|
checkFallbackTxs(t, requests, false)
|
|
|
|
// Subscriptions test
|
|
mp1.RunSubscriptions()
|
|
go ntr1.Run()
|
|
defer func() {
|
|
ntr1.Stop()
|
|
mp1.StopSubscriptions()
|
|
}()
|
|
finalizeWithError = false
|
|
requester1, _ := wallet.NewAccount()
|
|
requester2, _ := wallet.NewAccount()
|
|
amount := int64(100_0000_0000)
|
|
feer := NewNotaryFeerStub(bc)
|
|
transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester1.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50))
|
|
checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(amount))
|
|
transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester2.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50))
|
|
checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(2*amount))
|
|
// create request for 2 standard signatures => main tx should be completed after the second request is added to the pool
|
|
requests = createStandardRequest([]*wallet.Account{requester1, requester2})
|
|
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
|
|
}, time.Second, time.Millisecond)
|
|
checkFallbackTxs(t, requests, false)
|
|
}
|