Repository: nspcc-dev/neo-go
Pull request #3492 (2024-06-20 14:14:12)

invoker: add Signers() API
Signers are very important for notary checks and keeping/passing an additional copy of them is very inconvenient. Exposing them from invoker makes them available in actors too.

roman-khimov: Maybe we also need `SignerAccounts()` for actors, though.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/nspcc-dev/neo-go/pull/3492?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 86.13%. Comparing base [(`4ff2063`)](https://app.codecov.io/gh/nspcc-dev/neo-go/commit/4ff2063539430d18f499fea32c347f6396fadf38?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev) to head [(`500dbb9`)](https://app.codecov.io/gh/nspcc-dev/neo-go/commit/500dbb9c4e7bc4952cf8dc99ba29cbfaaab08fb9?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev).

> :exclamation: **Current head 500dbb9 differs from pull request most recent head 9469f8f**
> 
> Please [upload](https://docs.codecov.com/docs/codecov-uploader) reports for the commit 9469f8f to get more accurate results.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3492      +/-   ##
==========================================
- Coverage   86.19%   86.13%   -0.06%     
==========================================
  Files         331      331              
  Lines       38494    38501       +7     
==========================================
- Hits        33179    33164      -15     
- Misses       3785     3811      +26     
+ Partials     1530     1526       -4     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/nspcc-dev/neo-go/pull/3492?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev).


Patch:
diff --git a/pkg/rpcclient/actor/actor.go b/pkg/rpcclient/actor/actor.go
index 2884c3cd2..89b9a1d2a 100644
--- a/pkg/rpcclient/actor/actor.go
+++ b/pkg/rpcclient/actor/actor.go
@@ -280,6 +280,19 @@ func (a *Actor) SendUncheckedRun(script []byte, sysfee int64, attrs []transactio
 	return a.sendWrapper(a.MakeUncheckedRun(script, sysfee, attrs, txHook))
 }
 
+// SignerAccounts returns the array of actor's signers/accounts. It's useful in
+// case you need it elsewhere like for notary-related processing. Returned slice
+// is a newly allocated one with signers deeply copied, accounts however are not
+// so changing received account internals is an error.
+func (a *Actor) SignerAccounts() []SignerAccount {
+	var res = make([]SignerAccount, len(a.signers))
+	for i := range a.signers {
+		res[i].Signer = *a.signers[i].Signer.Copy()
+		res[i].Account = a.signers[i].Account
+	}
+	return res
+}
+
 // Sender return the sender address that will be used in transactions created
 // by Actor.
 func (a *Actor) Sender() util.Uint160 {
diff --git a/pkg/rpcclient/invoker/invoker.go b/pkg/rpcclient/invoker/invoker.go
index a56be5942..a40a0c2dd 100644
--- a/pkg/rpcclient/invoker/invoker.go
+++ b/pkg/rpcclient/invoker/invoker.go
@@ -137,6 +137,20 @@ func (h *historicConverter) TraverseIterator(sessionID, iteratorID uuid.UUID, ma
 	return h.client.TraverseIterator(sessionID, iteratorID, maxItemsCount)
 }
 
+// Signers returns the set of current invoker signers which is mostly useful
+// when working with upper-layer actors. Returned slice is a newly allocated
+// one (if this invoker has them), so it's safe to modify.
+func (v *Invoker) Signers() []transaction.Signer {
+	if v.signers == nil {
+		return nil
+	}
+	var res = make([]transaction.Signer, len(v.signers))
+	for i := range v.signers {
+		res[i] = *v.signers[i].Copy()
+	}
+	return res
+}
+
 // Call invokes a method of the contract with the given parameters (and
 // Invoker-specific list of signers) and returns the result as is.
 func (v *Invoker) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) {


Test patch:
diff --git a/pkg/rpcclient/actor/actor_test.go b/pkg/rpcclient/actor/actor_test.go
index 4bd1b98a8..1dc120129 100644
--- a/pkg/rpcclient/actor/actor_test.go
+++ b/pkg/rpcclient/actor/actor_test.go
@@ -101,9 +101,14 @@ func TestNew(t *testing.T) {
 	// Good simple.
 	a, err := NewSimple(client, acc)
 	require.NoError(t, err)
-	require.Equal(t, 1, len(a.signers))
+	require.Equal(t, []SignerAccount{{
+		Signer: transaction.Signer{
+			Account: acc.ScriptHash(),
+			Scopes:  transaction.CalledByEntry,
+		},
+		Account: acc,
+	}}, a.SignerAccounts())
 	require.Equal(t, 1, len(a.txSigners))
-	require.Equal(t, transaction.CalledByEntry, a.signers[0].Signer.Scopes)
 	require.Equal(t, transaction.CalledByEntry, a.txSigners[0].Scopes)
 
 	// Contractless account.
@@ -158,7 +163,7 @@ func TestNew(t *testing.T) {
 	signers[0].Signer.Account = acc.Contract.ScriptHash()
 	a, err = New(client, signers)
 	require.NoError(t, err)
-	require.Equal(t, 2, len(a.signers))
+	require.Equal(t, signers, a.SignerAccounts())
 	require.Equal(t, 2, len(a.txSigners))
 
 	// Good tuned
diff --git a/pkg/rpcclient/invoker/invoker_test.go b/pkg/rpcclient/invoker/invoker_test.go
index 5d5a6159f..39b21bcaf 100644
--- a/pkg/rpcclient/invoker/invoker_test.go
+++ b/pkg/rpcclient/invoker/invoker_test.go
@@ -158,3 +158,19 @@ func TestInvoker(t *testing.T) {
 		}
 	})
 }
+
+func TestInvokerSigners(t *testing.T) {
+	resExp := &result.Invoke{State: "HALT"}
+	ri := &rpcInv{resExp, true, nil, nil}
+	inv := New(ri, nil)
+
+	require.Nil(t, inv.Signers())
+
+	s := []transaction.Signer{}
+	inv = New(ri, s)
+	require.Equal(t, s, inv.Signers())
+
+	s = append(s, transaction.Signer{Account: util.Uint160{1, 2, 3}, Scopes: transaction.CalledByEntry})
+	inv = New(ri, s)
+	require.Equal(t, s, inv.Signers())
+}