Merge pull request #3060 from nspcc-dev/extend-rpc-signers

neorpc: adjust `SignerWithWitness` marshalling scheme
This commit is contained in:
Roman Khimov 2023-07-20 17:27:23 +03:00 committed by GitHub
commit 862c2e4ed3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 227 additions and 27 deletions

View file

@ -3,6 +3,7 @@ package transaction
//go:generate stringer -type=WitnessScope -linecomment -output=witness_scope_string.go //go:generate stringer -type=WitnessScope -linecomment -output=witness_scope_string.go
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"strings" "strings"
) )
@ -29,6 +30,19 @@ const (
Global WitnessScope = 0x80 Global WitnessScope = 0x80
) )
// ScopesFromByte converts byte to a set of WitnessScopes and performs validity
// check.
func ScopesFromByte(b byte) (WitnessScope, error) {
var res = WitnessScope(b)
if (res&Global != 0) && (res&(None|CalledByEntry|CustomContracts|CustomGroups|Rules) != 0) {
return 0, errors.New("Global scope can not be combined with other scopes")
}
if res&^(None|CalledByEntry|CustomContracts|CustomGroups|Rules|Global) != 0 {
return 0, fmt.Errorf("invalid scope %d", res)
}
return res, nil
}
// ScopesFromString converts string of comma-separated scopes to a set of scopes // ScopesFromString converts string of comma-separated scopes to a set of scopes
// (case-sensitive). String can combine several scopes, e.g. be any of: 'Global', // (case-sensitive). String can combine several scopes, e.g. be any of: 'Global',
// 'CalledByEntry,CustomGroups' etc. In case of an empty string an error will be // 'CalledByEntry,CustomGroups' etc. In case of an empty string an error will be
@ -54,7 +68,7 @@ func ScopesFromString(s string) (WitnessScope, error) {
return result, fmt.Errorf("invalid witness scope: %v", scopeStr) return result, fmt.Errorf("invalid witness scope: %v", scopeStr)
} }
if isGlobal && !(scope == Global) { if isGlobal && !(scope == Global) {
return result, fmt.Errorf("Global scope can not be combined with other scopes") return result, errors.New("Global scope can not be combined with other scopes")
} }
result |= scope result |= scope
if scope == Global { if scope == Global {

View file

@ -1,6 +1,7 @@
package transaction package transaction
import ( import (
"strconv"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -51,3 +52,64 @@ func TestScopesFromString(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, CalledByEntry|CustomGroups|CustomContracts, s) require.Equal(t, CalledByEntry|CustomGroups|CustomContracts, s)
} }
func TestScopesFromByte(t *testing.T) {
testCases := []struct {
in byte
expected WitnessScope
shouldFail bool
}{
{
in: 0,
expected: None,
},
{
in: 1,
expected: CalledByEntry,
},
{
in: 16,
expected: CustomContracts,
},
{
in: 32,
expected: CustomGroups,
},
{
in: 64,
expected: Rules,
},
{
in: 128,
expected: Global,
},
{
in: 17,
expected: CalledByEntry | CustomContracts,
},
{
in: 48,
expected: CustomContracts | CustomGroups,
},
{
in: 128 + 1, // Global can't be combined with others.
shouldFail: true,
},
{
in: 2, // No such scope.
shouldFail: true,
},
}
for _, tc := range testCases {
t.Run(strconv.Itoa(int(tc.in)), func(t *testing.T) {
actual, err := ScopesFromByte(tc.in)
if tc.shouldFail {
require.Error(t, err, tc.in)
} else {
require.NoError(t, err, tc.in)
require.Equal(t, tc.expected, actual, tc.in)
}
})
}
}

View file

@ -82,7 +82,7 @@ type (
// DisallowUnknownFields JSON marshaller setting. // DisallowUnknownFields JSON marshaller setting.
type signerWithWitnessAux struct { type signerWithWitnessAux struct {
Account string `json:"account"` Account string `json:"account"`
Scopes transaction.WitnessScope `json:"scopes"` Scopes json.RawMessage `json:"scopes"`
AllowedContracts []util.Uint160 `json:"allowedcontracts,omitempty"` AllowedContracts []util.Uint160 `json:"allowedcontracts,omitempty"`
AllowedGroups []*keys.PublicKey `json:"allowedgroups,omitempty"` AllowedGroups []*keys.PublicKey `json:"allowedgroups,omitempty"`
Rules []transaction.WitnessRule `json:"rules,omitempty"` Rules []transaction.WitnessRule `json:"rules,omitempty"`
@ -92,9 +92,13 @@ type signerWithWitnessAux struct {
// MarshalJSON implements the json.Marshaler interface. // MarshalJSON implements the json.Marshaler interface.
func (s *SignerWithWitness) MarshalJSON() ([]byte, error) { func (s *SignerWithWitness) MarshalJSON() ([]byte, error) {
sc, err := s.Scopes.MarshalJSON()
if err != nil {
return nil, fmt.Errorf("failed to marshal scopes: %w", err)
}
signer := &signerWithWitnessAux{ signer := &signerWithWitnessAux{
Account: s.Account.StringLE(), Account: `0x` + s.Account.StringLE(),
Scopes: s.Scopes, Scopes: sc,
AllowedContracts: s.AllowedContracts, AllowedContracts: s.AllowedContracts,
AllowedGroups: s.AllowedGroups, AllowedGroups: s.AllowedGroups,
Rules: s.Rules, Rules: s.Rules,
@ -118,9 +122,31 @@ func (s *SignerWithWitness) UnmarshalJSON(data []byte) error {
if err != nil { if err != nil {
return fmt.Errorf("not a signer: %w", err) return fmt.Errorf("not a signer: %w", err)
} }
var (
jStr string
jByte byte
scopes transaction.WitnessScope
)
if len(aux.Scopes) != 0 {
if err := json.Unmarshal(aux.Scopes, &jStr); err == nil {
scopes, err = transaction.ScopesFromString(jStr)
if err != nil {
return fmt.Errorf("failed to retrieve scopes from string: %w", err)
}
} else {
err := json.Unmarshal(aux.Scopes, &jByte)
if err != nil {
return fmt.Errorf("failed to unmarshal scopes from byte: %w", err)
}
scopes, err = transaction.ScopesFromByte(jByte)
if err != nil {
return fmt.Errorf("failed to retrieve scopes from byte: %w", err)
}
}
}
s.Signer = transaction.Signer{ s.Signer = transaction.Signer{
Account: acc, Account: acc,
Scopes: aux.Scopes, Scopes: scopes,
AllowedContracts: aux.AllowedContracts, AllowedContracts: aux.AllowedContracts,
AllowedGroups: aux.AllowedGroups, AllowedGroups: aux.AllowedGroups,
Rules: aux.Rules, Rules: aux.Rules,

40
pkg/neorpc/types_test.go Normal file
View file

@ -0,0 +1,40 @@
package neorpc
import (
"encoding/json"
"testing"
"github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestSignerWithWitnessMarshalUnmarshalJSON(t *testing.T) {
s := &SignerWithWitness{
Signer: transaction.Signer{
Account: util.Uint160{1, 2, 3},
Scopes: transaction.CalledByEntry | transaction.CustomContracts,
AllowedContracts: []util.Uint160{{1, 2, 3, 4}},
},
Witness: transaction.Witness{
InvocationScript: []byte{1, 2, 3},
VerificationScript: []byte{4, 5, 6},
},
}
testserdes.MarshalUnmarshalJSON(t, s, &SignerWithWitness{})
// Check marshalling separately to ensure Scopes are marshalled OK.
expected := `{"account":"0xcadb3dc2faa3ef14a13b619c9a43124755aa2569","scopes":"CalledByEntry, CustomContracts"}`
acc, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
require.NoError(t, err)
s = &SignerWithWitness{
Signer: transaction.Signer{
Account: acc,
Scopes: transaction.CalledByEntry | transaction.CustomContracts,
},
}
actual, err := json.Marshal(s)
require.NoError(t, err)
require.Equal(t, expected, string(actual))
}

View file

@ -245,38 +245,96 @@ func TestGetWitness(t *testing.T) {
testCases := []struct { testCases := []struct {
raw string raw string
expected neorpc.SignerWithWitness expected neorpc.SignerWithWitness
shouldFail bool
}{ }{
{`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`, neorpc.SignerWithWitness{ {
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: accountHash, Account: accountHash,
Scopes: transaction.None, Scopes: transaction.None,
}},
}, },
{`{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`, neorpc.SignerWithWitness{ },
},
{
raw: `{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: addrHash, Account: addrHash,
Scopes: transaction.Global, Scopes: transaction.Global,
}},
}, },
{`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}`, neorpc.SignerWithWitness{ },
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: accountHash, Account: accountHash,
Scopes: transaction.Global, Scopes: transaction.Global,
}}, },
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 128}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.Global,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.None,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 1}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.CalledByEntry,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 17}`,
expected: neorpc.SignerWithWitness{
Signer: transaction.Signer{
Account: accountHash,
Scopes: transaction.CalledByEntry | transaction.CustomContracts,
},
},
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 178}`,
shouldFail: true,
},
{
raw: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 2}`,
shouldFail: true,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
p := Param{RawMessage: json.RawMessage(tc.raw)} p := Param{RawMessage: json.RawMessage(tc.raw)}
actual, err := p.GetSignerWithWitness() actual, err := p.GetSignerWithWitness()
require.NoError(t, err) if tc.shouldFail {
require.Error(t, err, tc.raw)
} else {
require.NoError(t, err, tc.raw)
require.Equal(t, tc.expected, actual) require.Equal(t, tc.expected, actual)
actual, err = p.GetSignerWithWitness() // valid second invocation. actual, err = p.GetSignerWithWitness() // valid second invocation.
require.NoError(t, err) require.NoError(t, err, tc.raw)
require.Equal(t, tc.expected, actual) require.Equal(t, tc.expected, actual)
} }
} }
}
func TestParamGetUint256(t *testing.T) { func TestParamGetUint256(t *testing.T) {
gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" gas := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"