diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 54dd9120f..3fa6bb535 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -15,6 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/config" "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/chaindump" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/native" @@ -391,6 +392,9 @@ func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs .. w := io.NewBufBinWriter() emit.AppCall(w.BinWriter, sc, "transfer", callflag.All, from, to, amount, additionalArgs) emit.Opcodes(w.BinWriter, opcode.ASSERT) + if w.Err != nil { + panic(fmt.Errorf("failed to create nep17 transfer transaction: %w", w.Err)) + } script := w.Bytes() return transaction.New(testchain.Network(), script, 11000000) @@ -564,3 +568,19 @@ func checkBalanceOf(t *testing.T, chain *Blockchain, addr util.Uint160, expected balance := chain.GetNEP17Balances(addr).Trackers[chain.contracts.GAS.ContractID] require.Equal(t, int64(expected), balance.Balance.Int64()) } + +type NotaryFeerStub struct { + bc blockchainer.Blockchainer +} + +func (f NotaryFeerStub) FeePerByte() int64 { return f.bc.FeePerByte() } +func (f NotaryFeerStub) GetUtilityTokenBalance(acc util.Uint160) *big.Int { + return f.bc.GetNotaryBalance(acc) +} +func (f NotaryFeerStub) BlockHeight() uint32 { return f.bc.BlockHeight() } +func (f NotaryFeerStub) P2PSigExtensionsEnabled() bool { return f.bc.P2PSigExtensionsEnabled() } +func NewNotaryFeerStub(bc blockchainer.Blockchainer) NotaryFeerStub { + return NotaryFeerStub{ + bc: bc, + } +} diff --git a/pkg/core/notary_test.go b/pkg/core/notary_test.go new file mode 100644 index 000000000..0a5fdc277 --- /dev/null +++ b/pkg/core/notary_test.go @@ -0,0 +1,647 @@ +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(bc, pool, b) + }) + + 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) +} diff --git a/pkg/services/notary/node_test.go b/pkg/services/notary/node_test.go new file mode 100644 index 000000000..6c0686b88 --- /dev/null +++ b/pkg/services/notary/node_test.go @@ -0,0 +1,71 @@ +package notary + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/internal/fakechain" + "github.com/nspcc-dev/neo-go/pkg/config" + "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/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +func getTestNotary(t *testing.T, bc blockchainer.Blockchainer, walletPath, pass string) (*wallet.Account, *Notary, *mempool.Pool) { + bc.(*fakechain.FakeChain).ProtocolConfiguration.P2PNotary = config.P2PNotary{ + Enabled: true, + UnlockWallet: config.Wallet{ + Path: walletPath, + Password: pass, + }, + } + mp := mempool.New(10, 1, true) + ntr, err := NewNotary(bc, mp, zaptest.NewLogger(t), nil) + require.NoError(t, err) + + w, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + require.NoError(t, w.Accounts[0].Decrypt(pass)) + return w.Accounts[0], ntr, mp +} + +func TestUpdateNotaryNodes(t *testing.T) { + bc := fakechain.NewFakeChain() + acc, ntr, _ := getTestNotary(t, bc, "./testdata/notary1.json", "one") + randomKey, err := keys.NewPrivateKey() + require.NoError(t, err) + // currAcc is nil before UpdateNotaryNodes call + require.Nil(t, ntr.currAccount) + // set account for the first time + ntr.UpdateNotaryNodes(keys.PublicKeys{acc.PrivateKey().PublicKey()}) + require.Equal(t, acc, ntr.currAccount) + + t.Run("account is already set", func(t *testing.T) { + ntr.UpdateNotaryNodes(keys.PublicKeys{acc.PrivateKey().PublicKey(), randomKey.PublicKey()}) + require.Equal(t, acc, ntr.currAccount) + }) + + t.Run("another account from the same wallet", func(t *testing.T) { + t.Run("good config password", func(t *testing.T) { + w, err := wallet.NewWalletFromFile("./testdata/notary1.json") + require.NoError(t, err) + require.NoError(t, w.Accounts[1].Decrypt("one")) + ntr.UpdateNotaryNodes(keys.PublicKeys{w.Accounts[1].PrivateKey().PublicKey()}) + require.Equal(t, w.Accounts[1], ntr.currAccount) + }) + t.Run("bad config password", func(t *testing.T) { + w, err := wallet.NewWalletFromFile("./testdata/notary1.json") + require.NoError(t, err) + require.NoError(t, w.Accounts[2].Decrypt("four")) + ntr.UpdateNotaryNodes(keys.PublicKeys{w.Accounts[2].PrivateKey().PublicKey()}) + require.Nil(t, ntr.currAccount) + }) + }) + + t.Run("unknown account", func(t *testing.T) { + ntr.UpdateNotaryNodes(keys.PublicKeys{randomKey.PublicKey()}) + require.Nil(t, ntr.currAccount) + }) +} diff --git a/pkg/services/notary/notary.go b/pkg/services/notary/notary.go index 1c16ff9ba..5b4c51f3d 100644 --- a/pkg/services/notary/notary.go +++ b/pkg/services/notary/notary.go @@ -300,7 +300,7 @@ func (n *Notary) PostPersist(bc blockchainer.Blockchainer, pool *mempool.Pool, b func (n *Notary) finalize(tx *transaction.Transaction) error { acc := n.getAccount() if acc == nil { - panic(errors.New("no available Notary account")) + panic(errors.New("no available Notary account")) // unreachable code, because all callers of `finalize` check that acc != nil } notaryWitness := transaction.Witness{ InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc.PrivateKey().Sign(tx.GetSignedPart())...), diff --git a/pkg/services/notary/notary_test.go b/pkg/services/notary/notary_test.go new file mode 100644 index 000000000..452f6a173 --- /dev/null +++ b/pkg/services/notary/notary_test.go @@ -0,0 +1,427 @@ +package notary + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/internal/fakechain" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/mempool" + "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/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +func TestWallet(t *testing.T) { + bc := fakechain.NewFakeChain() + + t.Run("unexisting wallet", func(t *testing.T) { + bc.ProtocolConfiguration.P2PNotary = config.P2PNotary{ + Enabled: true, + UnlockWallet: config.Wallet{ + Path: "./testdata/does_not_exists.json", + Password: "one", + }, + } + _, err := NewNotary(bc, mempool.New(1, 1, true), zaptest.NewLogger(t), nil) + require.Error(t, err) + }) + + t.Run("bad password", func(t *testing.T) { + bc.ProtocolConfiguration.P2PNotary = config.P2PNotary{ + Enabled: true, + UnlockWallet: config.Wallet{ + Path: "./testdata/notary1.json", + Password: "invalid", + }, + } + _, err := NewNotary(bc, mempool.New(1, 1, true), zaptest.NewLogger(t), nil) + require.Error(t, err) + }) + + t.Run("good", func(t *testing.T) { + bc.ProtocolConfiguration.P2PNotary = config.P2PNotary{ + Enabled: true, + UnlockWallet: config.Wallet{ + Path: "./testdata/notary1.json", + Password: "one", + }, + } + _, err := NewNotary(bc, mempool.New(1, 1, true), zaptest.NewLogger(t), nil) + require.NoError(t, err) + }) +} + +func TestVerifyIncompleteRequest(t *testing.T) { + bc := fakechain.NewFakeChain() + notaryContractHash := util.Uint160{1, 2, 3} + bc.NotaryContractScriptHash = notaryContractHash + _, ntr, _ := getTestNotary(t, bc, "./testdata/notary1.json", "one") + sig := append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...) // we're not interested in signature correctness + acc1, _ := keys.NewPrivateKey() + acc2, _ := keys.NewPrivateKey() + acc3, _ := keys.NewPrivateKey() + sigScript1 := acc1.PublicKey().GetVerificationScript() + sigScript2 := acc2.PublicKey().GetVerificationScript() + sigScript3 := acc3.PublicKey().GetVerificationScript() + multisigScript1, err := smartcontract.CreateMultiSigRedeemScript(1, keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}) + require.NoError(t, err) + multisigScriptHash1 := hash.Hash160(multisigScript1) + multisigScript2, err := smartcontract.CreateMultiSigRedeemScript(2, keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}) + require.NoError(t, err) + multisigScriptHash2 := hash.Hash160(multisigScript2) + + checkErr := func(t *testing.T, tx *transaction.Transaction, nKeys uint8) { + typ, nSigs, pubs, err := ntr.verifyIncompleteWitnesses(tx, nKeys) + require.Error(t, err) + require.Equal(t, Unknown, typ) + require.Equal(t, uint8(0), nSigs) + require.Nil(t, pubs) + } + + errCases := map[string]struct { + tx *transaction.Transaction + nKeys uint8 + }{ + "not enough signers": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: notaryContractHash}}, + Scripts: []transaction.Witness{{}}, + }, + }, + "signers count and witnesses count mismatch": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: notaryContractHash}, {}}, + Scripts: []transaction.Witness{{}, {}, {}}, + }, + }, + "missing Notary witness": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.GetScriptHash()}, {Account: acc2.GetScriptHash()}}, + Scripts: []transaction.Witness{{}, {}}, + }, + }, + "unknown witness type": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + {}, + {}, + }, + }, + }, + "bad verification script": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: []byte{}, + VerificationScript: []byte{1, 2, 3}, + }, + {}, + }, + }, + }, + "several multisig witnesses": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: multisigScriptHash2}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + { + InvocationScript: sig, + VerificationScript: multisigScript2, + }, + {}, + }, + }, + nKeys: 2, + }, + "multisig + sig": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + { + InvocationScript: sig, + VerificationScript: sigScript1, + }, + {}, + }, + }, + nKeys: 2, + }, + "sig + multisig": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: sigScript1, + }, + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + {}, + }, + }, + nKeys: 2, + }, + "empty multisig + sig": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: []byte{}, + VerificationScript: multisigScript1, + }, + { + InvocationScript: sig, + VerificationScript: sigScript1, + }, + {}, + }, + }, + nKeys: 2, + }, + "sig + empty multisig": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: sigScript1, + }, + { + InvocationScript: []byte{}, + VerificationScript: multisigScript1, + }, + {}, + }, + }, + nKeys: 2, + }, + "multisig + empty sig": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + { + InvocationScript: []byte{}, + VerificationScript: sigScript1, + }, + {}, + }, + }, + nKeys: 2, + }, + "empty sig + multisig": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: []byte{}, + VerificationScript: sigScript1, + }, + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + {}, + }, + }, + nKeys: 2, + }, + "sig: bad nKeys": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: acc2.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: sigScript1, + }, + { + InvocationScript: sig, + VerificationScript: sigScript2, + }, + {}, + }, + }, + nKeys: 3, + }, + "multisig: bad witnesses count": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + }, + }, + nKeys: 2, + }, + "multisig: bad nKeys": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + {}, + }, + }, + nKeys: 2, + }, + } + + for name, errCase := range errCases { + t.Run(name, func(t *testing.T) { + checkErr(t, errCase.tx, errCase.nKeys) + }) + } + + testCases := map[string]struct { + tx *transaction.Transaction + nKeys uint8 + expectedType RequestType + expectedNSigs uint8 + expectedPubs keys.PublicKeys + }{ + "single sig": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.GetScriptHash()}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: sigScript1, + }, + {}, + }, + }, + nKeys: 1, + expectedType: Signature, + expectedNSigs: 1, + }, + "multiple sig": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.GetScriptHash()}, {Account: acc2.GetScriptHash()}, {Account: acc3.GetScriptHash()}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: sigScript1, + }, + { + InvocationScript: []byte{}, + VerificationScript: []byte{}, + }, + { + InvocationScript: sig, + VerificationScript: sigScript3, + }, + {}, + }, + }, + nKeys: 3, + expectedType: Signature, + expectedNSigs: 3, + }, + "multisig 1 out of 3": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + {}, + }, + }, + nKeys: 3, + expectedType: MultiSignature, + expectedNSigs: 1, + expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}, + }, + "multisig 2 out of 3": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: multisigScriptHash2}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: multisigScript2, + }, + {}, + }, + }, + nKeys: 3, + expectedType: MultiSignature, + expectedNSigs: 2, + expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}, + }, + "empty + multisig": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: []byte{}, + VerificationScript: []byte{}, + }, + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + {}, + }, + }, + nKeys: 3, + expectedType: MultiSignature, + expectedNSigs: 1, + expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}, + }, + "multisig + empty": { + tx: &transaction.Transaction{ + Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, + Scripts: []transaction.Witness{ + { + InvocationScript: sig, + VerificationScript: multisigScript1, + }, + { + InvocationScript: []byte{}, + VerificationScript: []byte{}, + }, + {}, + }, + }, + nKeys: 3, + expectedType: MultiSignature, + expectedNSigs: 1, + expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + typ, nSigs, pubs, err := ntr.verifyIncompleteWitnesses(testCase.tx, testCase.nKeys) + require.NoError(t, err) + assert.Equal(t, testCase.expectedType, typ) + assert.Equal(t, testCase.expectedNSigs, nSigs) + assert.ElementsMatch(t, testCase.expectedPubs, pubs) + }) + } +} diff --git a/pkg/services/notary/testdata/notary1.json b/pkg/services/notary/testdata/notary1.json new file mode 100644 index 000000000..0a8d45251 --- /dev/null +++ b/pkg/services/notary/testdata/notary1.json @@ -0,0 +1 @@ +{"version":"3.0","accounts":[{"address":"NSbjd7dSePTZ6QpADAuM5722QpBmL5124W","key":"6PYVWTfkNCYvyQhyFLHH5dyRyT6jSi8u8Z8kn122PACfsDWi4QgkGm8FyW","label":"NotaryNode1","contract":{"script":"DCEDm5PmbOfVPmYXTSVW903XnOhhNBTsF9oDlVYusIH/ui0LQZVEDXg=","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"lock":false,"isdefault":false},{"address":"NisSvmSd2Lp28tjr8EqCZB5ahHDvBExo2j","key":"6PYLvgnZNwhiiZPiSCw3B3bHSFwbSXgh3MkGt4gL69MD8Sw7LMnuUgM9KQ","label":"three","contract":{"script":"DCEDHRWEIGXHCwUU2Fc7B0qrYPezXR0sfdEduRExyzIKVC8LQZVEDXg=","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"lock":false,"isdefault":false},{"address":"NRCCdGifyUWKnFotZhcgKpmxhhVJSJb94r","key":"6PYKXkuJ7G6bTj62bjy8fsBLF5okYNdAEBhKPCv8nmcALCtk2yPtBo835p","label":"four","contract":{"script":"DCECmUfs/gqKHd3AdJm5+Ev6zkubV8pP8DZzgu8+t5WdphILQZVEDXg=","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"lock":false,"isdefault":false}],"scrypt":{"n":16384,"r":8,"p":8},"extra":{"Tokens":null}} diff --git a/pkg/services/notary/testdata/notary2.json b/pkg/services/notary/testdata/notary2.json new file mode 100644 index 000000000..329c11390 --- /dev/null +++ b/pkg/services/notary/testdata/notary2.json @@ -0,0 +1 @@ +{"version":"3.0","accounts":[{"address":"NfFcJvWcHe8SSS92hNZhyQUJ6cg3pb36Tf","key":"6PYU2QoD52Xt9Z6QmNGUJWn89qUD1W6QqAL4Y8nfTWtTKvmVpQh8wsH6qY","label":"NotaryNode2","contract":{"script":"DCECIcKj0GFdv4b1NZrw9X6zLNLWzmNKAxtw6olIMZxpPRQLQZVEDXg=","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"lock":false,"isdefault":false}],"scrypt":{"n":16384,"r":8,"p":8},"extra":{"Tokens":null}}