From 1a4da8c46217378ea246076952cb1cf9ba8f91a6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 16 Mar 2023 23:43:00 +0300 Subject: [PATCH 1/2] neorpc: add Copy to filters for easy deep copying --- pkg/neorpc/filters.go | 113 ++++++++++++++++++++++++++++++++++++ pkg/neorpc/filters_test.go | 116 +++++++++++++++++++++++++++++++++++++ pkg/neorpc/types.go | 33 ----------- 3 files changed, 229 insertions(+), 33 deletions(-) create mode 100644 pkg/neorpc/filters.go create mode 100644 pkg/neorpc/filters_test.go diff --git a/pkg/neorpc/filters.go b/pkg/neorpc/filters.go new file mode 100644 index 000000000..2980640ed --- /dev/null +++ b/pkg/neorpc/filters.go @@ -0,0 +1,113 @@ +package neorpc + +import ( + "github.com/nspcc-dev/neo-go/pkg/util" +) + +type ( + // BlockFilter is a wrapper structure for the block event filter. It allows + // to filter blocks by primary index and/or by block index (allowing blocks + // since/till the specified index inclusively). nil value treated as missing + // filter. + BlockFilter struct { + Primary *int `json:"primary,omitempty"` + Since *uint32 `json:"since,omitempty"` + Till *uint32 `json:"till,omitempty"` + } + // TxFilter is a wrapper structure for the transaction event filter. It + // allows to filter transactions by senders and/or signers. nil value treated + // as missing filter. + TxFilter struct { + Sender *util.Uint160 `json:"sender,omitempty"` + Signer *util.Uint160 `json:"signer,omitempty"` + } + // NotificationFilter is a wrapper structure representing a filter used for + // notifications generated during transaction execution. Notifications can + // be filtered by contract hash and/or by name. nil value treated as missing + // filter. + NotificationFilter struct { + Contract *util.Uint160 `json:"contract,omitempty"` + Name *string `json:"name,omitempty"` + } + // ExecutionFilter is a wrapper structure used for transaction and persisting + // scripts execution events. It allows to choose failing or successful + // transactions and persisting scripts based on their VM state and/or to + // choose execution event with the specified container. nil value treated as + // missing filter. + ExecutionFilter struct { + State *string `json:"state,omitempty"` + Container *util.Uint256 `json:"container,omitempty"` + } +) + +// Copy creates a deep copy of the BlockFilter. It handles nil BlockFilter correctly. +func (f *BlockFilter) Copy() *BlockFilter { + if f == nil { + return nil + } + var res = new(BlockFilter) + if f.Primary != nil { + res.Primary = new(int) + *res.Primary = *f.Primary + } + if f.Since != nil { + res.Since = new(uint32) + *res.Since = *f.Since + } + if f.Till != nil { + res.Till = new(uint32) + *res.Till = *f.Till + } + return res +} + +// Copy creates a deep copy of the TxFilter. It handles nil TxFilter correctly. +func (f *TxFilter) Copy() *TxFilter { + if f == nil { + return nil + } + var res = new(TxFilter) + if f.Sender != nil { + res.Sender = new(util.Uint160) + *res.Sender = *f.Sender + } + if f.Signer != nil { + res.Signer = new(util.Uint160) + *res.Signer = *f.Signer + } + return res +} + +// Copy creates a deep copy of the NotificationFilter. It handles nil NotificationFilter correctly. +func (f *NotificationFilter) Copy() *NotificationFilter { + if f == nil { + return nil + } + var res = new(NotificationFilter) + if f.Contract != nil { + res.Contract = new(util.Uint160) + *res.Contract = *f.Contract + } + if f.Name != nil { + res.Name = new(string) + *res.Name = *f.Name + } + return res +} + +// Copy creates a deep copy of the ExecutionFilter. It handles nil ExecutionFilter correctly. +func (f *ExecutionFilter) Copy() *ExecutionFilter { + if f == nil { + return nil + } + var res = new(ExecutionFilter) + if f.State != nil { + res.State = new(string) + *res.State = *f.State + } + if f.Container != nil { + res.Container = new(util.Uint256) + *res.Container = *f.Container + } + return res +} diff --git a/pkg/neorpc/filters_test.go b/pkg/neorpc/filters_test.go new file mode 100644 index 000000000..8f725a588 --- /dev/null +++ b/pkg/neorpc/filters_test.go @@ -0,0 +1,116 @@ +package neorpc + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestBlockFilterCopy(t *testing.T) { + var bf, tf *BlockFilter + + require.Nil(t, bf.Copy()) + + bf = new(BlockFilter) + tf = bf.Copy() + require.Equal(t, bf, tf) + + bf.Primary = new(int) + *bf.Primary = 42 + + tf = bf.Copy() + require.Equal(t, bf, tf) + *bf.Primary = 100500 + require.NotEqual(t, bf, tf) + + bf.Since = new(uint32) + *bf.Since = 42 + + tf = bf.Copy() + require.Equal(t, bf, tf) + *bf.Since = 100500 + require.NotEqual(t, bf, tf) + + bf.Till = new(uint32) + *bf.Till = 42 + + tf = bf.Copy() + require.Equal(t, bf, tf) + *bf.Till = 100500 + require.NotEqual(t, bf, tf) +} + +func TestTxFilterCopy(t *testing.T) { + var bf, tf *TxFilter + + require.Nil(t, bf.Copy()) + + bf = new(TxFilter) + tf = bf.Copy() + require.Equal(t, bf, tf) + + bf.Sender = &util.Uint160{1, 2, 3} + + tf = bf.Copy() + require.Equal(t, bf, tf) + *bf.Sender = util.Uint160{3, 2, 1} + require.NotEqual(t, bf, tf) + + bf.Signer = &util.Uint160{1, 2, 3} + + tf = bf.Copy() + require.Equal(t, bf, tf) + *bf.Signer = util.Uint160{3, 2, 1} + require.NotEqual(t, bf, tf) +} + +func TestNotificationFilterCopy(t *testing.T) { + var bf, tf *NotificationFilter + + require.Nil(t, bf.Copy()) + + bf = new(NotificationFilter) + tf = bf.Copy() + require.Equal(t, bf, tf) + + bf.Contract = &util.Uint160{1, 2, 3} + + tf = bf.Copy() + require.Equal(t, bf, tf) + *bf.Contract = util.Uint160{3, 2, 1} + require.NotEqual(t, bf, tf) + + bf.Name = new(string) + *bf.Name = "ololo" + + tf = bf.Copy() + require.Equal(t, bf, tf) + *bf.Name = "azaza" + require.NotEqual(t, bf, tf) +} + +func TestExecutionFilterCopy(t *testing.T) { + var bf, tf *ExecutionFilter + + require.Nil(t, bf.Copy()) + + bf = new(ExecutionFilter) + tf = bf.Copy() + require.Equal(t, bf, tf) + + bf.State = new(string) + *bf.State = "ololo" + + tf = bf.Copy() + require.Equal(t, bf, tf) + *bf.State = "azaza" + require.NotEqual(t, bf, tf) + + bf.Container = &util.Uint256{1, 2, 3} + + tf = bf.Copy() + require.Equal(t, bf, tf) + *bf.Container = util.Uint256{3, 2, 1} + require.NotEqual(t, bf, tf) +} diff --git a/pkg/neorpc/types.go b/pkg/neorpc/types.go index 20e1d2359..223cd591c 100644 --- a/pkg/neorpc/types.go +++ b/pkg/neorpc/types.go @@ -71,39 +71,6 @@ type ( Payload []interface{} `json:"params"` } - // BlockFilter is a wrapper structure for the block event filter. It allows - // to filter blocks by primary index and/or by block index (allowing blocks - // since/till the specified index inclusively). nil value treated as missing - // filter. - BlockFilter struct { - Primary *int `json:"primary,omitempty"` - Since *uint32 `json:"since,omitempty"` - Till *uint32 `json:"till,omitempty"` - } - // TxFilter is a wrapper structure for the transaction event filter. It - // allows to filter transactions by senders and/or signers. nil value treated - // as missing filter. - TxFilter struct { - Sender *util.Uint160 `json:"sender,omitempty"` - Signer *util.Uint160 `json:"signer,omitempty"` - } - // NotificationFilter is a wrapper structure representing a filter used for - // notifications generated during transaction execution. Notifications can - // be filtered by contract hash and/or by name. nil value treated as missing - // filter. - NotificationFilter struct { - Contract *util.Uint160 `json:"contract,omitempty"` - Name *string `json:"name,omitempty"` - } - // ExecutionFilter is a wrapper structure used for transaction and persisting - // scripts execution events. It allows to choose failing or successful - // transactions and persisting scripts based on their VM state and/or to - // choose execution event with the specified container. nil value treated as - // missing filter. - ExecutionFilter struct { - State *string `json:"state,omitempty"` - Container *util.Uint256 `json:"container,omitempty"` - } // SignerWithWitness represents transaction's signer with the corresponding witness. SignerWithWitness struct { transaction.Signer From e197f3faefbb1e12737be196f9b062ad9cbf6273 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 16 Mar 2023 23:50:56 +0300 Subject: [PATCH 2/2] rpcclient: copy subscription params, fix #2890 Clients can change things and we better be safe here. --- pkg/rpcclient/wsclient.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pkg/rpcclient/wsclient.go b/pkg/rpcclient/wsclient.go index 8cb224ccf..1d4987c7d 100644 --- a/pkg/rpcclient/wsclient.go +++ b/pkg/rpcclient/wsclient.go @@ -647,7 +647,8 @@ func (c *WSClient) performSubscription(params []interface{}, rcvr notificationRe func (c *WSClient) SubscribeForNewBlocks(primary *int) (string, error) { var flt interface{} if primary != nil { - flt = neorpc.BlockFilter{Primary: primary} + var f = neorpc.BlockFilter{Primary: primary} + flt = *f.Copy() } params := []interface{}{"block_added"} if flt != nil { @@ -678,6 +679,7 @@ func (c *WSClient) ReceiveBlocks(flt *neorpc.BlockFilter, rcvr chan<- *block.Blo } params := []interface{}{"block_added"} if flt != nil { + flt = flt.Copy() params = append(params, *flt) } r := &blockReceiver{ @@ -695,7 +697,8 @@ func (c *WSClient) ReceiveBlocks(flt *neorpc.BlockFilter, rcvr chan<- *block.Blo func (c *WSClient) SubscribeForNewTransactions(sender *util.Uint160, signer *util.Uint160) (string, error) { var flt interface{} if sender != nil || signer != nil { - flt = neorpc.TxFilter{Sender: sender, Signer: signer} + var f = neorpc.TxFilter{Sender: sender, Signer: signer} + flt = *f.Copy() } params := []interface{}{"transaction_added"} if flt != nil { @@ -726,6 +729,7 @@ func (c *WSClient) ReceiveTransactions(flt *neorpc.TxFilter, rcvr chan<- *transa } params := []interface{}{"transaction_added"} if flt != nil { + flt = flt.Copy() params = append(params, *flt) } r := &txReceiver{ @@ -744,7 +748,8 @@ func (c *WSClient) ReceiveTransactions(flt *neorpc.TxFilter, rcvr chan<- *transa func (c *WSClient) SubscribeForExecutionNotifications(contract *util.Uint160, name *string) (string, error) { var flt interface{} if contract != nil || name != nil { - flt = neorpc.NotificationFilter{Contract: contract, Name: name} + var f = neorpc.NotificationFilter{Contract: contract, Name: name} + flt = *f.Copy() } params := []interface{}{"notification_from_execution"} if flt != nil { @@ -774,6 +779,7 @@ func (c *WSClient) ReceiveExecutionNotifications(flt *neorpc.NotificationFilter, } params := []interface{}{"notification_from_execution"} if flt != nil { + flt = flt.Copy() params = append(params, *flt) } r := &executionNotificationReceiver{ @@ -795,7 +801,8 @@ func (c *WSClient) SubscribeForTransactionExecutions(state *string) (string, err if *state != "HALT" && *state != "FAULT" { return "", errors.New("bad state parameter") } - flt = neorpc.ExecutionFilter{State: state} + var f = neorpc.ExecutionFilter{State: state} + flt = *f.Copy() } params := []interface{}{"transaction_executed"} if flt != nil { @@ -832,6 +839,7 @@ func (c *WSClient) ReceiveExecutions(flt *neorpc.ExecutionFilter, rcvr chan<- *s return "", errors.New("bad state parameter") } } + flt = flt.Copy() params = append(params, *flt) } r := &executionReceiver{ @@ -850,7 +858,8 @@ func (c *WSClient) ReceiveExecutions(flt *neorpc.ExecutionFilter, rcvr chan<- *s func (c *WSClient) SubscribeForNotaryRequests(sender *util.Uint160, mainSigner *util.Uint160) (string, error) { var flt interface{} if sender != nil || mainSigner != nil { - flt = neorpc.TxFilter{Sender: sender, Signer: mainSigner} + var f = neorpc.TxFilter{Sender: sender, Signer: mainSigner} + flt = *f.Copy() } params := []interface{}{"notary_request_event"} if flt != nil { @@ -883,6 +892,7 @@ func (c *WSClient) ReceiveNotaryRequests(flt *neorpc.TxFilter, rcvr chan<- *resu } params := []interface{}{"notary_request_event"} if flt != nil { + flt = flt.Copy() params = append(params, *flt) } r := ¬aryRequestReceiver{