From d285342d543aac17d3369ea33338aad6780d55b3 Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Tue, 22 Aug 2023 10:24:59 +0100 Subject: [PATCH 1/4] core: add function IterateVerifiedTransactions IterateVerifiedTransactions iterates through verified transactions in memory pool and invokes function cont. Where cont callback returns whether we should continue with the traversal process. Signed-off-by: Tatiana Nesterenko --- pkg/core/mempool/mem_pool.go | 16 ++++ pkg/core/mempool/mem_pool_test.go | 134 +++++++++++++++++++++++++----- 2 files changed, 127 insertions(+), 23 deletions(-) diff --git a/pkg/core/mempool/mem_pool.go b/pkg/core/mempool/mem_pool.go index 2fbce047b..b03c1f750 100644 --- a/pkg/core/mempool/mem_pool.go +++ b/pkg/core/mempool/mem_pool.go @@ -609,3 +609,19 @@ func (mp *Pool) removeConflictsOf(tx *transaction.Transaction) { } } } + +// IterateVerifiedTransactions iterates through verified transactions and invokes +// function `cont`. Iterations continue while the function `cont` returns true. +// Function `cont` is executed within a read-locked memory pool, +// thus IterateVerifiedTransactions will block any write mempool operation, +// use it with care. Do not modify transaction or data via `cont`. +func (mp *Pool) IterateVerifiedTransactions(cont func(tx *transaction.Transaction, data any) bool) { + mp.lock.RLock() + defer mp.lock.RUnlock() + + for i := range mp.verifiedTxes { + if !cont(mp.verifiedTxes[i].txn, mp.verifiedTxes[i].data) { + return + } + } +} diff --git a/pkg/core/mempool/mem_pool_test.go b/pkg/core/mempool/mem_pool_test.go index d1d247dc9..9bcf43cba 100644 --- a/pkg/core/mempool/mem_pool_test.go +++ b/pkg/core/mempool/mem_pool_test.go @@ -637,26 +637,18 @@ func TestMempoolAddWithDataGetData(t *testing.T) { balance: 100, } mp := New(10, 1, false, nil) - newTx := func(t *testing.T, netFee int64) *transaction.Transaction { - tx := transaction.New([]byte{byte(opcode.RET)}, 0) - tx.Signers = []transaction.Signer{{}, {}} - tx.NetworkFee = netFee - nonce++ - tx.Nonce = nonce - return tx - } // bad, insufficient deposit r1 := &payload.P2PNotaryRequest{ - MainTransaction: newTx(t, 0), - FallbackTransaction: newTx(t, fs.balance+1), + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(fs.balance+1, &nonce), } require.ErrorIs(t, mp.Add(r1.FallbackTransaction, fs, r1), ErrInsufficientFunds) // good r2 := &payload.P2PNotaryRequest{ - MainTransaction: newTx(t, 0), - FallbackTransaction: newTx(t, smallNetFee), + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), } require.NoError(t, mp.Add(r2.FallbackTransaction, fs, r2)) require.True(t, mp.ContainsKey(r2.FallbackTransaction.Hash())) @@ -669,8 +661,8 @@ func TestMempoolAddWithDataGetData(t *testing.T) { // good, higher priority than r2. The resulting mp.verifiedTxes: [r3, r2] r3 := &payload.P2PNotaryRequest{ - MainTransaction: newTx(t, 0), - FallbackTransaction: newTx(t, smallNetFee+1), + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee+1, &nonce), } require.NoError(t, mp.Add(r3.FallbackTransaction, fs, r3)) require.True(t, mp.ContainsKey(r3.FallbackTransaction.Hash())) @@ -680,8 +672,8 @@ func TestMempoolAddWithDataGetData(t *testing.T) { // good, same priority as r2. The resulting mp.verifiedTxes: [r3, r2, r4] r4 := &payload.P2PNotaryRequest{ - MainTransaction: newTx(t, 0), - FallbackTransaction: newTx(t, smallNetFee), + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), } require.NoError(t, mp.Add(r4.FallbackTransaction, fs, r4)) require.True(t, mp.ContainsKey(r4.FallbackTransaction.Hash())) @@ -691,8 +683,8 @@ func TestMempoolAddWithDataGetData(t *testing.T) { // good, same priority as r2. The resulting mp.verifiedTxes: [r3, r2, r4, r5] r5 := &payload.P2PNotaryRequest{ - MainTransaction: newTx(t, 0), - FallbackTransaction: newTx(t, smallNetFee), + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), } require.NoError(t, mp.Add(r5.FallbackTransaction, fs, r5)) require.True(t, mp.ContainsKey(r5.FallbackTransaction.Hash())) @@ -713,7 +705,7 @@ func TestMempoolAddWithDataGetData(t *testing.T) { require.False(t, ok) // but getting nil data is OK. The resulting mp.verifiedTxes: [r3, r2, r4, r5, r6] - r6 := newTx(t, smallNetFee) + r6 := mkTwoSignersTx(smallNetFee, &nonce) require.NoError(t, mp.Add(r6, fs, nil)) require.True(t, mp.ContainsKey(r6.Hash())) data, ok = mp.TryGetData(r6.Hash()) @@ -722,14 +714,14 @@ func TestMempoolAddWithDataGetData(t *testing.T) { // getting data: item is in verifiedMap, but not in verifiedTxes r7 := &payload.P2PNotaryRequest{ - MainTransaction: newTx(t, 0), - FallbackTransaction: newTx(t, smallNetFee), + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), } require.NoError(t, mp.Add(r7.FallbackTransaction, fs, r4)) require.True(t, mp.ContainsKey(r7.FallbackTransaction.Hash())) r8 := &payload.P2PNotaryRequest{ - MainTransaction: newTx(t, 0), - FallbackTransaction: newTx(t, smallNetFee-1), + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee-1, &nonce), } require.NoError(t, mp.Add(r8.FallbackTransaction, fs, r4)) require.True(t, mp.ContainsKey(r8.FallbackTransaction.Hash())) @@ -737,3 +729,99 @@ func TestMempoolAddWithDataGetData(t *testing.T) { _, ok = mp.TryGetData(r7.FallbackTransaction.Hash()) require.False(t, ok) } + +func mkTwoSignersTx(netFee int64, nonce *uint32) *transaction.Transaction { + tx := transaction.New([]byte{byte(opcode.RET)}, 0) + tx.Signers = []transaction.Signer{{}, {}} + tx.NetworkFee = netFee + *nonce++ + tx.Nonce = *nonce + return tx +} + +func TestMempoolIterateVerifiedTransactions(t *testing.T) { + var ( + smallNetFee int64 = 3 + nonce uint32 + r1, r2, r3, r4, r5 *payload.P2PNotaryRequest + ) + fs := &FeerStub{ + feePerByte: 0, + p2pSigExt: true, + blockHeight: 5, + balance: 100, + } + mp := New(10, 1, false, nil) + + checkRequestsOrder := func(orderedRequests []*payload.P2PNotaryRequest) { + var pooledRequests []*payload.P2PNotaryRequest + mp.IterateVerifiedTransactions(func(tx *transaction.Transaction, data any) bool { + d := data.(*payload.P2PNotaryRequest) + pooledRequests = append(pooledRequests, d) + return true + }) + require.Equal(t, orderedRequests, pooledRequests) + } + + r1 = &payload.P2PNotaryRequest{ + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), + } + require.NoError(t, mp.Add(r1.FallbackTransaction, fs, r1)) + checkRequestsOrder([]*payload.P2PNotaryRequest{r1}) + + // r2 has higher priority than r1. The resulting mp.verifiedTxes: [r2, r1] + r2 = &payload.P2PNotaryRequest{ + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee+1, &nonce), + } + require.NoError(t, mp.Add(r2.FallbackTransaction, fs, r2)) + checkRequestsOrder([]*payload.P2PNotaryRequest{r2, r1}) + + // r3 has the same priority as r1. The resulting mp.verifiedTxes: [r2, r1, r3] + r3 = &payload.P2PNotaryRequest{ + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), + } + require.NoError(t, mp.Add(r3.FallbackTransaction, fs, r3)) + checkRequestsOrder([]*payload.P2PNotaryRequest{r2, r1, r3}) + + // r4 has the same priority as r1. The resulting mp.verifiedTxes: [r2, r1, r3, r4] + r4 = &payload.P2PNotaryRequest{ + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), + } + require.NoError(t, mp.Add(r4.FallbackTransaction, fs, r4)) + checkRequestsOrder([]*payload.P2PNotaryRequest{r2, r1, r3, r4}) + + checkPooledRequest := func(t *testing.T, r *payload.P2PNotaryRequest, isPooled bool) { + cont := true + notaryRequest := &payload.P2PNotaryRequest{} + mp.IterateVerifiedTransactions(func(tx *transaction.Transaction, data any) bool { + if data != nil { + notaryRequest = data.(*payload.P2PNotaryRequest) + if notaryRequest.MainTransaction.Hash() == r.MainTransaction.Hash() { + cont = false + } + } + return cont + }) + + if isPooled { + require.Equal(t, false, cont) + require.Equal(t, r, notaryRequest) + } else { + require.Equal(t, true, cont) + } + } + checkPooledRequest(t, r1, true) + checkPooledRequest(t, r2, true) + checkPooledRequest(t, r3, true) + checkPooledRequest(t, r4, true) + + r5 = &payload.P2PNotaryRequest{ + MainTransaction: mkTwoSignersTx(0, &nonce), + FallbackTransaction: mkTwoSignersTx(smallNetFee, &nonce), + } + checkPooledRequest(t, r5, false) +} From 9e31e42bd98420d55146c74b9a52cc53e1c2aef0 Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Tue, 22 Aug 2023 10:36:11 +0100 Subject: [PATCH 2/4] rpcsrv: add getrawnotarypool, getrawnotarytransaction handlers `getrawnotarytransaction` takes a transaction hash and attempts to find the corresponding transaction in the notary requests mempool. It searches through all the verified main and fallback transactions. `getrawnotarypool` returns hashes of all the verified transactions, including both main and fallback transactions. Additionally add struct result.RawNotaryPool. Close https://github.com/nspcc-dev/neo-go/issues/2951 Signed-off-by: Tatiana Nesterenko --- pkg/neorpc/result/raw_notary_pool.go | 48 +++++ pkg/services/rpcsrv/server.go | 53 +++++ pkg/services/rpcsrv/server_test.go | 252 +++++++++++++++++------ pkg/services/rpcsrv/subscription_test.go | 4 +- 4 files changed, 290 insertions(+), 67 deletions(-) create mode 100644 pkg/neorpc/result/raw_notary_pool.go diff --git a/pkg/neorpc/result/raw_notary_pool.go b/pkg/neorpc/result/raw_notary_pool.go new file mode 100644 index 000000000..09bd8902f --- /dev/null +++ b/pkg/neorpc/result/raw_notary_pool.go @@ -0,0 +1,48 @@ +package result + +import ( + "encoding/json" + "strings" + + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// RawNotaryPool represents a result of `getrawnotarypool` RPC call. +// The structure consist of `Hashes`. `Hashes` field is a map, where key is +// the hash of the main transaction and value is a slice of related fallback +// transaction hashes. +type RawNotaryPool struct { + Hashes map[util.Uint256][]util.Uint256 +} + +// rawNotaryPoolAux is an auxiliary struct for RawNotaryPool JSON marshalling. +type rawNotaryPoolAux struct { + Hashes map[string][]util.Uint256 `json:"hashes,omitempty"` +} + +// MarshalJSON implements the json.Marshaler interface. +func (p RawNotaryPool) MarshalJSON() ([]byte, error) { + var aux rawNotaryPoolAux + aux.Hashes = make(map[string][]util.Uint256, len(p.Hashes)) + for main, fallbacks := range p.Hashes { + aux.Hashes["0x"+main.StringLE()] = fallbacks + } + return json.Marshal(aux) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (p *RawNotaryPool) UnmarshalJSON(data []byte) error { + var aux rawNotaryPoolAux + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + p.Hashes = make(map[util.Uint256][]util.Uint256, len(aux.Hashes)) + for main, fallbacks := range aux.Hashes { + hashMain, err := util.Uint256DecodeStringLE(strings.TrimPrefix(main, "0x")) + if err != nil { + return err + } + p.Hashes[hashMain] = fallbacks + } + return nil +} diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index c6fe8d936..5e66e0e3a 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -229,6 +229,8 @@ var rpcHandlers = map[string]func(*Server, params.Params) (any, *neorpc.Error){ "getpeers": (*Server).getPeers, "getproof": (*Server).getProof, "getrawmempool": (*Server).getRawMempool, + "getrawnotarypool": (*Server).getRawNotaryPool, + "getrawnotarytransaction": (*Server).getRawNotaryTransaction, "getrawtransaction": (*Server).getrawtransaction, "getstate": (*Server).getState, "getstateheight": (*Server).getStateHeight, @@ -3090,3 +3092,54 @@ func (s *Server) Addresses() []string { } return res } + +func (s *Server) getRawNotaryPool(_ params.Params) (any, *neorpc.Error) { + if !s.chain.P2PSigExtensionsEnabled() { + return nil, neorpc.NewInternalServerError("P2PSignatureExtensions are disabled") + } + nrp := s.coreServer.GetNotaryPool() + res := &result.RawNotaryPool{Hashes: make(map[util.Uint256][]util.Uint256)} + nrp.IterateVerifiedTransactions(func(tx *transaction.Transaction, data any) bool { + if data != nil { + d := data.(*payload.P2PNotaryRequest) + mainHash := d.MainTransaction.Hash() + fallbackHash := d.FallbackTransaction.Hash() + res.Hashes[mainHash] = append(res.Hashes[mainHash], fallbackHash) + } + return true + }) + return res, nil +} + +func (s *Server) getRawNotaryTransaction(reqParams params.Params) (any, *neorpc.Error) { + if !s.chain.P2PSigExtensionsEnabled() { + return nil, neorpc.NewInternalServerError("P2PSignatureExtensions are disabled") + } + + txHash, err := reqParams.Value(0).GetUint256() + if err != nil { + return nil, neorpc.ErrInvalidParams + } + nrp := s.coreServer.GetNotaryPool() + // Try to find fallback transaction. + tx, ok := nrp.TryGetValue(txHash) + if !ok { + // Try to find main transaction. + nrp.IterateVerifiedTransactions(func(t *transaction.Transaction, data any) bool { + if data != nil && data.(*payload.P2PNotaryRequest).MainTransaction.Hash().Equals(txHash) { + tx = data.(*payload.P2PNotaryRequest).MainTransaction + return false + } + return true + }) + // The transaction was not found. + if tx == nil { + return nil, neorpc.ErrUnknownTransaction + } + } + + if v, _ := reqParams.Value(1).GetBoolean(); v { + return tx, nil + } + return tx.Bytes(), nil +} diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index 6fb1222f4..da27c69ce 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -2275,8 +2275,11 @@ func TestSubmitOracle(t *testing.T) { t.Run("Valid", runCase(t, false, 0, pubStr, `1`, txSigStr, msgSigStr)) } -func TestSubmitNotaryRequest(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "submitnotaryrequest", "params": %s}` +func TestNotaryRequestRPC(t *testing.T) { + var notaryRequest1, notaryRequest2 *payload.P2PNotaryRequest + rpcSubmit := `{"jsonrpc": "2.0", "id": 1, "method": "submitnotaryrequest", "params": %s}` + rpcPool := `{"jsonrpc": "2.0", "id": 1, "method": "getrawnotarypool", "params": []}` + rpcTx := `{"jsonrpc": "2.0", "id": 1, "method": "getrawnotarytransaction", "params": ["%s", %d]}` t.Run("disabled P2PSigExtensions", func(t *testing.T) { chain, rpcSrv, httpSrv := initClearServerWithCustomConfig(t, func(c *config.Config) { @@ -2284,87 +2287,206 @@ func TestSubmitNotaryRequest(t *testing.T) { }) defer chain.Close() defer rpcSrv.Shutdown() - req := fmt.Sprintf(rpc, "[]") - body := doRPCCallOverHTTP(req, httpSrv.URL, t) - checkErrGetResult(t, body, true, neorpc.InternalServerErrorCode) + t.Run("submitnotaryrequest", func(t *testing.T) { + body := doRPCCallOverHTTP(fmt.Sprintf(rpcSubmit, "[]"), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.InternalServerErrorCode) + }) + t.Run("getrawnotarypool", func(t *testing.T) { + body := doRPCCallOverHTTP(rpcPool, httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.InternalServerErrorCode) + }) + t.Run("getrawnotarytransaction", func(t *testing.T) { + body := doRPCCallOverHTTP(fmt.Sprintf(rpcTx, " ", 1), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.InternalServerErrorCode) + }) }) chain, rpcSrv, httpSrv := initServerWithInMemoryChainAndServices(t, false, true, false) defer chain.Close() defer rpcSrv.Shutdown() - runCase := func(t *testing.T, fail bool, errCode int64, params ...string) func(t *testing.T) { + submitNotaryRequest := func(t *testing.T, fail bool, errCode int64, params ...string) func(t *testing.T) { return func(t *testing.T) { ps := `[` + strings.Join(params, ",") + `]` - req := fmt.Sprintf(rpc, ps) + req := fmt.Sprintf(rpcSubmit, ps) body := doRPCCallOverHTTP(req, httpSrv.URL, t) checkErrGetResult(t, body, fail, errCode) } } - t.Run("missing request", runCase(t, true, neorpc.InvalidParamsCode)) - t.Run("not a base64", runCase(t, true, neorpc.InvalidParamsCode, `"not-a-base64$"`)) - t.Run("invalid request bytes", runCase(t, true, neorpc.InvalidParamsCode, `"not-a-request"`)) - t.Run("invalid request", func(t *testing.T) { - mainTx := &transaction.Transaction{ + + t.Run("getrawnotarypool", func(t *testing.T) { + t.Run("empty pool", func(t *testing.T) { + body := doRPCCallOverHTTP(rpcPool, httpSrv.URL, t) + res := checkErrGetResult(t, body, false, 0) + actual := new(result.RawNotaryPool) + require.NoError(t, json.Unmarshal(res, actual)) + require.Equal(t, 0, len(actual.Hashes)) + }) + + sender := testchain.PrivateKeyByID(0) // owner of the deposit in testchain + notaryRequest1 = createValidNotaryRequest(chain, sender, 1, 2_0000_0000, nil) + nrBytes, err := notaryRequest1.Bytes() + require.NoError(t, err) + str := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(nrBytes)) + submitNotaryRequest(t, false, 0, str)(t) + + t.Run("nonempty pool", func(t *testing.T) { + //get notary pool & check tx hashes + body := doRPCCallOverHTTP(rpcPool, httpSrv.URL, t) + res := checkErrGetResult(t, body, false, 0) + actual := new(result.RawNotaryPool) + require.NoError(t, json.Unmarshal(res, actual)) + require.Equal(t, 1, len(actual.Hashes)) + for actMain, actFallbacks := range actual.Hashes { + require.Equal(t, notaryRequest1.MainTransaction.Hash(), actMain) + require.Equal(t, 1, len(actFallbacks)) + require.Equal(t, notaryRequest1.FallbackTransaction.Hash(), actFallbacks[0]) + } + }) + + notaryRequest2 = createValidNotaryRequest(chain, sender, 2, 3_0000_0000, notaryRequest1.MainTransaction) + nrBytes2, err := notaryRequest2.Bytes() + require.NoError(t, err) + str2 := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(nrBytes2)) + submitNotaryRequest(t, false, 0, str2)(t) + + t.Run("pool with 2", func(t *testing.T) { + //get notary pool & check tx hashes + body := doRPCCallOverHTTP(rpcPool, httpSrv.URL, t) + res := checkErrGetResult(t, body, false, 0) + actual := new(result.RawNotaryPool) + require.NoError(t, json.Unmarshal(res, actual)) + require.Equal(t, 1, len(actual.Hashes)) + for actMain, actFallbacks := range actual.Hashes { + require.Equal(t, notaryRequest1.MainTransaction.Hash(), actMain) + require.Equal(t, 2, len(actFallbacks)) + // The second fallback transaction has higher priority, so it's first in the slice. + require.Equal(t, notaryRequest1.FallbackTransaction.Hash(), actFallbacks[1]) + require.Equal(t, notaryRequest2.FallbackTransaction.Hash(), actFallbacks[0]) + } + }) + }) + + t.Run("submitnotaryrequest", func(t *testing.T) { + t.Run("missing request", submitNotaryRequest(t, true, neorpc.InvalidParamsCode)) + t.Run("not a base64", submitNotaryRequest(t, true, neorpc.InvalidParamsCode, `"not-a-base64$"`)) + t.Run("invalid request bytes", submitNotaryRequest(t, true, neorpc.InvalidParamsCode, `"not-a-request"`)) + t.Run("invalid request", func(t *testing.T) { + mainTx := &transaction.Transaction{ + Attributes: []transaction.Attribute{{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 1}}}, + Script: []byte{byte(opcode.RET)}, + ValidUntilBlock: 123, + Signers: []transaction.Signer{{Account: util.Uint160{1, 5, 9}}}, + Scripts: []transaction.Witness{{ + InvocationScript: []byte{1, 4, 7}, + VerificationScript: []byte{3, 6, 9}, + }}, + } + fallbackTx := &transaction.Transaction{ + Script: []byte{byte(opcode.RET)}, + ValidUntilBlock: 123, + Attributes: []transaction.Attribute{ + {Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}}, + {Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainTx.Hash()}}, + {Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}}, + }, + Signers: []transaction.Signer{{Account: util.Uint160{1, 4, 7}}, {Account: util.Uint160{9, 8, 7}}}, + Scripts: []transaction.Witness{ + {InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, make([]byte, keys.SignatureLen)...), VerificationScript: make([]byte, 0)}, + {InvocationScript: []byte{1, 2, 3}, VerificationScript: []byte{1, 2, 3}}}, + } + p := &payload.P2PNotaryRequest{ + MainTransaction: mainTx, + FallbackTransaction: fallbackTx, + Witness: transaction.Witness{ + InvocationScript: []byte{1, 2, 3}, + VerificationScript: []byte{7, 8, 9}, + }, + } + nrBytes, err := p.Bytes() + require.NoError(t, err) + str := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(nrBytes)) + submitNotaryRequest(t, true, neorpc.ErrVerificationFailedCode, str)(t) + }) + t.Run("valid request", func(t *testing.T) { + sender := testchain.PrivateKeyByID(0) // owner of the deposit in testchain + notaryRequest1 = createValidNotaryRequest(chain, sender, 3, 2_0000_0000, nil) + nrBytes, err := notaryRequest1.Bytes() + require.NoError(t, err) + str := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(nrBytes)) + submitNotaryRequest(t, false, 0, str)(t) + }) + }) + + t.Run("getrawnotarytransaction", func(t *testing.T) { + t.Run("invalid param", func(t *testing.T) { + req := fmt.Sprintf(rpcTx, "invalid", 1) + body := doRPCCallOverHTTP(req, httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.InvalidParamsCode) + }) + t.Run("unknown transaction", func(t *testing.T) { + req := fmt.Sprintf(rpcTx, (util.Uint256{0, 0, 0}).StringLE(), 1) + body := doRPCCallOverHTTP(req, httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrUnknownTransactionCode) + }) + + checkGetTxVerbose := func(t *testing.T, tx *transaction.Transaction) { + req := fmt.Sprintf(rpcTx, tx.Hash().StringLE(), 1) + body := doRPCCallOverHTTP(req, httpSrv.URL, t) + res := checkErrGetResult(t, body, false, 0) + actual := new(transaction.Transaction) + require.NoError(t, json.Unmarshal(res, actual)) + _ = tx.Size() + require.Equal(t, tx, actual) + } + t.Run("mainTx verbose", func(t *testing.T) { + checkGetTxVerbose(t, notaryRequest1.MainTransaction) + }) + t.Run("fallbackTx verbose", func(t *testing.T) { + checkGetTxVerbose(t, notaryRequest1.FallbackTransaction) + checkGetTxVerbose(t, notaryRequest2.FallbackTransaction) + }) + + checkGetTxBytes := func(t *testing.T, tx *transaction.Transaction) { + req := fmt.Sprintf(rpcTx, tx.Hash().StringLE(), 0) + body := doRPCCallOverHTTP(req, httpSrv.URL, t) + res := checkErrGetResult(t, body, false, 0) + + var s string + err := json.Unmarshal(res, &s) + require.NoErrorf(t, err, "could not parse response: %s", res) + txBin, err := testserdes.EncodeBinary(tx) + require.NoError(t, err) + expected := base64.StdEncoding.EncodeToString(txBin) + assert.Equal(t, expected, s) + } + t.Run("mainTx bytes", func(t *testing.T) { + checkGetTxBytes(t, notaryRequest1.MainTransaction) + }) + t.Run("fallbackTx bytes", func(t *testing.T) { + checkGetTxBytes(t, notaryRequest1.FallbackTransaction) + checkGetTxBytes(t, notaryRequest2.FallbackTransaction) + }) + }) +} + +// createValidNotaryRequest creates and signs P2PNotaryRequest payload which can +// pass verification. It uses the provided mainTx if it's a nonempty structure. +func createValidNotaryRequest(chain *core.Blockchain, sender *keys.PrivateKey, nonce uint32, networkFee int64, mainTx *transaction.Transaction) *payload.P2PNotaryRequest { + h := chain.BlockHeight() + // If mainTx is nil, then generate it. + if mainTx == nil { + mainTx = &transaction.Transaction{ + Nonce: nonce, Attributes: []transaction.Attribute{{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 1}}}, Script: []byte{byte(opcode.RET)}, - ValidUntilBlock: 123, - Signers: []transaction.Signer{{Account: util.Uint160{1, 5, 9}}}, + ValidUntilBlock: h + 100, + Signers: []transaction.Signer{{Account: sender.GetScriptHash()}}, Scripts: []transaction.Witness{{ InvocationScript: []byte{1, 4, 7}, VerificationScript: []byte{3, 6, 9}, }}, } - fallbackTx := &transaction.Transaction{ - Script: []byte{byte(opcode.RET)}, - ValidUntilBlock: 123, - Attributes: []transaction.Attribute{ - {Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}}, - {Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainTx.Hash()}}, - {Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}}, - }, - Signers: []transaction.Signer{{Account: util.Uint160{1, 4, 7}}, {Account: util.Uint160{9, 8, 7}}}, - Scripts: []transaction.Witness{ - {InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, make([]byte, keys.SignatureLen)...), VerificationScript: make([]byte, 0)}, - {InvocationScript: []byte{1, 2, 3}, VerificationScript: []byte{1, 2, 3}}}, - } - p := &payload.P2PNotaryRequest{ - MainTransaction: mainTx, - FallbackTransaction: fallbackTx, - Witness: transaction.Witness{ - InvocationScript: []byte{1, 2, 3}, - VerificationScript: []byte{7, 8, 9}, - }, - } - bytes, err := p.Bytes() - require.NoError(t, err) - str := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(bytes)) - runCase(t, true, neorpc.ErrVerificationFailedCode, str)(t) - }) - t.Run("valid request", func(t *testing.T) { - sender := testchain.PrivateKeyByID(0) // owner of the deposit in testchain - p := createValidNotaryRequest(chain, sender, 1) - bytes, err := p.Bytes() - require.NoError(t, err) - str := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(bytes)) - runCase(t, false, 0, str)(t) - }) -} - -// createValidNotaryRequest creates and signs P2PNotaryRequest payload which can -// pass verification. -func createValidNotaryRequest(chain *core.Blockchain, sender *keys.PrivateKey, nonce uint32) *payload.P2PNotaryRequest { - h := chain.BlockHeight() - mainTx := &transaction.Transaction{ - Nonce: nonce, - Attributes: []transaction.Attribute{{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 1}}}, - Script: []byte{byte(opcode.RET)}, - ValidUntilBlock: h + 100, - Signers: []transaction.Signer{{Account: sender.GetScriptHash()}}, - Scripts: []transaction.Witness{{ - InvocationScript: []byte{1, 4, 7}, - VerificationScript: []byte{3, 6, 9}, - }}, } fallbackTx := &transaction.Transaction{ Script: []byte{byte(opcode.RET)}, @@ -2378,7 +2500,7 @@ func createValidNotaryRequest(chain *core.Blockchain, sender *keys.PrivateKey, n Scripts: []transaction.Witness{ {InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, make([]byte, keys.SignatureLen)...), VerificationScript: []byte{}}, }, - NetworkFee: 2_0000_0000, + NetworkFee: networkFee, } fallbackTx.Scripts = append(fallbackTx.Scripts, transaction.Witness{ InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, sender.SignHashable(uint32(testchain.Network()), fallbackTx)...), diff --git a/pkg/services/rpcsrv/subscription_test.go b/pkg/services/rpcsrv/subscription_test.go index 52ff0c6fb..c3b17fa54 100644 --- a/pkg/services/rpcsrv/subscription_test.go +++ b/pkg/services/rpcsrv/subscription_test.go @@ -144,7 +144,7 @@ func TestSubscriptions(t *testing.T) { // We should manually add NotaryRequest to test notification. sender := testchain.PrivateKeyByID(0) - err := rpcSrv.coreServer.RelayP2PNotaryRequest(createValidNotaryRequest(chain, sender, 1)) + err := rpcSrv.coreServer.RelayP2PNotaryRequest(createValidNotaryRequest(chain, sender, 1, 2_0000_0000, nil)) require.NoError(t, err) for { resp := getNotification(t, respMsgs) @@ -390,7 +390,7 @@ func TestFilteredNotaryRequestSubscriptions(t *testing.T) { t.Run(name, func(t *testing.T) { subID := callSubscribe(t, c, respMsgs, this.params) - err := rpcSrv.coreServer.RelayP2PNotaryRequest(createValidNotaryRequest(chain, priv0, nonce)) + err := rpcSrv.coreServer.RelayP2PNotaryRequest(createValidNotaryRequest(chain, priv0, nonce, 2_0000_0000, nil)) require.NoError(t, err) nonce++ From d06f135792ee346aad4f0f7365a077474feef088 Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Sun, 27 Aug 2023 19:49:46 +0100 Subject: [PATCH 3/4] rpcclient: support getrawnotarytransaction and getrawnotarypool RPC methods GetRawNotaryTransaction returns a fallback or main transaction that was previously added to the memory pool by P2PNotaryRequest. This function invokes the RPC server's `getrawnotarytransaction` method. GetRawNotaryPool returns hashes from all the verified transactions, including both main and fallback transactions. This function invokes the RPC server's `getrawnotarypool` method. Also, these functions were added to doc.go. Signed-off-by: Tatiana Nesterenko --- pkg/rpcclient/doc.go | 2 + pkg/rpcclient/rpc.go | 41 +++++++++ pkg/rpcclient/rpc_test.go | 83 ++++++++++++++++++ pkg/services/rpcsrv/client_test.go | 131 +++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) diff --git a/pkg/rpcclient/doc.go b/pkg/rpcclient/doc.go index 8871a5371..ef2e1d198 100644 --- a/pkg/rpcclient/doc.go +++ b/pkg/rpcclient/doc.go @@ -96,6 +96,8 @@ Supported methods Extensions: getblocksysfee + getrawnotarypool + getrawnotarytransaction submitnotaryrequest Unsupported methods diff --git a/pkg/rpcclient/rpc.go b/pkg/rpcclient/rpc.go index 0a7215558..c6a66df38 100644 --- a/pkg/rpcclient/rpc.go +++ b/pkg/rpcclient/rpc.go @@ -1285,3 +1285,44 @@ func (c *Client) TerminateSession(sessionID uuid.UUID) (bool, error) { return resp, nil } + +// GetRawNotaryTransaction returns main or fallback transaction from the +// RPC node's notary request pool. +func (c *Client) GetRawNotaryTransaction(hash util.Uint256) (*transaction.Transaction, error) { + var ( + params = []any{hash.StringLE()} + resp []byte + err error + ) + if err = c.performRequest("getrawnotarytransaction", params, &resp); err != nil { + return nil, err + } + return transaction.NewTransactionFromBytes(resp) +} + +// GetRawNotaryTransactionVerbose returns main or fallback transaction from the +// RPC node's notary request pool. +// NOTE: to get transaction.ID and transaction.Size, use t.Hash() and +// io.GetVarSize(t) respectively. +func (c *Client) GetRawNotaryTransactionVerbose(hash util.Uint256) (*transaction.Transaction, error) { + var ( + params = []any{hash.StringLE(), 1} // 1 for verbose. + resp = &transaction.Transaction{} + err error + ) + if err = c.performRequest("getrawnotarytransaction", params, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetRawNotaryPool returns hashes of main P2PNotaryRequest transactions that +// are currently in the RPC node's notary request pool with the corresponding +// hashes of fallback transactions. +func (c *Client) GetRawNotaryPool() (*result.RawNotaryPool, error) { + resp := &result.RawNotaryPool{} + if err := c.performRequest("getrawnotarypool", nil, resp); err != nil { + return nil, err + } + return resp, nil +} diff --git a/pkg/rpcclient/rpc_test.go b/pkg/rpcclient/rpc_test.go index 5562a21ac..0c0b51a23 100644 --- a/pkg/rpcclient/rpc_test.go +++ b/pkg/rpcclient/rpc_test.go @@ -1375,6 +1375,89 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, }, }, + "getrawnotarytransaction": { + { + name: "positive", + invoke: func(c *Client) (any, error) { + hash, err := util.Uint256DecodeStringLE("ad1c2875de823a54188949490e2d68580fd070fcc5ff409609f478d23d12355f") + if err != nil { + panic(err) + } + return c.GetRawNotaryTransaction(hash) + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":"AAMAAAAAAAAAAAAAAAAAAAAAAAAAewAAAAHunqIsJ+NL0BSPxBCOCPdOj1BIsgABIgEBQAEDAQQHAwMGCQ=="}`, + result: func(c *Client) any { + return &transaction.Transaction{} + }, + check: func(t *testing.T, c *Client, uns any) { + res, ok := uns.(*transaction.Transaction) + require.True(t, ok) + assert.NotNil(t, res) + expectHash, err := util.Uint256DecodeStringLE("ad1c2875de823a54188949490e2d68580fd070fcc5ff409609f478d23d12355f") + require.NoError(t, err) + assert.Equal(t, expectHash, res.Hash()) + }, + }, + { + name: "positive verbose", + invoke: func(c *Client) (any, error) { + hash, err := util.Uint256DecodeStringLE("ad1c2875de823a54188949490e2d68580fd070fcc5ff409609f478d23d12355f") + if err != nil { + panic(err) + } + return c.GetRawNotaryTransactionVerbose(hash) + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xad1c2875de823a54188949490e2d68580fd070fcc5ff409609f478d23d12355f","size":61,"version":0,"nonce":3,"sender":"Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn","sysfee":"0","netfee":"0","validuntilblock":123,"attributes":[{"nkeys":1,"type":"NotaryAssisted"}],"signers":[{"account":"0xb248508f4ef7088e10c48f14d04be3272ca29eee","scopes":"None"}],"script":"QA==","witnesses":[{"invocation":"AQQH","verification":"AwYJ"}]}}`, + result: func(c *Client) any { + return &transaction.Transaction{} + }, + check: func(t *testing.T, c *Client, uns any) { + res, ok := uns.(*transaction.Transaction) + require.True(t, ok) + assert.NotNil(t, res) + expectHash, err := util.Uint256DecodeStringLE("ad1c2875de823a54188949490e2d68580fd070fcc5ff409609f478d23d12355f") + require.NoError(t, err) + assert.Equal(t, expectHash, res.Hash()) + }, + }, + }, + "getrawnotarypool": { + { + name: "empty pool", + invoke: func(c *Client) (any, error) { + return c.GetRawNotaryPool() + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{}}`, + result: func(c *Client) any { + return &result.RawNotaryPool{ + Hashes: map[util.Uint256][]util.Uint256{}, + } + }, + }, + { + name: "nonempty pool", + invoke: func(c *Client) (any, error) { + return c.GetRawNotaryPool() + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hashes":{"0xd86b5346e9bbe6dba845cc4192fa716535a3d05c4f2084431edc99dc3862a299":["0xbb0b2f1d5539dd776637f00e5011d97921a1400d3a63c02977a38446180c6d7c"]}}}`, + result: func(c *Client) any { + return &result.RawNotaryPool{ + Hashes: map[util.Uint256][]util.Uint256{}, + } + }, + check: func(t *testing.T, c *Client, uns any) { + res, ok := uns.(*result.RawNotaryPool) + require.True(t, ok) + mainHash, err := util.Uint256DecodeStringLE("d86b5346e9bbe6dba845cc4192fa716535a3d05c4f2084431edc99dc3862a299") + require.NoError(t, err, "can't decode `mainHash` result hash") + fallbackHash, err := util.Uint256DecodeStringLE("bb0b2f1d5539dd776637f00e5011d97921a1400d3a63c02977a38446180c6d7c") + require.NoError(t, err, "can't decode `fallbackHash` result hash") + fallbacks, ok := res.Hashes[mainHash] + require.True(t, ok) + assert.Equal(t, fallbacks[0], fallbackHash) + }, + }, + }, } type rpcClientErrorCase struct { diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index 0e3b0dd6d..7beb69f8b 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -1135,6 +1135,137 @@ func TestSignAndPushP2PNotaryRequest(t *testing.T) { }) } +func TestGetRawNotaryPoolAndTransaction(t *testing.T) { + var ( + mainHash1, fallbackHash1, mainHash2, fallbackHash2 util.Uint256 + tx1, tx2 *transaction.Transaction + ) + + chain, rpcSrv, httpSrv := initServerWithInMemoryChainAndServices(t, false, true, false) + defer chain.Close() + defer rpcSrv.Shutdown() + + c, err := rpcclient.New(context.Background(), httpSrv.URL, rpcclient.Options{}) + require.NoError(t, err) + t.Run("getrawnotarypool", func(t *testing.T) { + t.Run("empty pool", func(t *testing.T) { + np, err := c.GetRawNotaryPool() + require.NoError(t, err) + require.Equal(t, 0, len(np.Hashes)) + }) + + sender := testchain.PrivateKeyByID(0) // owner of the deposit in testchain + acc := wallet.NewAccountFromPrivateKey(sender) + + comm, err := c.GetCommittee() + require.NoError(t, err) + + multiAcc := &wallet.Account{} + *multiAcc = *acc + require.NoError(t, multiAcc.ConvertMultisig(smartcontract.GetMajorityHonestNodeCount(len(comm)), comm)) + + nact, err := notary.NewActor(c, []actor.SignerAccount{{ + Signer: transaction.Signer{ + Account: multiAcc.Contract.ScriptHash(), + Scopes: transaction.CalledByEntry, + }, + Account: multiAcc, + }}, acc) + require.NoError(t, err) + neoW := neo.New(nact) + // Send the 1st notary request + tx1, err = neoW.SetRegisterPriceTransaction(1_0000_0000) + require.NoError(t, err) + mainHash1, fallbackHash1, _, err = nact.Notarize(tx1, err) + require.NoError(t, err) + + checkTxInPool := func(t *testing.T, mainHash, fallbackHash util.Uint256, res *result.RawNotaryPool) { + actFallbacks, ok := res.Hashes[mainHash] + require.Equal(t, true, ok) + require.Equal(t, 1, len(actFallbacks)) + require.Equal(t, fallbackHash, actFallbacks[0]) + } + t.Run("nonempty pool", func(t *testing.T) { + actNotaryPool, err := c.GetRawNotaryPool() + require.NoError(t, err) + require.Equal(t, 1, len(actNotaryPool.Hashes)) + checkTxInPool(t, mainHash1, fallbackHash1, actNotaryPool) + }) + + // Send the 2nd notary request + tx2, err = neoW.SetRegisterPriceTransaction(2_0000_0000) + require.NoError(t, err) + mainHash2, fallbackHash2, _, err = nact.Notarize(tx2, err) + require.NoError(t, err) + + t.Run("pool with 2", func(t *testing.T) { + actNotaryPool, err := c.GetRawNotaryPool() + require.NoError(t, err) + require.Equal(t, 2, len(actNotaryPool.Hashes)) + checkTxInPool(t, mainHash1, fallbackHash1, actNotaryPool) + checkTxInPool(t, mainHash2, fallbackHash2, actNotaryPool) + }) + }) + t.Run("getrawnotarytransaction", func(t *testing.T) { + t.Run("client GetRawNotaryTransaction", func(t *testing.T) { + t.Run("unknown transaction", func(t *testing.T) { + _, err := c.GetRawNotaryTransaction(util.Uint256{0, 0, 0}) + require.Error(t, err) + require.ErrorIs(t, err, neorpc.ErrUnknownTransaction) + }) + _ = tx1.Size() + _ = tx2.Size() + // RPC server returns empty scripts in transaction.Witness, + // thus here the nil-value was changed to empty value. + if tx1.Scripts[1].InvocationScript == nil && tx1.Scripts[1].VerificationScript == nil { + tx1.Scripts[1] = transaction.Witness{ + InvocationScript: []byte{}, + VerificationScript: []byte{}, + } + } + if tx2.Scripts[1].InvocationScript == nil && tx2.Scripts[1].VerificationScript == nil { + tx2.Scripts[1] = transaction.Witness{ + InvocationScript: []byte{}, + VerificationScript: []byte{}, + } + } + t.Run("transactions from pool", func(t *testing.T) { + mainTx1, err := c.GetRawNotaryTransaction(mainHash1) + require.NoError(t, err) + require.Equal(t, tx1, mainTx1) + _, err = c.GetRawNotaryTransaction(fallbackHash1) + require.NoError(t, err) + + mainTx2, err := c.GetRawNotaryTransaction(mainHash2) + require.NoError(t, err) + require.Equal(t, tx2, mainTx2) + _, err = c.GetRawNotaryTransaction(fallbackHash2) + require.NoError(t, err) + }) + }) + t.Run("client GetRawNotaryTransactionVerbose", func(t *testing.T) { + t.Run("unknown transaction", func(t *testing.T) { + _, err := c.GetRawNotaryTransactionVerbose(util.Uint256{0, 0, 0}) + require.Error(t, err) + require.ErrorIs(t, err, neorpc.ErrUnknownTransaction) + }) + t.Run("transactions from pool", func(t *testing.T) { + mainTx1, err := c.GetRawNotaryTransactionVerbose(mainHash1) + require.NoError(t, err) + require.Equal(t, tx1, mainTx1) + _, err = c.GetRawNotaryTransactionVerbose(fallbackHash1) + require.NoError(t, err) + + mainTx2, err := c.GetRawNotaryTransactionVerbose(mainHash2) + require.NoError(t, err) + require.Equal(t, tx2, mainTx2) + _, err = c.GetRawNotaryTransactionVerbose(fallbackHash2) + require.NoError(t, err) + }) + }) + }) +} + func TestCalculateNotaryFee(t *testing.T) { chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) defer chain.Close() From 7afa950eb7659c100e490aad3138529522f43f24 Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Sun, 27 Aug 2023 20:15:00 +0100 Subject: [PATCH 4/4] docs: add getrawnotarypool, getrawnotarytransaction Signed-off-by: Tatiana Nesterenko --- docs/rpc.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/rpc.md b/docs/rpc.md index 2fd0777a6..068c55c5e 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -284,7 +284,27 @@ state has all its values got from MPT with the specified stateroot. This allows to track the contract storage scheme using the specified past chain state. These methods may be useful for debugging purposes. -#### `submitnotaryrequest` call +#### P2PNotary extensions + +The following P2PNotary extensions can be used on P2P Notary enabled networks +only. + +##### `getrawnotarypool` call + +`getrawnotarypool` method provides the ability to retrieve the content of the +RPC node's notary pool (a map from main transaction hashes to the corresponding +fallback transaction hashes for currently processing P2PNotaryRequest payloads). +You can use the `getrawnotarytransaction` method to iterate through +the results of `getrawnotarypool`, retrieve main/fallback transactions, +check their contents and act accordingly. + +##### `getrawnotarytransaction` call + +The `getrawnotarytransaction` method takes a transaction hash and aims to locate +the corresponding transaction in the P2PNotaryRequest pool. It performs +this search across all the verified main and fallback transactions. + +##### `submitnotaryrequest` call This method can be used on P2P Notary enabled networks to submit new notary payloads to be relayed from RPC to P2P.