router: Make defaultChainRouter match a request by listing chains with iterator #62

Merged
fyrchik merged 1 commit from aarifullin/policy-engine:feat/list_by_iterator into master 2024-04-26 06:20:45 +00:00
3 changed files with 63 additions and 35 deletions
Showing only changes of commit 4acfe0e62f - Show all commits

2
go.mod
View file

@ -3,7 +3,7 @@ module git.frostfs.info/TrueCloudLab/policy-engine
go 1.20
require (
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45
github.com/google/uuid v1.3.1
github.com/mailru/easyjson v0.7.7
github.com/nspcc-dev/neo-go v0.105.0

4
go.sum
View file

@ -1,5 +1,5 @@
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0 h1:FzurjElUwC7InY9v5rzXReKbfBL5yRJKSWJPq6BKhH0=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45 h1:Tp4I+XOLp3VCJORfxSamQtj3RZNISbaLM4WD5iIzXxg=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=

View file

@ -6,6 +6,7 @@ import (
"math/big"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
@ -13,6 +14,7 @@ import (
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
neoinvoker "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
aarifullin marked this conversation as resolved Outdated

Empty line

Empty line

fixed

fixed
"github.com/nspcc-dev/neo-go/pkg/wallet"
@ -26,6 +28,10 @@ var (
// ContractStorage is the interface to manage chain rules within Policy contract.
type ContractStorage struct {
hash util.Uint160
actor ContractStorageActor
contractInterface *client.Contract
}
@ -33,23 +39,49 @@ var _ engine.MorphRuleChainStorage = (*ContractStorage)(nil)
// ContractStorageReader is the interface to read data from Policy contract.
type ContractStorageReader struct {
hash util.Uint160
invoker ContractStorageInvoker
contractReaderInterface *client.ContractReader
}
type ContractStorageActor interface {
client.Actor
GetRPCInvoker() neoinvoker.RPCInvoke
}
dkirillov marked this conversation as resolved Outdated

Do we really need pass actor and invoker separately? Maybe we can pass just more general actor?

Do we really need pass actor and invoker separately? Maybe we can pass just more general actor?

It is a good point. Actually, I've already tried many ways to generilize that and here is a final solution:

type ContractStorageActor interface {
	client.Actor
	GetRPCInvoker() neoinvoker.RPCInvoke
}

type ContractStorageInvoker interface {
	client.Invoker
	GetRPCInvoker() neoinvoker.RPCInvoke
}
  1. I cannot unite client.Actor and neoinvoker.RPCInvoke in one interface, because they are intersected by method TraverseIterator, TerminateSession (duplicate method error)
  2. Getter is needed because client, who's going to implement these interfaces, must guarantee that he returns valid Invoker that uses working connection (basically, websocket connection)
It is a good point. Actually, I've already tried many ways to generilize that and here is a final solution: ```go type ContractStorageActor interface { client.Actor GetRPCInvoker() neoinvoker.RPCInvoke } type ContractStorageInvoker interface { client.Invoker GetRPCInvoker() neoinvoker.RPCInvoke } ``` 1. I cannot unite `client.Actor` and `neoinvoker.RPCInvoke` in one interface, because they are intersected by method `TraverseIterator`, `TerminateSession` (`duplicate method` **error**) 2. Getter is needed because client, who's going to implement these interfaces, must guarantee that he returns valid `Invoker` that uses working connection (basically, websocket connection)
var _ engine.MorphRuleChainStorageReader = (*ContractStorageReader)(nil)
func NewContractStorage(actor client.Actor, contract util.Uint160) *ContractStorage {
func NewContractStorage(actor ContractStorageActor, contract util.Uint160) *ContractStorage {
return &ContractStorage{
hash: contract,
actor: actor,
contractInterface: client.New(actor, contract),
}
}
type contractStorageActorImpl struct {
client.Actor
rpcActor actor.RPCActor
}
var _ ContractStorageActor = &contractStorageActorImpl{}
func (c *contractStorageActorImpl) GetRPCInvoker() neoinvoker.RPCInvoke {
return c.rpcActor
}
// NewContractStorageWithSimpleActor constructs core actor from `rpcActor`.
//
// Note: NewContractStorageWithSimpleActor is appropriate only for call-only-once cases (for example, in CLIs). Otherwise, it is unsafe,
// because core actor may use invalidated `rpcActor` if some connection errors occurred.
func NewContractStorageWithSimpleActor(rpcActor actor.RPCActor, acc *wallet.Account, contract util.Uint160) (*ContractStorage, error) {
act, err := actor.NewSimple(rpcActor, acc)
if err != nil {
return nil, fmt.Errorf("failed to create simple actor: %w", err)
}
return NewContractStorage(act, contract), nil
return NewContractStorage(&contractStorageActorImpl{Actor: act, rpcActor: rpcActor}, contract), nil
}
func (s *ContractStorage) AddMorphRuleChain(name chain.Name, target engine.Target, c *chain.Chain) (txHash util.Uint256, vub uint32, err error) {
@ -98,17 +130,26 @@ func (s *ContractStorage) RemoveMorphRuleChainsByTarget(name chain.Name, target
return
}
func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
func listChains(name chain.Name, target engine.Target, rpcInvoker neoinvoker.RPCInvoke, hash util.Uint160) ([]*chain.Chain, error) {
kind, err := policyKind(target.Type)
if err != nil {
return nil, err
}
items, err := s.contractInterface.ListChainsByPrefix(big.NewInt(int64(kind)), target.Name, []byte(name))
if err != nil {
return nil, err
const (
method = "iteratorChainsByPrefix"
batchSize = neoinvoker.DefaultIteratorResultItems
)
inv := neoinvoker.New(rpcInvoker, nil)
params := []any{
big.NewInt(int64(kind)), target.Name, []byte(name),
dkirillov marked this conversation as resolved Outdated

Question: Can we use

func ReadIteratorItems(inv Invoker, batchSize int, contract util.Uint160, method string, params ...any) ([]stackitem.Item, error) {

?

Question: Can we use https://git.frostfs.info/TrueCloudLab/frostfs-contract/src/commit/694daebb1928ffc4eb54e314933dfafe121dad93/commonclient/iterator.go#L14 ?

Oh, that's a good find! I am going to check if I can use this, thank you!

Oh, that's a good find! I am going to check if I can use this, thank you!

That worked out, thanks!

That worked out, thanks!
}
items, err := commonclient.ReadIteratorItems(inv, batchSize, hash, method, params...)
dkirillov marked this conversation as resolved Outdated

Inside this method we don't explicitly terminate session so it can lead to unwrap session iterator: Internal error (-32603) - max session capacity reached. Maybe we can update this method in contract first?

Inside this method we don't explicitly terminate session so it can lead to `unwrap session iterator: Internal error (-32603) - max session capacity reached`. Maybe we can update this method in contract first?

You're absolutely correct. I'll fix this in frostfs-contract

You're absolutely correct. I'll fix this in `frostfs-contract`

go.mod has been updated with TrueCloudLab/frostfs-contract#85

`go.mod` has been updated with https://git.frostfs.info/TrueCloudLab/frostfs-contract/pulls/85
if err != nil {
return nil, fmt.Errorf("read items error: %w", err)
}
var chains []*chain.Chain
for _, item := range items {
serialized, err := bytesFromStackItem(item)
@ -121,10 +162,13 @@ func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Tar
}
chains = append(chains, c)
}
return chains, nil
}
func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
return listChains(name, target, s.actor.GetRPCInvoker(), s.hash)
}
func (s *ContractStorage) ListTargetsIterator(targetType engine.TargetType) (uuid.UUID, result.Iterator, error) {
kind, err := policyKind(targetType)
if err != nil {
@ -141,37 +185,21 @@ func (s *ContractStorage) SetAdmin(addr util.Uint160) (util.Uint256, uint32, err
return s.contractInterface.SetAdmin(addr)
}
func NewContractStorageReader(inv client.Invoker, contract util.Uint160) *ContractStorageReader {
type ContractStorageInvoker interface {
client.Invoker
GetRPCInvoker() neoinvoker.RPCInvoke
}
func NewContractStorageReader(inv ContractStorageInvoker, contract util.Uint160) *ContractStorageReader {
return &ContractStorageReader{
hash: contract,
invoker: inv,
contractReaderInterface: client.NewReader(inv, contract),
}
}
func (s *ContractStorageReader) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
kind, err := policyKind(target.Type)
if err != nil {
return nil, err
}
items, err := s.contractReaderInterface.ListChainsByPrefix(big.NewInt(int64(kind)), target.Name, []byte(name))
if err != nil {
return nil, err
}
var chains []*chain.Chain
for _, item := range items {
serialized, err := bytesFromStackItem(item)
if err != nil {
return nil, err
}
c := new(chain.Chain)
if err := c.DecodeBytes(serialized); err != nil {
return nil, err
}
chains = append(chains, c)
}
return chains, nil
return listChains(name, target, s.invoker.GetRPCInvoker(), s.hash)
}
func (s *ContractStorageReader) GetAdmin() (util.Uint160, error) {